diff options
3 files changed, 288 insertions, 76 deletions
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 6e45397411d7..831c6cfa94e2 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 @@ -459,7 +459,9 @@ class DesktopTasksController( "DesktopTasksController: cancelDragToDesktop taskId=%d", task.taskId ) - dragToDesktopTransitionHandler.cancelDragToDesktopTransition() + dragToDesktopTransitionHandler.cancelDragToDesktopTransition( + DragToDesktopTransitionHandler.CancelState.STANDARD_CANCEL + ) } private fun moveToFullscreenWithAnimation(task: RunningTaskInfo, position: Point) { @@ -1078,20 +1080,31 @@ class DesktopTasksController( @JvmOverloads fun requestSplit( taskInfo: RunningTaskInfo, - leftOrTop: Boolean = false, + leftOrTop: Boolean = false ) { - val windowingMode = taskInfo.windowingMode - if ( - windowingMode == WINDOWING_MODE_FULLSCREEN || windowingMode == WINDOWING_MODE_FREEFORM - ) { - val wct = WindowContainerTransaction() - addMoveToSplitChanges(wct, taskInfo) - splitScreenController.requestEnterSplitSelect( - taskInfo, - wct, - if (leftOrTop) SPLIT_POSITION_TOP_OR_LEFT else SPLIT_POSITION_BOTTOM_OR_RIGHT, - taskInfo.configuration.windowConfiguration.bounds - ) + // If a drag to desktop is in progress, we want to enter split select + // even if the requesting task is already in split. + val isDragging = dragToDesktopTransitionHandler.inProgress + val shouldRequestSplit = taskInfo.isFullscreen || taskInfo.isFreeform || isDragging + if (shouldRequestSplit) { + if (isDragging) { + releaseVisualIndicator() + val cancelState = if (leftOrTop) { + DragToDesktopTransitionHandler.CancelState.CANCEL_SPLIT_LEFT + } else { + DragToDesktopTransitionHandler.CancelState.CANCEL_SPLIT_RIGHT + } + dragToDesktopTransitionHandler.cancelDragToDesktopTransition(cancelState) + } else { + val wct = WindowContainerTransaction() + addMoveToSplitChanges(wct, taskInfo) + splitScreenController.requestEnterSplitSelect( + taskInfo, + wct, + if (leftOrTop) SPLIT_POSITION_TOP_OR_LEFT else SPLIT_POSITION_BOTTOM_OR_RIGHT, + taskInfo.configuration.windowConfiguration.bounds + ) + } } } @@ -1220,7 +1233,10 @@ class DesktopTasksController( * @param taskInfo the task being dragged. * @param y height of drag, to be checked against status bar height. */ - fun onDragPositioningEndThroughStatusBar(inputCoordinates: PointF, taskInfo: RunningTaskInfo) { + fun onDragPositioningEndThroughStatusBar( + inputCoordinates: PointF, + taskInfo: RunningTaskInfo, + ) { val indicator = getVisualIndicator() ?: return val indicatorType = indicator.updateIndicatorType(inputCoordinates, taskInfo.windowingMode) when (indicatorType) { @@ -1237,10 +1253,10 @@ class DesktopTasksController( cancelDragToDesktop(taskInfo) } DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_LEFT_INDICATOR -> { - finalizeDragToDesktop(taskInfo, getSnapBounds(taskInfo, SnapPosition.LEFT)) + requestSplit(taskInfo, leftOrTop = true) } DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_RIGHT_INDICATOR -> { - finalizeDragToDesktop(taskInfo, getSnapBounds(taskInfo, SnapPosition.RIGHT)) + requestSplit(taskInfo, leftOrTop = false) } } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt index 98c79d7174a9..d99b724c936f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt @@ -4,6 +4,7 @@ import android.animation.Animator import android.animation.AnimatorListenerAdapter import android.animation.RectEvaluator import android.animation.ValueAnimator +import android.app.ActivityManager.RunningTaskInfo import android.app.ActivityOptions import android.app.ActivityOptions.SourceInfo import android.app.ActivityTaskManager.INVALID_TASK_ID @@ -12,9 +13,11 @@ import android.app.PendingIntent.FLAG_ALLOW_UNSAFE_IMPLICIT_INTENT import android.app.PendingIntent.FLAG_MUTABLE import android.app.WindowConfiguration.ACTIVITY_TYPE_HOME import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM +import android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW import android.content.Context import android.content.Intent import android.content.Intent.FILL_IN_COMPONENT +import android.graphics.PointF import android.graphics.Rect import android.os.Bundle import android.os.IBinder @@ -30,6 +33,7 @@ import com.android.wm.shell.RootTaskDisplayAreaOrganizer import com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT import com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT import com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED +import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition import com.android.wm.shell.protolog.ShellProtoLogGroup import com.android.wm.shell.shared.TransitionUtil import com.android.wm.shell.splitscreen.SplitScreenController @@ -186,7 +190,7 @@ class DragToDesktopTransitionHandler( * outside the desktop drop zone and is instead dropped back into the status bar region that * means the user wants to remain in their current windowing mode. */ - fun cancelDragToDesktopTransition() { + fun cancelDragToDesktopTransition(cancelState: CancelState) { if (!inProgress) { // Don't attempt to cancel a drag to desktop transition since there is no transition in // progress which means that the drag to desktop transition was never successfully @@ -200,13 +204,32 @@ class DragToDesktopTransitionHandler( clearState() return } - state.cancelled = true - if (state.draggedTaskChange != null) { + state.cancelState = cancelState + + if (state.draggedTaskChange != null && cancelState == CancelState.STANDARD_CANCEL) { // Regular case, transient launch of Home happened as is waiting for the cancel // transient to start and merge. Animate the cancellation (scale back to original // bounds) first before actually starting the cancel transition so that the wallpaper // is visible behind the animating task. startCancelAnimation() + } else if ( + state.draggedTaskChange != null && + (cancelState == CancelState.CANCEL_SPLIT_LEFT || + cancelState == CancelState.CANCEL_SPLIT_RIGHT) + ) { + // We have a valid dragged task, but the animation will be handled by + // SplitScreenController; request the transition here. + @SplitPosition val splitPosition = if (cancelState == CancelState.CANCEL_SPLIT_LEFT) { + SPLIT_POSITION_TOP_OR_LEFT + } else { + SPLIT_POSITION_BOTTOM_OR_RIGHT + } + val wct = WindowContainerTransaction() + restoreWindowOrder(wct, state) + state.startTransitionFinishTransaction?.apply() + state.startTransitionFinishCb?.onTransitionFinished(null /* wct */) + requestSplitFromScaledTask(splitPosition, wct) + clearState() } else { // There's no dragged task, this can happen when the "cancel" happened too quickly // before the "start" transition is even ready (like on a fling gesture). The @@ -217,6 +240,54 @@ class DragToDesktopTransitionHandler( } } + /** Calculate the bounds of a scaled task, then use those bounds to request split select. */ + private fun requestSplitFromScaledTask( + @SplitPosition splitPosition: Int, + wct: WindowContainerTransaction + ) { + val state = requireTransitionState() + val taskInfo = state.draggedTaskChange?.taskInfo + ?: error("Expected non-null taskInfo") + val taskBounds = Rect(taskInfo.configuration.windowConfiguration.bounds) + val taskScale = state.dragAnimator.scale + val scaledWidth = taskBounds.width() * taskScale + val scaledHeight = taskBounds.height() * taskScale + val dragPosition = PointF(state.dragAnimator.position) + state.dragAnimator.cancelAnimator() + val animatedTaskBounds = Rect( + dragPosition.x.toInt(), + dragPosition.y.toInt(), + (dragPosition.x + scaledWidth).toInt(), + (dragPosition.y + scaledHeight).toInt() + ) + requestSplitSelect(wct, taskInfo, splitPosition, animatedTaskBounds) + } + + private fun requestSplitSelect( + wct: WindowContainerTransaction, + taskInfo: RunningTaskInfo, + @SplitPosition splitPosition: Int, + taskBounds: Rect = Rect(taskInfo.configuration.windowConfiguration.bounds) + ) { + // Prepare to exit split in order to enter split select. + if (taskInfo.windowingMode == WINDOWING_MODE_MULTI_WINDOW) { + splitScreenController.prepareExitSplitScreen( + wct, + splitScreenController.getStageOfTask(taskInfo.taskId), + SplitScreenController.EXIT_REASON_DESKTOP_MODE + ) + splitScreenController.transitionHandler.onSplitToDesktop() + } + wct.setWindowingMode(taskInfo.token, WINDOWING_MODE_MULTI_WINDOW) + wct.setDensityDpi(taskInfo.token, context.resources.displayMetrics.densityDpi) + splitScreenController.requestEnterSplitSelect( + taskInfo, + wct, + splitPosition, + taskBounds + ) + } + override fun startAnimation( transition: IBinder, info: TransitionInfo, @@ -261,7 +332,7 @@ class DragToDesktopTransitionHandler( is TransitionState.FromSplit -> { state.splitRootChange = change val layer = - if (!state.cancelled) { + if (state.cancelState == CancelState.NO_CANCEL) { // Normal case, split root goes to the bottom behind everything // else. appLayers - i @@ -311,8 +382,18 @@ class DragToDesktopTransitionHandler( // Do not do this in the cancel-early case though, since in that case nothing should // happen on screen so the layering will remain the same as if no transition // occurred. - if (change.taskInfo?.taskId == state.draggedTaskId && !state.cancelled) { + if ( + change.taskInfo?.taskId == state.draggedTaskId && + state.cancelState != CancelState.STANDARD_CANCEL + ) { + // We need access to the dragged task's change in both non-cancel and split + // cancel cases. state.draggedTaskChange = change + } + if ( + change.taskInfo?.taskId == state.draggedTaskId && + state.cancelState == CancelState.NO_CANCEL + ) { taskDisplayAreaOrganizer.reparentToDisplayArea( change.endDisplayId, change.leash, @@ -331,11 +412,11 @@ class DragToDesktopTransitionHandler( state.startTransitionFinishTransaction = finishTransaction startTransaction.apply() - if (!state.cancelled) { + if (state.cancelState == CancelState.NO_CANCEL) { // Normal case, start animation to scale down the dragged task. It'll also be moved to // follow the finger and when released we'll start the next phase/transition. state.dragAnimator.startAnimation() - } else { + } else if (state.cancelState == CancelState.STANDARD_CANCEL) { // Cancel-early case, the state was flagged was cancelled already, which means the // gesture ended in the cancel region. This can happen even before the start transition // is ready/animate here when cancelling quickly like with a fling. There's no point @@ -343,6 +424,26 @@ class DragToDesktopTransitionHandler( // directly into starting the cancel transition to restore WM order. Surfaces should // not move as if no transition happened. startCancelDragToDesktopTransition() + } else if ( + state.cancelState == CancelState.CANCEL_SPLIT_LEFT || + state.cancelState == CancelState.CANCEL_SPLIT_RIGHT + ){ + // Cancel-early case for split-cancel. The state was flagged already as a cancel for + // requesting split select. Similar to the above, this can happen due to quick fling + // gestures. We can simply request split here without needing to calculate animated + // task bounds as the task has not shrunk at all. + val splitPosition = if (state.cancelState == CancelState.CANCEL_SPLIT_LEFT) { + SPLIT_POSITION_TOP_OR_LEFT + } else { + SPLIT_POSITION_BOTTOM_OR_RIGHT + } + val taskInfo = state.draggedTaskChange?.taskInfo + ?: error("Expected non-null task info.") + val wct = WindowContainerTransaction() + restoreWindowOrder(wct) + state.startTransitionFinishTransaction?.apply() + state.startTransitionFinishCb?.onTransitionFinished(null /* wct */) + requestSplitSelect(wct, taskInfo, splitPosition) } return true } @@ -355,6 +456,12 @@ class DragToDesktopTransitionHandler( finishCallback: Transitions.TransitionFinishCallback ) { val state = requireTransitionState() + // We don't want to merge the split select animation if that's what we requested. + if (state.cancelState == CancelState.CANCEL_SPLIT_LEFT || + state.cancelState == CancelState.CANCEL_SPLIT_RIGHT) { + clearState() + return + } val isCancelTransition = info.type == TRANSIT_DESKTOP_MODE_CANCEL_DRAG_TO_DESKTOP && transition == state.cancelTransitionToken && @@ -552,6 +659,17 @@ class DragToDesktopTransitionHandler( private fun startCancelDragToDesktopTransition() { val state = requireTransitionState() val wct = WindowContainerTransaction() + restoreWindowOrder(wct, state) + state.cancelTransitionToken = + transitions.startTransition( + TRANSIT_DESKTOP_MODE_CANCEL_DRAG_TO_DESKTOP, wct, this + ) + } + + private fun restoreWindowOrder( + wct: WindowContainerTransaction, + state: TransitionState = requireTransitionState() + ) { when (state) { is TransitionState.FromFullscreen -> { // There may have been tasks sent behind home that are not the dragged task (like @@ -580,9 +698,6 @@ class DragToDesktopTransitionHandler( } val homeWc = state.homeToken ?: error("Home task should be non-null before cancelling") wct.restoreTransientOrder(homeWc) - - state.cancelTransitionToken = - transitions.startTransition(TRANSIT_DESKTOP_MODE_CANCEL_DRAG_TO_DESKTOP, wct, this) } private fun clearState() { @@ -624,7 +739,7 @@ class DragToDesktopTransitionHandler( abstract var cancelTransitionToken: IBinder? abstract var homeToken: WindowContainerToken? abstract var draggedTaskChange: Change? - abstract var cancelled: Boolean + abstract var cancelState: CancelState abstract var startAborted: Boolean data class FromFullscreen( @@ -636,7 +751,7 @@ class DragToDesktopTransitionHandler( override var cancelTransitionToken: IBinder? = null, override var homeToken: WindowContainerToken? = null, override var draggedTaskChange: Change? = null, - override var cancelled: Boolean = false, + override var cancelState: CancelState = CancelState.NO_CANCEL, override var startAborted: Boolean = false, var otherRootChanges: MutableList<Change> = mutableListOf() ) : TransitionState() @@ -650,13 +765,25 @@ class DragToDesktopTransitionHandler( override var cancelTransitionToken: IBinder? = null, override var homeToken: WindowContainerToken? = null, override var draggedTaskChange: Change? = null, - override var cancelled: Boolean = false, + override var cancelState: CancelState = CancelState.NO_CANCEL, override var startAborted: Boolean = false, var splitRootChange: Change? = null, var otherSplitTask: Int ) : TransitionState() } + /** Enum to provide context on cancelling a drag to desktop event. */ + enum class CancelState { + /** No cancel case; this drag is not flagged for a cancel event. */ + NO_CANCEL, + /** A standard cancel event; should restore task to previous windowing mode. */ + STANDARD_CANCEL, + /** A cancel event where the task will request to enter split on the left side. */ + CANCEL_SPLIT_LEFT, + /** A cancel event where the task will request to enter split on the right side. */ + CANCEL_SPLIT_RIGHT + } + companion object { /** The duration of the animation to commit or cancel the drag-to-desktop gesture. */ private const val DRAG_TO_DESKTOP_FINISH_ANIM_DURATION_MS = 336L diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt index 2ade3fba9b08..bbf523bc40d2 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt @@ -18,6 +18,8 @@ import androidx.test.filters.SmallTest import com.android.wm.shell.RootTaskDisplayAreaOrganizer import com.android.wm.shell.ShellTestCase import com.android.wm.shell.TestRunningTaskInfoBuilder +import com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT +import com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT import com.android.wm.shell.splitscreen.SplitScreenController import com.android.wm.shell.transition.Transitions import com.android.wm.shell.transition.Transitions.TRANSIT_DESKTOP_MODE_CANCEL_DRAG_TO_DESKTOP @@ -48,6 +50,7 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() { @Mock private lateinit var transitions: Transitions @Mock private lateinit var taskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer @Mock private lateinit var splitScreenController: SplitScreenController + @Mock private lateinit var dragAnimator: MoveToDesktopAnimator private val transactionSupplier = Supplier { mock<SurfaceControl.Transaction>() } @@ -68,7 +71,6 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() { @Test fun startDragToDesktop_animateDragWhenReady() { val task = createTask() - val dragAnimator = mock<MoveToDesktopAnimator>() // Simulate transition is started. val transition = startDragToDesktopTransition(task, dragAnimator) @@ -90,36 +92,36 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() { @Test fun startDragToDesktop_cancelledBeforeReady_startCancelTransition() { - val task = createTask() - val dragAnimator = mock<MoveToDesktopAnimator>() - // Simulate transition is started and is ready to animate. - val transition = startDragToDesktopTransition(task, dragAnimator) - - handler.cancelDragToDesktopTransition() + performEarlyCancel(DragToDesktopTransitionHandler.CancelState.STANDARD_CANCEL) + verify(transitions) + .startTransition(eq(TRANSIT_DESKTOP_MODE_CANCEL_DRAG_TO_DESKTOP), any(), eq(handler)) + } - handler.startAnimation( - transition = transition, - info = - createTransitionInfo( - type = TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP, - draggedTask = task - ), - startTransaction = mock(), - finishTransaction = mock(), - finishCallback = {} + @Test + fun startDragToDesktop_cancelledBeforeReady_verifySplitLeftCancel() { + performEarlyCancel(DragToDesktopTransitionHandler.CancelState.CANCEL_SPLIT_LEFT) + verify(splitScreenController).requestEnterSplitSelect( + any(), + any(), + eq(SPLIT_POSITION_TOP_OR_LEFT), + any() ) + } - // Don't even animate the "drag" since it was already cancelled. - verify(dragAnimator, never()).startAnimation() - // Instead, start the cancel transition. - verify(transitions) - .startTransition(eq(TRANSIT_DESKTOP_MODE_CANCEL_DRAG_TO_DESKTOP), any(), eq(handler)) + @Test + fun startDragToDesktop_cancelledBeforeReady_verifySplitRightCancel() { + performEarlyCancel(DragToDesktopTransitionHandler.CancelState.CANCEL_SPLIT_RIGHT) + verify(splitScreenController).requestEnterSplitSelect( + any(), + any(), + eq(SPLIT_POSITION_BOTTOM_OR_RIGHT), + any() + ) } @Test fun startDragToDesktop_aborted_finishDropped() { val task = createTask() - val dragAnimator = mock<MoveToDesktopAnimator>() // Simulate transition is started. val transition = startDragToDesktopTransition(task, dragAnimator) // But the transition was aborted. @@ -137,14 +139,15 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() { @Test fun startDragToDesktop_aborted_cancelDropped() { val task = createTask() - val dragAnimator = mock<MoveToDesktopAnimator>() // Simulate transition is started. val transition = startDragToDesktopTransition(task, dragAnimator) // But the transition was aborted. handler.onTransitionConsumed(transition, aborted = true, mock()) // Attempt to finish the failed drag start. - handler.cancelDragToDesktopTransition() + handler.cancelDragToDesktopTransition( + DragToDesktopTransitionHandler.CancelState.STANDARD_CANCEL + ) // Should not be attempted and state should be reset. assertFalse(handler.inProgress) @@ -153,7 +156,6 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() { @Test fun startDragToDesktop_anotherTransitionInProgress_startDropped() { val task = createTask() - val dragAnimator = mock<MoveToDesktopAnimator>() // Simulate attempt to start two drag to desktop transitions. startDragToDesktopTransition(task, dragAnimator) @@ -169,39 +171,63 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() { @Test fun cancelDragToDesktop_startWasReady_cancel() { - val task = createTask() - val dragAnimator = mock<MoveToDesktopAnimator>() - whenever(dragAnimator.position).thenReturn(PointF()) - // Simulate transition is started and is ready to animate. - val transition = startDragToDesktopTransition(task, dragAnimator) - handler.startAnimation( - transition = transition, - info = - createTransitionInfo( - type = TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP, - draggedTask = task - ), - startTransaction = mock(), - finishTransaction = mock(), - finishCallback = {} - ) + startDrag() // Then user cancelled after it had already started. - handler.cancelDragToDesktopTransition() + handler.cancelDragToDesktopTransition( + DragToDesktopTransitionHandler.CancelState.STANDARD_CANCEL + ) // Cancel animation should run since it had already started. verify(dragAnimator).cancelAnimator() } @Test + fun cancelDragToDesktop_splitLeftCancelType_splitRequested() { + startDrag() + + // Then user cancelled it, requesting split. + handler.cancelDragToDesktopTransition( + DragToDesktopTransitionHandler.CancelState.CANCEL_SPLIT_LEFT + ) + + // Verify the request went through split controller. + verify(splitScreenController).requestEnterSplitSelect( + any(), + any(), + eq(SPLIT_POSITION_TOP_OR_LEFT), + any() + ) + } + + @Test + fun cancelDragToDesktop_splitRightCancelType_splitRequested() { + startDrag() + + // Then user cancelled it, requesting split. + handler.cancelDragToDesktopTransition( + DragToDesktopTransitionHandler.CancelState.CANCEL_SPLIT_RIGHT + ) + + // Verify the request went through split controller. + verify(splitScreenController).requestEnterSplitSelect( + any(), + any(), + eq(SPLIT_POSITION_BOTTOM_OR_RIGHT), + any() + ) + } + + @Test fun cancelDragToDesktop_startWasNotReady_animateCancel() { val task = createTask() - val dragAnimator = mock<MoveToDesktopAnimator>() // Simulate transition is started and is ready to animate. startDragToDesktopTransition(task, dragAnimator) // Then user cancelled before the transition was ready and animated. - handler.cancelDragToDesktopTransition() + handler.cancelDragToDesktopTransition( + DragToDesktopTransitionHandler.CancelState.STANDARD_CANCEL + ) // No need to animate the cancel since the start animation couldn't even start. verifyZeroInteractions(dragAnimator) @@ -210,7 +236,9 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() { @Test fun cancelDragToDesktop_transitionNotInProgress_dropCancel() { // Then cancel is called before the transition was started. - handler.cancelDragToDesktopTransition() + handler.cancelDragToDesktopTransition( + DragToDesktopTransitionHandler.CancelState.STANDARD_CANCEL + ) // Verify cancel is dropped. verify(transitions, never()).startTransition( @@ -233,6 +261,24 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() { ) } + private fun startDrag() { + val task = createTask() + whenever(dragAnimator.position).thenReturn(PointF()) + // Simulate transition is started and is ready to animate. + val transition = startDragToDesktopTransition(task, dragAnimator) + handler.startAnimation( + transition = transition, + info = + createTransitionInfo( + type = TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP, + draggedTask = task + ), + startTransaction = mock(), + finishTransaction = mock(), + finishCallback = {} + ) + } + private fun startDragToDesktopTransition( task: RunningTaskInfo, dragAnimator: MoveToDesktopAnimator @@ -250,6 +296,29 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() { return token } + private fun performEarlyCancel(cancelState: DragToDesktopTransitionHandler.CancelState) { + val task = createTask() + // Simulate transition is started and is ready to animate. + val transition = startDragToDesktopTransition(task, dragAnimator) + + handler.cancelDragToDesktopTransition(cancelState) + + handler.startAnimation( + transition = transition, + info = + createTransitionInfo( + type = TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP, + draggedTask = task + ), + startTransaction = mock(), + finishTransaction = mock(), + finishCallback = {} + ) + + // Don't even animate the "drag" since it was already cancelled. + verify(dragAnimator, never()).startAnimation() + } + private fun createTask( @WindowingMode windowingMode: Int = WINDOWING_MODE_FULLSCREEN, isHome: Boolean = false, |