summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt118
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt35
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImplTest.kt36
-rw-r--r--packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterInternal.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterInternalImpl.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/animation/ActivityTransitionAnimatorTest.kt269
9 files changed, 291 insertions, 194 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 c8d3430bf54b..f03bd3d9a2a7 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt
@@ -73,6 +73,9 @@ import com.android.wm.shell.shared.ShellTransitions
import com.android.wm.shell.shared.TransitionUtil
import java.util.concurrent.Executor
import kotlin.math.roundToInt
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withTimeoutOrNull
private const val TAG = "ActivityTransitionAnimator"
@@ -241,7 +244,7 @@ constructor(
override fun onTransitionAnimationProgress(linearProgress: Float) {
LinkedHashSet(listeners).forEach {
- it.onTransitionAnimationProgress(linearProgress)
+ it.onTransitionAnimationProgress(linearProgress)
}
}
@@ -494,15 +497,19 @@ constructor(
/**
* Create a new animation [Runner] controlled by the [Controller] that [controllerFactory] can
- * create based on [forLaunch].
+ * create based on [forLaunch] and within the given [scope].
*
* This method must only be used for long-lived registrations. Otherwise, use
* [createEphemeralRunner].
*/
@VisibleForTesting
- fun createLongLivedRunner(controllerFactory: ControllerFactory, forLaunch: Boolean): Runner {
+ fun createLongLivedRunner(
+ controllerFactory: ControllerFactory,
+ scope: CoroutineScope,
+ forLaunch: Boolean,
+ ): Runner {
assertLongLivedReturnAnimations()
- return Runner(callback!!, transitionAnimator, lifecycleListener) {
+ return Runner(scope, callback!!, transitionAnimator, lifecycleListener) {
controllerFactory.createController(forLaunch)
}
}
@@ -564,7 +571,7 @@ constructor(
* Creates a [Controller] for launching or returning from the activity linked to [cookie]
* and [component].
*/
- abstract fun createController(forLaunch: Boolean): Controller
+ abstract suspend fun createController(forLaunch: Boolean): Controller
}
/**
@@ -691,9 +698,14 @@ constructor(
* animations.
*
* The [Controller]s created by [controllerFactory] will only be used for transitions matching
- * the [cookie], or the [ComponentName] defined within it if the cookie matching fails.
+ * the [cookie], or the [ComponentName] defined within it if the cookie matching fails. These
+ * [Controller]s can only be created within [scope].
*/
- fun register(cookie: TransitionCookie, controllerFactory: ControllerFactory) {
+ fun register(
+ cookie: TransitionCookie,
+ controllerFactory: ControllerFactory,
+ scope: CoroutineScope,
+ ) {
assertLongLivedReturnAnimations()
if (transitionRegister == null) {
@@ -725,7 +737,7 @@ constructor(
}
val launchRemoteTransition =
RemoteTransition(
- OriginTransition(createLongLivedRunner(controllerFactory, forLaunch = true)),
+ OriginTransition(createLongLivedRunner(controllerFactory, scope, forLaunch = true)),
"${cookie}_launchTransition",
)
transitionRegister.register(launchFilter, launchRemoteTransition, includeTakeover = true)
@@ -749,7 +761,9 @@ constructor(
}
val returnRemoteTransition =
RemoteTransition(
- OriginTransition(createLongLivedRunner(controllerFactory, forLaunch = false)),
+ OriginTransition(
+ createLongLivedRunner(controllerFactory, scope, forLaunch = false)
+ ),
"${cookie}_returnTransition",
)
transitionRegister.register(returnFilter, returnRemoteTransition, includeTakeover = true)
@@ -952,7 +966,9 @@ constructor(
* Reusable factory to generate single-use controllers. In case of an ephemeral [Runner],
* this must be null and [controller] must be defined instead.
*/
- private val controllerFactory: (() -> Controller)?,
+ private val controllerFactory: (suspend () -> Controller)?,
+ /** The scope to use when this runner is based on [controllerFactory]. */
+ private val scope: CoroutineScope? = null,
private val callback: Callback,
/** The animator to use to animate the window transition. */
private val transitionAnimator: TransitionAnimator,
@@ -973,13 +989,15 @@ constructor(
)
constructor(
+ scope: CoroutineScope,
callback: Callback,
transitionAnimator: TransitionAnimator,
listener: Listener? = null,
- controllerFactory: () -> Controller,
+ controllerFactory: suspend () -> Controller,
) : this(
controller = null,
controllerFactory = controllerFactory,
+ scope = scope,
callback = callback,
transitionAnimator = transitionAnimator,
listener = listener,
@@ -994,12 +1012,12 @@ constructor(
assert((controller != null).xor(controllerFactory != null))
delegate = null
- if (controller != null) {
+ controller?.let {
// Ephemeral launches bundle the runner with the launch request (instead of being
// registered ahead of time for later use). This means that there could be a timeout
// between creation and invocation, so the delegate needs to exist from the
// beginning in order to handle such timeout.
- createDelegate()
+ createDelegate(it)
}
}
@@ -1040,49 +1058,79 @@ constructor(
finishedCallback: IRemoteAnimationFinishedCallback?,
performAnimation: (AnimationDelegate) -> Unit,
) {
- maybeSetUp()
- val delegate = delegate
- mainExecutor.execute {
- if (delegate == null) {
- Log.i(TAG, "onAnimationStart called after completion")
- // Animation started too late and timed out already. We need to still
- // signal back that we're done with it.
- finishedCallback?.onAnimationFinished()
- } else {
- performAnimation(delegate)
+ val controller = controller
+ val controllerFactory = controllerFactory
+
+ if (controller != null) {
+ maybeSetUp(controller)
+ val success = startAnimation(performAnimation)
+ if (!success) finishedCallback?.onAnimationFinished()
+ } else if (controllerFactory != null) {
+ scope?.launch {
+ val success =
+ withTimeoutOrNull(TRANSITION_TIMEOUT) {
+ setUp(controllerFactory)
+ startAnimation(performAnimation)
+ } ?: false
+ if (!success) finishedCallback?.onAnimationFinished()
}
+ } else {
+ // This should never happen, as either the controller or factory should always be
+ // defined. This final call is for safety in case something goes wrong.
+ Log.wtf(TAG, "initAndRun with neither a controller nor factory")
+ finishedCallback?.onAnimationFinished()
+ }
+ }
+
+ /** Tries to start the animation on the main thread and returns whether it succeeded. */
+ @BinderThread
+ private fun startAnimation(performAnimation: (AnimationDelegate) -> Unit): Boolean {
+ val delegate = delegate
+ return if (delegate != null) {
+ mainExecutor.execute { performAnimation(delegate) }
+ true
+ } else {
+ // Animation started too late and timed out already.
+ Log.i(TAG, "startAnimation called after completion")
+ false
}
}
@BinderThread
override fun onAnimationCancelled() {
val delegate = delegate
- mainExecutor.execute {
- delegate ?: Log.wtf(TAG, "onAnimationCancelled called after completion")
- delegate?.onAnimationCancelled()
+ if (delegate != null) {
+ mainExecutor.execute { delegate.onAnimationCancelled() }
+ } else {
+ Log.wtf(TAG, "onAnimationCancelled called after completion")
}
}
+ /**
+ * Posts the default animation timeouts. Since this only applies to ephemeral launches, this
+ * method is a no-op if [controller] is not defined.
+ */
@VisibleForTesting
@UiThread
fun postTimeouts() {
- maybeSetUp()
+ controller?.let { maybeSetUp(it) }
delegate?.postTimeouts()
}
@AnyThread
- private fun maybeSetUp() {
- if (controllerFactory == null || delegate != null) return
- createDelegate()
+ private fun maybeSetUp(controller: Controller) {
+ if (delegate != null) return
+ createDelegate(controller)
}
@AnyThread
- private fun createDelegate() {
- var controller = controller
- val factory = controllerFactory
- if (controller == null && factory == null) return
+ private suspend fun setUp(createController: suspend () -> Controller) {
+ val controller = createController()
+ createDelegate(controller)
+ }
- controller = controller ?: factory!!.invoke()
+ @AnyThread
+ private fun createDelegate(controller: Controller) {
delegate =
AnimationDelegate(
mainExecutor,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt
index b0b80a9419e2..52c41a07198d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt
@@ -25,11 +25,14 @@ import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.ActivityTransitionAnimator
import com.android.systemui.flags.EnableSceneContainer
+import com.android.systemui.kosmos.testScope
import com.android.systemui.shared.Flags as SharedFlags
import com.android.systemui.statusbar.SysuiStatusBarStateController
+import com.android.systemui.testKosmos
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -48,6 +51,7 @@ class ActivityStarterImplTest : SysuiTestCase() {
@Mock private lateinit var activityStarterInternal: ActivityStarterInternalImpl
@Mock private lateinit var statusBarStateController: SysuiStatusBarStateController
private lateinit var underTest: ActivityStarterImpl
+ private val kosmos = testKosmos()
private val mainExecutor = FakeExecutor(FakeSystemClock())
@Before
@@ -69,12 +73,18 @@ class ActivityStarterImplTest : SysuiTestCase() {
@EnableSceneContainer
@Test
fun registerTransition_forwardsTheRequest() {
- val cookie = mock(ActivityTransitionAnimator.TransitionCookie::class.java)
- val controllerFactory = mock(ActivityTransitionAnimator.ControllerFactory::class.java)
-
- underTest.registerTransition(cookie, controllerFactory)
-
- verify(activityStarterInternal).registerTransition(eq(cookie), eq(controllerFactory))
+ with(kosmos) {
+ testScope.runTest {
+ val cookie = mock(ActivityTransitionAnimator.TransitionCookie::class.java)
+ val controllerFactory =
+ mock(ActivityTransitionAnimator.ControllerFactory::class.java)
+
+ underTest.registerTransition(cookie, controllerFactory, testScope)
+
+ verify(activityStarterInternal)
+ .registerTransition(eq(cookie), eq(controllerFactory), eq(testScope))
+ }
+ }
}
@DisableFlags(
@@ -83,12 +93,17 @@ class ActivityStarterImplTest : SysuiTestCase() {
)
@Test
fun registerTransition_doesNotForwardTheRequest_whenFlaggedOff() {
- val cookie = mock(ActivityTransitionAnimator.TransitionCookie::class.java)
- val controllerFactory = mock(ActivityTransitionAnimator.ControllerFactory::class.java)
+ with(kosmos) {
+ testScope.runTest {
+ val cookie = mock(ActivityTransitionAnimator.TransitionCookie::class.java)
+ val controllerFactory =
+ mock(ActivityTransitionAnimator.ControllerFactory::class.java)
- underTest.registerTransition(cookie, controllerFactory)
+ underTest.registerTransition(cookie, controllerFactory, testScope)
- verify(activityStarterInternal, never()).registerTransition(any(), any())
+ verify(activityStarterInternal, never()).registerTransition(any(), any(), any())
+ }
+ }
}
@EnableFlags(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImplTest.kt
index 5406acf694ff..dfa5c9a26d79 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImplTest.kt
@@ -42,6 +42,7 @@ import com.android.systemui.communal.domain.interactor.CommunalSettingsInteracto
import com.android.systemui.communal.domain.interactor.communalSettingsInteractor
import com.android.systemui.keyguard.KeyguardViewMediator
import com.android.systemui.keyguard.WakefulnessLifecycle
+import com.android.systemui.kosmos.testScope
import com.android.systemui.plugins.ActivityStarter.OnDismissAction
import com.android.systemui.settings.UserTracker
import com.android.systemui.shade.ShadeController
@@ -58,12 +59,14 @@ import com.android.systemui.statusbar.policy.DeviceProvisionedController
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.statusbar.window.StatusBarWindowController
import com.android.systemui.statusbar.window.StatusBarWindowControllerStore
+import com.android.systemui.testKosmos
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import java.util.Optional
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertThrows
import org.junit.Before
import org.junit.Test
@@ -109,6 +112,7 @@ class LegacyActivityStarterInternalImplTest : SysuiTestCase() {
@Mock private lateinit var communalSceneInteractor: CommunalSceneInteractor
@Mock private lateinit var communalSettingsInteractor: CommunalSettingsInteractor
private lateinit var underTest: LegacyActivityStarterInternalImpl
+ private val kosmos = testKosmos()
private val mainExecutor = FakeExecutor(FakeSystemClock())
private val shadeAnimationInteractor =
ShadeAnimationInteractorLegacyImpl(ShadeAnimationRepository(), FakeShadeRepository())
@@ -157,13 +161,18 @@ class LegacyActivityStarterInternalImplTest : SysuiTestCase() {
)
@Test
fun registerTransition_registers() {
- val cookie = mock(ActivityTransitionAnimator.TransitionCookie::class.java)
- val controllerFactory = mock(ActivityTransitionAnimator.ControllerFactory::class.java)
- `when`(controllerFactory.cookie).thenReturn(cookie)
+ with(kosmos) {
+ testScope.runTest {
+ val cookie = mock(ActivityTransitionAnimator.TransitionCookie::class.java)
+ val controllerFactory =
+ mock(ActivityTransitionAnimator.ControllerFactory::class.java)
+ `when`(controllerFactory.cookie).thenReturn(cookie)
- underTest.registerTransition(cookie, controllerFactory)
+ underTest.registerTransition(cookie, controllerFactory, testScope)
- verify(activityTransitionAnimator).register(eq(cookie), any())
+ verify(activityTransitionAnimator).register(eq(cookie), any(), eq(testScope))
+ }
+ }
}
@DisableFlags(
@@ -172,14 +181,19 @@ class LegacyActivityStarterInternalImplTest : SysuiTestCase() {
)
@Test
fun registerTransition_throws_whenFlagsAreDisabled() {
- val cookie = mock(ActivityTransitionAnimator.TransitionCookie::class.java)
- val controllerFactory = mock(ActivityTransitionAnimator.ControllerFactory::class.java)
+ with(kosmos) {
+ testScope.runTest {
+ val cookie = mock(ActivityTransitionAnimator.TransitionCookie::class.java)
+ val controllerFactory =
+ mock(ActivityTransitionAnimator.ControllerFactory::class.java)
- assertThrows(IllegalStateException::class.java) {
- underTest.registerTransition(cookie, controllerFactory)
- }
+ assertThrows(IllegalStateException::class.java) {
+ underTest.registerTransition(cookie, controllerFactory, testScope)
+ }
- verify(activityTransitionAnimator, never()).register(any(), any())
+ verify(activityTransitionAnimator, never()).register(any(), any(), any())
+ }
+ }
}
@EnableFlags(
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java
index ca98cbf20c3a..18891dba4b0d 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java
@@ -25,6 +25,8 @@ import android.view.View;
import com.android.systemui.animation.ActivityTransitionAnimator;
import com.android.systemui.plugins.annotations.ProvidesInterface;
+import kotlinx.coroutines.CoroutineScope;
+
/**
* An interface to start activities. This is used as a callback from the views to
* {@link PhoneStatusBar} to allow custom handling for starting the activity, i.e. dismissing the
@@ -37,11 +39,12 @@ public interface ActivityStarter {
/**
* Registers the given {@link ActivityTransitionAnimator.ControllerFactory} for launching and
* closing transitions matching the {@link ActivityTransitionAnimator.TransitionCookie} and the
- * {@link ComponentName} that it contains.
+ * {@link ComponentName} that it contains, within the given {@link CoroutineScope}.
*/
void registerTransition(
ActivityTransitionAnimator.TransitionCookie cookie,
- ActivityTransitionAnimator.ControllerFactory controllerFactory);
+ ActivityTransitionAnimator.ControllerFactory controllerFactory,
+ CoroutineScope scope);
/**
* Unregisters the {@link ActivityTransitionAnimator.ControllerFactory} previously registered
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt
index 5a63c0cd84e6..bd1d7f755a74 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt
@@ -30,6 +30,7 @@ import com.android.systemui.statusbar.SysuiStatusBarStateController
import com.android.systemui.util.concurrency.DelayableExecutor
import dagger.Lazy
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
/** Handles start activity logic in SystemUI. */
@SysUISingleton
@@ -52,9 +53,10 @@ constructor(
override fun registerTransition(
cookie: ActivityTransitionAnimator.TransitionCookie,
controllerFactory: ActivityTransitionAnimator.ControllerFactory,
+ scope: CoroutineScope,
) {
if (!TransitionAnimator.longLivedReturnAnimationsEnabled()) return
- activityStarterInternal.registerTransition(cookie, controllerFactory)
+ activityStarterInternal.registerTransition(cookie, controllerFactory, scope)
}
override fun unregisterTransition(cookie: ActivityTransitionAnimator.TransitionCookie) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterInternal.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterInternal.kt
index 5e427fbf1f7e..015ec3052134 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterInternal.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterInternal.kt
@@ -25,15 +25,17 @@ import android.view.View
import com.android.systemui.ActivityIntentHelper
import com.android.systemui.animation.ActivityTransitionAnimator
import com.android.systemui.plugins.ActivityStarter
+import kotlinx.coroutines.CoroutineScope
interface ActivityStarterInternal {
/**
* Registers the given [controllerFactory] for launching and closing transitions matching the
- * [cookie] and the [ComponentName] that it contains.
+ * [cookie] and the [ComponentName] that it contains, within the given [scope].
*/
fun registerTransition(
cookie: ActivityTransitionAnimator.TransitionCookie,
controllerFactory: ActivityTransitionAnimator.ControllerFactory,
+ scope: CoroutineScope,
)
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterInternalImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterInternalImpl.kt
index 7289c2ed5897..6e82d7f7401a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterInternalImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterInternalImpl.kt
@@ -66,6 +66,7 @@ import com.android.systemui.util.kotlin.getOrNull
import dagger.Lazy
import java.util.Optional
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
/**
* Encapsulates the activity logic for activity starter when the SceneContainerFlag is enabled.
@@ -105,6 +106,7 @@ constructor(
override fun registerTransition(
cookie: ActivityTransitionAnimator.TransitionCookie,
controllerFactory: ActivityTransitionAnimator.ControllerFactory,
+ scope: CoroutineScope,
) {
check(TransitionAnimator.longLivedReturnAnimationsEnabled())
@@ -116,7 +118,7 @@ constructor(
controllerFactory.launchCujType,
controllerFactory.returnCujType,
) {
- override fun createController(
+ override suspend fun createController(
forLaunch: Boolean
): ActivityTransitionAnimator.Controller {
val baseController = controllerFactory.createController(forLaunch)
@@ -132,7 +134,7 @@ constructor(
}
}
- activityTransitionAnimator.register(cookie, factory)
+ activityTransitionAnimator.register(cookie, factory, scope)
}
override fun unregisterTransition(cookie: ActivityTransitionAnimator.TransitionCookie) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt
index d7a29c36f2ce..76f67dc6c146 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt
@@ -64,6 +64,7 @@ import com.android.systemui.util.kotlin.getOrNull
import dagger.Lazy
import java.util.Optional
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
/** Encapsulates the activity logic for activity starter. */
@SysUISingleton
@@ -102,6 +103,7 @@ constructor(
override fun registerTransition(
cookie: ActivityTransitionAnimator.TransitionCookie,
controllerFactory: ActivityTransitionAnimator.ControllerFactory,
+ scope: CoroutineScope,
) {
check(TransitionAnimator.longLivedReturnAnimationsEnabled())
@@ -113,7 +115,7 @@ constructor(
controllerFactory.launchCujType,
controllerFactory.returnCujType,
) {
- override fun createController(
+ override suspend fun createController(
forLaunch: Boolean
): ActivityTransitionAnimator.Controller {
val baseController = controllerFactory.createController(forLaunch)
@@ -129,7 +131,7 @@ constructor(
}
}
- activityTransitionAnimator.register(cookie, factory)
+ activityTransitionAnimator.register(cookie, factory, scope)
}
override fun unregisterTransition(cookie: ActivityTransitionAnimator.TransitionCookie) {
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 fd751d9cc7c3..845be0252581 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityTransitionAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityTransitionAnimatorTest.kt
@@ -27,7 +27,10 @@ import android.window.WindowAnimationState
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.runTest
+import com.android.systemui.kosmos.testScope
import com.android.systemui.shared.Flags
+import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.any
import com.android.wm.shell.shared.ShellTransitions
import com.google.common.truth.Truth.assertThat
@@ -38,6 +41,9 @@ import junit.framework.Assert.assertTrue
import junit.framework.AssertionFailedError
import kotlin.concurrent.thread
import kotlin.test.assertEquals
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.advanceUntilIdle
+import kotlinx.coroutines.test.runTest
import org.junit.After
import org.junit.Assert.assertThrows
import org.junit.Before
@@ -54,10 +60,12 @@ import org.mockito.Mockito.`when`
import org.mockito.Spy
import org.mockito.junit.MockitoJUnit
+@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
@RunWithLooper
class ActivityTransitionAnimatorTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
private val transitionContainer = LinearLayout(mContext)
private val mainExecutor = context.mainExecutor
private val testTransitionAnimator = fakeTransitionAnimator(mainExecutor)
@@ -67,12 +75,12 @@ class ActivityTransitionAnimatorTest : SysuiTestCase() {
@Spy private val controller = TestTransitionAnimatorController(transitionContainer)
@Mock lateinit var iCallback: IRemoteAnimationFinishedCallback
- private lateinit var activityTransitionAnimator: ActivityTransitionAnimator
+ private lateinit var underTest: ActivityTransitionAnimator
@get:Rule val rule = MockitoJUnit.rule()
@Before
fun setup() {
- activityTransitionAnimator =
+ underTest =
ActivityTransitionAnimator(
mainExecutor,
ActivityTransitionAnimator.TransitionRegister.fromShellTransitions(
@@ -82,17 +90,17 @@ class ActivityTransitionAnimatorTest : SysuiTestCase() {
testTransitionAnimator,
disableWmTimeout = true,
)
- activityTransitionAnimator.callback = callback
- activityTransitionAnimator.addListener(listener)
+ underTest.callback = callback
+ underTest.addListener(listener)
}
@After
fun tearDown() {
- activityTransitionAnimator.removeListener(listener)
+ underTest.removeListener(listener)
}
private fun startIntentWithAnimation(
- animator: ActivityTransitionAnimator = this.activityTransitionAnimator,
+ animator: ActivityTransitionAnimator = underTest,
controller: ActivityTransitionAnimator.Controller? = this.controller,
animate: Boolean = true,
intentStarter: (RemoteAnimationAdapter?) -> Int,
@@ -157,7 +165,7 @@ class ActivityTransitionAnimatorTest : SysuiTestCase() {
val willAnimateCaptor = ArgumentCaptor.forClass(Boolean::class.java)
var animationAdapter: RemoteAnimationAdapter? = null
- startIntentWithAnimation(activityTransitionAnimator) { adapter ->
+ startIntentWithAnimation(underTest) { adapter ->
animationAdapter = adapter
ActivityManager.START_DELIVERED_TO_TOP
}
@@ -185,9 +193,7 @@ class ActivityTransitionAnimatorTest : SysuiTestCase() {
fun registersReturnIffCookieIsPresent() {
`when`(callback.isOnKeyguard()).thenReturn(false)
- startIntentWithAnimation(activityTransitionAnimator, controller) { _ ->
- ActivityManager.START_DELIVERED_TO_TOP
- }
+ startIntentWithAnimation(underTest, controller) { ActivityManager.START_DELIVERED_TO_TOP }
waitForIdleSync()
assertTrue(testShellTransitions.remotes.isEmpty())
@@ -199,9 +205,7 @@ class ActivityTransitionAnimatorTest : SysuiTestCase() {
get() = ActivityTransitionAnimator.TransitionCookie("testCookie")
}
- startIntentWithAnimation(activityTransitionAnimator, controller) { _ ->
- ActivityManager.START_DELIVERED_TO_TOP
- }
+ startIntentWithAnimation(underTest, controller) { ActivityManager.START_DELIVERED_TO_TOP }
waitForIdleSync()
assertEquals(1, testShellTransitions.remotes.size)
@@ -214,13 +218,15 @@ class ActivityTransitionAnimatorTest : SysuiTestCase() {
)
@Test
fun registersLongLivedTransition() {
- var factory = controllerFactory()
- activityTransitionAnimator.register(factory.cookie, factory)
- assertEquals(2, testShellTransitions.remotes.size)
-
- factory = controllerFactory()
- activityTransitionAnimator.register(factory.cookie, factory)
- assertEquals(4, testShellTransitions.remotes.size)
+ kosmos.runTest {
+ var factory = controllerFactory()
+ underTest.register(factory.cookie, factory, testScope)
+ assertEquals(2, testShellTransitions.remotes.size)
+
+ factory = controllerFactory()
+ underTest.register(factory.cookie, factory, testScope)
+ assertEquals(4, testShellTransitions.remotes.size)
+ }
}
@EnableFlags(
@@ -229,49 +235,55 @@ class ActivityTransitionAnimatorTest : SysuiTestCase() {
)
@Test
fun registersLongLivedTransitionOverridingPreviousRegistration() {
- val cookie = ActivityTransitionAnimator.TransitionCookie("test_cookie")
- var factory = controllerFactory(cookie)
- activityTransitionAnimator.register(cookie, factory)
- val transitions = testShellTransitions.remotes.values.toList()
-
- factory = controllerFactory(cookie)
- activityTransitionAnimator.register(cookie, factory)
- assertEquals(2, testShellTransitions.remotes.size)
- for (transition in transitions) {
- assertThat(testShellTransitions.remotes.values).doesNotContain(transition)
+ kosmos.runTest {
+ val cookie = ActivityTransitionAnimator.TransitionCookie("test_cookie")
+ var factory = controllerFactory(cookie)
+ underTest.register(cookie, factory, testScope)
+ val transitions = testShellTransitions.remotes.values.toList()
+
+ factory = controllerFactory(cookie)
+ underTest.register(cookie, factory, testScope)
+ assertEquals(2, testShellTransitions.remotes.size)
+ for (transition in transitions) {
+ assertThat(testShellTransitions.remotes.values).doesNotContain(transition)
+ }
}
}
@DisableFlags(Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LONG_LIVED)
@Test
fun doesNotRegisterLongLivedTransitionIfFlagIsDisabled() {
- val factory = controllerFactory(component = null)
- assertThrows(IllegalStateException::class.java) {
- activityTransitionAnimator.register(factory.cookie, factory)
+ kosmos.runTest {
+ val factory = controllerFactory(component = null)
+ assertThrows(IllegalStateException::class.java) {
+ underTest.register(factory.cookie, factory, testScope)
+ }
}
}
@EnableFlags(Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LONG_LIVED)
@Test
fun doesNotRegisterLongLivedTransitionIfMissingRequiredProperties() {
- // No ComponentName
- var factory = controllerFactory(component = null)
- assertThrows(IllegalStateException::class.java) {
- activityTransitionAnimator.register(factory.cookie, factory)
- }
+ kosmos.runTest {
+ // No ComponentName
+ var factory = controllerFactory(component = null)
+ assertThrows(IllegalStateException::class.java) {
+ underTest.register(factory.cookie, factory, testScope)
+ }
- // No TransitionRegister
- activityTransitionAnimator =
- ActivityTransitionAnimator(
- mainExecutor,
- transitionRegister = null,
- testTransitionAnimator,
- testTransitionAnimator,
- disableWmTimeout = true,
- )
- factory = controllerFactory()
- assertThrows(IllegalStateException::class.java) {
- activityTransitionAnimator.register(factory.cookie, factory)
+ // No TransitionRegister
+ val activityTransitionAnimator =
+ ActivityTransitionAnimator(
+ mainExecutor,
+ transitionRegister = null,
+ testTransitionAnimator,
+ testTransitionAnimator,
+ disableWmTimeout = true,
+ )
+ factory = controllerFactory()
+ assertThrows(IllegalStateException::class.java) {
+ activityTransitionAnimator.register(factory.cookie, factory, testScope)
+ }
}
}
@@ -281,27 +293,29 @@ class ActivityTransitionAnimatorTest : SysuiTestCase() {
)
@Test
fun unregistersLongLivedTransition() {
- val cookies = arrayOfNulls<ActivityTransitionAnimator.TransitionCookie>(3)
+ kosmos.runTest {
+ val cookies = arrayOfNulls<ActivityTransitionAnimator.TransitionCookie>(3)
- for (index in 0 until 3) {
- cookies[index] = mock(ActivityTransitionAnimator.TransitionCookie::class.java)
- val factory = controllerFactory(cookies[index]!!)
- activityTransitionAnimator.register(factory.cookie, factory)
- }
+ for (index in 0 until 3) {
+ cookies[index] = mock(ActivityTransitionAnimator.TransitionCookie::class.java)
+ val factory = controllerFactory(cookies[index]!!)
+ underTest.register(factory.cookie, factory, testScope)
+ }
- activityTransitionAnimator.unregister(cookies[0]!!)
- assertEquals(4, testShellTransitions.remotes.size)
+ underTest.unregister(cookies[0]!!)
+ assertEquals(4, testShellTransitions.remotes.size)
- activityTransitionAnimator.unregister(cookies[2]!!)
- assertEquals(2, testShellTransitions.remotes.size)
+ underTest.unregister(cookies[2]!!)
+ assertEquals(2, testShellTransitions.remotes.size)
- activityTransitionAnimator.unregister(cookies[1]!!)
- assertThat(testShellTransitions.remotes).isEmpty()
+ underTest.unregister(cookies[1]!!)
+ assertThat(testShellTransitions.remotes).isEmpty()
+ }
}
@Test
fun doesNotStartIfAnimationIsCancelled() {
- val runner = activityTransitionAnimator.createEphemeralRunner(controller)
+ val runner = underTest.createEphemeralRunner(controller)
runner.onAnimationCancelled()
runner.onAnimationStart(TRANSIT_NONE, emptyArray(), emptyArray(), emptyArray(), iCallback)
@@ -315,7 +329,7 @@ class ActivityTransitionAnimatorTest : SysuiTestCase() {
@Test
fun cancelsIfNoOpeningWindowIsFound() {
- val runner = activityTransitionAnimator.createEphemeralRunner(controller)
+ val runner = underTest.createEphemeralRunner(controller)
runner.onAnimationStart(TRANSIT_NONE, emptyArray(), emptyArray(), emptyArray(), iCallback)
waitForIdleSync()
@@ -328,7 +342,7 @@ class ActivityTransitionAnimatorTest : SysuiTestCase() {
@Test
fun startsAnimationIfWindowIsOpening() {
- val runner = activityTransitionAnimator.createEphemeralRunner(controller)
+ val runner = underTest.createEphemeralRunner(controller)
runner.onAnimationStart(
TRANSIT_NONE,
arrayOf(fakeWindow()),
@@ -354,9 +368,11 @@ class ActivityTransitionAnimatorTest : SysuiTestCase() {
)
@Test
fun creatingRunnerWithLazyInitializationThrows_whenTheFlagsAreDisabled() {
- assertThrows(IllegalStateException::class.java) {
- val factory = controllerFactory()
- activityTransitionAnimator.createLongLivedRunner(factory, forLaunch = true)
+ kosmos.runTest {
+ assertThrows(IllegalStateException::class.java) {
+ val factory = controllerFactory()
+ underTest.createLongLivedRunner(factory, testScope, forLaunch = true)
+ }
}
}
@@ -365,44 +381,34 @@ class ActivityTransitionAnimatorTest : SysuiTestCase() {
Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LONG_LIVED,
)
@Test
- fun runnerCreatesDelegateLazily_whenPostingTimeouts() {
- val factory = controllerFactory()
- val runner = activityTransitionAnimator.createLongLivedRunner(factory, forLaunch = 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 factory = controllerFactory()
- val runner = activityTransitionAnimator.createLongLivedRunner(factory, forLaunch = true)
- assertNull(runner.delegate)
-
- var delegateInitialized = false
- activityTransitionAnimator.addListener(
- object : ActivityTransitionAnimator.Listener {
- override fun onTransitionAnimationStart() {
- // This is called iff the delegate was initialized, so it's a good proxy for
- // checking the initialization.
- delegateInitialized = true
+ kosmos.runTest {
+ val factory = controllerFactory()
+ val runner = underTest.createLongLivedRunner(factory, testScope, forLaunch = true)
+ assertNull(runner.delegate)
+
+ var delegateInitialized = false
+ underTest.addListener(
+ object : ActivityTransitionAnimator.Listener {
+ override fun onTransitionAnimationStart() {
+ // This is called iff the delegate was initialized, so it's a good proxy for
+ // checking the initialization.
+ delegateInitialized = true
+ }
}
- }
- )
- runner.onAnimationStart(
- TRANSIT_NONE,
- arrayOf(fakeWindow()),
- emptyArray(),
- emptyArray(),
- iCallback,
- )
+ )
+ runner.onAnimationStart(
+ TRANSIT_NONE,
+ arrayOf(fakeWindow()),
+ emptyArray(),
+ emptyArray(),
+ iCallback,
+ )
+ testScope.advanceUntilIdle()
+ waitForIdleSync()
- waitForIdleSync()
- assertTrue(delegateInitialized)
+ assertTrue(delegateInitialized)
+ }
}
@EnableFlags(
@@ -411,29 +417,32 @@ class ActivityTransitionAnimatorTest : SysuiTestCase() {
)
@Test
fun runnerCreatesDelegateLazily_onAnimationTakeover() {
- val factory = controllerFactory()
- val runner = activityTransitionAnimator.createLongLivedRunner(factory, forLaunch = false)
- assertNull(runner.delegate)
-
- var delegateInitialized = false
- activityTransitionAnimator.addListener(
- object : ActivityTransitionAnimator.Listener {
- override fun onTransitionAnimationStart() {
- // This is called iff the delegate was initialized, so it's a good proxy for
- // checking the initialization.
- delegateInitialized = true
+ kosmos.runTest {
+ val factory = controllerFactory()
+ val runner = underTest.createLongLivedRunner(factory, testScope, forLaunch = false)
+ assertNull(runner.delegate)
+
+ var delegateInitialized = false
+ underTest.addListener(
+ object : ActivityTransitionAnimator.Listener {
+ override fun onTransitionAnimationStart() {
+ // This is called iff the delegate was initialized, so it's a good proxy for
+ // checking the initialization.
+ delegateInitialized = true
+ }
}
- }
- )
- runner.takeOverAnimation(
- arrayOf(fakeWindow(MODE_CLOSING)),
- arrayOf(WindowAnimationState()),
- SurfaceControl.Transaction(),
- iCallback,
- )
+ )
+ runner.takeOverAnimation(
+ arrayOf(fakeWindow(MODE_CLOSING)),
+ arrayOf(WindowAnimationState()),
+ SurfaceControl.Transaction(),
+ iCallback,
+ )
+ testScope.advanceUntilIdle()
+ waitForIdleSync()
- waitForIdleSync()
- assertTrue(delegateInitialized)
+ assertTrue(delegateInitialized)
+ }
}
@DisableFlags(
@@ -442,7 +451,7 @@ class ActivityTransitionAnimatorTest : SysuiTestCase() {
)
@Test
fun animationTakeoverThrows_whenTheFlagsAreDisabled() {
- val runner = activityTransitionAnimator.createEphemeralRunner(controller)
+ val runner = underTest.createEphemeralRunner(controller)
assertThrows(IllegalStateException::class.java) {
runner.takeOverAnimation(
arrayOf(fakeWindow()),
@@ -459,7 +468,7 @@ class ActivityTransitionAnimatorTest : SysuiTestCase() {
)
@Test
fun disposeRunner_delegateDereferenced() {
- val runner = activityTransitionAnimator.createEphemeralRunner(controller)
+ val runner = underTest.createEphemeralRunner(controller)
assertNotNull(runner.delegate)
runner.dispose()
waitForIdleSync()
@@ -469,13 +478,13 @@ class ActivityTransitionAnimatorTest : SysuiTestCase() {
@Test
fun concurrentListenerModification_doesNotThrow() {
// Need a second listener to trigger the concurrent modification.
- activityTransitionAnimator.addListener(object : ActivityTransitionAnimator.Listener {})
+ underTest.addListener(object : ActivityTransitionAnimator.Listener {})
`when`(listener.onTransitionAnimationStart()).thenAnswer {
- activityTransitionAnimator.removeListener(listener)
+ underTest.removeListener(listener)
listener
}
- val runner = activityTransitionAnimator.createEphemeralRunner(controller)
+ val runner = underTest.createEphemeralRunner(controller)
runner.onAnimationStart(
TRANSIT_NONE,
arrayOf(fakeWindow()),
@@ -494,7 +503,7 @@ class ActivityTransitionAnimatorTest : SysuiTestCase() {
component: ComponentName? = mock(ComponentName::class.java),
): ActivityTransitionAnimator.ControllerFactory {
return object : ActivityTransitionAnimator.ControllerFactory(cookie, component) {
- override fun createController(forLaunch: Boolean) =
+ override suspend fun createController(forLaunch: Boolean) =
object : DelegateTransitionAnimatorController(controller) {
override val isLaunching: Boolean
get() = forLaunch