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

Commit 0b993fce authored by Sergey Pinkevich's avatar Sergey Pinkevich Committed by Android (Google) Code Review
Browse files

Merge "Animating tab tearing launch from dropped tab bounds" into main

parents d84b4198 f65c752e
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -1400,6 +1400,7 @@ public abstract class WMShellModule {
            CloseDesktopTaskTransitionHandler closeDesktopTaskTransitionHandler,
            Optional<DesktopImmersiveController> desktopImmersiveController,
            DesktopMinimizationTransitionHandler desktopMinimizationTransitionHandler,
            DesktopModeDragAndDropTransitionHandler desktopModeDragAndDropTransitionHandler,
            InteractionJankMonitor interactionJankMonitor,
            @ShellMainThread Handler handler,
            ShellInit shellInit,
@@ -1419,6 +1420,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
@@ -1461,6 +1461,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",
@@ -1527,6 +1528,7 @@ class DesktopTasksController(
                    taskId = launchingTaskId,
                    minimizingTaskId = taskIdToMinimize,
                    exitingImmersiveTask = exitImmersiveResult.asExit()?.exitingTask,
                    dragEvent = dragEvent,
                )
            } else if (taskIdToMinimize == null) {
                val remoteTransitionHandler = OneShotRemoteHandler(mainExecutor, remoteTransition)
@@ -4273,8 +4275,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,
@@ -4282,9 +4286,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