summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Luca Zuccarini <acul@google.com> 2024-05-23 13:32:54 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2024-05-23 13:32:54 +0000
commit7c86fdfa790abc502c18d950237ec69d2ea3b0bf (patch)
treefe2108c8befba154f003ed31e5b4c6d92bc0bdb3
parent40c5bba366394feea1a1eeabe23fbcf0d6e0972c (diff)
parentabd997f638908509390589cf8f0dd6ce5b9f4511 (diff)
Merge "Introduce long-lived registrations." into main
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt121
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/Expandable.kt15
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewTransitionAnimatorController.kt2
-rw-r--r--packages/SystemUI/compose/core/src/com/android/compose/animation/ExpandableController.kt7
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/animation/ActivityTransitionAnimatorTest.kt135
5 files changed, 272 insertions, 8 deletions
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt
index 23df26fdb246..b6e4e9b13a1c 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt
@@ -20,6 +20,8 @@ import android.app.ActivityManager
import android.app.ActivityTaskManager
import android.app.PendingIntent
import android.app.TaskInfo
+import android.app.WindowConfiguration
+import android.content.ComponentName
import android.graphics.Matrix
import android.graphics.Rect
import android.graphics.RectF
@@ -38,7 +40,9 @@ import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
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_FRONT
import android.view.animation.PathInterpolator
import android.window.RemoteTransition
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
* [intentStarter], which should use the provided [RemoteAnimationAdapter] and return the launch
@@ -497,6 +505,7 @@ constructor(
view: View,
cujType: Int? = null,
cookie: TransitionCookie? = null,
+ component: ComponentName? = null,
returnCujType: Int? = null
): Controller? {
// Make sure the View we launch from implements LaunchableView to avoid visibility
@@ -519,7 +528,13 @@ constructor(
return null
}
- return GhostedViewTransitionAnimatorController(view, cujType, cookie, returnCujType)
+ return GhostedViewTransitionAnimatorController(
+ view,
+ cujType,
+ cookie,
+ component,
+ returnCujType
+ )
}
}
@@ -554,6 +569,16 @@ constructor(
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
* animation will not be started.
*/
@@ -572,6 +597,91 @@ constructor(
}
/**
+ * 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
* events to the passed [delegate].
*/
@@ -817,13 +927,16 @@ constructor(
if (it.mode == targetMode) {
if (activityTransitionUseLargestWindow()) {
if (returnAnimationFrameworkLibrary()) {
- // If the controller contains a cookie, _only_ match if the candidate
- // contains the matching cookie.
+ // If the controller contains a cookie, _only_ match if either the
+ // candidate contains the matching cookie, or a component is also
+ // defined and is a match.
if (
controller.transitionCookie != null &&
it.taskInfo
?.launchCookies
- ?.contains(controller.transitionCookie) != true
+ ?.contains(controller.transitionCookie) != true &&
+ (controller.component == null ||
+ it.taskInfo?.topActivity != controller.component)
) {
continue
}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/Expandable.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/Expandable.kt
index 21557b8bb402..3ba9a2974846 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/Expandable.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/Expandable.kt
@@ -16,6 +16,7 @@
package com.android.systemui.animation
+import android.content.ComponentName
import android.view.View
/** 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]
* associated to 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]
* associated to the return animation that will use this controller.
*/
fun activityTransitionController(
launchCujType: Int? = null,
cookie: ActivityTransitionAnimator.TransitionCookie? = null,
+ component: ComponentName? = null,
returnCujType: Int? = null
): ActivityTransitionAnimator.Controller?
@@ -47,7 +51,12 @@ interface Expandable {
fun activityTransitionController(
launchCujType: Int? = null
): 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(
launchCujType: Int?,
cookie: ActivityTransitionAnimator.TransitionCookie?,
+ component: ComponentName?,
returnCujType: Int?
): ActivityTransitionAnimator.Controller? {
return ActivityTransitionAnimator.Controller.fromView(
view,
launchCujType,
cookie,
+ component,
returnCujType
)
}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewTransitionAnimatorController.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewTransitionAnimatorController.kt
index 9d4507337e51..e626c04675e1 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewTransitionAnimatorController.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewTransitionAnimatorController.kt
@@ -16,6 +16,7 @@
package com.android.systemui.animation
+import android.content.ComponentName
import android.graphics.Canvas
import android.graphics.ColorFilter
import android.graphics.Insets
@@ -62,6 +63,7 @@ constructor(
/** The [CujType] associated to this launch animation. */
private val launchCujType: Int? = null,
override val transitionCookie: ActivityTransitionAnimator.TransitionCookie? = null,
+ override val component: ComponentName? = null,
/** The [CujType] associated to this return animation. */
private val returnCujType: Int? = null,
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/ExpandableController.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/ExpandableController.kt
index 17a606171a9e..a55df2b36a80 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/animation/ExpandableController.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/ExpandableController.kt
@@ -16,6 +16,7 @@
package com.android.compose.animation
+import android.content.ComponentName
import android.view.View
import android.view.ViewGroup
import android.view.ViewGroupOverlay
@@ -136,13 +137,14 @@ internal class ExpandableControllerImpl(
override fun activityTransitionController(
launchCujType: Int?,
cookie: ActivityTransitionAnimator.TransitionCookie?,
+ component: ComponentName?,
returnCujType: Int?
): ActivityTransitionAnimator.Controller? {
if (!isComposed.value) {
return null
}
- return activityController(launchCujType, cookie, returnCujType)
+ return activityController(launchCujType, cookie, component, returnCujType)
}
override fun dialogTransitionController(
@@ -170,7 +172,6 @@ internal class ExpandableControllerImpl(
override var transitionContainer: ViewGroup = composeViewRoot.rootView as ViewGroup
- // TODO(b/323863002): update to be dependant on usage.
override val isLaunching: Boolean = true
override fun onTransitionAnimationEnd(isExpandingFullyAbove: Boolean) {
@@ -267,6 +268,7 @@ internal class ExpandableControllerImpl(
private fun activityController(
launchCujType: Int?,
cookie: ActivityTransitionAnimator.TransitionCookie?,
+ component: ComponentName?,
returnCujType: Int?
): ActivityTransitionAnimator.Controller {
val delegate = transitionController()
@@ -284,6 +286,7 @@ internal class ExpandableControllerImpl(
}
override val transitionCookie = cookie
+ override val component = component
override fun onTransitionAnimationStart(isExpandingFullyAbove: Boolean) {
delegate.onTransitionAnimationStart(isExpandingFullyAbove)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityTransitionAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityTransitionAnimatorTest.kt
index fd37cad72371..70a544ca6d2c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityTransitionAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityTransitionAnimatorTest.kt
@@ -25,6 +25,7 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.shared.Flags
import com.android.systemui.util.mockito.any
import com.android.wm.shell.shared.ShellTransitions
+import com.google.common.truth.Truth.assertThat
import junit.framework.Assert.assertFalse
import junit.framework.Assert.assertNotNull
import junit.framework.Assert.assertNull
@@ -203,6 +204,140 @@ class ActivityTransitionAnimatorTest : SysuiTestCase() {
}
@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
fun doesNotStartIfAnimationIsCancelled() {
val runner = activityTransitionAnimator.createRunner(controller)
runner.onAnimationCancelled()