1 /*
<lambda>null2  * 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.shared.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.shared.animation.PhysicsAnimatorTestUtils.prepareForTest
23 import java.util.ArrayDeque
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         PhysicsAnimator.onAnimatorCreated = { animator, target ->
66             allAnimatedObjects.add(target)
67             animatorTestHelpers[animator] = AnimatorTestHelper(animator)
68         }
69 
70         timeoutMs = 2000
71         startBlocksUntilAnimationsEnd = false
72         allAnimatedObjects.clear()
73     }
74 
75     @JvmStatic
76     fun tearDown() {
77         if (Looper.myLooper() == animationThreadHandler.looper) {
78             animatorTestHelpers.keys.forEach { it.cancel() }
79         } else {
80             val latch = CountDownLatch(1)
81             animationThreadHandler.post {
82                 animatorTestHelpers.keys.forEach { it.cancel() }
83                 latch.countDown()
84             }
85             latch.await(5, TimeUnit.SECONDS)
86         }
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         vararg 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     /** Whether any animation is currently running. */
175     @JvmStatic
176     fun isAnyAnimationRunning(): Boolean {
177         for (target in allAnimatedObjects) {
178             val animator = PhysicsAnimator.getInstance(target)
179             if (animator.isRunning()) return true
180         }
181         return false
182     }
183 
184     /**
185      * Blocks the calling thread until the first animation frame in which predicate returns true. If
186      * the given object isn't animating, returns without blocking.
187      */
188     @JvmStatic
189     @Throws(InterruptedException::class)
190     fun <T : Any> blockUntilFirstAnimationFrameWhereTrue(
191         animator: PhysicsAnimator<T>,
192         predicate: (T) -> Boolean
193     ) {
194         if (animator.isRunning()) {
195             val latch = CountDownLatch(1)
196             getAnimationTestHelper(animator).addTestUpdateListener(object : PhysicsAnimator
197             .UpdateListener<T> {
198                 override fun onAnimationUpdateForProperty(
199                     target: T,
200                     values: UpdateMap<T>
201                 ) {
202                     if (predicate(target)) {
203                         latch.countDown()
204                     }
205                 }
206             })
207 
208             latch.await(timeoutMs, TimeUnit.MILLISECONDS)
209         }
210     }
211 
212     /**
213      * Verifies that the animator reported animation frame values to update listeners that satisfy
214      * the given matchers, in order. Not all frames need to satisfy a matcher - we'll run through
215      * all animation frames, and check them against the current predicate. If it returns false, we
216      * continue through the frames until it returns true, and then move on to the next matcher.
217      * Verification fails if we run out of frames while unsatisfied matchers remain.
218      *
219      * If verification is successful, all frames to this point are considered 'verified' and will be
220      * cleared. Subsequent calls to this method will start verification at the next animation frame.
221      *
222      * Example: Verify that an animation surpassed x = 50f before going negative.
223      * verifyAnimationUpdateFrames(
224      *    animator, TRANSLATION_X,
225      *    { u -> u.value > 50f },
226      *    { u -> u.value < 0f })
227      *
228      * Example: verify that an animation went backwards at some point while still being on-screen.
229      * verifyAnimationUpdateFrames(
230      *    animator, TRANSLATION_X,
231      *    { u -> u.velocity < 0f && u.value >= 0f })
232      *
233      * This method is intended to help you test longer, more complicated animations where it's
234      * critical that certain values were reached. Using this method to test short animations can
235      * fail due to the animation having fewer frames than provided matchers. For example, an
236      * animation from x = 1f to x = 5f might only have two frames, at x = 3f and x = 5f. The
237      * following would then fail despite it seeming logically sound:
238      *
239      * verifyAnimationUpdateFrames(
240      *    animator, TRANSLATION_X,
241      *    { u -> u.value > 1f },
242      *    { u -> u.value > 2f },
243      *    { u -> u.value > 3f })
244      *
245      * Tests might also fail if your matchers are too granular, such as this example test after an
246      * animation from x = 0f to x = 100f. It's unlikely there was a frame specifically between 2f
247      * and 3f.
248      *
249      * verifyAnimationUpdateFrames(
250      *    animator, TRANSLATION_X,
251      *    { u -> u.value > 2f && u.value < 3f },
252      *    { u -> u.value >= 50f })
253      *
254      * Failures will print a helpful log of all animation frames so you can see what caused the test
255      * to fail.
256      */
257     fun <T : Any> verifyAnimationUpdateFrames(
258         animator: PhysicsAnimator<T>,
259         property: FloatPropertyCompat<in T>,
260         firstUpdateMatcher: UpdateMatcher,
261         vararg additionalUpdateMatchers: UpdateMatcher
262     ) {
263         val updateFrames: UpdateFramesPerProperty<T> = getAnimationUpdateFrames(animator)
264 
265         if (!updateFrames.containsKey(property)) {
266             error("No frames for given target object and property.")
267         }
268 
269         // Copy the frames to avoid a ConcurrentModificationException if the animation update
270         // listeners attempt to add a new frame while we're verifying these.
271         val framesForProperty = ArrayList(updateFrames[property]!!)
272         val matchers = ArrayDeque<UpdateMatcher>(
273                 additionalUpdateMatchers.toList())
274         val frameTraceMessage = StringBuilder()
275 
276         var curMatcher = firstUpdateMatcher
277 
278         // Loop through the updates from the testable animator.
279         for (update in framesForProperty) {
280             // Check whether this frame satisfies the current matcher.
281             if (curMatcher(update)) {
282                 // If that was the last unsatisfied matcher, we're good here. 'Verify' all remaining
283                 // frames and return without failing.
284                 if (matchers.size == 0) {
285                     getAnimationUpdateFrames(animator).remove(property)
286                     return
287                 }
288 
289                 frameTraceMessage.append("$update\t(satisfied matcher)\n")
290                 curMatcher = matchers.pop() // Get the next matcher and keep going.
291             } else {
292                 frameTraceMessage.append("${update}\n")
293             }
294         }
295 
296         val readablePropertyName = PhysicsAnimator.getReadablePropertyName(property)
297         getAnimationUpdateFrames(animator).remove(property)
298 
299         throw RuntimeException(
300                 "Failed to verify animation frames for property $readablePropertyName: " +
301                         "Provided ${additionalUpdateMatchers.size + 1} matchers, " +
302                         "however ${matchers.size + 1} remained unsatisfied.\n\n" +
303                         "All frames:\n$frameTraceMessage")
304     }
305 
306     /**
307      * Overload of [verifyAnimationUpdateFrames] that builds matchers for you, from given float
308      * values. For example, to verify that an animations passed from 0f to 50f to 100f back to 50f:
309      *
310      * verifyAnimationUpdateFrames(animator, TRANSLATION_X, 0f, 50f, 100f, 50f)
311      *
312      * This verifies that update frames were received with values of >= 0f, >= 50f, >= 100f, and
313      * <= 50f.
314      *
315      * The same caveats apply: short animations might not have enough frames to satisfy all of the
316      * matchers, and overly specific calls (such as 0f, 1f, 2f, 3f, etc. for an animation from
317      * x = 0f to x = 100f) might fail as the animation only had frames at 0f, 25f, 50f, 75f, and
318      * 100f. As with [verifyAnimationUpdateFrames], failures will print a helpful log of all frames
319      * so you can see what caused the test to fail.
320      */
321     fun <T : Any> verifyAnimationUpdateFrames(
322         animator: PhysicsAnimator<T>,
323         property: FloatPropertyCompat<in T>,
324         startValue: Float,
325         firstTargetValue: Float,
326         vararg additionalTargetValues: Float
327     ) {
328         val matchers = ArrayList<UpdateMatcher>()
329 
330         val values = ArrayList<Float>().also {
331             it.add(firstTargetValue)
332             it.addAll(additionalTargetValues.toList())
333         }
334 
335         var prevVal = startValue
336         for (value in values) {
337             if (value > prevVal) {
338                 matchers.add { update -> update.value >= value }
339             } else {
340                 matchers.add { update -> update.value <= value }
341             }
342 
343             prevVal = value
344         }
345 
346         verifyAnimationUpdateFrames(
347                 animator, property, matchers[0], *matchers.drop(0).toTypedArray())
348     }
349 
350     /**
351      * Returns all of the values that have ever been reported to update listeners, per property.
352      */
353     @Suppress("UNCHECKED_CAST")
354     fun <T : Any> getAnimationUpdateFrames(
355         animator: PhysicsAnimator<T>
356     ): UpdateFramesPerProperty<T> {
357         return animatorTestHelpers[animator]?.getUpdates() as UpdateFramesPerProperty<T>
358     }
359 
360     /**
361      * Clears animation frame updates from the given animator so they aren't used the next time its
362      * passed to [verifyAnimationUpdateFrames].
363      */
364     fun <T : Any> clearAnimationUpdateFrames(animator: PhysicsAnimator<T>) {
365         animatorTestHelpers[animator]?.clearUpdates()
366     }
367 
368     @Suppress("UNCHECKED_CAST")
369     private fun <T> getAnimationTestHelper(animator: PhysicsAnimator<T>): AnimatorTestHelper<T> {
370         return animatorTestHelpers[animator] as AnimatorTestHelper<T>
371     }
372 
373     /**
374      * Helper class for testing an animator. This replaces the animator's start action with
375      * [startForTest] and adds test listeners to enable other test utility behaviors. We build one
376      * these for each Animator and keep them around so we can access the updates.
377      */
378     class AnimatorTestHelper<T> (private val animator: PhysicsAnimator<T>) {
379 
380         /** All updates received for each property animation. */
381         private val allUpdates =
382                 ArrayMap<FloatPropertyCompat<in T>, ArrayList<PhysicsAnimator.AnimationUpdate>>()
383 
384         private val testEndListeners = ArrayList<PhysicsAnimator.EndListener<T>>()
385         private val testUpdateListeners = ArrayList<PhysicsAnimator.UpdateListener<T>>()
386 
387         /** Whether we're currently in the middle of executing startInternal(). */
388         private var currentlyRunningStartInternal = false
389 
390         init {
391             animator.startAction = ::startForTest
392             animator.cancelAction = ::cancelForTest
393         }
394 
395         internal fun addTestEndListener(listener: PhysicsAnimator.EndListener<T>) {
396             testEndListeners.add(listener)
397         }
398 
399         internal fun addTestUpdateListener(listener: PhysicsAnimator.UpdateListener<T>) {
400             testUpdateListeners.add(listener)
401         }
402 
403         internal fun getUpdates(): UpdateFramesPerProperty<T> {
404             return allUpdates
405         }
406 
407         internal fun clearUpdates() {
408             allUpdates.clear()
409         }
410 
411         private fun startForTest() {
412             // The testable animator needs to block the main thread until super.start() has been
413             // called, since callers expect .start() to be synchronous but we're posting it to a
414             // handler here. We may also continue blocking until all animations end, if
415             // startBlocksUntilAnimationsEnd = true.
416             val unblockLatch = CountDownLatch(if (startBlocksUntilAnimationsEnd) 2 else 1)
417 
418             animationThreadHandler.post {
419                 // Add an update listener that dispatches to any test update listeners added by
420                 // tests.
421                 animator.addUpdateListener(object : PhysicsAnimator.UpdateListener<T> {
422                     override fun onAnimationUpdateForProperty(
423                         target: T,
424                         values: ArrayMap<FloatPropertyCompat<in T>, PhysicsAnimator.AnimationUpdate>
425                     ) {
426                         values.forEach { (property, value) ->
427                             allUpdates.getOrPut(property, { ArrayList() }).add(value)
428                         }
429 
430                         for (listener in testUpdateListeners) {
431                             listener.onAnimationUpdateForProperty(target, values)
432                         }
433                     }
434                 })
435 
436                 // Add an end listener that dispatches to any test end listeners added by tests, and
437                 // unblocks the main thread if required.
438                 animator.addEndListener(object : PhysicsAnimator.EndListener<T> {
439                     override fun onAnimationEnd(
440                         target: T,
441                         property: FloatPropertyCompat<in T>,
442                         wasFling: Boolean,
443                         canceled: Boolean,
444                         finalValue: Float,
445                         finalVelocity: Float,
446                         allRelevantPropertyAnimsEnded: Boolean
447                     ) {
448                         for (listener in testEndListeners) {
449                             listener.onAnimationEnd(
450                                     target, property, wasFling, canceled, finalValue, finalVelocity,
451                                     allRelevantPropertyAnimsEnded)
452                         }
453 
454                         if (allRelevantPropertyAnimsEnded) {
455                             testEndListeners.clear()
456                             testUpdateListeners.clear()
457 
458                             if (startBlocksUntilAnimationsEnd) {
459                                 unblockLatch.countDown()
460                             }
461                         }
462                     }
463                 })
464 
465                 currentlyRunningStartInternal = true
466                 animator.startInternal()
467                 currentlyRunningStartInternal = false
468                 unblockLatch.countDown()
469             }
470 
471             unblockLatch.await(timeoutMs, TimeUnit.MILLISECONDS)
472         }
473 
474         private fun cancelForTest(properties: Set<FloatPropertyCompat<in T>>) {
475             // If this was called from startInternal, we are already on the animation thread, and
476             // should just call cancelInternal rather than posting it. If we post it, the
477             // cancellation will occur after the rest of startInternal() and we'll immediately
478             // cancel the animation we worked so hard to start!
479             if (currentlyRunningStartInternal) {
480                 animator.cancelInternal(properties)
481                 return
482             }
483 
484             val unblockLatch = CountDownLatch(1)
485 
486             animationThreadHandler.post {
487                 animator.cancelInternal(properties)
488                 unblockLatch.countDown()
489             }
490 
491             unblockLatch.await(timeoutMs, TimeUnit.MILLISECONDS)
492         }
493     }
494 }
495