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

Commit d6986170 authored by Daichi Hirono's avatar Daichi Hirono
Browse files

Corrects animation for moving ActivityEmbeddings

Previously, DesktopModeMoveToDisplayTransitionHandler applied animation
to a single TransitionInfo.Change. This was inadequate for
TransitionInfo moving a task with ActivityEmbedding, as it includes
three TransitionInfo.Change objects: one for each embedded activity and
one for the parent container. To fix the animation:

- Applies animation to all TransitionInfo.Change objects that involve
  moving to another display.

- Sets the relative position of the leash surface instead of the
  absolute position. This ensures the leash surface of embedded
  activities, parented to the container task, is placed correctly.

Bug: 376355478
Test: DesktopModeMoveToDisplayTransitionHandlerTest
Flag: com.android.window.flags.enable_move_to_next_display_shortcut
Change-Id: I3b6035c4aed42977a29defc4c9d23adaacf6d90a
parent 12bd0c83
Loading
Loading
Loading
Loading
+43 −38
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
package com.android.wm.shell.desktopmode

import android.animation.Animator
import android.animation.AnimatorSet
import android.animation.ValueAnimator
import android.os.IBinder
import android.view.Choreographer
@@ -28,9 +29,7 @@ import com.android.wm.shell.shared.animation.Interpolators
import com.android.wm.shell.transition.Transitions
import kotlin.time.Duration.Companion.milliseconds

/**
 * Transition handler for moving a window to a different display.
 */
