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

Commit 0f223887 authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "[1/N] Cancel DragToDesktop on incoming transition if bookend requested" into main

parents 7e2d8841 70c391aa
Loading
Loading
Loading
Loading
+4 −0
Original line number Diff line number Diff line
@@ -238,6 +238,10 @@ class DesktopTasksController(
                removeVisualIndicator()
            }

            override fun onTransitionInterrupted() {
                removeVisualIndicator()
            }

            private fun removeVisualIndicator() {
                visualIndicator?.fadeOutIndicator { releaseVisualIndicator() }
            }
+144 −6
Original line number Diff line number Diff line
@@ -24,8 +24,10 @@ import android.os.SystemClock
import android.os.SystemProperties
import android.os.UserHandle
import android.view.SurfaceControl
import android.view.SurfaceControl.Transaction
import android.view.WindowManager.TRANSIT_CLOSE
import android.window.DesktopModeFlags
import android.window.DesktopModeFlags.ENABLE_DRAG_TO_DESKTOP_INCOMING_TRANSITIONS_BUGFIX
import android.window.TransitionInfo
import android.window.TransitionInfo.Change
import android.window.TransitionRequestInfo
@@ -185,18 +187,29 @@ sealed class DragToDesktopTransitionHandler(
     */
    fun finishDragToDesktopTransition(wct: WindowContainerTransaction): IBinder? {
        if (!inProgress) {
            logV("finishDragToDesktop: not in progress, returning")
            // Don't attempt to finish a drag to desktop transition since there is no transition in
            // progress which means that the drag to desktop transition was never successfully
            // started.
            return null
        }
        if (requireTransitionState().startAborted) {
        val state = requireTransitionState()
        if (state.startAborted) {
            logV("finishDragToDesktop: start was aborted, clearing state")
            // Don't attempt to complete the drag-to-desktop since the start transition didn't
            // succeed as expected. Just reset the state as if nothing happened.
            clearState()
            return null
        }
        return transitions.startTransition(TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP, wct, this)
        if (state.startInterrupted) {
            logV("finishDragToDesktop: start was interrupted, returning")
            // We should only have interrupted the start transition after receiving a cancel/end
            // request, let that existing request play out and just return here.
            return null
        }
        state.endTransitionToken =
            transitions.startTransition(TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP, wct, this)
        return state.endTransitionToken
    }

    /**
@@ -220,6 +233,11 @@ sealed class DragToDesktopTransitionHandler(
            clearState()
            return
        }
        if (state.startInterrupted) {
            // We should only have interrupted the start transition after receiving a cancel/end
            // request, let that existing request play out and just return here.
            return
        }
        state.cancelState = cancelState

        if (state.draggedTaskChange != null && cancelState == CancelState.STANDARD_CANCEL) {
@@ -227,7 +245,7 @@ sealed class DragToDesktopTransitionHandler(
            // 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()
            state.activeCancelAnimation = startCancelAnimation()
        } else if (
            state.draggedTaskChange != null &&
                (cancelState == CancelState.CANCEL_SPLIT_LEFT ||
@@ -255,7 +273,7 @@ sealed class DragToDesktopTransitionHandler(
        ) {
            if (bubbleController.isEmpty || state !is TransitionState.FromFullscreen) {
                // TODO(b/388853233): add support for dragging split task to bubble
                startCancelAnimation()
                state.activeCancelAnimation = startCancelAnimation()
            } else {
                // Animation is handled by BubbleController
                val wct = WindowContainerTransaction()
@@ -357,6 +375,19 @@ sealed class DragToDesktopTransitionHandler(
    ): Boolean {
        val state = requireTransitionState()

        if (
            handleCancelOrExitAfterInterrupt(
                transition,
                info,
                startTransaction,
                finishTransaction,
                finishCallback,
                state,
            )
        ) {
            return true
        }

        val isStartDragToDesktop =
            info.type == TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP &&
                transition == state.startTransitionToken
@@ -539,6 +570,58 @@ sealed class DragToDesktopTransitionHandler(
        }
    }

    private fun handleCancelOrExitAfterInterrupt(
        transition: IBinder,
        info: TransitionInfo,
        startTransaction: Transaction,
        finishTransaction: Transaction,
        finishCallback: Transitions.TransitionFinishCallback,
        state: TransitionState,
    ): Boolean {
        if (!ENABLE_DRAG_TO_DESKTOP_INCOMING_TRANSITIONS_BUGFIX.isTrue) {
            return false
        }
        val isCancelDragToDesktop =
            info.type == TRANSIT_DESKTOP_MODE_CANCEL_DRAG_TO_DESKTOP &&
                transition == state.cancelTransitionToken
        val isEndDragToDesktop =
            info.type == TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP &&
                transition == state.endTransitionToken
        // We should only receive cancel or end transitions through startAnimation() if the
        // start transition was interrupted while a cancel- or end-transition had already
        // been requested. Finish the cancel/end transition to avoid having to deal with more
        // incoming transitions, and clear the state for the next start-drag transition.
        if (!isCancelDragToDesktop && !isEndDragToDesktop) {
            return false
        }
        if (!state.startInterrupted) {
            logW(
                "Not interrupted, but received startAnimation for cancel/end drag." +
                    "isCancel=$isCancelDragToDesktop, isEnd=$isEndDragToDesktop"
            )
            return false
        }
        logV(
            "startAnimation: interrupted -> " +
                "isCancel=$isCancelDragToDesktop, isEnd=$isEndDragToDesktop"
        )
        if (isEndDragToDesktop) {
            setupEndDragToDesktop(info, startTransaction, finishTransaction)
            animateEndDragToDesktop(startTransaction = startTransaction, finishCallback)
        } else { // isCancelDragToDesktop
            // Similar to when we merge the cancel transition: ensure all tasks involved in the
            // cancel transition are shown, and finish the transition immediately.
            info.changes.forEach { change ->
                startTransaction.show(change.leash)
                finishTransaction.show(change.leash)
            }
        }
        startTransaction.apply()
        finishCallback.onTransitionFinished(/* wct= */ null)
        clearState()
        return true
    }

    /**
     * Calculates start drag to desktop layers for transition [info]. The leash layer is calculated
     * based on its change position in the transition, e.g. `appLayer = appLayers - i`, where i is
@@ -590,6 +673,7 @@ sealed class DragToDesktopTransitionHandler(
                ?: error("Start transition expected to be waiting for merge but wasn't")
        if (isEndTransition) {
            logV("mergeAnimation: end-transition, target=$mergeTarget")
            state.mergedEndTransition = true
            setupEndDragToDesktop(
                info,
                startTransaction = startT,
@@ -617,6 +701,41 @@ sealed class DragToDesktopTransitionHandler(
            return
        }
        logW("unhandled merge transition: transitionInfo=$info")
        // Handle unknown incoming transitions by finishing the start transition. For now, only do
        // this if we've already requested a cancel- or end transition. If we've already merged the
        // end-transition, or if the end-transition is running on its own, then just wait until that
        // finishes instead. If we've merged the cancel-transition we've finished the
        // start-transition and won't reach this code.
        if (
            mergeTarget == state.startTransitionToken &&
                isCancelOrEndTransitionRequested(state) &&
                !state.mergedEndTransition
        ) {
            interruptStartTransition(state)
        }
    }

    private fun isCancelOrEndTransitionRequested(state: TransitionState): Boolean =
        state.cancelTransitionToken != null || state.endTransitionToken != null

    private fun interruptStartTransition(state: TransitionState) {
        if (!ENABLE_DRAG_TO_DESKTOP_INCOMING_TRANSITIONS_BUGFIX.isTrue) {
            return
        }
        logV("interruptStartTransition")
        state.startTransitionFinishCb?.onTransitionFinished(/* wct= */ null)
        state.dragAnimator.cancelAnimator()
        state.activeCancelAnimation?.removeAllListeners()
        state.activeCancelAnimation?.cancel()
        state.activeCancelAnimation = null
        // Keep the transition state so we can deal with Cancel/End properly in #startAnimation.
        state.startInterrupted = true
        dragToDesktopStateListener?.onTransitionInterrupted()
        // Cancel CUJs here as they won't be accurate now that an incoming transition is playing.
        interactionJankMonitor.cancel(CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_HOLD)
        interactionJankMonitor.cancel(CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE)
        LatencyTracker.getInstance(context)
            .onActionCancel(LatencyTracker.ACTION_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG)
    }

    protected open fun setupEndDragToDesktop(
@@ -783,7 +902,7 @@ sealed class DragToDesktopTransitionHandler(
        } ?: false
    }

    private fun startCancelAnimation() {
    private fun startCancelAnimation(): Animator {
        val state = requireTransitionState()
        val dragToDesktopAnimator = state.dragAnimator

@@ -800,7 +919,7 @@ sealed class DragToDesktopTransitionHandler(
        val dx = targetX - x
        val dy = targetY - y
        val tx: SurfaceControl.Transaction = transactionSupplier.get()
        ValueAnimator.ofFloat(DRAG_FREEFORM_SCALE, 1f)
        return ValueAnimator.ofFloat(DRAG_FREEFORM_SCALE, 1f)
            .setDuration(DRAG_TO_DESKTOP_FINISH_ANIM_DURATION_MS)
            .apply {
                addUpdateListener { animator ->
@@ -818,6 +937,7 @@ sealed class DragToDesktopTransitionHandler(
                addListener(
                    object : AnimatorListenerAdapter() {
                        override fun onAnimationEnd(animation: Animator) {
                            state.activeCancelAnimation = null
                            dragToDesktopStateListener?.onCancelToDesktopAnimationEnd()
                            // Start the cancel transition to restore order.
                            startCancelDragToDesktopTransition()
@@ -910,10 +1030,16 @@ sealed class DragToDesktopTransitionHandler(
        val dragLayer: Int,
    )

    /** Listener for various events happening during the DragToDesktop transition. */
    interface DragToDesktopStateListener {
        /** Indicates that the animation into Desktop has started. */
        fun onCommitToDesktopAnimationStart()

        /** Called when the animation to cancel the desktop-drag has finished. */
        fun onCancelToDesktopAnimationEnd()

        /** Indicates that the drag-to-desktop transition has been interrupted. */
        fun onTransitionInterrupted()
    }

    sealed class TransitionState {
@@ -930,6 +1056,10 @@ sealed class DragToDesktopTransitionHandler(
        abstract var cancelState: CancelState
        abstract var startAborted: Boolean
        abstract val visualIndicator: DesktopModeVisualIndicator?
        abstract var startInterrupted: Boolean
        abstract var endTransitionToken: IBinder?
        abstract var mergedEndTransition: Boolean
        abstract var activeCancelAnimation: Animator?

        data class FromFullscreen(
            override val draggedTaskId: Int,
@@ -945,6 +1075,10 @@ sealed class DragToDesktopTransitionHandler(
            override var cancelState: CancelState = CancelState.NO_CANCEL,
            override var startAborted: Boolean = false,
            override val visualIndicator: DesktopModeVisualIndicator?,
            override var startInterrupted: Boolean = false,
            override var endTransitionToken: IBinder? = null,
            override var mergedEndTransition: Boolean = false,
            override var activeCancelAnimation: Animator? = null,
            var otherRootChanges: MutableList<Change> = mutableListOf(),
        ) : TransitionState()

@@ -962,6 +1096,10 @@ sealed class DragToDesktopTransitionHandler(
            override var cancelState: CancelState = CancelState.NO_CANCEL,
            override var startAborted: Boolean = false,
            override val visualIndicator: DesktopModeVisualIndicator?,
            override var startInterrupted: Boolean = false,
            override var endTransitionToken: IBinder? = null,
            override var mergedEndTransition: Boolean = false,
            override var activeCancelAnimation: Animator? = null,
            var splitRootChange: Change? = null,
            var otherSplitTask: Int,
        ) : TransitionState()
+133 −10
Original line number Diff line number Diff line
@@ -26,6 +26,7 @@ import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_HOLD
import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE
import com.android.internal.jank.InteractionJankMonitor
import com.android.window.flags.Flags
import com.android.window.flags.Flags.FLAG_ENABLE_DRAG_TO_DESKTOP_INCOMING_TRANSITIONS_BUGFIX
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.ShellTestCase
import com.android.wm.shell.TestRunningTaskInfoBuilder
@@ -34,6 +35,7 @@ import com.android.wm.shell.bubbles.BubbleTransitions
import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_DESKTOP_MODE_CANCEL_DRAG_TO_DESKTOP
import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP
import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP
import com.android.wm.shell.desktopmode.DragToDesktopTransitionHandler.CancelState
import com.android.wm.shell.desktopmode.DragToDesktopTransitionHandler.Companion.DRAG_TO_DESKTOP_FINISH_ANIM_DURATION_MS
import com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT
import com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT
@@ -56,6 +58,7 @@ import org.mockito.ArgumentMatchers.anyInt
import org.mockito.ArgumentMatchers.eq
import org.mockito.Mock
import org.mockito.MockitoSession
import org.mockito.kotlin.anyOrNull
import org.mockito.kotlin.argThat
import org.mockito.kotlin.mock
import org.mockito.kotlin.never
@@ -118,6 +121,14 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() {
                .strictness(Strictness.LENIENT)
                .mockStatic(SystemProperties::class.java)
                .startMocking()
        whenever(
                transitions.startTransition(
                    eq(TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP),
                    /* wct= */ any(),
                    eq(defaultHandler),
                )
            )
            .thenReturn(mock<IBinder>())
    }

    @After
@@ -679,17 +690,11 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() {
        val startTransition = startDrag(defaultHandler, task)
        val endTransition = mock<IBinder>()
        defaultHandler.onTaskResizeAnimationListener = mock()
        defaultHandler.mergeAnimation(
        mergeAnimation(
            transition = endTransition,
            info =
                createTransitionInfo(
            type = TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP,
                    draggedTask = task,
                ),
            startT = mock<SurfaceControl.Transaction>(),
            finishT = mock<SurfaceControl.Transaction>(),
            task = task,
            mergeTarget = startTransition,
            finishCallback = mock<Transitions.TransitionFinishCallback>(),
        )

        defaultHandler.onTransitionConsumed(endTransition, aborted = true, mock())
@@ -700,6 +705,123 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() {
            .cancel(eq(CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_HOLD))
    }

    @Test
    @EnableFlags(FLAG_ENABLE_DRAG_TO_DESKTOP_INCOMING_TRANSITIONS_BUGFIX)
    fun mergeOtherTransition_cancelAndEndNotYetRequested_doesntInterruptsStartDrag() {
        val finishCallback = mock<Transitions.TransitionFinishCallback>()
        val task = createTask()
        defaultHandler.onTaskResizeAnimationListener = mock()
        val startTransition = startDrag(defaultHandler, task, finishCallback = finishCallback)

        mergeInterruptingTransition(mergeTarget = startTransition)

        verify(finishCallback, never()).onTransitionFinished(anyOrNull())
        verify(dragAnimator, never()).cancelAnimator()
    }

    @Test
    @EnableFlags(FLAG_ENABLE_DRAG_TO_DESKTOP_INCOMING_TRANSITIONS_BUGFIX)
    fun mergeOtherTransition_endDragAlreadyMerged_doesNotInterruptStartDrag() {
        val startDragFinishCallback = mock<Transitions.TransitionFinishCallback>()
        val task = createTask()
        val startTransition =
            startDrag(defaultHandler, task, finishCallback = startDragFinishCallback)
        defaultHandler.onTaskResizeAnimationListener = mock()
        mergeAnimation(
            type = TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP,
            task = task,
            mergeTarget = startTransition,
        )

        mergeInterruptingTransition(mergeTarget = startTransition)

        verify(startDragFinishCallback, never()).onTransitionFinished(anyOrNull())
    }

    @Test
    @EnableFlags(FLAG_ENABLE_DRAG_TO_DESKTOP_INCOMING_TRANSITIONS_BUGFIX)
    fun startEndAnimation_otherTransitionInterruptedStartAfterEndRequest_finishImmediately() {
        val task1 = createTask()
        val startTransition = startDrag(defaultHandler, task1)
        val endTransition =
            defaultHandler.finishDragToDesktopTransition(WindowContainerTransaction())
        val startTransaction = mock<SurfaceControl.Transaction>()
        val endDragFinishCallback = mock<Transitions.TransitionFinishCallback>()
        defaultHandler.onTaskResizeAnimationListener = mock()
        mergeInterruptingTransition(mergeTarget = startTransition)

        val didAnimate =
            defaultHandler.startAnimation(
                transition = requireNotNull(endTransition),
                info =
                    createTransitionInfo(
                        type = TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP,
                        draggedTask = task1,
                    ),
                startTransaction = startTransaction,
                finishTransaction = mock(),
                finishCallback = endDragFinishCallback,
            )

        assertThat(didAnimate).isTrue()
        verify(startTransaction).apply()
        verify(endDragFinishCallback).onTransitionFinished(anyOrNull())
    }

    @Test
    @EnableFlags(FLAG_ENABLE_DRAG_TO_DESKTOP_INCOMING_TRANSITIONS_BUGFIX)
    fun startDrag_otherTransitionInterruptedStartAfterEndRequested_animatesDragWhenReady() {
        val task1 = createTask()
        val startTransition = startDrag(defaultHandler, task1)
        verify(dragAnimator).startAnimation()
        val endTransition =
            defaultHandler.finishDragToDesktopTransition(WindowContainerTransaction())
        defaultHandler.onTaskResizeAnimationListener = mock()
        mergeInterruptingTransition(mergeTarget = startTransition)
        defaultHandler.startAnimation(
            transition = requireNotNull(endTransition),
            info =
                createTransitionInfo(
                    type = TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP,
                    draggedTask = task1,
                ),
            startTransaction = mock(),
            finishTransaction = mock(),
            finishCallback = mock(),
        )

        startDrag(defaultHandler, createTask())

        verify(dragAnimator, times(2)).startAnimation()
    }

    private fun mergeInterruptingTransition(mergeTarget: IBinder) {
        defaultHandler.mergeAnimation(
            transition = mock<IBinder>(),
            info = createTransitionInfo(type = TRANSIT_OPEN, draggedTask = createTask()),
            startT = mock(),
            finishT = mock(),
            mergeTarget = mergeTarget,
            finishCallback = mock(),
        )
    }

    private fun mergeAnimation(
        transition: IBinder = mock(),
        type: Int,
        mergeTarget: IBinder,
        task: RunningTaskInfo,
    ) {
        defaultHandler.mergeAnimation(
            transition = transition,
            info = createTransitionInfo(type = type, draggedTask = task),
            startT = mock(),
            finishT = mock(),
            mergeTarget = mergeTarget,
            finishCallback = mock(),
        )
    }

    @Test
    fun getAnimationFraction_returnsFraction() {
        val fraction =
@@ -785,6 +907,7 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() {
        finishTransaction: SurfaceControl.Transaction = mock(),
        homeChange: TransitionInfo.Change? = createHomeChange(),
        transitionRootLeash: SurfaceControl = mock(),
        finishCallback: Transitions.TransitionFinishCallback = mock(),
    ): IBinder {
        whenever(dragAnimator.position).thenReturn(PointF())
        // Simulate transition is started and is ready to animate.
@@ -800,7 +923,7 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() {
                ),
            startTransaction = startTransaction,
            finishTransaction = finishTransaction,
            finishCallback = {},
            finishCallback = finishCallback,
        )
        return transition
    }