Loading libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt +6 −2 Original line number Diff line number Diff line Loading @@ -5669,10 +5669,14 @@ class DesktopTasksController( validDragArea, ) if (destinationBounds == dragStartBounds) { if ( destinationBounds == dragStartBounds && destinationBounds != currentDragBounds ) { // There's no actual difference between the start and end bounds, so while a // WCT change isn't needed, the dragged surface still needs to be snapped back // to its original location. // to its original location. This is as long as it moved some in the first // place, if it didn't and |currentDragBounds| is already at destination then // there's no need to animate. releaseVisualIndicator() returnToDragStartAnimator.start( taskInfo.taskId, Loading libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ReturnToDragStartAnimator.kt +51 −23 Original line number Diff line number Diff line Loading @@ -24,6 +24,8 @@ import android.view.SurfaceControl import androidx.core.animation.addListener import com.android.internal.jank.Cuj import com.android.internal.jank.InteractionJankMonitor import com.android.internal.protolog.ProtoLog import com.android.wm.shell.protolog.ShellProtoLogGroup import com.android.wm.shell.windowdecor.OnTaskRepositionAnimationListener import java.util.function.Supplier Loading Loading @@ -52,6 +54,15 @@ class ReturnToDragStartAnimator( endBounds: Rect, doOnEnd: (() -> Unit)? = null, ) { if (startBounds == endBounds) { ProtoLog.w( ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE, "%s: reposition animation request with equal start/end bounds=%s, ignoring", TAG, startBounds, ) return } val tx = transactionSupplier.get() boundsAnimator?.cancel() Loading @@ -61,27 +72,31 @@ class ReturnToDragStartAnimator( .apply { addListener( onStart = { taskSurface.checkValidOrCancel()?.let { surface -> val startTransaction = transactionSupplier.get() startTransaction .setPosition( taskSurface, surface, startBounds.left.toFloat(), startBounds.top.toFloat(), ) .show(taskSurface) .show(surface) .apply() taskRepositionAnimationListener.onAnimationStart(taskId) } }, onEnd = { taskSurface.checkValid()?.let { surface -> val finishTransaction = transactionSupplier.get() finishTransaction .setPosition( taskSurface, surface, endBounds.left.toFloat(), endBounds.top.toFloat(), ) .show(taskSurface) .show(surface) .apply() } taskRepositionAnimationListener.onAnimationEnd(taskId) boundsAnimator = null doOnEnd?.invoke() Loading @@ -89,16 +104,29 @@ class ReturnToDragStartAnimator( }, ) addUpdateListener { anim -> taskSurface.checkValidOrCancel()?.let { surface -> val rect = anim.animatedValue as Rect tx.setPosition(taskSurface, rect.left.toFloat(), rect.top.toFloat()) .show(taskSurface) tx.setPosition(surface, rect.left.toFloat(), rect.top.toFloat()) .show(surface) .apply() } } } .also(ValueAnimator::start) } private fun SurfaceControl.checkValid(): SurfaceControl? = if (isValid) this else null private fun SurfaceControl.checkValidOrCancel(): SurfaceControl? { if (isValid) { return this } boundsAnimator?.cancel() return null } companion object { private const val TAG = "ReturnToDragStartAnimator" const val RETURN_TO_DRAG_START_ANIMATION_MS = 300L } } libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt +44 −0 Original line number Diff line number Diff line Loading @@ -9265,6 +9265,50 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() .logTaskResizingEnded(any(), any(), any(), any(), any(), any(), any()) } @Test fun onDesktopDragEnd_noIndicator_noBoundsMovement_noReturnToStartAnimation() { val task = setUpFreeformTask(bounds = STABLE_BOUNDS) val spyController = spy(controller) val mockSurface = mock(SurfaceControl::class.java) val mockDisplayLayout = mock(DisplayLayout::class.java) 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(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator) whenever(desktopModeVisualIndicator.updateIndicatorType(any(), anyOrNull())) .thenReturn(DesktopModeVisualIndicator.IndicatorType.NO_INDICATOR) whenever(motionEvent.displayId).thenReturn(DEFAULT_DISPLAY) // "Drag move" the task, but the bounds aren't actually changing. Which can be the case // on mouse clicks that generate ACTION_MOVE events without position changes. val currentDragBounds = Rect(200, 200, 700, 700) spyController.onDragPositioningMove( taskInfo = task, taskSurface = mockSurface, displayId = DEFAULT_DISPLAY, inputX = 500f, inputY = 210f, taskBounds = currentDragBounds, ) spyController.onDragPositioningEnd( taskInfo = task, taskSurface = mockSurface, displayId = DEFAULT_DISPLAY, inputCoordinate = PointF(510f, 210f), currentDragBounds = currentDragBounds, validDragArea = Rect(0, 50, 2000, 2000), dragStartBounds = currentDragBounds, motionEvent = motionEvent, ) // Even though end bounds are the same as start bounds, there's no need to animate the // return because the current bounds are also the same as start/end. verify(mReturnToDragStartAnimator, never()) .start(eq(task.taskId), eq(mockSurface), any(), any(), anyOrNull()) } @Test fun enterSplit_freeformTaskIsMovedToSplit() { val task1 = setUpFreeformTask() Loading Loading
libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt +6 −2 Original line number Diff line number Diff line Loading @@ -5669,10 +5669,14 @@ class DesktopTasksController( validDragArea, ) if (destinationBounds == dragStartBounds) { if ( destinationBounds == dragStartBounds && destinationBounds != currentDragBounds ) { // There's no actual difference between the start and end bounds, so while a // WCT change isn't needed, the dragged surface still needs to be snapped back // to its original location. // to its original location. This is as long as it moved some in the first // place, if it didn't and |currentDragBounds| is already at destination then // there's no need to animate. releaseVisualIndicator() returnToDragStartAnimator.start( taskInfo.taskId, Loading
libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ReturnToDragStartAnimator.kt +51 −23 Original line number Diff line number Diff line Loading @@ -24,6 +24,8 @@ import android.view.SurfaceControl import androidx.core.animation.addListener import com.android.internal.jank.Cuj import com.android.internal.jank.InteractionJankMonitor import com.android.internal.protolog.ProtoLog import com.android.wm.shell.protolog.ShellProtoLogGroup import com.android.wm.shell.windowdecor.OnTaskRepositionAnimationListener import java.util.function.Supplier Loading Loading @@ -52,6 +54,15 @@ class ReturnToDragStartAnimator( endBounds: Rect, doOnEnd: (() -> Unit)? = null, ) { if (startBounds == endBounds) { ProtoLog.w( ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE, "%s: reposition animation request with equal start/end bounds=%s, ignoring", TAG, startBounds, ) return } val tx = transactionSupplier.get() boundsAnimator?.cancel() Loading @@ -61,27 +72,31 @@ class ReturnToDragStartAnimator( .apply { addListener( onStart = { taskSurface.checkValidOrCancel()?.let { surface -> val startTransaction = transactionSupplier.get() startTransaction .setPosition( taskSurface, surface, startBounds.left.toFloat(), startBounds.top.toFloat(), ) .show(taskSurface) .show(surface) .apply() taskRepositionAnimationListener.onAnimationStart(taskId) } }, onEnd = { taskSurface.checkValid()?.let { surface -> val finishTransaction = transactionSupplier.get() finishTransaction .setPosition( taskSurface, surface, endBounds.left.toFloat(), endBounds.top.toFloat(), ) .show(taskSurface) .show(surface) .apply() } taskRepositionAnimationListener.onAnimationEnd(taskId) boundsAnimator = null doOnEnd?.invoke() Loading @@ -89,16 +104,29 @@ class ReturnToDragStartAnimator( }, ) addUpdateListener { anim -> taskSurface.checkValidOrCancel()?.let { surface -> val rect = anim.animatedValue as Rect tx.setPosition(taskSurface, rect.left.toFloat(), rect.top.toFloat()) .show(taskSurface) tx.setPosition(surface, rect.left.toFloat(), rect.top.toFloat()) .show(surface) .apply() } } } .also(ValueAnimator::start) } private fun SurfaceControl.checkValid(): SurfaceControl? = if (isValid) this else null private fun SurfaceControl.checkValidOrCancel(): SurfaceControl? { if (isValid) { return this } boundsAnimator?.cancel() return null } companion object { private const val TAG = "ReturnToDragStartAnimator" const val RETURN_TO_DRAG_START_ANIMATION_MS = 300L } }
libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt +44 −0 Original line number Diff line number Diff line Loading @@ -9265,6 +9265,50 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() .logTaskResizingEnded(any(), any(), any(), any(), any(), any(), any()) } @Test fun onDesktopDragEnd_noIndicator_noBoundsMovement_noReturnToStartAnimation() { val task = setUpFreeformTask(bounds = STABLE_BOUNDS) val spyController = spy(controller) val mockSurface = mock(SurfaceControl::class.java) val mockDisplayLayout = mock(DisplayLayout::class.java) 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(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator) whenever(desktopModeVisualIndicator.updateIndicatorType(any(), anyOrNull())) .thenReturn(DesktopModeVisualIndicator.IndicatorType.NO_INDICATOR) whenever(motionEvent.displayId).thenReturn(DEFAULT_DISPLAY) // "Drag move" the task, but the bounds aren't actually changing. Which can be the case // on mouse clicks that generate ACTION_MOVE events without position changes. val currentDragBounds = Rect(200, 200, 700, 700) spyController.onDragPositioningMove( taskInfo = task, taskSurface = mockSurface, displayId = DEFAULT_DISPLAY, inputX = 500f, inputY = 210f, taskBounds = currentDragBounds, ) spyController.onDragPositioningEnd( taskInfo = task, taskSurface = mockSurface, displayId = DEFAULT_DISPLAY, inputCoordinate = PointF(510f, 210f), currentDragBounds = currentDragBounds, validDragArea = Rect(0, 50, 2000, 2000), dragStartBounds = currentDragBounds, motionEvent = motionEvent, ) // Even though end bounds are the same as start bounds, there's no need to animate the // return because the current bounds are also the same as start/end. verify(mReturnToDragStartAnimator, never()) .start(eq(task.taskId), eq(mockSurface), any(), any(), anyOrNull()) } @Test fun enterSplit_freeformTaskIsMovedToSplit() { val task1 = setUpFreeformTask() Loading