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

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

Merge "Physics based animations for drag to desktop transition" into main

parents 97c5bc38 2b87c416
Loading
Loading
Loading
Loading
+13 −2
Original line number Diff line number Diff line
@@ -27,6 +27,7 @@ import android.animation.AnimatorListenerAdapter;
import android.animation.RectEvaluator;
import android.animation.ValueAnimator;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.WindowConfiguration;
import android.content.Context;
@@ -262,12 +263,22 @@ public class DesktopModeVisualIndicator {

    /**
     * Fade out indicator without fully releasing it. Animator fades it out while shrinking bounds.
     *
     * @param finishCallback called when animation ends or gets cancelled
     */
    private void fadeOutIndicator() {
    void fadeOutIndicator(@Nullable Runnable finishCallback) {
        final VisualIndicatorAnimator animator = VisualIndicatorAnimator
                .fadeBoundsOut(mView, mCurrentType,
                        mDisplayController.getDisplayLayout(mTaskInfo.displayId));
        animator.start();
        if (finishCallback != null) {
            animator.addListener(new AnimatorListenerAdapter() {
                @Override
                public void onAnimationEnd(Animator animation) {
                    finishCallback.run();
                }
            });
        }
        mCurrentType = IndicatorType.NO_INDICATOR;
    }

@@ -282,7 +293,7 @@ public class DesktopModeVisualIndicator {
        if (mCurrentType == IndicatorType.NO_INDICATOR) {
            fadeInIndicator(newType);
        } else if (newType == IndicatorType.NO_INDICATOR) {
            fadeOutIndicator();
            fadeOutIndicator(null /* finishCallback */);
        } else {
            final VisualIndicatorAnimator animator = VisualIndicatorAnimator.animateIndicatorType(
                    mView, mDisplayController.getDisplayLayout(mTaskInfo.displayId), mCurrentType,
+6 −4
Original line number Diff line number Diff line
@@ -163,10 +163,12 @@ class DesktopTasksController(
            }

            private fun removeVisualIndicator(tx: SurfaceControl.Transaction) {
                visualIndicator?.fadeOutIndicator {
                    visualIndicator?.releaseVisualIndicator(tx)
                    visualIndicator = null
                }
            }
        }

    /** Task id of the task currently being dragged from fullscreen/split. */
    val draggingTaskId
@@ -193,7 +195,7 @@ class DesktopTasksController(
        )
        transitions.addHandler(this)
        taskRepository.addVisibleTasksListener(taskVisibilityListener, mainExecutor)
        dragToDesktopTransitionHandler.setDragToDesktopStateListener(dragToDesktopStateListener)
        dragToDesktopTransitionHandler.dragToDesktopStateListener = dragToDesktopStateListener
        recentsTransitionHandler.addTransitionStateListener(
            object : RecentsTransitionStateListener {
                override fun onAnimationStateChanged(running: Boolean) {
@@ -213,7 +215,7 @@ class DesktopTasksController(
    fun setOnTaskResizeAnimationListener(listener: OnTaskResizeAnimationListener) {
        toggleResizeDesktopTaskTransitionHandler.setOnTaskResizeAnimationListener(listener)
        enterDesktopTaskTransitionHandler.setOnTaskResizeAnimationListener(listener)
        dragToDesktopTransitionHandler.setOnTaskResizeAnimatorListener(listener)
        dragToDesktopTransitionHandler.onTaskResizeAnimationListener = listener
    }

    fun setOnTaskRepositionAnimationListener(listener: OnTaskRepositionAnimationListener) {
+143 −17
Original line number Diff line number Diff line
@@ -28,17 +28,20 @@ import android.window.TransitionInfo
import android.window.TransitionInfo.Change
import android.window.TransitionRequestInfo
import android.window.WindowContainerTransaction
import androidx.dynamicanimation.animation.SpringForce
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.internal.protolog.ProtoLog
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.animation.FloatProperties
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.shared.animation.PhysicsAnimator
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
@@ -49,6 +52,7 @@ import com.android.wm.shell.windowdecor.MoveToDesktopAnimator
import com.android.wm.shell.windowdecor.MoveToDesktopAnimator.Companion.DRAG_FREEFORM_SCALE
import com.android.wm.shell.windowdecor.OnTaskResizeAnimationListener
import java.util.function.Supplier
import kotlin.math.max

/**
 * Handles the transition to enter desktop from fullscreen by dragging on the handle bar. It also
@@ -64,17 +68,15 @@ sealed class DragToDesktopTransitionHandler(
    private val context: Context,
    private val transitions: Transitions,
    private val taskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer,
    private val interactionJankMonitor: InteractionJankMonitor,
    private val transactionSupplier: Supplier<SurfaceControl.Transaction>,
    protected val interactionJankMonitor: InteractionJankMonitor,
    protected val transactionSupplier: Supplier<SurfaceControl.Transaction>,
) : TransitionHandler {

    private val rectEvaluator = RectEvaluator(Rect())
    protected val rectEvaluator = RectEvaluator(Rect())
    private val launchHomeIntent = Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME)

    private var dragToDesktopStateListener: DragToDesktopStateListener? = null
    private lateinit var splitScreenController: SplitScreenController
    private var transitionState: TransitionState? = null
    private lateinit var onTaskResizeAnimationListener: OnTaskResizeAnimationListener

    /** Whether a drag-to-desktop transition is in progress. */
    val inProgress: Boolean
@@ -83,20 +85,18 @@ sealed class DragToDesktopTransitionHandler(
    /** The task id of the task currently being dragged from fullscreen/split. */
    val draggingTaskId: Int
        get() = transitionState?.draggedTaskId ?: INVALID_TASK_ID
    /** Sets a listener to receive callback about events during the transition animation. */
    fun setDragToDesktopStateListener(listener: DragToDesktopStateListener) {
        dragToDesktopStateListener = listener
    }

    /** Listener to receive callback about events during the transition animation. */
    var dragToDesktopStateListener: DragToDesktopStateListener? = null

    /** Task listener for animation start, task bounds resize, and the animation finish */
    lateinit var onTaskResizeAnimationListener: OnTaskResizeAnimationListener

    /** Setter needed to avoid cyclic dependency. */
    fun setSplitScreenController(controller: SplitScreenController) {
        splitScreenController = controller
    }

    fun setOnTaskResizeAnimatorListener(listener: OnTaskResizeAnimationListener) {
        onTaskResizeAnimationListener = listener
    }

    /**
     * Starts a transition that performs a transient launch of Home so that Home is brought to the
     * front while still keeping the currently focused task that is being dragged resumed. This
@@ -503,6 +503,7 @@ sealed class DragToDesktopTransitionHandler(
        finishTransaction: SurfaceControl.Transaction
    ) {
        val state = requireTransitionState()
        val freeformTaskChanges = mutableListOf<Change>()
        info.changes.forEachIndexed { i, change ->
            when {
                state is TransitionState.FromSplit &&
@@ -527,12 +528,15 @@ sealed class DragToDesktopTransitionHandler(
                            ?: error("Expected dragged leash to be non-null")
                    startTransaction.setRelativeLayer(change.leash, draggedTaskLeash, -i)
                    finishTransaction.setRelativeLayer(change.leash, draggedTaskLeash, -i)
                    freeformTaskChanges.add(change)
                }
            }
        }

        state.freeformTaskChanges = freeformTaskChanges
    }

    private fun animateEndDragToDesktop(
    protected open fun animateEndDragToDesktop(
        startTransaction: SurfaceControl.Transaction,
        startTransitionFinishCb: Transitions.TransitionFinishCallback
    ) {
@@ -597,7 +601,7 @@ sealed class DragToDesktopTransitionHandler(
                    object : AnimatorListenerAdapter() {
                        override fun onAnimationEnd(animation: Animator) {
                            onTaskResizeAnimationListener.onAnimationEnd(state.draggedTaskId)
                            startTransitionFinishCb.onTransitionFinished(null /* null */)
                            startTransitionFinishCb.onTransitionFinished(/* wct = */ null)
                            clearState()
                            interactionJankMonitor.end(
                                CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE
@@ -728,7 +732,7 @@ sealed class DragToDesktopTransitionHandler(
        wct.restoreTransientOrder(homeWc)
    }

    private fun clearState() {
    protected fun clearState() {
        transitionState = null
    }

@@ -778,6 +782,7 @@ sealed class DragToDesktopTransitionHandler(
        abstract var cancelTransitionToken: IBinder?
        abstract var homeChange: Change?
        abstract var draggedTaskChange: Change?
        abstract var freeformTaskChanges: List<Change>
        abstract var cancelState: CancelState
        abstract var startAborted: Boolean

@@ -790,6 +795,7 @@ sealed class DragToDesktopTransitionHandler(
            override var cancelTransitionToken: IBinder? = null,
            override var homeChange: Change? = null,
            override var draggedTaskChange: Change? = null,
            override var freeformTaskChanges: List<Change> = emptyList(),
            override var cancelState: CancelState = CancelState.NO_CANCEL,
            override var startAborted: Boolean = false,
            var otherRootChanges: MutableList<Change> = mutableListOf()
@@ -804,6 +810,7 @@ sealed class DragToDesktopTransitionHandler(
            override var cancelTransitionToken: IBinder? = null,
            override var homeChange: Change? = null,
            override var draggedTaskChange: Change? = null,
            override var freeformTaskChanges: List<Change> = emptyList(),
            override var cancelState: CancelState = CancelState.NO_CANCEL,
            override var startAborted: Boolean = false,
            var splitRootChange: Change? = null,
@@ -825,7 +832,7 @@ sealed class DragToDesktopTransitionHandler(

    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
        internal const val DRAG_TO_DESKTOP_FINISH_ANIM_DURATION_MS = 336L
    }
}

@@ -885,6 +892,15 @@ constructor(
        transactionSupplier
    ) {

    private val positionSpringConfig =
        PhysicsAnimator.SpringConfig(
            SpringForce.STIFFNESS_LOW,
            SpringForce.DAMPING_RATIO_LOW_BOUNCY
        )

    private val sizeSpringConfig =
        PhysicsAnimator.SpringConfig(SpringForce.STIFFNESS_LOW, SpringForce.DAMPING_RATIO_NO_BOUNCY)

    /**
     * @return layers in order:
     * - appLayers - below everything z < 0, effectively hides the leash
@@ -911,5 +927,115 @@ constructor(
        val homeLeash = state.homeChange?.leash ?: error("Expects home leash to be non-null")
        // Hide home on finish to prevent flickering when wallpaper activity flag is enabled
        finishTransaction.hide(homeLeash)
        // Setup freeform tasks before animation
        state.freeformTaskChanges.forEach { change ->
            val startScale = DRAG_TO_DESKTOP_FREEFORM_TASK_INITIAL_SCALE
            val startX =
                change.endAbsBounds.left + change.endAbsBounds.width() * (1 - startScale) / 2
            val startY =
                change.endAbsBounds.top + change.endAbsBounds.height() * (1 - startScale) / 2
            startTransaction.setPosition(change.leash, startX, startY)
            startTransaction.setScale(change.leash, startScale, startScale)
            startTransaction.setAlpha(change.leash, 0f)
        }
    }

    override fun animateEndDragToDesktop(
        startTransaction: SurfaceControl.Transaction,
        startTransitionFinishCb: Transitions.TransitionFinishCallback
    ) {
        val state = requireTransitionState()
        val draggedTaskChange =
            state.draggedTaskChange ?: error("Expected non-null change of dragged task")
        val draggedTaskLeash = draggedTaskChange.leash
        val freeformTaskChanges = state.freeformTaskChanges
        val startBounds = draggedTaskChange.startAbsBounds
        val endBounds = draggedTaskChange.endAbsBounds
        val currentVelocity = state.dragAnimator.computeCurrentVelocity()

        // Cancel any animation that may be currently playing; we will use the relevant
        // details of that animation here.
        state.dragAnimator.cancelAnimator()
        // We still apply scale to task bounds; as we animate the bounds to their
        // end value, animate scale to 1.
        val startScale = state.dragAnimator.scale
        val startPosition = state.dragAnimator.position
        val startBoundsWithOffset =
            Rect(startBounds).apply { offset(startPosition.x.toInt(), startPosition.y.toInt()) }

        dragToDesktopStateListener?.onCommitToDesktopAnimationStart(startTransaction)
        // Accept the merge by applying the merging transaction (applied by #showResizeVeil)
        // and finish callback. Show the veil and position the task at the first frame before
        // starting the final animation.
        onTaskResizeAnimationListener.onAnimationStart(
            state.draggedTaskId,
            startTransaction,
            startBoundsWithOffset
        )

        val tx: SurfaceControl.Transaction = transactionSupplier.get()
        PhysicsAnimator.getInstance(startBoundsWithOffset)
            .spring(
                FloatProperties.RECT_X,
                endBounds.left.toFloat(),
                currentVelocity.x,
                positionSpringConfig
            )
            .spring(
                FloatProperties.RECT_Y,
                endBounds.top.toFloat(),
                currentVelocity.y,
                positionSpringConfig
            )
            .spring(FloatProperties.RECT_WIDTH, endBounds.width().toFloat(), sizeSpringConfig)
            .spring(FloatProperties.RECT_HEIGHT, endBounds.height().toFloat(), sizeSpringConfig)
            .addUpdateListener { animBounds, _ ->
                val animFraction =
                    (animBounds.width() - startBounds.width()).toFloat() /
                        (endBounds.width() - startBounds.width())
                val animScale = startScale + animFraction * (1 - startScale)
                // Freeform animation starts 50% in the animation
                val freeformAnimFraction = max(animFraction - 0.5f, 0f) * 2f
                val freeformStartScale = DRAG_TO_DESKTOP_FREEFORM_TASK_INITIAL_SCALE
                val freeformAnimScale =
                    freeformStartScale + freeformAnimFraction * (1 - freeformStartScale)
                tx.apply {
                    // Update dragged task
                    setScale(draggedTaskLeash, animScale, animScale)
                    setPosition(
                        draggedTaskLeash,
                        animBounds.left.toFloat(),
                        animBounds.top.toFloat()
                    )
                    // Update freeform tasks
                    freeformTaskChanges.forEach {
                        val startX =
                            it.endAbsBounds.left +
                                it.endAbsBounds.width() * (1 - freeformAnimScale) / 2
                        val startY =
                            it.endAbsBounds.top +
                                it.endAbsBounds.height() * (1 - freeformAnimScale) / 2
                        setPosition(it.leash, startX, startY)
                        setScale(it.leash, freeformAnimScale, freeformAnimScale)
                        setAlpha(it.leash, freeformAnimFraction)
                    }
                }
                onTaskResizeAnimationListener.onBoundsChange(state.draggedTaskId, tx, animBounds)
            }
            .withEndActions({
                onTaskResizeAnimationListener.onAnimationEnd(state.draggedTaskId)
                startTransitionFinishCb.onTransitionFinished(/* wct = */ null)
                clearState()
                interactionJankMonitor.end(CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE)
            })
            .start()
    }

    companion object {
        /**
         * The initial scale of the freeform tasks in the animation to commit the drag-to-desktop
         * gesture.
         */
        private const val DRAG_TO_DESKTOP_FREEFORM_TASK_INITIAL_SCALE = 0.9f
    }
}
+12 −0
Original line number Diff line number Diff line
@@ -7,6 +7,7 @@ import android.graphics.PointF
import android.graphics.Rect
import android.view.MotionEvent
import android.view.SurfaceControl
import android.view.VelocityTracker
import com.android.wm.shell.R

/**
@@ -34,6 +35,7 @@ class MoveToDesktopAnimator @JvmOverloads constructor(
    val scale: Float
        get() = dragToDesktopAnimator.animatedValue as Float
    private val mostRecentInput = PointF()
    private val velocityTracker = VelocityTracker.obtain()
    private val dragToDesktopAnimator: ValueAnimator = ValueAnimator.ofFloat(1f,
            DRAG_FREEFORM_SCALE)
            .setDuration(ANIMATION_DURATION.toLong())
@@ -90,6 +92,7 @@ class MoveToDesktopAnimator @JvmOverloads constructor(
        if (!allowSurfaceChangesOnMove || dragToDesktopAnimator.isRunning) {
            return
        }
        velocityTracker.addMovement(ev)
        setTaskPosition(ev.rawX, ev.rawY)
        val t = transactionFactory()
        t.setPosition(taskSurface, position.x, position.y)
@@ -109,6 +112,15 @@ class MoveToDesktopAnimator @JvmOverloads constructor(
     * Cancels the animation, intended to be used when another animator will take over.
     */
    fun cancelAnimator() {
        velocityTracker.clear()
        dragToDesktopAnimator.cancel()
    }

    /**
     * Computes the current velocity per second based on the points that have been collected.
     */
    fun computeCurrentVelocity(): PointF {
        velocityTracker.computeCurrentVelocity(/* units = */ 1000)
        return PointF(velocityTracker.xVelocity, velocityTracker.yVelocity)
    }
}
 No newline at end of file
+3 −2
Original line number Diff line number Diff line
@@ -304,7 +304,7 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() {
        val task = createTask()
        val startTransition =
            startDrag(defaultHandler, task, finishTransaction = playingFinishTransaction)
        defaultHandler.setOnTaskResizeAnimatorListener(mock())
        defaultHandler.onTaskResizeAnimationListener = mock()

        defaultHandler.mergeAnimation(
            transition = mock(),
@@ -327,13 +327,14 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() {

    @Test
    fun mergeAnimation_endTransition_springHandler_hidesHome() {
        whenever(dragAnimator.computeCurrentVelocity()).thenReturn(PointF())
        val playingFinishTransaction = mock<SurfaceControl.Transaction>()
        val mergedStartTransaction = mock<SurfaceControl.Transaction>()
        val finishCallback = mock<Transitions.TransitionFinishCallback>()
        val task = createTask()
        val startTransition =
            startDrag(springHandler, task, finishTransaction = playingFinishTransaction)
        springHandler.setOnTaskResizeAnimatorListener(mock())
        springHandler.onTaskResizeAnimationListener = mock()

        springHandler.mergeAnimation(
            transition = mock(),