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

Commit d1847194 authored by Luca Zuccarini's avatar Luca Zuccarini
Browse files

Introduce animation takeovers to ActivityTransitionAnimator.

I want to add motion tests to this class too, but that is a larger
effort better suited for its own CL.

Bug: 323863002
Flag: com.android.systemui.shared.return_animation_framework_library
Flag: com.android.systemui.shared.return_animation_framework_long_lived
Test: atest ActivityTransitionAnimatorTest
Test: manual with flags on and off
Change-Id: Ib545f5bdce953d7fb033334edc3ab7f9d19e8a58
parent f795bcef
Loading
Loading
Loading
Loading
+469 −76

File changed.

Preview size limit exceeded, changes collapsed.

+20 −5
Original line number Diff line number Diff line
@@ -38,6 +38,7 @@ import com.android.internal.annotations.VisibleForTesting
import com.android.internal.dynamicanimation.animation.SpringAnimation
import com.android.internal.dynamicanimation.animation.SpringForce
import com.android.systemui.shared.Flags.returnAnimationFrameworkLibrary
import com.android.systemui.shared.Flags.returnAnimationFrameworkLongLived
import java.util.concurrent.Executor
import kotlin.math.abs
import kotlin.math.max
@@ -113,13 +114,26 @@ class TransitionAnimator(
            )
        }

        internal fun checkReturnAnimationFrameworkFlag() {
            check(returnAnimationFrameworkLibrary()) {
                "isLaunching cannot be false when the returnAnimationFrameworkLibrary flag is " +
                    "disabled"
        internal fun assertReturnAnimations() {
            check(returnAnimationsEnabled()) {
                "isLaunching cannot be false when the returnAnimationFrameworkLibrary flag " +
                    "is disabled"
            }
        }

        internal fun returnAnimationsEnabled() = returnAnimationFrameworkLibrary()

        internal fun assertLongLivedReturnAnimations() {
            check(longLivedReturnAnimationsEnabled()) {
                "Long-lived registrations cannot be used when the " +
                    "returnAnimationFrameworkLibrary or the " +
                    "returnAnimationFrameworkLongLived flag are disabled"
            }
        }

        internal fun longLivedReturnAnimationsEnabled() =
            returnAnimationFrameworkLibrary() && returnAnimationFrameworkLongLived()

        internal fun WindowAnimationState.toTransitionState() =
            State().also {
                bounds?.let { b ->
@@ -467,7 +481,8 @@ class TransitionAnimator(
        drawHole: Boolean = false,
        startVelocity: PointF? = null,
    ): Animation {
        if (!controller.isLaunching || startVelocity != null) checkReturnAnimationFrameworkFlag()
        if (!controller.isLaunching) assertReturnAnimations()
        if (startVelocity != null) assertLongLivedReturnAnimations()

        // We add an extra layer with the same color as the dialog/app splash screen background
        // color, which is usually the same color of the app background. We first fade in this layer
+123 −6
Original line number Diff line number Diff line
@@ -16,10 +16,12 @@ import android.view.RemoteAnimationAdapter
import android.view.RemoteAnimationTarget
import android.view.SurfaceControl
import android.view.ViewGroup
import android.view.WindowManager.TRANSIT_NONE
import android.widget.FrameLayout
import android.widget.LinearLayout
import android.window.RemoteTransition
import android.window.TransitionFilter
import android.window.WindowAnimationState
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -34,6 +36,10 @@ import junit.framework.Assert.assertTrue
import junit.framework.AssertionFailedError
import kotlin.concurrent.thread
import kotlin.test.assertEquals
import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withTimeout
import org.junit.After
import org.junit.Assert.assertThrows
import org.junit.Before
@@ -258,7 +264,6 @@ class ActivityTransitionAnimatorTest : SysuiTestCase() {
    @DisableFlags(Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LONG_LIVED)
    @Test
    fun doesNotRegisterLongLivedTransitionIfFlagIsDisabled() {

        val controller =
            object : DelegateTransitionAnimatorController(controller) {
                override val transitionCookie =
@@ -273,7 +278,6 @@ class ActivityTransitionAnimatorTest : SysuiTestCase() {
    @EnableFlags(Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LONG_LIVED)
    @Test
    fun doesNotRegisterLongLivedTransitionIfMissingRequiredProperties() {

        // No TransitionCookie
        val controllerWithoutCookie =
            object : DelegateTransitionAnimatorController(controller) {
@@ -348,7 +352,7 @@ class ActivityTransitionAnimatorTest : SysuiTestCase() {
    fun doesNotStartIfAnimationIsCancelled() {
        val runner = activityTransitionAnimator.createRunner(controller)
        runner.onAnimationCancelled()
        runner.onAnimationStart(0, emptyArray(), emptyArray(), emptyArray(), iCallback)
        runner.onAnimationStart(TRANSIT_NONE, emptyArray(), emptyArray(), emptyArray(), iCallback)

        waitForIdleSync()
        verify(controller).onTransitionAnimationCancelled()
@@ -361,7 +365,7 @@ class ActivityTransitionAnimatorTest : SysuiTestCase() {
    @Test
    fun cancelsIfNoOpeningWindowIsFound() {
        val runner = activityTransitionAnimator.createRunner(controller)
        runner.onAnimationStart(0, emptyArray(), emptyArray(), emptyArray(), iCallback)
        runner.onAnimationStart(TRANSIT_NONE, emptyArray(), emptyArray(), emptyArray(), iCallback)

        waitForIdleSync()
        verify(controller).onTransitionAnimationCancelled()
@@ -374,7 +378,13 @@ class ActivityTransitionAnimatorTest : SysuiTestCase() {
    @Test
    fun startsAnimationIfWindowIsOpening() {
        val runner = activityTransitionAnimator.createRunner(controller)
        runner.onAnimationStart(0, arrayOf(fakeWindow()), emptyArray(), emptyArray(), iCallback)
        runner.onAnimationStart(
            TRANSIT_NONE,
            arrayOf(fakeWindow()),
            emptyArray(),
            emptyArray(),
            iCallback,
        )
        waitForIdleSync()
        verify(listener).onTransitionAnimationStart()
        verify(controller).onTransitionAnimationStart(anyBoolean())
@@ -387,6 +397,113 @@ class ActivityTransitionAnimatorTest : SysuiTestCase() {
        }
    }

    @DisableFlags(
        Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LIBRARY,
        Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LONG_LIVED,
    )
    @Test
    fun creatingRunnerWithLazyInitializationThrows_whenTheFlagsAreDisabled() {
        assertThrows(IllegalStateException::class.java) {
            activityTransitionAnimator.createRunner(controller, initializeLazily = true)
        }
    }

    @EnableFlags(
        Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LIBRARY,
        Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LONG_LIVED,
    )
    @Test
    fun runnerCreatesDelegateLazily_whenPostingTimeouts() {
        val runner = activityTransitionAnimator.createRunner(controller, initializeLazily = true)
        assertNull(runner.delegate)
        runner.postTimeouts()
        assertNotNull(runner.delegate)
    }

    @EnableFlags(
        Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LIBRARY,
        Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LONG_LIVED,
    )
    @Test
    fun runnerCreatesDelegateLazily_onAnimationStart() {
        val runner = activityTransitionAnimator.createRunner(controller, initializeLazily = true)
        assertNull(runner.delegate)

        // The delegate is cleaned up after execution (which happens in another thread), so what we
        // do instead is check if it becomes non-null at any point with a 1 second timeout. This
        // will tell us that takeOverWithAnimation() triggered the lazy initialization.
        var delegateInitialized = false
        runBlocking {
            val initChecker = launch {
                withTimeout(1.seconds) {
                    while (runner.delegate == null) continue
                    delegateInitialized = true
                }
            }
            runner.onAnimationStart(
                TRANSIT_NONE,
                arrayOf(fakeWindow()),
                emptyArray(),
                emptyArray(),
                iCallback,
            )
            initChecker.join()
        }
        assertTrue(delegateInitialized)
    }

    @EnableFlags(
        Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LIBRARY,
        Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LONG_LIVED,
    )
    @Test
    fun runnerCreatesDelegateLazily_onAnimationTakeover() {
        val runner = activityTransitionAnimator.createRunner(controller, initializeLazily = true)
        assertNull(runner.delegate)

        // The delegate is cleaned up after execution (which happens in another thread), so what we
        // do instead is check if it becomes non-null at any point with a 1 second timeout. This
        // will tell us that takeOverWithAnimation() triggered the lazy initialization.
        var delegateInitialized = false
        runBlocking {
            val initChecker = launch {
                withTimeout(1.seconds) {
                    while (runner.delegate == null) continue
                    delegateInitialized = true
                }
            }
            runner.takeOverAnimation(
                arrayOf(fakeWindow()),
                arrayOf(WindowAnimationState()),
                SurfaceControl.Transaction(),
                iCallback,
            )
            initChecker.join()
        }
        assertTrue(delegateInitialized)
    }

    @DisableFlags(
        Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LIBRARY,
        Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LONG_LIVED,
    )
    @Test
    fun animationTakeoverThrows_whenTheFlagsAreDisabled() {
        val runner = activityTransitionAnimator.createRunner(controller, initializeLazily = false)
        assertThrows(IllegalStateException::class.java) {
            runner.takeOverAnimation(
                arrayOf(fakeWindow()),
                emptyArray(),
                SurfaceControl.Transaction(),
                iCallback,
            )
        }
    }

    @DisableFlags(
        Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LIBRARY,
        Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LONG_LIVED,
    )
    @Test
    fun disposeRunner_delegateDereferenced() {
        val runner = activityTransitionAnimator.createRunner(controller)
@@ -409,7 +526,7 @@ class ActivityTransitionAnimatorTest : SysuiTestCase() {
            false,
            Rect(),
            Rect(),
            0,
            1,
            Point(),
            Rect(),
            bounds,