diff --git a/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimator.kt b/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimator.kt index f4157f21e1583..8625d63a3c7ef 100644 --- a/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimator.kt +++ b/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimator.kt @@ -125,6 +125,13 @@ class PhysicsAnimator private constructor (val target: T) { */ internal var startAction: () -> Unit = ::startInternal + /** + * Action to run when [cancel] is called. This can be changed by + * [PhysicsAnimatorTestUtils.prepareForTest] to cancel animations from the main thread, which + * is required. + */ + internal var cancelAction: (Set>) -> Unit = ::cancelInternal + /** * Springs a property to the given value, using the provided configuration settings. * @@ -429,10 +436,13 @@ class PhysicsAnimator private constructor (val target: T) { max = max(currentValue, this.max) } - // Apply the configuration and start the animation. Since flings can't be - // redirected while in motion, cancel it first. + // Flings can't be updated to a new position while maintaining velocity, because + // we're using the explicitly provided start velocity. Cancel any flings (or + // springs) on this property before flinging. + cancel(animatedProperty) + + // Apply the configuration and start the animation. getFlingAnimation(animatedProperty) - .also { it.cancel() } .also { flingConfig.applyToAnimation(it) } .start() } @@ -707,11 +717,26 @@ class PhysicsAnimator private constructor (val target: T) { return springConfigs.keys.union(flingConfigs.keys) } + /** + * Cancels the given properties. This is typically called immediately by [cancel], unless this + * animator is under test. + */ + internal fun cancelInternal(properties: Set>) { + for (property in properties) { + flingAnimations[property]?.cancel() + springAnimations[property]?.cancel() + } + } + /** Cancels all in progress animations on all properties. */ fun cancel() { - for (dynamicAnim in flingAnimations.values.union(springAnimations.values)) { - dynamicAnim.cancel() - } + cancelAction(flingAnimations.keys) + cancelAction(springAnimations.keys) + } + + /** Cancels in progress animations on the provided properties only. */ + fun cancel(vararg properties: FloatPropertyCompat) { + cancelAction(properties.toSet()) } /** diff --git a/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimatorTestUtils.kt b/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimatorTestUtils.kt index 965decd255a03..c50eeac80d7a9 100644 --- a/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimatorTestUtils.kt +++ b/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimatorTestUtils.kt @@ -363,8 +363,12 @@ object PhysicsAnimatorTestUtils { private val testEndListeners = ArrayList>() private val testUpdateListeners = ArrayList>() + /** Whether we're currently in the middle of executing startInternal(). */ + private var currentlyRunningStartInternal = false + init { animator.startAction = ::startForTest + animator.cancelAction = ::cancelForTest } internal fun addTestEndListener(listener: PhysicsAnimator.EndListener) { @@ -437,7 +441,29 @@ object PhysicsAnimatorTestUtils { } }) + currentlyRunningStartInternal = true animator.startInternal() + currentlyRunningStartInternal = false + unblockLatch.countDown() + } + + unblockLatch.await(timeoutMs, TimeUnit.MILLISECONDS) + } + + private fun cancelForTest(properties: Set>) { + // If this was called from startInternal, we are already on the animation thread, and + // should just call cancelInternal rather than posting it. If we post it, the + // cancellation will occur after the rest of startInternal() and we'll immediately + // cancel the animation we worked so hard to start! + if (currentlyRunningStartInternal) { + animator.cancelInternal(properties) + return + } + + val unblockLatch = CountDownLatch(1) + + animationThreadHandler.post { + animator.cancelInternal(properties) unblockLatch.countDown() }