/** Transition handler for moving a window to a different display. */
class DesktopModeMoveToDisplayTransitionHandler(
    private val animationTransaction: SurfaceControl.Transaction
) : Transitions.TransitionHandler {
@@ -47,30 +46,37 @@ class DesktopModeMoveToDisplayTransitionHandler(
        finishTransaction: SurfaceControl.Transaction,
        finishCallback: Transitions.TransitionFinishCallback,
    ): Boolean {
        val change = info.changes.find { it.startDisplayId != it.endDisplayId } ?: return false
        ValueAnimator.ofFloat(0f, 1f)
            .apply {
        val changes = info.changes.filter { it.startDisplayId != it.endDisplayId }
        if (changes.isEmpty()) return false
        for (change in changes) {
            val endBounds = change.endAbsBounds
            // The position should be relative to the parent. For example, in ActivityEmbedding, the
            // leash surface for the embedded Activity is parented to the container.
            val endPosition = change.endRelOffset
            startTransaction
                .setPosition(change.leash, endPosition.x.toFloat(), endPosition.y.toFloat())
                .setWindowCrop(change.leash, endBounds.width(), endBounds.height())
        }
        startTransaction.apply()

        val animator = AnimatorSet()
        animator.playTogether(
            changes.map {
                ValueAnimator.ofFloat(0f, 1f).apply {
                    duration = ANIM_DURATION.inWholeMilliseconds
                    interpolator = Interpolators.LINEAR
                    addUpdateListener { animation ->
                        animationTransaction
                        .setAlpha(change.leash, animation.animatedValue as Float)
                            .setAlpha(it.leash, animation.animatedValue as Float)
                            .setFrameTimeline(Choreographer.getInstance().vsyncId)
                            .apply()
                    }
                addListener(
                    object : Animator.AnimatorListener {
                        override fun onAnimationStart(animation: Animator) {
                            val endBounds = change.endAbsBounds
                            startTransaction
                                .setPosition(
                                    change.leash,
                                    endBounds.left.toFloat(),
                                    endBounds.top.toFloat(),
                                )
                                .setWindowCrop(change.leash, endBounds.width(), endBounds.height())
                                .apply()
                }
            }
        )
        animator.addListener(
            object : Animator.AnimatorListener {
                override fun onAnimationStart(animation: Animator) = Unit

                override fun onAnimationEnd(animation: Animator) {
                    finishTransaction.apply()
@@ -85,8 +91,7 @@ class DesktopModeMoveToDisplayTransitionHandler(
                override fun onAnimationRepeat(animation: Animator) = Unit
            }
        )
            }
            .start()
        animator.start()
        return true
    }

+83 −2
Original line number Diff line number Diff line
@@ -16,8 +16,10 @@

package com.android.wm.shell.desktopmode

import android.graphics.Rect
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
import android.view.SurfaceControl
import android.view.WindowManager
import android.window.TransitionInfo
import androidx.test.filters.SmallTest
@@ -30,6 +32,8 @@ import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.mock
import org.mockito.kotlin.spy
import org.mockito.kotlin.verify

@SmallTest
@RunWithLooper
@@ -55,7 +59,9 @@ class DesktopModeMoveToDisplayTransitionHandlerTest : ShellTestCase() {
                info =
                    TransitionInfo(WindowManager.TRANSIT_CHANGE, /* flags= */ 0).apply {
                        addChange(
                            TransitionInfo.Change(mock(), mock()).apply { setDisplayId(1, 1) }
                            TransitionInfo.Change(mock(), mock()).apply {
                                setDisplayId(/* start= */ 1, /* end= */ 1)
                            }
                        )
                    },
                startTransaction = StubTransaction(),
@@ -74,7 +80,9 @@ class DesktopModeMoveToDisplayTransitionHandlerTest : ShellTestCase() {
                info =
                    TransitionInfo(WindowManager.TRANSIT_CHANGE, /* flags= */ 0).apply {
                        addChange(
                            TransitionInfo.Change(mock(), mock()).apply { setDisplayId(1, 2) }
                            TransitionInfo.Change(mock(), mock()).apply {
                                setDisplayId(/* start= */ 1, /* end= */ 2)
                            }
                        )
                    },
                startTransaction = StubTransaction(),
@@ -84,4 +92,77 @@ class DesktopModeMoveToDisplayTransitionHandlerTest : ShellTestCase() {

        assertTrue("Should animate display change transition", animates)
    }

    @Test
    fun startAnimation_movingActivityEmbedding_shouldSetCorrectBounds() {
        val leashLeft = mock<SurfaceControl>()
        val leashRight = mock<SurfaceControl>()
        val leashContainer = mock<SurfaceControl>()
        val startTransaction = spy(StubTransaction())

        handler.startAnimation(
            transition = mock(),
            info =
                TransitionInfo(WindowManager.TRANSIT_CHANGE, /* flags= */ 0).apply {
                    addChange(
                        TransitionInfo.Change(mock(), mock()).apply {
                            flags = TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY
                            leash = leashLeft
                            setDisplayId(/* start= */ 1, /* end= */ 2)
                            setEndAbsBounds(
                                Rect(
                                    /* left= */ 100,
                                    /* top= */ 100,
                                    /* right= */ 500,
                                    /* bottom= */ 700,
                                )
                            )
                            setEndRelOffset(/* left= */ 0, /* top= */ 0)
                        }
                    )
                    addChange(
                        TransitionInfo.Change(mock(), mock()).apply {
                            flags = TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY
                            leash = leashRight
                            setDisplayId(1, 2)
                            setEndAbsBounds(
                                Rect(
                                    /* left= */ 500,
                                    /* top= */ 100,
                                    /* right= */ 900,
                                    /* bottom= */ 700,
                                )
                            )
                            setEndRelOffset(/* left= */ 400, /* top= */ 0)
                        }
                    )
                    addChange(
                        TransitionInfo.Change(mock(), mock()).apply {
                            flags = TransitionInfo.FLAG_TRANSLUCENT
                            leash = leashContainer
                            setDisplayId(/* start= */ 1, /* end= */ 2)
                            setEndAbsBounds(
                                Rect(
                                    /* left= */ 100,
                                    /* top= */ 100,
                                    /* right= */ 900,
                                    /* bottom= */ 700,
                                )
                            )
                            setEndRelOffset(/* left= */ 100, /* top= */ 100)
                        }
                    )
                },
            startTransaction = startTransaction,
            finishTransaction = StubTransaction(),
            finishCallback = mock(),
        )

        verify(startTransaction).setPosition(leashLeft, 0f, 0f)
        verify(startTransaction).setPosition(leashRight, 400f, 0f)
        verify(startTransaction).setPosition(leashContainer, 100f, 100f)
        verify(startTransaction).setWindowCrop(leashLeft, 400, 600)
        verify(startTransaction).setWindowCrop(leashRight, 400, 600)
        verify(startTransaction).setWindowCrop(leashContainer, 800, 600)
    }
}