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

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

Introduce long-lived registrations.

These are used for any launch/return animations that can happen more
than once and aren't necessarily linked in pairs (i.e. only return if
the associated launch has happened).

Bug: 323863002
Flag: ACONFIG com.android.systemui.shared.return_animation_framework_library DISABLED
Test: unit tests and tested with ongoing call chip

Change-Id: Id6ced4fc5ea3665cf2f5946356e3b752fdf86085
parent d10af358
Loading
Loading
Loading
Loading
+117 −4
Original line number Original line Diff line number Diff line
@@ -20,6 +20,8 @@ import android.app.ActivityManager
import android.app.ActivityTaskManager
import android.app.ActivityTaskManager
import android.app.PendingIntent
import android.app.PendingIntent
import android.app.TaskInfo
import android.app.TaskInfo
import android.app.WindowConfiguration
import android.content.ComponentName
import android.graphics.Matrix
import android.graphics.Matrix
import android.graphics.Rect
import android.graphics.Rect
import android.graphics.RectF
import android.graphics.RectF
@@ -38,7 +40,9 @@ import android.view.View
import android.view.ViewGroup
import android.view.ViewGroup
import android.view.WindowManager
import android.view.WindowManager
import android.view.WindowManager.TRANSIT_CLOSE
import android.view.WindowManager.TRANSIT_CLOSE
import android.view.WindowManager.TRANSIT_OPEN
import android.view.WindowManager.TRANSIT_TO_BACK
import android.view.WindowManager.TRANSIT_TO_BACK
import android.view.WindowManager.TRANSIT_TO_FRONT
import android.view.animation.PathInterpolator
import android.view.animation.PathInterpolator
import android.window.RemoteTransition
import android.window.RemoteTransition
import android.window.TransitionFilter
import android.window.TransitionFilter
@@ -203,6 +207,10 @@ constructor(
            }
            }
        }
        }


    /** Book-keeping for long-lived transitions that are currently registered. */
    private val longLivedTransitions =
        HashMap<TransitionCookie, Pair<RemoteTransition, RemoteTransition>>()

    /**
    /**
     * Start an intent and animate the opening window. The intent will be started by running
     * Start an intent and animate the opening window. The intent will be started by running
     * [intentStarter], which should use the provided [RemoteAnimationAdapter] and return the launch
     * [intentStarter], which should use the provided [RemoteAnimationAdapter] and return the launch
@@ -497,6 +505,7 @@ constructor(
                view: View,
                view: View,
                cujType: Int? = null,
                cujType: Int? = null,
                cookie: TransitionCookie? = null,
                cookie: TransitionCookie? = null,
                component: ComponentName? = null,
                returnCujType: Int? = null
                returnCujType: Int? = null
            ): Controller? {
            ): Controller? {
                // Make sure the View we launch from implements LaunchableView to avoid visibility
                // Make sure the View we launch from implements LaunchableView to avoid visibility
@@ -519,7 +528,13 @@ constructor(
                    return null
                    return null
                }
                }


                return GhostedViewTransitionAnimatorController(view, cujType, cookie, returnCujType)
                return GhostedViewTransitionAnimatorController(
                    view,
                    cujType,
                    cookie,
                    component,
                    returnCujType
                )
            }
            }
        }
        }


@@ -553,6 +568,16 @@ constructor(
        val transitionCookie: TransitionCookie?
        val transitionCookie: TransitionCookie?
            get() = null
            get() = null


        /**
         * The [ComponentName] of the activity whose window is tied to this [Controller].
         *
         * This is used as a fallback when a cookie is defined but there is no match (e.g. when a
         * matching activity was launched by a mean different from the launchable in this
         * [Controller]), and should be defined for all long-lived registered [Controller]s.
         */
        val component: ComponentName?
            get() = null

        /**
        /**
         * The intent was started. If [willAnimate] is false, nothing else will happen and the
         * The intent was started. If [willAnimate] is false, nothing else will happen and the
         * animation will not be started.
         * animation will not be started.
@@ -571,6 +596,91 @@ constructor(
        fun onTransitionAnimationCancelled(newKeyguardOccludedState: Boolean? = null) {}
        fun onTransitionAnimationCancelled(newKeyguardOccludedState: Boolean? = null) {}
    }
    }


    /**
     * Registers [controller] as a long-lived transition handler for launch and return animations.
     *
     * The [controller] will only be used for transitions matching the [TransitionCookie] defined
     * within it, or the [ComponentName] if the cookie matching fails. Both fields are mandatory for
     * this registration.
     */
    fun register(controller: Controller) {
        check(returnAnimationFrameworkLibrary()) {
            "Long-lived registrations cannot be used when the returnAnimationFrameworkLibrary " +
                "flag is disabled"
        }

        if (transitionRegister == null) {
            throw IllegalStateException(
                "A RemoteTransitionRegister must be provided when creating this animator in " +
                    "order to use long-lived animations"
            )
        }

        val cookie =
            controller.transitionCookie
                ?: throw IllegalStateException(
                    "A cookie must be defined in order to use long-lived animations"
                )
        val component =
            controller.component
                ?: throw IllegalStateException(
                    "A component must be defined in order to use long-lived animations"
                )

        // Make sure that any previous registrations linked to the same cookie are gone.
        unregister(cookie)

        val launchFilter =
            TransitionFilter().apply {
                mRequirements =
                    arrayOf(
                        TransitionFilter.Requirement().apply {
                            mActivityType = WindowConfiguration.ACTIVITY_TYPE_STANDARD
                            mModes = intArrayOf(TRANSIT_OPEN, TRANSIT_TO_FRONT)
                            mTopActivity = component
                        }
                    )
            }
        val launchRemoteTransition =
            RemoteTransition(
                RemoteAnimationRunnerCompat.wrap(createRunner(controller)),
                "${cookie}_launchTransition"
            )
        transitionRegister.register(launchFilter, launchRemoteTransition)

        val returnController =
            object : Controller by controller {
                override val isLaunching: Boolean = false
            }
        val returnFilter =
            TransitionFilter().apply {
                mRequirements =
                    arrayOf(
                        TransitionFilter.Requirement().apply {
                            mActivityType = WindowConfiguration.ACTIVITY_TYPE_STANDARD
                            mModes = intArrayOf(TRANSIT_CLOSE, TRANSIT_TO_BACK)
                            mTopActivity = component
                        }
                    )
            }
        val returnRemoteTransition =
            RemoteTransition(
                RemoteAnimationRunnerCompat.wrap(createRunner(returnController)),
                "${cookie}_returnTransition"
            )
        transitionRegister.register(returnFilter, returnRemoteTransition)

        longLivedTransitions[cookie] = Pair(launchRemoteTransition, returnRemoteTransition)
    }

    /** Unregisters all controllers previously registered that contain [cookie]. */
    fun unregister(cookie: TransitionCookie) {
        val transitions = longLivedTransitions[cookie] ?: return
        transitionRegister?.unregister(transitions.first)
        transitionRegister?.unregister(transitions.second)
        longLivedTransitions.remove(cookie)
    }

    /**
    /**
     * Invokes [onAnimationComplete] when animation is either cancelled or completed. Delegates all
     * Invokes [onAnimationComplete] when animation is either cancelled or completed. Delegates all
     * events to the passed [delegate].
     * events to the passed [delegate].
@@ -817,13 +927,16 @@ constructor(
                if (it.mode == targetMode) {
                if (it.mode == targetMode) {
                    if (activityTransitionUseLargestWindow()) {
                    if (activityTransitionUseLargestWindow()) {
                        if (returnAnimationFrameworkLibrary()) {
                        if (returnAnimationFrameworkLibrary()) {
                            // If the controller contains a cookie, _only_ match if the candidate
                            // If the controller contains a cookie, _only_ match if either the
                            // contains the matching cookie.
                            // candidate contains the matching cookie, or a component is also
                            // defined and is a match.
                            if (
                            if (
                                controller.transitionCookie != null &&
                                controller.transitionCookie != null &&
                                    it.taskInfo
                                    it.taskInfo
                                        ?.launchCookies
                                        ?.launchCookies
                                        ?.contains(controller.transitionCookie) != true
                                        ?.contains(controller.transitionCookie) != true &&
                                    (controller.component == null ||
                                        it.taskInfo?.topActivity != controller.component)
                            ) {
                            ) {
                                continue
                                continue
                            }
                            }
+13 −2
Original line number Original line Diff line number Diff line
@@ -16,6 +16,7 @@


package com.android.systemui.animation
package com.android.systemui.animation


import android.content.ComponentName
import android.view.View
import android.view.View


/** A piece of UI that can be expanded into a Dialog or an Activity. */
/** A piece of UI that can be expanded into a Dialog or an Activity. */
@@ -28,13 +29,16 @@ interface Expandable {
     * @param launchCujType The CUJ type from the [com.android.internal.jank.InteractionJankMonitor]
     * @param launchCujType The CUJ type from the [com.android.internal.jank.InteractionJankMonitor]
     *   associated to the launch that will use this controller.
     *   associated to the launch that will use this controller.
     * @param cookie The unique cookie associated with the launch that will use this controller.
     * @param cookie The unique cookie associated with the launch that will use this controller.
     *   This is required iff the a return animation should be included.
     *   This is required iff a return animation should be included.
     * @param component The name of the activity that will be launched by this controller. This is
     *   required for long-lived registrations only.
     * @param returnCujType The CUJ type from the [com.android.internal.jank.InteractionJankMonitor]
     * @param returnCujType The CUJ type from the [com.android.internal.jank.InteractionJankMonitor]
     *   associated to the return animation that will use this controller.
     *   associated to the return animation that will use this controller.
     */
     */
    fun activityTransitionController(
    fun activityTransitionController(
        launchCujType: Int? = null,
        launchCujType: Int? = null,
        cookie: ActivityTransitionAnimator.TransitionCookie? = null,
        cookie: ActivityTransitionAnimator.TransitionCookie? = null,
        component: ComponentName? = null,
        returnCujType: Int? = null
        returnCujType: Int? = null
    ): ActivityTransitionAnimator.Controller?
    ): ActivityTransitionAnimator.Controller?


@@ -47,7 +51,12 @@ interface Expandable {
    fun activityTransitionController(
    fun activityTransitionController(
        launchCujType: Int? = null
        launchCujType: Int? = null
    ): ActivityTransitionAnimator.Controller? {
    ): ActivityTransitionAnimator.Controller? {
        return activityTransitionController(launchCujType, cookie = null, returnCujType = null)
        return activityTransitionController(
            launchCujType,
            cookie = null,
            component = null,
            returnCujType = null
        )
    }
    }


    /**
    /**
@@ -70,12 +79,14 @@ interface Expandable {
                override fun activityTransitionController(
                override fun activityTransitionController(
                    launchCujType: Int?,
                    launchCujType: Int?,
                    cookie: ActivityTransitionAnimator.TransitionCookie?,
                    cookie: ActivityTransitionAnimator.TransitionCookie?,
                    component: ComponentName?,
                    returnCujType: Int?
                    returnCujType: Int?
                ): ActivityTransitionAnimator.Controller? {
                ): ActivityTransitionAnimator.Controller? {
                    return ActivityTransitionAnimator.Controller.fromView(
                    return ActivityTransitionAnimator.Controller.fromView(
                        view,
                        view,
                        launchCujType,
                        launchCujType,
                        cookie,
                        cookie,
                        component,
                        returnCujType
                        returnCujType
                    )
                    )
                }
                }
+2 −0
Original line number Original line Diff line number Diff line
@@ -16,6 +16,7 @@


package com.android.systemui.animation
package com.android.systemui.animation


import android.content.ComponentName
import android.graphics.Canvas
import android.graphics.Canvas
import android.graphics.ColorFilter
import android.graphics.ColorFilter
import android.graphics.Insets
import android.graphics.Insets
@@ -62,6 +63,7 @@ constructor(
    /** The [CujType] associated to this launch animation. */
    /** The [CujType] associated to this launch animation. */
    private val launchCujType: Int? = null,
    private val launchCujType: Int? = null,
    override val transitionCookie: ActivityTransitionAnimator.TransitionCookie? = null,
    override val transitionCookie: ActivityTransitionAnimator.TransitionCookie? = null,
    override val component: ComponentName? = null,


    /** The [CujType] associated to this return animation. */
    /** The [CujType] associated to this return animation. */
    private val returnCujType: Int? = null,
    private val returnCujType: Int? = null,
+5 −2
Original line number Original line Diff line number Diff line
@@ -16,6 +16,7 @@


package com.android.compose.animation
package com.android.compose.animation


import android.content.ComponentName
import android.view.View
import android.view.View
import android.view.ViewGroup
import android.view.ViewGroup
import android.view.ViewGroupOverlay
import android.view.ViewGroupOverlay
@@ -136,13 +137,14 @@ internal class ExpandableControllerImpl(
            override fun activityTransitionController(
            override fun activityTransitionController(
                launchCujType: Int?,
                launchCujType: Int?,
                cookie: ActivityTransitionAnimator.TransitionCookie?,
                cookie: ActivityTransitionAnimator.TransitionCookie?,
                component: ComponentName?,
                returnCujType: Int?
                returnCujType: Int?
            ): ActivityTransitionAnimator.Controller? {
            ): ActivityTransitionAnimator.Controller? {
                if (!isComposed.value) {
                if (!isComposed.value) {
                    return null
                    return null
                }
                }


                return activityController(launchCujType, cookie, returnCujType)
                return activityController(launchCujType, cookie, component, returnCujType)
            }
            }


            override fun dialogTransitionController(
            override fun dialogTransitionController(
@@ -170,7 +172,6 @@ internal class ExpandableControllerImpl(


            override var transitionContainer: ViewGroup = composeViewRoot.rootView as ViewGroup
            override var transitionContainer: ViewGroup = composeViewRoot.rootView as ViewGroup


            // TODO(b/323863002): update to be dependant on usage.
            override val isLaunching: Boolean = true
            override val isLaunching: Boolean = true


            override fun onTransitionAnimationEnd(isExpandingFullyAbove: Boolean) {
            override fun onTransitionAnimationEnd(isExpandingFullyAbove: Boolean) {
@@ -267,6 +268,7 @@ internal class ExpandableControllerImpl(
    private fun activityController(
    private fun activityController(
        launchCujType: Int?,
        launchCujType: Int?,
        cookie: ActivityTransitionAnimator.TransitionCookie?,
        cookie: ActivityTransitionAnimator.TransitionCookie?,
        component: ComponentName?,
        returnCujType: Int?
        returnCujType: Int?
    ): ActivityTransitionAnimator.Controller {
    ): ActivityTransitionAnimator.Controller {
        val delegate = transitionController()
        val delegate = transitionController()
@@ -284,6 +286,7 @@ internal class ExpandableControllerImpl(
                    }
                    }


            override val transitionCookie = cookie
            override val transitionCookie = cookie
            override val component = component


            override fun onTransitionAnimationStart(isExpandingFullyAbove: Boolean) {
            override fun onTransitionAnimationStart(isExpandingFullyAbove: Boolean) {
                delegate.onTransitionAnimationStart(isExpandingFullyAbove)
                delegate.onTransitionAnimationStart(isExpandingFullyAbove)
+135 −0
Original line number Original line Diff line number Diff line
@@ -25,6 +25,7 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.shared.Flags
import com.android.systemui.shared.Flags
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.any
import com.android.wm.shell.shared.ShellTransitions
import com.android.wm.shell.shared.ShellTransitions
import com.google.common.truth.Truth.assertThat
import junit.framework.Assert.assertFalse
import junit.framework.Assert.assertFalse
import junit.framework.Assert.assertNotNull
import junit.framework.Assert.assertNotNull
import junit.framework.Assert.assertNull
import junit.framework.Assert.assertNull
@@ -202,6 +203,140 @@ class ActivityTransitionAnimatorTest : SysuiTestCase() {
        assertTrue(testShellTransitions.remotesForTakeover.isEmpty())
        assertTrue(testShellTransitions.remotesForTakeover.isEmpty())
    }
    }


    @Test
    fun registersLongLivedTransition() {
        setFlagsRule.enableFlags(Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LIBRARY)

        activityTransitionAnimator.register(
            object : DelegateTransitionAnimatorController(controller) {
                override val transitionCookie =
                    ActivityTransitionAnimator.TransitionCookie("test_cookie_1")
                override val component = ComponentName("com.test.package", "Test1")
            }
        )
        assertEquals(2, testShellTransitions.remotes.size)

        activityTransitionAnimator.register(
            object : DelegateTransitionAnimatorController(controller) {
                override val transitionCookie =
                    ActivityTransitionAnimator.TransitionCookie("test_cookie_2")
                override val component = ComponentName("com.test.package", "Test2")
            }
        )
        assertEquals(4, testShellTransitions.remotes.size)
    }

    @Test
    fun registersLongLivedTransitionOverridingPreviousRegistration() {
        setFlagsRule.enableFlags(Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LIBRARY)

        val cookie = ActivityTransitionAnimator.TransitionCookie("test_cookie")
        activityTransitionAnimator.register(
            object : DelegateTransitionAnimatorController(controller) {
                override val transitionCookie = cookie
                override val component = ComponentName("com.test.package", "Test1")
            }
        )
        val transitions = testShellTransitions.remotes.values.toList()

        activityTransitionAnimator.register(
            object : DelegateTransitionAnimatorController(controller) {
                override val transitionCookie = cookie
                override val component = ComponentName("com.test.package", "Test2")
            }
        )
        assertEquals(2, testShellTransitions.remotes.size)
        for (transition in transitions) {
            assertThat(testShellTransitions.remotes.values).doesNotContain(transition)
        }
    }

    @Test
    fun doesNotRegisterLongLivedTransitionIfFlagIsDisabled() {
        setFlagsRule.disableFlags(Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LIBRARY)

        val controller =
            object : DelegateTransitionAnimatorController(controller) {
                override val transitionCookie =
                    ActivityTransitionAnimator.TransitionCookie("test_cookie")
                override val component = ComponentName("com.test.package", "Test")
            }
        assertThrows(IllegalStateException::class.java) {
            activityTransitionAnimator.register(controller)
        }
    }

    @Test
    fun doesNotRegisterLongLivedTransitionIfMissingRequiredProperties() {
        setFlagsRule.enableFlags(Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LIBRARY)

        // No TransitionCookie
        val controllerWithoutCookie =
            object : DelegateTransitionAnimatorController(controller) {
                override val transitionCookie = null
            }
        assertThrows(IllegalStateException::class.java) {
            activityTransitionAnimator.register(controllerWithoutCookie)
        }

        // No ComponentName
        val controllerWithoutComponent =
            object : DelegateTransitionAnimatorController(controller) {
                override val transitionCookie =
                    ActivityTransitionAnimator.TransitionCookie("test_cookie")
                override val component = null
            }
        assertThrows(IllegalStateException::class.java) {
            activityTransitionAnimator.register(controllerWithoutComponent)
        }

        // No TransitionRegister
        activityTransitionAnimator =
            ActivityTransitionAnimator(
                mainExecutor,
                transitionRegister = null,
                testTransitionAnimator,
                testTransitionAnimator,
                disableWmTimeout = true,
            )
        val validController =
            object : DelegateTransitionAnimatorController(controller) {
                override val transitionCookie =
                    ActivityTransitionAnimator.TransitionCookie("test_cookie")
                override val component = ComponentName("com.test.package", "Test")
            }
        assertThrows(IllegalStateException::class.java) {
            activityTransitionAnimator.register(validController)
        }
    }

    @Test
    fun unregistersLongLivedTransition() {
        setFlagsRule.enableFlags(Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LIBRARY)

        val cookies = arrayOfNulls<ActivityTransitionAnimator.TransitionCookie>(3)

        for (index in 0 until 3) {
            cookies[index] = ActivityTransitionAnimator.TransitionCookie("test_cookie_$index")

            val controller =
                object : DelegateTransitionAnimatorController(controller) {
                    override val transitionCookie = cookies[index]
                    override val component = ComponentName("foo.bar", "Foobar")
                }
            activityTransitionAnimator.register(controller)
        }

        activityTransitionAnimator.unregister(cookies[0]!!)
        assertEquals(4, testShellTransitions.remotes.size)

        activityTransitionAnimator.unregister(cookies[2]!!)
        assertEquals(2, testShellTransitions.remotes.size)

        activityTransitionAnimator.unregister(cookies[1]!!)
        assertThat(testShellTransitions.remotes).isEmpty()
    }

    @Test
    @Test
    fun doesNotStartIfAnimationIsCancelled() {
    fun doesNotStartIfAnimationIsCancelled() {
        val runner = activityTransitionAnimator.createRunner(controller)
        val runner = activityTransitionAnimator.createRunner(controller)