Loading libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeMoveToDisplayTransitionHandler.kt +43 −38 Original line number Original line Diff line number Diff line Loading @@ -17,6 +17,7 @@ package com.android.wm.shell.desktopmode package com.android.wm.shell.desktopmode import android.animation.Animator import android.animation.Animator import android.animation.AnimatorSet import android.animation.ValueAnimator import android.animation.ValueAnimator import android.os.IBinder import android.os.IBinder import android.view.Choreographer import android.view.Choreographer Loading @@ -28,9 +29,7 @@ import com.android.wm.shell.shared.animation.Interpolators import com.android.wm.shell.transition.Transitions import com.android.wm.shell.transition.Transitions import kotlin.time.Duration.Companion.milliseconds 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( class DesktopModeMoveToDisplayTransitionHandler( private val animationTransaction: SurfaceControl.Transaction private val animationTransaction: SurfaceControl.Transaction ) : Transitions.TransitionHandler { ) : Transitions.TransitionHandler { Loading @@ -47,30 +46,37 @@ class DesktopModeMoveToDisplayTransitionHandler( finishTransaction: SurfaceControl.Transaction, finishTransaction: SurfaceControl.Transaction, finishCallback: Transitions.TransitionFinishCallback, finishCallback: Transitions.TransitionFinishCallback, ): Boolean { ): Boolean { val change = info.changes.find { it.startDisplayId != it.endDisplayId } ?: return false val changes = info.changes.filter { it.startDisplayId != it.endDisplayId } ValueAnimator.ofFloat(0f, 1f) if (changes.isEmpty()) return false .apply { 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 duration = ANIM_DURATION.inWholeMilliseconds interpolator = Interpolators.LINEAR interpolator = Interpolators.LINEAR addUpdateListener { animation -> addUpdateListener { animation -> animationTransaction animationTransaction .setAlpha(change.leash, animation.animatedValue as Float) .setAlpha(it.leash, animation.animatedValue as Float) .setFrameTimeline(Choreographer.getInstance().vsyncId) .setFrameTimeline(Choreographer.getInstance().vsyncId) .apply() .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) { override fun onAnimationEnd(animation: Animator) { finishTransaction.apply() finishTransaction.apply() Loading @@ -85,8 +91,7 @@ class DesktopModeMoveToDisplayTransitionHandler( override fun onAnimationRepeat(animation: Animator) = Unit override fun onAnimationRepeat(animation: Animator) = Unit } } ) ) } animator.start() .start() return true return true } } Loading libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeMoveToDisplayTransitionHandlerTest.kt +83 −2 Original line number Original line Diff line number Diff line Loading @@ -16,8 +16,10 @@ package com.android.wm.shell.desktopmode package com.android.wm.shell.desktopmode import android.graphics.Rect import android.testing.AndroidTestingRunner import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper import android.testing.TestableLooper.RunWithLooper import android.view.SurfaceControl import android.view.WindowManager import android.view.WindowManager import android.window.TransitionInfo import android.window.TransitionInfo import androidx.test.filters.SmallTest import androidx.test.filters.SmallTest Loading @@ -30,6 +32,8 @@ import org.junit.Before import org.junit.Test import org.junit.Test import org.junit.runner.RunWith import org.junit.runner.RunWith import org.mockito.kotlin.mock import org.mockito.kotlin.mock import org.mockito.kotlin.spy import org.mockito.kotlin.verify @SmallTest @SmallTest @RunWithLooper @RunWithLooper Loading @@ -55,7 +59,9 @@ class DesktopModeMoveToDisplayTransitionHandlerTest : ShellTestCase() { info = info = TransitionInfo(WindowManager.TRANSIT_CHANGE, /* flags= */ 0).apply { TransitionInfo(WindowManager.TRANSIT_CHANGE, /* flags= */ 0).apply { addChange( addChange( TransitionInfo.Change(mock(), mock()).apply { setDisplayId(1, 1) } TransitionInfo.Change(mock(), mock()).apply { setDisplayId(/* start= */ 1, /* end= */ 1) } ) ) }, }, startTransaction = StubTransaction(), startTransaction = StubTransaction(), Loading @@ -74,7 +80,9 @@ class DesktopModeMoveToDisplayTransitionHandlerTest : ShellTestCase() { info = info = TransitionInfo(WindowManager.TRANSIT_CHANGE, /* flags= */ 0).apply { TransitionInfo(WindowManager.TRANSIT_CHANGE, /* flags= */ 0).apply { addChange( addChange( TransitionInfo.Change(mock(), mock()).apply { setDisplayId(1, 2) } TransitionInfo.Change(mock(), mock()).apply { setDisplayId(/* start= */ 1, /* end= */ 2) } ) ) }, }, startTransaction = StubTransaction(), startTransaction = StubTransaction(), Loading @@ -84,4 +92,77 @@ class DesktopModeMoveToDisplayTransitionHandlerTest : ShellTestCase() { assertTrue("Should animate display change transition", animates) 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) } } } Loading
libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeMoveToDisplayTransitionHandler.kt +43 −38 Original line number Original line Diff line number Diff line Loading @@ -17,6 +17,7 @@ package com.android.wm.shell.desktopmode package com.android.wm.shell.desktopmode import android.animation.Animator import android.animation.Animator import android.animation.AnimatorSet import android.animation.ValueAnimator import android.animation.ValueAnimator import android.os.IBinder import android.os.IBinder import android.view.Choreographer import android.view.Choreographer Loading @@ -28,9 +29,7 @@ import com.android.wm.shell.shared.animation.Interpolators import com.android.wm.shell.transition.Transitions import com.android.wm.shell.transition.Transitions import kotlin.time.Duration.Companion.milliseconds 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( class DesktopModeMoveToDisplayTransitionHandler( private val animationTransaction: SurfaceControl.Transaction private val animationTransaction: SurfaceControl.Transaction ) : Transitions.TransitionHandler { ) : Transitions.TransitionHandler { Loading @@ -47,30 +46,37 @@ class DesktopModeMoveToDisplayTransitionHandler( finishTransaction: SurfaceControl.Transaction, finishTransaction: SurfaceControl.Transaction, finishCallback: Transitions.TransitionFinishCallback, finishCallback: Transitions.TransitionFinishCallback, ): Boolean { ): Boolean { val change = info.changes.find { it.startDisplayId != it.endDisplayId } ?: return false val changes = info.changes.filter { it.startDisplayId != it.endDisplayId } ValueAnimator.ofFloat(0f, 1f) if (changes.isEmpty()) return false .apply { 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 duration = ANIM_DURATION.inWholeMilliseconds interpolator = Interpolators.LINEAR interpolator = Interpolators.LINEAR addUpdateListener { animation -> addUpdateListener { animation -> animationTransaction animationTransaction .setAlpha(change.leash, animation.animatedValue as Float) .setAlpha(it.leash, animation.animatedValue as Float) .setFrameTimeline(Choreographer.getInstance().vsyncId) .setFrameTimeline(Choreographer.getInstance().vsyncId) .apply() .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) { override fun onAnimationEnd(animation: Animator) { finishTransaction.apply() finishTransaction.apply() Loading @@ -85,8 +91,7 @@ class DesktopModeMoveToDisplayTransitionHandler( override fun onAnimationRepeat(animation: Animator) = Unit override fun onAnimationRepeat(animation: Animator) = Unit } } ) ) } animator.start() .start() return true return true } } Loading
libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeMoveToDisplayTransitionHandlerTest.kt +83 −2 Original line number Original line Diff line number Diff line Loading @@ -16,8 +16,10 @@ package com.android.wm.shell.desktopmode package com.android.wm.shell.desktopmode import android.graphics.Rect import android.testing.AndroidTestingRunner import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper import android.testing.TestableLooper.RunWithLooper import android.view.SurfaceControl import android.view.WindowManager import android.view.WindowManager import android.window.TransitionInfo import android.window.TransitionInfo import androidx.test.filters.SmallTest import androidx.test.filters.SmallTest Loading @@ -30,6 +32,8 @@ import org.junit.Before import org.junit.Test import org.junit.Test import org.junit.runner.RunWith import org.junit.runner.RunWith import org.mockito.kotlin.mock import org.mockito.kotlin.mock import org.mockito.kotlin.spy import org.mockito.kotlin.verify @SmallTest @SmallTest @RunWithLooper @RunWithLooper Loading @@ -55,7 +59,9 @@ class DesktopModeMoveToDisplayTransitionHandlerTest : ShellTestCase() { info = info = TransitionInfo(WindowManager.TRANSIT_CHANGE, /* flags= */ 0).apply { TransitionInfo(WindowManager.TRANSIT_CHANGE, /* flags= */ 0).apply { addChange( addChange( TransitionInfo.Change(mock(), mock()).apply { setDisplayId(1, 1) } TransitionInfo.Change(mock(), mock()).apply { setDisplayId(/* start= */ 1, /* end= */ 1) } ) ) }, }, startTransaction = StubTransaction(), startTransaction = StubTransaction(), Loading @@ -74,7 +80,9 @@ class DesktopModeMoveToDisplayTransitionHandlerTest : ShellTestCase() { info = info = TransitionInfo(WindowManager.TRANSIT_CHANGE, /* flags= */ 0).apply { TransitionInfo(WindowManager.TRANSIT_CHANGE, /* flags= */ 0).apply { addChange( addChange( TransitionInfo.Change(mock(), mock()).apply { setDisplayId(1, 2) } TransitionInfo.Change(mock(), mock()).apply { setDisplayId(/* start= */ 1, /* end= */ 2) } ) ) }, }, startTransaction = StubTransaction(), startTransaction = StubTransaction(), Loading @@ -84,4 +92,77 @@ class DesktopModeMoveToDisplayTransitionHandlerTest : ShellTestCase() { assertTrue("Should animate display change transition", animates) 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) } } }