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

Commit f65c752e authored by Sergey Pinkevich's avatar Sergey Pinkevich
Browse files

Animating tab tearing launch from dropped tab bounds

Bug: 376389593
Flag: com.android.window.flags.enable_desktop_tab_tearing_launch_animation
Test: DesktopModeDragAndDropAnimatorHelperTest

Change-Id: I780ba9eab6c7c084859a2476c642f9d033d8948a
parent e5961aff
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -1398,6 +1398,7 @@ public abstract class WMShellModule {
            CloseDesktopTaskTransitionHandler closeDesktopTaskTransitionHandler,
            Optional<DesktopImmersiveController> desktopImmersiveController,
            DesktopMinimizationTransitionHandler desktopMinimizationTransitionHandler,
            DesktopModeDragAndDropTransitionHandler desktopModeDragAndDropTransitionHandler,
            InteractionJankMonitor interactionJankMonitor,
            @ShellMainThread Handler handler,
            ShellInit shellInit,
@@ -1417,6 +1418,7 @@ public abstract class WMShellModule {
                        closeDesktopTaskTransitionHandler,
                        desktopImmersiveController.get(),
                        desktopMinimizationTransitionHandler,
                        desktopModeDragAndDropTransitionHandler,
                        interactionJankMonitor,
                        handler,
                        shellInit,
+18 −1
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@ import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
import android.content.Context
import android.os.Handler
import android.os.IBinder
import android.view.DragEvent
import android.view.SurfaceControl
import android.view.WindowManager
import android.view.WindowManager.TRANSIT_OPEN
@@ -54,6 +55,7 @@ class DesktopMixedTransitionHandler(
    private val closeDesktopTaskTransitionHandler: CloseDesktopTaskTransitionHandler,
    private val desktopImmersiveController: DesktopImmersiveController,
    private val desktopMinimizationTransitionHandler: DesktopMinimizationTransitionHandler,
    private val desktopModeDragAndDropTransitionHandler: DesktopModeDragAndDropTransitionHandler,
    private val interactionJankMonitor: InteractionJankMonitor,
    @ShellMainThread private val handler: Handler,
    shellInit: ShellInit,
@@ -136,10 +138,12 @@ class DesktopMixedTransitionHandler(
        taskId: Int?,
        minimizingTaskId: Int? = null,
        exitingImmersiveTask: Int? = null,
        dragEvent: DragEvent? = null,
    ): IBinder {
        if (
            !DesktopModeFlags.ENABLE_FULLY_IMMERSIVE_IN_DESKTOP.isTrue &&
                !DesktopModeFlags.ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX.isTrue
                !DesktopModeFlags.ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX.isTrue &&
                !DesktopExperienceFlags.ENABLE_DESKTOP_TAB_TEARING_LAUNCH_ANIMATION.isTrue
        ) {
            return transitions.startTransition(transitionType, wct, /* handler= */ null)
        }
@@ -160,6 +164,7 @@ class DesktopMixedTransitionHandler(
                    launchingTask = taskId,
                    minimizingTask = minimizingTaskId,
                    exitingImmersiveTask = exitingImmersiveTask,
                    dragEvent = dragEvent,
                )
            )
        }
@@ -298,6 +303,17 @@ class DesktopMixedTransitionHandler(
                applyMinimizeChangeReparenting(info, minimizeChange, startTransaction)
            }
        }
        if (
            DesktopExperienceFlags.ENABLE_DESKTOP_TAB_TEARING_LAUNCH_ANIMATION.isTrue &&
                pending.dragEvent != null
        ) {
            return desktopModeDragAndDropTransitionHandler.startAnimation(
                info,
                pending.dragEvent,
                startTransaction,
                finishCallback,
            )
        }
        if (immersiveExitChange != null) {
            subAnimationCount = 2
            // Animate the immersive exit change separately.
@@ -521,6 +537,7 @@ class DesktopMixedTransitionHandler(
            val launchingTask: Int?,
            val minimizingTask: Int?,
            val exitingImmersiveTask: Int?,
            val dragEvent: DragEvent? = null,
        ) : PendingMixedTransition()

        /**
+118 −6
Original line number Diff line number Diff line
@@ -20,9 +20,19 @@ import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.animation.ValueAnimator
import android.content.Context
import android.graphics.Rect
import android.view.Choreographer
import android.view.SurfaceControl.Transaction
import android.window.DesktopExperienceFlags
import android.window.TransitionInfo.Change
import androidx.core.util.Supplier
import com.android.wm.shell.animation.FloatProperties
import com.android.wm.shell.desktopmode.SpringDragToDesktopTransitionHandler.Companion.POSITION_SPRING_DAMPING_RATIO
import com.android.wm.shell.desktopmode.SpringDragToDesktopTransitionHandler.Companion.POSITION_SPRING_STIFFNESS
import com.android.wm.shell.desktopmode.SpringDragToDesktopTransitionHandler.Companion.SIZE_SPRING_DAMPING_RATIO
import com.android.wm.shell.desktopmode.SpringDragToDesktopTransitionHandler.Companion.SIZE_SPRING_STIFFNESS
import com.android.wm.shell.desktopmode.SpringDragToDesktopTransitionHandler.Companion.getAnimationFraction
import com.android.wm.shell.shared.animation.PhysicsAnimator
import com.android.wm.shell.transition.Transitions.TransitionFinishCallback
import javax.inject.Inject

@@ -30,6 +40,9 @@ import javax.inject.Inject
 * Helper class for creating and managing animations related to drag and drop operations in Desktop
 * Mode. This class provides methods to create different types of animations, for example, covers
 * different animations for tab tearing.
 *
 * <p>It utilizes {@link PhysicsAnimator} for physics-based animations and {@link ValueAnimator} for
 * simpler animations like fade-in.</p>
 */
class DesktopModeDragAndDropAnimatorHelper
@Inject
@@ -46,10 +59,14 @@ constructor(val context: Context, val transactionSupplier: Supplier<Transaction>
     *   information like the view that should be animated (leash) and the start/end values.
     * @param finishCallback A [TransitionFinishCallback] that will be invoked when the animation
     *   completes. It will inform the caller that the transition is finished.
     * @return An [Animator] instance configured to perform the change described by the `change`
     *   parameter.
     * @return An [DesktopModeDragAndDropAnimator] instance configured to perform the change
     *   described by the `change` parameter.
     */
    fun createAnimator(change: Change, finishCallback: TransitionFinishCallback): Animator {
    fun createAnimator(
        change: Change,
        draggedTaskBounds: Rect,
        finishCallback: TransitionFinishCallback,
    ): DesktopModeDragAndDropAnimator {
        val transaction = transactionSupplier.get()

        val animatorStartedCallback: () -> Unit = {
@@ -58,14 +75,72 @@ constructor(val context: Context, val transactionSupplier: Supplier<Transaction>
        }
        val animatorFinishedCallback: () -> Unit = { finishCallback.onTransitionFinished(null) }

        return createAlphaAnimator(change, animatorStartedCallback, animatorFinishedCallback)
        return if (DesktopExperienceFlags.ENABLE_DESKTOP_TAB_TEARING_LAUNCH_ANIMATION.isTrue) {
            createSpringAnimator(
                change,
                draggedTaskBounds,
                animatorStartedCallback,
                animatorFinishedCallback,
            )
        } else {
            createAlphaAnimator(change, animatorStartedCallback, animatorFinishedCallback)
        }
    }

    private fun createSpringAnimator(
        change: Change,
        draggedTaskBounds: Rect,
        onStart: () -> Unit,
        onFinish: () -> Unit,
    ): DesktopModeDragAndDropAnimator {
        val transaction = transactionSupplier.get()

        val positionSpringConfig =
            PhysicsAnimator.SpringConfig(POSITION_SPRING_STIFFNESS, POSITION_SPRING_DAMPING_RATIO)
        val sizeSpringConfig =
            PhysicsAnimator.SpringConfig(SIZE_SPRING_STIFFNESS, SIZE_SPRING_DAMPING_RATIO)
        val endBounds = change.endAbsBounds

        var hasCalledStart = false
        return DesktopModeDragAndDropSpringAnimator(
            PhysicsAnimator.getInstance(Rect(draggedTaskBounds))
                // TODO(b/412571881): Add velocity to tab tearing animation
                .spring(FloatProperties.RECT_X, endBounds.left.toFloat(), positionSpringConfig)
                .spring(FloatProperties.RECT_Y, endBounds.top.toFloat(), positionSpringConfig)
                .spring(FloatProperties.RECT_WIDTH, endBounds.width().toFloat(), sizeSpringConfig)
                .spring(FloatProperties.RECT_HEIGHT, endBounds.height().toFloat(), sizeSpringConfig)
                .addUpdateListener { animBounds, _ ->
                    if (!hasCalledStart) {
                        onStart.invoke()
                        hasCalledStart = true
                    }
                    val animFraction =
                        getAnimationFraction(
                            startBounds = draggedTaskBounds,
                            endBounds = endBounds,
                            animBounds = animBounds,
                        )
                    transaction.apply {
                        setAlpha(change.leash, animFraction)
                        setScale(change.leash, animFraction, animFraction)
                        setPosition(
                            change.leash,
                            animBounds.left.toFloat(),
                            animBounds.top.toFloat(),
                        )
                        setFrameTimeline(Choreographer.getInstance().vsyncId)
                        apply()
                    }
                }
                .withEndActions({ onFinish.invoke() })
        )
    }

    private fun createAlphaAnimator(
        change: Change,
        onStart: () -> Unit,
        onFinish: () -> Unit,
    ): Animator {
    ): DesktopModeDragAndDropAnimator {
        val transaction = transactionSupplier.get()

        val alphaAnimator = ValueAnimator()
@@ -88,10 +163,47 @@ constructor(val context: Context, val transactionSupplier: Supplier<Transaction>
            transaction.apply()
        }

        return alphaAnimator
        return DesktopModeDragAndDropAlphaAnimator(alphaAnimator)
    }

    companion object {
        const val FADE_IN_ANIMATION_DURATION = 300L
    }
}

/**
 * Abstract base class defining the contract for animations related to drag-and-drop operations
 * within a Desktop Mode feature.
 *
 * This abstract class serves as a necessary wrapper to provide compatibility between different
 * types of animators used in specific drag-and-drop scenarios. Subclasses might use standard
 * Android `Animator` instances or more specialized animators like `PhysicsAnimator`.
 */
abstract class DesktopModeDragAndDropAnimator {
    /** Starts the specific drag-and-drop animation sequence. */
    abstract fun start()
}

/**
 * A concrete implementation of [DesktopModeDragAndDropAnimator] specifically designed for animating
 * alpha of the window.
 *
 * @param animator The standard Android [Animator] instance that executes the launch animation.
 */
class DesktopModeDragAndDropAlphaAnimator(val animator: Animator) :
    DesktopModeDragAndDropAnimator() {

    override fun start() = animator.start()
}

/**
 * A concrete implementation of [DesktopModeDragAndDropAnimator] specifically designed for spring
 * animations of different properties of the window (position, size etc.)
 *
 * @param animator The `PhysicsAnimator<Rect>` instance responsible for the spring animation.
 */
class DesktopModeDragAndDropSpringAnimator(val animator: PhysicsAnimator<Rect>) :
    DesktopModeDragAndDropAnimator() {

    override fun start() = animator.start()
}
+49 −9
Original line number Diff line number Diff line
@@ -15,7 +15,9 @@
 */
package com.android.wm.shell.desktopmode

import android.graphics.Rect
import android.os.IBinder
import android.view.DragEvent
import android.view.SurfaceControl.Transaction
import android.view.WindowManager.TRANSIT_OPEN
import android.window.TransitionInfo
@@ -30,39 +32,68 @@ class DesktopModeDragAndDropTransitionHandler(
    private val animatorHelper: DesktopModeDragAndDropAnimatorHelper,
) : Transitions.TransitionHandler {

    private val pendingTransitionTokens: MutableList<IBinder> = mutableListOf()
    private val pendingTransitionTokens: MutableList<Pair<IBinder, DragEvent>> = mutableListOf()

    /**
     * Begin a transition when a [android.app.PendingIntent] is dropped without a window to accept
     * it.
     */
    fun handleDropEvent(wct: WindowContainerTransaction): IBinder {
    fun handleDropEvent(wct: WindowContainerTransaction, dragEvent: DragEvent): IBinder {
        val token = transitions.startTransition(TRANSIT_OPEN, wct, this)
        pendingTransitionTokens.add(token)
        pendingTransitionTokens.add(Pair(token, dragEvent))
        return token
    }

    override fun startAnimation(
        transition: IBinder,
    /**
     * Starts the animation for a task transition.
     *
     * This function orchestrates the beginning of an animation for a task transition, which
     * involves hiding the task's leash, setting the initial crop, and initiating the animator.
     *
     * @param info The [TransitionInfo] object containing information about the transition.
     * @param dragEvent The [DragEvent] that triggered the transition, used to extract the initial
     *   dragged task bounds.
     * @param startTransaction The [WindowContainerTransaction] used to manipulate the window
     *   hierarchy, such as hiding the task's leash.
     * @param finishCallback The [TransitionFinishCallback] to be called when the animation
     *   finishes.
     * @return true if transition was handled, false if not (falls-back to default).
     */
    fun startAnimation(
        info: TransitionInfo,
        dragEvent: DragEvent,
        startTransaction: Transaction,
        finishTransaction: Transaction,
        finishCallback: TransitionFinishCallback,
    ): Boolean {
        if (!pendingTransitionTokens.contains(transition)) return false
        val draggedTaskBounds = extractBounds(dragEvent)
        val change = findRelevantChange(info)
        val leash = change.leash
        val endBounds = change.endAbsBounds

        startTransaction
            .hide(leash)
            .setWindowCrop(leash, endBounds.width(), endBounds.height())
            .apply()
        val animator = animatorHelper.createAnimator(change, finishCallback)

        val animator = animatorHelper.createAnimator(change, draggedTaskBounds, finishCallback)
        animator.start()
        pendingTransitionTokens.remove(transition)
        return true
    }

    override fun startAnimation(
        transition: IBinder,
        info: TransitionInfo,
        startTransaction: Transaction,
        finishTransaction: Transaction,
        finishCallback: TransitionFinishCallback,
    ): Boolean {
        val dragEvent =
            pendingTransitionTokens.firstOrNull { it.first == transition }?.second ?: return false
        val result = startAnimation(info, dragEvent, startTransaction, finishCallback)
        pendingTransitionTokens.removeIf { it.first == transition }
        return result
    }

    private fun findRelevantChange(info: TransitionInfo): TransitionInfo.Change {
        val matchingChanges =
            info.changes.filter { change ->
@@ -79,6 +110,15 @@ class DesktopModeDragAndDropTransitionHandler(
    private fun isValidTaskChange(change: TransitionInfo.Change): Boolean =
        change.taskInfo != null && change.taskInfo?.taskId != -1

    private fun extractBounds(dragEvent: DragEvent): Rect {
        return Rect(
            /* left= */ dragEvent.x.toInt(),
            /* top= */ dragEvent.y.toInt(),
            /* right= */ dragEvent.x.toInt() + dragEvent.dragSurface.width,
            /* bottom= */ dragEvent.y.toInt() + dragEvent.dragSurface.height,
        )
    }

    override fun handleRequest(
        transition: IBinder,
        request: TransitionRequestInfo,
+8 −3
Original line number Diff line number Diff line
@@ -1446,6 +1446,7 @@ class DesktopTasksController(
        deskId: Int?,
        displayId: Int,
        unminimizeReason: UnminimizeReason = UnminimizeReason.UNKNOWN,
        dragEvent: DragEvent? = null,
    ): IBinder {
        logV(
            "startLaunchTransition type=%s launchingTaskId=%d deskId=%d displayId=%d",
@@ -1509,6 +1510,7 @@ class DesktopTasksController(
                    taskId = launchingTaskId,
                    minimizingTaskId = taskIdToMinimize,
                    exitingImmersiveTask = exitImmersiveResult.asExit()?.exitingTask,
                    dragEvent = dragEvent,
                )
            } else if (taskIdToMinimize == null) {
                val remoteTransitionHandler = OneShotRemoteHandler(mainExecutor, remoteTransition)
@@ -4242,8 +4244,10 @@ class DesktopTasksController(
        val wct = WindowContainerTransaction()
        wct.sendPendingIntent(launchIntent, null, opts.toBundle())
        if (windowingMode == WINDOWING_MODE_FREEFORM) {
            if (DesktopModeFlags.ENABLE_DESKTOP_TAB_TEARING_MINIMIZE_ANIMATION_BUGFIX.isTrue()) {
                // TODO b/376389593: Use a custom tab tearing transition/animation
            if (
                DesktopModeFlags.ENABLE_DESKTOP_TAB_TEARING_MINIMIZE_ANIMATION_BUGFIX.isTrue ||
                    DesktopExperienceFlags.ENABLE_DESKTOP_TAB_TEARING_LAUNCH_ANIMATION.isTrue
            ) {
                val deskId = getOrCreateDefaultDeskId(DEFAULT_DISPLAY) ?: return false
                startLaunchTransition(
                    TRANSIT_OPEN,
@@ -4251,9 +4255,10 @@ class DesktopTasksController(
                    launchingTaskId = null,
                    deskId = deskId,
                    displayId = DEFAULT_DISPLAY,
                    dragEvent = dragEvent,
                )
            } else {
                desktopModeDragAndDropTransitionHandler.handleDropEvent(wct)
                desktopModeDragAndDropTransitionHandler.handleDropEvent(wct, dragEvent)
            }
        } else {
            transitions.startTransition(TRANSIT_OPEN, wct, null)
Loading