1 /*
2  * Copyright (C) 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.android.wm.shell.animation
17 
18 import android.os.Handler
19 import android.os.Looper
20 import android.util.ArrayMap
21 import androidx.dynamicanimation.animation.FloatPropertyCompat
22 import com.android.wm.shell.animation.PhysicsAnimatorTestUtils.prepareForTest
23 import java.util.*
24 import java.util.concurrent.CountDownLatch
25 import java.util.concurrent.TimeUnit
26 import kotlin.collections.ArrayList
27 import kotlin.collections.HashMap
28 import kotlin.collections.HashSet
29 import kotlin.collections.Set
30 import kotlin.collections.component1
31 import kotlin.collections.component2
32 import kotlin.collections.drop
33 import kotlin.collections.forEach
34 import kotlin.collections.getOrPut
35 import kotlin.collections.set
36 import kotlin.collections.toList
37 import kotlin.collections.toTypedArray
38 
39 typealias UpdateMatcher = (PhysicsAnimator.AnimationUpdate) -> Boolean
40 typealias UpdateFramesPerProperty<T> =
41         ArrayMap<FloatPropertyCompat<in T>, ArrayList<PhysicsAnimator.AnimationUpdate>>
42 
43 /**
44  * Utilities for testing code that uses [PhysicsAnimator].
45  *
46  * Start by calling [prepareForTest] at the beginning of each test - this will modify the behavior
47  * of all PhysicsAnimator instances so that they post animations to the main thread (so they don't
48  * crash). It'll also enable the use of the other static helper methods in this class, which you can
49  * use to do things like block the test until animations complete (so you can test end states), or
50  * verify keyframes.
51  */
52 object PhysicsAnimatorTestUtils {
53     var timeoutMs: Long = 2000
54     private var startBlocksUntilAnimationsEnd = false
55     private val animationThreadHandler = Handler(Looper.getMainLooper())
56     private val allAnimatedObjects = HashSet<Any>()
57     private val animatorTestHelpers = HashMap<PhysicsAnimator<*>, AnimatorTestHelper<*>>()
58 
59     /**
60      * Modifies the behavior of all [PhysicsAnimator] instances so that they post animations to the
61      * main thread, and report all of their
62      */
63     @JvmStatic
64     fun prepareForTest() {
65         val defaultConstructor = PhysicsAnimator.instanceConstructor
66         PhysicsAnimator.instanceConstructor = fun(target: Any): PhysicsAnimator<*> {
67             val animator = defaultConstructor(target)
68             allAnimatedObjects.add(target)
69             animatorTestHelpers[animator] = AnimatorTestHelper(animator)
70             return animator
71         }
72 
73         timeoutMs = 2000
74         startBlocksUntilAnimationsEnd = false
75         allAnimatedObjects.clear()
76     }
77 
78     @JvmStatic
79     fun tearDown() {
80         val latch = CountDownLatch(1)
81         animationThreadHandler.post {
82             animatorTestHelpers.keys.forEach { it.cancel() }
83             latch.countDown()
84         }
85 
86         latch.await()
87 
88         animatorTestHelpers.clear()
89         animators.clear()
90         allAnimatedObjects.clear()
91     }
92 
93     /**
94      * Sets the maximum time (in milliseconds) to block the test thread while waiting for animations
95      * before throwing an exception.
96      */
97     @JvmStatic
98     fun setBlockTimeout(timeoutMs: Long) {
99         PhysicsAnimatorTestUtils.timeoutMs = timeoutMs
100     }
101 
102     /**
103      * Sets whether all animations should block the test thread until they end. This is typically
104      * the desired behavior, since you can invoke code that runs an animation and then assert things
105      * about its end state.
106      */
107     @JvmStatic
108     fun setAllAnimationsBlock(block: Boolean) {
109         startBlocksUntilAnimationsEnd = block
110     }
111 
112     /**
113      * Blocks the calling thread until animations of the given property on the target object end.
114      */
115     @JvmStatic
116     @Throws(InterruptedException::class)
117     fun <T : Any> blockUntilAnimationsEnd(
118         animator: PhysicsAnimator<T>,
119         vararg properties: FloatPropertyCompat<in T>
120     ) {
121         val animatingProperties = HashSet<FloatPropertyCompat<in T>>()
122         for (property in properties) {
123             if (animator.isPropertyAnimating(property)) {
124                 animatingProperties.add(property)
125             }
126         }
127 
128         if (animatingProperties.size > 0) {
129             val latch = CountDownLatch(animatingProperties.size)
130             getAnimationTestHelper(animator).addTestEndListener(
131                     object : PhysicsAnimator.EndListener<T> {
132                 override fun onAnimationEnd(
133                     target: T,
134                     property: FloatPropertyCompat<in T>,
135                     wasFling: Boolean,
136                     canceled: Boolean,
137                     finalValue: Float,
138                     finalVelocity: Float,
139                     allRelevantPropertyAnimsEnded: Boolean
140                 ) {
141                     if (animatingProperties.contains(property)) {
142                         latch.countDown()
143                     }
144                 }
145             })
146 
147             latch.await(timeoutMs, TimeUnit.MILLISECONDS)
148         }
149     }
150 
151     /**
152      * Blocks the calling thread until all animations of the given property (on all target objects)
153      * have ended. Useful when you don't have access to the objects being animated, but still need
154      * to wait for them to end so that other testable side effects occur (such as update/end
155      * listeners).
156      */
157     @JvmStatic
158     @Throws(InterruptedException::class)
159     @Suppress("UNCHECKED_CAST")
160     fun <T : Any> blockUntilAnimationsEnd(
161         properties: FloatPropertyCompat<in T>
162     ) {
163         for (target in allAnimatedObjects) {
164             try {
165                 blockUntilAnimationsEnd(
166                         PhysicsAnimator.getInstance(target) as PhysicsAnimator<T>, properties)
167             } catch (e: ClassCastException) {
168                 // Keep checking the other objects for ones whose types match the provided
169                 // properties.
170             }
171         }
172     }
173 
174     /**
175      * Blocks the calling thread until the first animation frame in which predicate returns true. If
176      * the given object isn't animating, returns without blocking.
177      */
178     @JvmStatic
179     @Throws(InterruptedException::class)
180     fun <T : Any> blockUntilFirstAnimationFrameWhereTrue(
181         animator: PhysicsAnimator<T>,
182         predicate: (T) -> Boolean
183     ) {
184         if (animator.isRunning()) {
185             val latch = CountDownLatch(1)
186             getAnimationTestHelper(animator).addTestUpdateListener(object : PhysicsAnimator
187             .UpdateListener<T> {
188                 override fun onAnimationUpdateForProperty(
189                     target: T,
190                     values: UpdateMap<T>
191                 ) {
192                     if (predicate(target)) {
193                         latch.countDown()
194                     }
195                 }
196             })
197 
198             latch.await(timeoutMs, TimeUnit.MILLISECONDS)
199         }
200     }
201 
202     /**
203      * Verifies that the animator reported animation frame values to update listeners that satisfy
204      * the given matchers, in order. Not all frames need to satisfy a matcher - we'll run through
205      * all animation frames, and check them against the current predicate. If it returns false, we
206      * continue through the frames until it returns true, and then move on to the next matcher.
207      * Verification fails if we run out of frames while unsatisfied matchers remain.
208      *
209      * If verification is successful, all frames to this point are considered 'verified' and will be
210      * cleared. Subsequent calls to this method will start verification at the next animation frame.
211      *
212      * Example: Verify that an animation surpassed x = 50f before going negative.
213      * verifyAnimationUpdateFrames(
214      *    animator, TRANSLATION_X,
215      *    { u -> u.value > 50f },
216      *    { u -> u.value < 0f })
217      *
218      * Example: verify that an animation went backwards at some point while still being on-screen.
219      * verifyAnimationUpdateFrames(
220      *    animator, TRANSLATION_X,
221      *    { u -> u.velocity < 0f && u.value >= 0f })
222      *
223      * This method is intended to help you test longer, more complicated animations where it's
224      * critical that certain values were reached. Using this method to test short animations can
225      * fail due to the animation having fewer frames than provided matchers. For example, an
226      * animation from x = 1f to x = 5f might only have two frames, at x = 3f and x = 5f. The
227      * following would then fail despite it seeming logically sound:
228      *
229      * verifyAnimationUpdateFrames(
230      *    animator, TRANSLATION_X,
231      *    { u -> u.value > 1f },
232      *    { u -> u.value > 2f },
233      *    { u -> u.value > 3f })
234      *
235      * Tests might also fail if your matchers are too granular, such as this example test after an
236      * animation from x = 0f to x = 100f. It's unlikely there was a frame specifically between 2f
237      * and 3f.
238      *
239      * verifyAnimationUpdateFrames(
240      *    animator, TRANSLATION_X,
241      *    { u -> u.value > 2f && u.value < 3f },
242      *    { u -> u.value >= 50f })
243      *
244      * Failures will print a helpful log of all animation frames so you can see what caused the test
245      * to fail.
246      */
247     fun <T : Any> verifyAnimationUpdateFrames(
248         animator: PhysicsAnimator<T>,
249         property: FloatPropertyCompat<in T>,
250         firstUpdateMatcher: UpdateMatcher,
251         vararg additionalUpdateMatchers: UpdateMatcher
252     ) {
253         val updateFrames: UpdateFramesPerProperty<T> = getAnimationUpdateFrames(animator)
254 
255         if (!updateFrames.containsKey(property)) {
256             error("No frames for given target object and property.")
257         }
258 
259         // Copy the frames to avoid a ConcurrentModificationException if the animation update
260         // listeners attempt to add a new frame while we're verifying these.
261         val framesForProperty = ArrayList(updateFrames[property]!!)
262         val matchers = ArrayDeque<UpdateMatcher>(
263                 additionalUpdateMatchers.toList())
264         val frameTraceMessage = StringBuilder()
265 
266         var curMatcher = firstUpdateMatcher
267 
268         // Loop through the updates from the testable animator.
269         for (update in framesForProperty) {
270 
271             // Check whether this frame satisfies the current matcher.
272             if (curMatcher(update)) {
273 
274                 // If that was the last unsatisfied matcher, we're good here. 'Verify' all remaining
275                 // frames and return without failing.
276                 if (matchers.size == 0) {
277                     getAnimationUpdateFrames(animator).remove(property)
278                     return
279                 }
280 
281                 frameTraceMessage.append("$update\t(satisfied matcher)\n")
282                 curMatcher = matchers.pop() // Get the next matcher and keep going.
283             } else {
284                 frameTraceMessage.append("${update}\n")
285             }
286         }
287 
288         val readablePropertyName = PhysicsAnimator.getReadablePropertyName(property)
289         getAnimationUpdateFrames(animator).remove(property)
290 
291         throw RuntimeException(
292                 "Failed to verify animation frames for property $readablePropertyName: " +
293                         "Provided ${additionalUpdateMatchers.size + 1} matchers, " +
294                         "however ${matchers.size + 1} remained unsatisfied.\n\n" +
295                         "All frames:\n$frameTraceMessage")
296     }
297 
298     /**
299      * Overload of [verifyAnimationUpdateFrames] that builds matchers for you, from given float
300      * values. For example, to verify that an animations passed from 0f to 50f to 100f back to 50f:
301      *
302      * verifyAnimationUpdateFrames(animator, TRANSLATION_X, 0f, 50f, 100f, 50f)
303      *
304      * This verifies that update frames were received with values of >= 0f, >= 50f, >= 100f, and
305      * <= 50f.
306      *
307      * The same caveats apply: short animations might not have enough frames to satisfy all of the
308      * matchers, and overly specific calls (such as 0f, 1f, 2f, 3f, etc. for an animation from
309      * x = 0f to x = 100f) might fail as the animation only had frames at 0f, 25f, 50f, 75f, and
310      * 100f. As with [verifyAnimationUpdateFrames], failures will print a helpful log of all frames
311      * so you can see what caused the test to fail.
312      */
313     fun <T : Any> verifyAnimationUpdateFrames(
314         animator: PhysicsAnimator<T>,
315         property: FloatPropertyCompat<in T>,
316         startValue: Float,
317         firstTargetValue: Float,
318         vararg additionalTargetValues: Float
319     ) {
320         val matchers = ArrayList<UpdateMatcher>()
321 
322         val values = ArrayList<Float>().also {
323             it.add(firstTargetValue)
324             it.addAll(additionalTargetValues.toList())
325         }
326 
327         var prevVal = startValue
328         for (value in values) {
329             if (value > prevVal) {
330                 matchers.add { update -> update.value >= value }
331             } else {
332                 matchers.add { update -> update.value <= value }
333             }
334 
335             prevVal = value
336         }
337 
338         verifyAnimationUpdateFrames(
339                 animator, property, matchers[0], *matchers.drop(0).toTypedArray())
340     }
341 
342     /**
343      * Returns all of the values that have ever been reported to update listeners, per property.
344      */
345     @Suppress("UNCHECKED_CAST")
346     fun <T : Any> getAnimationUpdateFrames(animator: PhysicsAnimator<T>):
347             UpdateFramesPerProperty<T> {
348         return animatorTestHelpers[animator]?.getUpdates() as UpdateFramesPerProperty<T>
349     }
350 
351     /**
352      * Clears animation frame updates from the given animator so they aren't used the next time its
353      * passed to [verifyAnimationUpdateFrames].
354      */
355     fun <T : Any> clearAnimationUpdateFrames(animator: PhysicsAnimator<T>) {
356         animatorTestHelpers[animator]?.clearUpdates()
357     }
358 
359     @Suppress("UNCHECKED_CAST")
360     private fun <T> getAnimationTestHelper(animator: PhysicsAnimator<T>): AnimatorTestHelper<T> {
361         return animatorTestHelpers[animator] as AnimatorTestHelper<T>
362     }
363 
364     /**
365      * Helper class for testing an animator. This replaces the animator's start action with
366      * [startForTest] and adds test listeners to enable other test utility behaviors. We build one
367      * these for each Animator and keep them around so we can access the updates.
368      */
369     class AnimatorTestHelper<T> (private val animator: PhysicsAnimator<T>) {
370 
371         /** All updates received for each property animation. */
372         private val allUpdates =
373                 ArrayMap<FloatPropertyCompat<in T>, ArrayList<PhysicsAnimator.AnimationUpdate>>()
374 
375         private val testEndListeners = ArrayList<PhysicsAnimator.EndListener<T>>()
376         private val testUpdateListeners = ArrayList<PhysicsAnimator.UpdateListener<T>>()
377 
378         /** Whether we're currently in the middle of executing startInternal(). */
379         private var currentlyRunningStartInternal = false
380 
381         init {
382             animator.startAction = ::startForTest
383             animator.cancelAction = ::cancelForTest
384         }
385 
386         internal fun addTestEndListener(listener: PhysicsAnimator.EndListener<T>) {
387             testEndListeners.add(listener)
388         }
389 
390         internal fun addTestUpdateListener(listener: PhysicsAnimator.UpdateListener<T>) {
391             testUpdateListeners.add(listener)
392         }
393 
394         internal fun getUpdates(): UpdateFramesPerProperty<T> {
395             return allUpdates
396         }
397 
398         internal fun clearUpdates() {
399             allUpdates.clear()
400         }
401 
402         private fun startForTest() {
403             // The testable animator needs to block the main thread until super.start() has been
404             // called, since callers expect .start() to be synchronous but we're posting it to a
405             // handler here. We may also continue blocking until all animations end, if
406             // startBlocksUntilAnimationsEnd = true.
407             val unblockLatch = CountDownLatch(if (startBlocksUntilAnimationsEnd) 2 else 1)
408 
409             animationThreadHandler.post {
410                 // Add an update listener that dispatches to any test update listeners added by
411                 // tests.
412                 animator.addUpdateListener(object : PhysicsAnimator.UpdateListener<T> {
413                     override fun onAnimationUpdateForProperty(
414                         target: T,
415                         values: ArrayMap<FloatPropertyCompat<in T>, PhysicsAnimator.AnimationUpdate>
416                     ) {
417                         values.forEach { (property, value) ->
418                             allUpdates.getOrPut(property, { ArrayList() }).add(value)
419                         }
420 
421                         for (listener in testUpdateListeners) {
422                             listener.onAnimationUpdateForProperty(target, values)
423                         }
424                     }
425                 })
426 
427                 // Add an end listener that dispatches to any test end listeners added by tests, and
428                 // unblocks the main thread if required.
429                 animator.addEndListener(object : PhysicsAnimator.EndListener<T> {
430                     override fun onAnimationEnd(
431                         target: T,
432                         property: FloatPropertyCompat<in T>,
433                         wasFling: Boolean,
434                         canceled: Boolean,
435                         finalValue: Float,
436                         finalVelocity: Float,
437                         allRelevantPropertyAnimsEnded: Boolean
438                     ) {
439                         for (listener in testEndListeners) {
440                             listener.onAnimationEnd(
441                                     target, property, wasFling, canceled, finalValue, finalVelocity,
442                                     allRelevantPropertyAnimsEnded)
443                         }
444 
445                         if (allRelevantPropertyAnimsEnded) {
446                             testEndListeners.clear()
447                             testUpdateListeners.clear()
448 
449                             if (startBlocksUntilAnimationsEnd) {
450                                 unblockLatch.countDown()
451                             }
452                         }
453                     }
454                 })
455 
456                 currentlyRunningStartInternal = true
457                 animator.startInternal()
458                 currentlyRunningStartInternal = false
459                 unblockLatch.countDown()
460             }
461 
462             unblockLatch.await(timeoutMs, TimeUnit.MILLISECONDS)
463         }
464 
465         private fun cancelForTest(properties: Set<FloatPropertyCompat<in T>>) {
466             // If this was called from startInternal, we are already on the animation thread, and
467             // should just call cancelInternal rather than posting it. If we post it, the
468             // cancellation will occur after the rest of startInternal() and we'll immediately
469             // cancel the animation we worked so hard to start!
470             if (currentlyRunningStartInternal) {
471                 animator.cancelInternal(properties)
472                 return
473             }
474 
475             val unblockLatch = CountDownLatch(1)
476 
477             animationThreadHandler.post {
478                 animator.cancelInternal(properties)
479                 unblockLatch.countDown()
480             }
481 
482             unblockLatch.await(timeoutMs, TimeUnit.MILLISECONDS)
483         }
484     }
485 }
486