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 Original line 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.SpringAnimation
import com.android.internal.dynamicanimation.animation.SpringForce
import com.android.internal.dynamicanimation.animation.SpringForce
import com.android.systemui.shared.Flags.returnAnimationFrameworkLibrary
import com.android.systemui.shared.Flags.returnAnimationFrameworkLibrary
import com.android.systemui.shared.Flags.returnAnimationFrameworkLongLived
import java.util.concurrent.Executor
import java.util.concurrent.Executor
import kotlin.math.abs
import kotlin.math.abs
import kotlin.math.max
import kotlin.math.max
@@ -113,13 +114,26 @@ class TransitionAnimator(
            )
            )
        }
        }


        internal fun checkReturnAnimationFrameworkFlag() {
        internal fun assertReturnAnimations() {
            check(returnAnimationFrameworkLibrary()) {
            check(returnAnimationsEnabled()) {
                "isLaunching cannot be false when the returnAnimationFrameworkLibrary flag is " +
                "isLaunching cannot be false when the returnAnimationFrameworkLibrary flag " +
                    "disabled"
                    "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() =
        internal fun WindowAnimationState.toTransitionState() =
            State().also {
            State().also {
                bounds?.let { b ->
                bounds?.let { b ->
@@ -467,7 +481,8 @@ class TransitionAnimator(
        drawHole: Boolean = false,
        drawHole: Boolean = false,
        startVelocity: PointF? = null,
        startVelocity: PointF? = null,
    ): Animation {
    ): 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
        // 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
        // color, which is usually the same color of the app background. We first fade in this layer
+123 −6
Original line number Original line Diff line number Diff line
@@ -16,10 +16,12 @@ import android.view.RemoteAnimationAdapter
import android.view.RemoteAnimationTarget
import android.view.RemoteAnimationTarget
import android.view.SurfaceControl
import android.view.SurfaceControl
import android.view.ViewGroup
import android.view.ViewGroup
import android.view.WindowManager.TRANSIT_NONE
import android.widget.FrameLayout
import android.widget.FrameLayout
import android.widget.LinearLayout
import android.widget.LinearLayout
import android.window.RemoteTransition
import android.window.RemoteTransition
import android.window.TransitionFilter
import android.window.TransitionFilter
import android.window.WindowAnimationState
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.SysuiTestCase
@@ -34,6 +36,10 @@ import junit.framework.Assert.assertTrue
import junit.framework.AssertionFailedError
import junit.framework.AssertionFailedError
import kotlin.concurrent.thread
import kotlin.concurrent.thread
import kotlin.test.assertEquals
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.After
import org.junit.Assert.assertThrows
import org.junit.Assert.assertThrows
import org.junit.Before
import org.junit.Before
@@ -258,7 +264,6 @@ class ActivityTransitionAnimatorTest : SysuiTestCase() {
    @DisableFlags(Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LONG_LIVED)
    @DisableFlags(Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LONG_LIVED)
    @Test
    @Test
    fun doesNotRegisterLongLivedTransitionIfFlagIsDisabled() {
    fun doesNotRegisterLongLivedTransitionIfFlagIsDisabled() {

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

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


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


        waitForIdleSync()
        waitForIdleSync()
        verify(controller).onTransitionAnimationCancelled()
        verify(controller).onTransitionAnimationCancelled()
@@ -374,7 +378,13 @@ class ActivityTransitionAnimatorTest : SysuiTestCase() {
    @Test
    @Test
    fun startsAnimationIfWindowIsOpening() {
    fun startsAnimationIfWindowIsOpening() {
        val runner = activityTransitionAnimator.createRunner(controller)
        val runner = activityTransitionAnimator.createRunner(controller)
        runner.onAnimationStart(0, arrayOf(fakeWindow()), emptyArray(), emptyArray(), iCallback)
        runner.onAnimationStart(
            TRANSIT_NONE,
            arrayOf(fakeWindow()),
            emptyArray(),
            emptyArray(),
            iCallback,
        )
        waitForIdleSync()
        waitForIdleSync()
        verify(listener).onTransitionAnimationStart()
        verify(listener).onTransitionAnimationStart()
        verify(controller).onTransitionAnimationStart(anyBoolean())
        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
    @Test
    fun disposeRunner_delegateDereferenced() {
    fun disposeRunner_delegateDereferenced() {
        val runner = activityTransitionAnimator.createRunner(controller)
        val runner = activityTransitionAnimator.createRunner(controller)
@@ -409,7 +526,7 @@ class ActivityTransitionAnimatorTest : SysuiTestCase() {
            false,
            false,
            Rect(),
            Rect(),
            Rect(),
            Rect(),
            0,
            1,
            Point(),
            Point(),
            Rect(),
            Rect(),
            bounds,
            bounds,