1 /*
2  * Copyright (C) 2012 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 android.animation.cts;
17 
18 import static com.android.compatibility.common.util.CtsMockitoUtils.within;
19 
20 import static org.junit.Assert.assertEquals;
21 import static org.junit.Assert.assertNotNull;
22 import static org.junit.Assert.assertTrue;
23 import static org.mockito.Mockito.mock;
24 import static org.mockito.Mockito.verify;
25 
26 import android.animation.Animator;
27 import android.animation.AnimatorListenerAdapter;
28 import android.animation.ArgbEvaluator;
29 import android.animation.Keyframe;
30 import android.animation.ObjectAnimator;
31 import android.animation.PropertyValuesHolder;
32 import android.animation.TypeConverter;
33 import android.animation.ValueAnimator;
34 import android.app.Instrumentation;
35 import android.graphics.Color;
36 import android.graphics.Path;
37 import android.graphics.PointF;
38 import android.graphics.drawable.ShapeDrawable;
39 import android.os.SystemClock;
40 import android.support.test.InstrumentationRegistry;
41 import android.support.test.filters.LargeTest;
42 import android.support.test.rule.ActivityTestRule;
43 import android.support.test.runner.AndroidJUnit4;
44 import android.util.FloatProperty;
45 import android.util.Property;
46 import android.view.View;
47 import android.view.animation.AccelerateInterpolator;
48 
49 import org.junit.Before;
50 import org.junit.Rule;
51 import org.junit.Test;
52 import org.junit.runner.RunWith;
53 
54 import java.util.concurrent.CountDownLatch;
55 import java.util.concurrent.TimeUnit;
56 
57 @LargeTest
58 @RunWith(AndroidJUnit4.class)
59 public class PropertyValuesHolderTest {
60     private static final float LINE1_START = -32f;
61     private static final float LINE1_END = -2f;
62     private static final float LINE2_START = 2f;
63     private static final float LINE2_END = 12f;
64     private static final float QUADRATIC_CTRL_PT1_X = 0f;
65     private static final float QUADRATIC_CTRL_PT1_Y = 0f;
66     private static final float QUADRATIC_CTRL_PT2_X = 50f;
67     private static final float QUADRATIC_CTRL_PT2_Y = 20f;
68     private static final float QUADRATIC_CTRL_PT3_X = 100f;
69     private static final float QUADRATIC_CTRL_PT3_Y = 0f;
70     private static final float EPSILON = .001f;
71 
72     private Instrumentation mInstrumentation;
73     private AnimationActivity mActivity;
74     private long mDuration = 1000;
75     private float mStartY;
76     private float mEndY;
77     private Object mObject;
78     private String mProperty;
79 
80     @Rule
81     public ActivityTestRule<AnimationActivity> mActivityRule =
82             new ActivityTestRule<>(AnimationActivity.class);
83 
84     @Before
setup()85     public void setup() {
86         mInstrumentation = InstrumentationRegistry.getInstrumentation();
87         mInstrumentation.setInTouchMode(false);
88         mActivity = mActivityRule.getActivity();
89         mProperty = "y";
90         mStartY = mActivity.mStartY;
91         mEndY = mActivity.mStartY + mActivity.mDeltaY;
92         mObject = mActivity.view.newBall;
93     }
94 
95     @Test
testGetPropertyName()96     public void testGetPropertyName() {
97         float[] values = {mStartY, mEndY};
98         PropertyValuesHolder pVHolder = PropertyValuesHolder.ofFloat(mProperty, values);
99         assertEquals(mProperty, pVHolder.getPropertyName());
100     }
101 
102     @Test
testSetPropertyName()103     public void testSetPropertyName() {
104         float[] values = {mStartY, mEndY};
105         PropertyValuesHolder pVHolder = PropertyValuesHolder.ofFloat("", values);
106         pVHolder.setPropertyName(mProperty);
107         assertEquals(mProperty, pVHolder.getPropertyName());
108     }
109 
110     @Test
testClone()111     public void testClone() {
112         float[] values = {mStartY, mEndY};
113         PropertyValuesHolder pVHolder = PropertyValuesHolder.ofFloat(mProperty, values);
114         PropertyValuesHolder cloneHolder = pVHolder.clone();
115         assertEquals(pVHolder.getPropertyName(), cloneHolder.getPropertyName());
116     }
117 
118     @Test
testSetValues()119     public void testSetValues() throws Throwable {
120         float[] dummyValues = {100, 150};
121         float[] values = {mStartY, mEndY};
122         PropertyValuesHolder pVHolder = PropertyValuesHolder.ofFloat(mProperty, dummyValues);
123         pVHolder.setFloatValues(values);
124 
125         ObjectAnimator objAnimator = ObjectAnimator.ofPropertyValuesHolder(mObject,pVHolder);
126         assertTrue(objAnimator != null);
127         setAnimatorProperties(objAnimator);
128 
129         startAnimation(objAnimator);
130         assertTrue(objAnimator != null);
131         float[] yArray = getYPosition();
132         assertResults(yArray, mStartY, mEndY);
133     }
134 
createAnimator(Keyframe... keyframes)135     private ObjectAnimator createAnimator(Keyframe... keyframes) {
136         PropertyValuesHolder pVHolder = PropertyValuesHolder.ofKeyframe(mProperty, keyframes);
137         ObjectAnimator objAnimator = ObjectAnimator.ofPropertyValuesHolder(mObject,pVHolder);
138         objAnimator.setDuration(mDuration);
139         objAnimator.setInterpolator(new AccelerateInterpolator());
140         return objAnimator;
141     }
142 
waitUntilFinished(ObjectAnimator objectAnimator, long timeoutMilliseconds)143     private void waitUntilFinished(ObjectAnimator objectAnimator, long timeoutMilliseconds)
144             throws InterruptedException {
145         final Animator.AnimatorListener listener = mock(Animator.AnimatorListener.class);
146         objectAnimator.addListener(listener);
147         verify(listener, within(timeoutMilliseconds)).onAnimationEnd(objectAnimator, false);
148         mInstrumentation.waitForIdleSync();
149     }
150 
setTarget(final Animator animator, final Object target)151     private void setTarget(final Animator animator, final Object target) throws Throwable {
152         mActivityRule.runOnUiThread(() -> animator.setTarget(target));
153     }
154 
startSingleAnimation(final Animator animator)155     private void startSingleAnimation(final Animator animator) throws Throwable {
156         mActivityRule.runOnUiThread(() -> mActivity.startSingleAnimation(animator));
157     }
158 
159     @Test
testResetValues()160     public void testResetValues() throws Throwable {
161         final float initialY = mActivity.view.newBall.getY();
162         Keyframe emptyKeyframe1 = Keyframe.ofFloat(.0f);
163         ObjectAnimator objAnimator1 = createAnimator(emptyKeyframe1, Keyframe.ofFloat(1f, 100f));
164         startSingleAnimation(objAnimator1);
165         assertTrue("Keyframe should be assigned a value", emptyKeyframe1.hasValue());
166         assertEquals("Keyframe should get the value from the target",
167                 (float) emptyKeyframe1.getValue(), initialY, 0.0f);
168         waitUntilFinished(objAnimator1, mDuration * 2);
169         assertEquals(100f, mActivity.view.newBall.getY(), 0.0f);
170         startSingleAnimation(objAnimator1);
171         waitUntilFinished(objAnimator1, mDuration * 2);
172 
173         // run another ObjectAnimator that will move the Y value to something else
174         Keyframe emptyKeyframe2 = Keyframe.ofFloat(.0f);
175         ObjectAnimator objAnimator2 = createAnimator(emptyKeyframe2, Keyframe.ofFloat(1f, 200f));
176         startSingleAnimation(objAnimator2);
177         assertTrue("Keyframe should be assigned a value", emptyKeyframe2.hasValue());
178         assertEquals("Keyframe should get the value from the target",
179                 (float) emptyKeyframe2.getValue(), 100f, 0.0f);
180         waitUntilFinished(objAnimator2, mDuration * 2);
181         assertEquals(200f, mActivity.view.newBall.getY(), 0.0f);
182 
183         // re-run first object animator. since its target did not change, it should have the same
184         // start value for kf1
185         startSingleAnimation(objAnimator1);
186         assertEquals((float) emptyKeyframe1.getValue(), initialY, 0.0f);
187         waitUntilFinished(objAnimator1, mDuration * 2);
188 
189         Keyframe fullKeyframe = Keyframe.ofFloat(.0f, 333f);
190         ObjectAnimator objAnimator3 = createAnimator(fullKeyframe, Keyframe.ofFloat(1f, 500f));
191         startSingleAnimation(objAnimator3);
192         assertEquals("When keyframe has value, should not be assigned from the target object",
193                 (float) fullKeyframe.getValue(), 333f, 0.0f);
194         waitUntilFinished(objAnimator3, mDuration * 2);
195 
196         // now, null out the target of the first animator
197         float updatedY = mActivity.view.newBall.getY();
198         setTarget(objAnimator1, null);
199         startSingleAnimation(objAnimator1);
200         assertTrue("Keyframe should get a value", emptyKeyframe1.hasValue());
201         assertEquals("Keyframe should get the updated Y value",
202                 (float) emptyKeyframe1.getValue(), updatedY, 0.0f);
203         waitUntilFinished(objAnimator1, mDuration * 2);
204         assertEquals("Animation should run as expected", 100f, mActivity.view.newBall.getY(), 0.0f);
205 
206         // now, reset the target of the fully defined animation.
207         setTarget(objAnimator3, null);
208         startSingleAnimation(objAnimator3);
209         assertEquals("When keyframe is fully defined, its value should not change when target is"
210                 + " reset", (float) fullKeyframe.getValue(), 333f, 0.0f);
211         waitUntilFinished(objAnimator3, mDuration * 2);
212 
213         // run the other one to change Y value
214         startSingleAnimation(objAnimator2);
215         waitUntilFinished(objAnimator2, mDuration * 2);
216         // now, set another target w/ the same View type. it should still reset
217         ShapeHolder view = new ShapeHolder(new ShapeDrawable());
218         updatedY = mActivity.view.newBall.getY();
219         setTarget(objAnimator1, view);
220         startSingleAnimation(objAnimator1);
221         assertTrue("Keyframe should get a value when target is set to another view of the same"
222                 + " class", emptyKeyframe1.hasValue());
223         assertEquals("Keyframe should get the updated Y value when target is set to another view"
224                 + " of the same class", (float) emptyKeyframe1.getValue(), updatedY, 0.0f);
225         waitUntilFinished(objAnimator1, mDuration * 2);
226         assertEquals("Animation should run as expected", 100f, mActivity.view.newBall.getY(), 0.0f);
227     }
228 
229     @Test
testOfFloat()230     public void testOfFloat() throws Throwable {
231         float[] values = {mStartY, mEndY};
232         PropertyValuesHolder pVHolder = PropertyValuesHolder.ofFloat(mProperty, values);
233         assertNotNull(pVHolder);
234         ObjectAnimator objAnimator = ObjectAnimator.ofPropertyValuesHolder(mObject,pVHolder);
235         assertTrue(objAnimator != null);
236 
237         setAnimatorProperties(objAnimator);
238         startAnimation(objAnimator);
239         assertTrue(objAnimator != null);
240         float[] yArray = getYPosition();
241         assertResults(yArray, mStartY, mEndY);
242     }
243 
244     @Test
testOfFloat_Property()245     public void testOfFloat_Property() throws Throwable {
246         float[] values = {mStartY, mEndY};
247         ShapeHolderYProperty property=new ShapeHolderYProperty(ShapeHolder.class,"y");
248         property.setObject(mObject);
249         PropertyValuesHolder pVHolder = PropertyValuesHolder.ofFloat(property, values);
250         assertNotNull(pVHolder);
251         ObjectAnimator objAnimator = ObjectAnimator.ofPropertyValuesHolder(mObject,pVHolder);
252         assertTrue(objAnimator != null);
253 
254         setAnimatorProperties(objAnimator);
255         startAnimation(objAnimator);
256         assertTrue(objAnimator != null);
257         float[] yArray = getYPosition();
258         assertResults(yArray, mStartY, mEndY);
259     }
260 
261     @Test
testOfInt()262     public void testOfInt() throws Throwable {
263         int start = 0;
264         int end = 10;
265         int[] values = {start, end};
266         PropertyValuesHolder pVHolder = PropertyValuesHolder.ofInt(mProperty, values);
267         assertNotNull(pVHolder);
268         final ObjectAnimator objAnimator = ObjectAnimator.ofPropertyValuesHolder(mObject,pVHolder);
269         assertTrue(objAnimator != null);
270         setAnimatorProperties(objAnimator);
271         mActivityRule.runOnUiThread(objAnimator::start);
272         SystemClock.sleep(1000);
273         assertTrue(objAnimator.isRunning());
274         Integer animatedValue = (Integer) objAnimator.getAnimatedValue();
275         assertTrue(animatedValue >= start);
276         assertTrue(animatedValue <= end);
277     }
278 
279     @Test
testOfInt_Property()280     public void testOfInt_Property() throws Throwable{
281         Object object = mActivity.view;
282         String property = "backgroundColor";
283         int startColor = mActivity.view.RED;
284         int endColor = mActivity.view.BLUE;
285         int values[] = {startColor, endColor};
286 
287         ViewColorProperty colorProperty=new ViewColorProperty(Integer.class,property);
288         colorProperty.setObject(object);
289         PropertyValuesHolder pVHolder = PropertyValuesHolder.ofInt(colorProperty, values);
290         assertNotNull(pVHolder);
291 
292         ObjectAnimator colorAnimator = ObjectAnimator.ofPropertyValuesHolder(object,pVHolder);
293         colorAnimator.setDuration(1000);
294         colorAnimator.setEvaluator(new ArgbEvaluator());
295         colorAnimator.setRepeatCount(ValueAnimator.INFINITE);
296         colorAnimator.setRepeatMode(ValueAnimator.REVERSE);
297 
298         ObjectAnimator objectAnimator = (ObjectAnimator) mActivity.createAnimatorWithDuration(
299             mDuration);
300         startAnimation(objectAnimator, colorAnimator);
301         SystemClock.sleep(1000);
302         Integer animatedValue = (Integer) colorAnimator.getAnimatedValue();
303         int redMin = Math.min(Color.red(startColor), Color.red(endColor));
304         int redMax = Math.max(Color.red(startColor), Color.red(endColor));
305         int blueMin = Math.min(Color.blue(startColor), Color.blue(endColor));
306         int blueMax = Math.max(Color.blue(startColor), Color.blue(endColor));
307         assertTrue(Color.red(animatedValue) >= redMin);
308         assertTrue(Color.red(animatedValue) <= redMax);
309         assertTrue(Color.blue(animatedValue) >= blueMin);
310         assertTrue(Color.blue(animatedValue) <= blueMax);
311     }
312 
313     @Test
testOfMultiFloat_Path()314     public void testOfMultiFloat_Path() throws Throwable {
315         // Test for PropertyValuesHolder.ofMultiFloat(String, Path);
316         // Create a quadratic bezier curve that are symmetric about the vertical line (x = 50).
317         // Expect when fraction < 0.5, x < 50, otherwise, x >= 50.
318         Path path = new Path();
319         path.moveTo(QUADRATIC_CTRL_PT1_X, QUADRATIC_CTRL_PT1_Y);
320         path.quadTo(QUADRATIC_CTRL_PT2_X, QUADRATIC_CTRL_PT2_Y,
321                 QUADRATIC_CTRL_PT3_X, QUADRATIC_CTRL_PT3_Y);
322 
323         PropertyValuesHolder pvh = PropertyValuesHolder.ofMultiFloat("position", path);
324         final ValueAnimator anim = ValueAnimator.ofPropertyValuesHolder(pvh);
325 
326         // Linear interpolator
327         anim.setInterpolator(null);
328         anim.setDuration(200);
329 
330         anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
331             float lastFraction = 0;
332             float lastX = 0;
333             float lastY = 0;
334             @Override
335             public void onAnimationUpdate(ValueAnimator animation) {
336                 float[] values = (float[]) animation.getAnimatedValue();
337                 assertEquals(2, values.length);
338                 float x = values[0];
339                 float y = values[1];
340                 float fraction = animation.getAnimatedFraction();
341                 // Given that the curve is symmetric about the line (x = 50), x should be less than
342                 // 50 for half of the animation duration.
343                 if (fraction < 0.5) {
344                     assertTrue(x < QUADRATIC_CTRL_PT2_X);
345                 } else {
346                     assertTrue(x >= QUADRATIC_CTRL_PT2_X);
347                 }
348 
349                 if (lastFraction > 0.5) {
350                     // x should be increasing, y should be decreasing
351                     assertTrue(x >= lastX);
352                     assertTrue(y <= lastY);
353                 } else if (fraction <= 0.5) {
354                     // when fraction <= 0.5, both x, y should be increasing
355                     assertTrue(x >= lastX);
356                     assertTrue(y >= lastY);
357                 }
358                 lastX = x;
359                 lastY = y;
360                 lastFraction = fraction;
361             }
362         });
363         final Animator.AnimatorListener listener = mock(Animator.AnimatorListener.class);
364         anim.addListener(listener);
365         mActivityRule.runOnUiThread(anim::start);
366         verify(listener, within(400)).onAnimationEnd(anim, false);
367     }
368 
369     @Test
testOfMultiFloat_Array()370     public void testOfMultiFloat_Array() throws Throwable {
371         // Test for PropertyValuesHolder.ofMultiFloat(String, float[][]);
372         final float[][] data = new float[10][];
373         for (int i = 0; i < data.length; i++) {
374             data[i] = new float[3];
375             data[i][0] = i;
376             data[i][1] = i * 2;
377             data[i][2] = 0f;
378         }
379         final CountDownLatch endLatch = new CountDownLatch(1);
380         final PropertyValuesHolder pvh = PropertyValuesHolder.ofMultiFloat("position", data);
381 
382         final ValueAnimator anim = ValueAnimator.ofPropertyValuesHolder(pvh);
383         anim.setInterpolator(null);
384         anim.setDuration(60);
385         anim.addListener(new AnimatorListenerAdapter() {
386             @Override
387             public void onAnimationEnd(Animator animation) {
388                 endLatch.countDown();
389             }
390         });
391 
392         anim.addUpdateListener((ValueAnimator animation) -> {
393             float fraction = animation.getAnimatedFraction();
394             float[] values = (float[]) animation.getAnimatedValue();
395             assertEquals(3, values.length);
396 
397             float expectedX = fraction * (data.length - 1);
398 
399             assertEquals(expectedX, values[0], EPSILON);
400             assertEquals(expectedX * 2, values[1], EPSILON);
401             assertEquals(0.0f, values[2], 0.0f);
402         });
403 
404         mActivityRule.runOnUiThread(anim::start);
405         assertTrue(endLatch.await(200, TimeUnit.MILLISECONDS));
406     }
407 
408     @Test
testOfMultiInt_Path()409     public void testOfMultiInt_Path() throws Throwable {
410         // Test for PropertyValuesHolder.ofMultiInt(String, Path);
411         // Create a quadratic bezier curve that are symmetric about the vertical line (x = 50).
412         // Expect when fraction < 0.5, x < 50, otherwise, x >= 50.
413         Path path = new Path();
414         path.moveTo(QUADRATIC_CTRL_PT1_X, QUADRATIC_CTRL_PT1_Y);
415         path.quadTo(QUADRATIC_CTRL_PT2_X, QUADRATIC_CTRL_PT2_Y,
416                 QUADRATIC_CTRL_PT3_X, QUADRATIC_CTRL_PT3_Y);
417 
418         final PropertyValuesHolder pvh = PropertyValuesHolder.ofMultiInt("position", path);
419         final ValueAnimator anim = ValueAnimator.ofPropertyValuesHolder(pvh);
420         // Linear interpolator
421         anim.setInterpolator(null);
422         anim.setDuration(200);
423 
424         anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
425             float lastFraction = 0;
426             int lastX = 0;
427             int lastY = 0;
428             @Override
429             public void onAnimationUpdate(ValueAnimator animation) {
430                 int[] values = (int[]) animation.getAnimatedValue();
431                 assertEquals(2, values.length);
432                 int x = values[0];
433                 int y = values[1];
434                 float fraction = animation.getAnimatedFraction();
435                 // Given that the curve is symmetric about the line (x = 50), x should be less than
436                 // 50 for half of the animation duration.
437                 if (fraction < 0.5) {
438                     assertTrue(x < QUADRATIC_CTRL_PT2_X);
439                 } else {
440                     assertTrue(x >= QUADRATIC_CTRL_PT2_X);
441                 }
442 
443                 if (lastFraction > 0.5) {
444                     // x should be increasing, y should be decreasing
445                     assertTrue(x >= lastX);
446                     assertTrue(y <= lastY);
447                 } else if (fraction <= 0.5) {
448                     // when fraction <= 0.5, both x, y should be increasing
449                     assertTrue(x >= lastX);
450                     assertTrue(y >= lastY);
451                 }
452                 lastX = x;
453                 lastY = y;
454                 lastFraction = fraction;
455             }
456         });
457         final Animator.AnimatorListener listener = mock(Animator.AnimatorListener.class);
458         anim.addListener(listener);
459         mActivityRule.runOnUiThread(anim::start);
460         verify(listener, within(400)).onAnimationEnd(anim, false);
461     }
462 
463     @Test
testOfMultiInt_Array()464     public void testOfMultiInt_Array() throws Throwable {
465         // Test for PropertyValuesHolder.ofMultiFloat(String, int[][]);
466         final int[][] data = new int[10][];
467         for (int i = 0; i < data.length; i++) {
468             data[i] = new int[3];
469             data[i][0] = i;
470             data[i][1] = i * 2;
471             data[i][2] = 0;
472         }
473 
474         final CountDownLatch endLatch = new CountDownLatch(1);
475         final PropertyValuesHolder pvh = PropertyValuesHolder.ofMultiInt("position", data);
476         final ValueAnimator anim = ValueAnimator.ofPropertyValuesHolder(pvh);
477         anim.setInterpolator(null);
478         anim.setDuration(60);
479         anim.addListener(new AnimatorListenerAdapter() {
480             @Override
481             public void onAnimationEnd(Animator animation) {
482                 endLatch.countDown();
483             }
484         });
485 
486         anim.addUpdateListener((ValueAnimator animation) -> {
487             float fraction = animation.getAnimatedFraction();
488             int[] values = (int[]) animation.getAnimatedValue();
489             assertEquals(3, values.length);
490 
491             int expectedX = Math.round(fraction * (data.length - 1));
492             int expectedY = Math.round(fraction * (data.length - 1) * 2);
493 
494             // Allow a delta of 1 for rounding errors.
495             assertEquals(expectedX, values[0], 1);
496             assertEquals(expectedY, values[1], 1);
497             assertEquals(0, values[2]);
498         });
499 
500         mActivityRule.runOnUiThread(anim::start);
501         assertTrue(endLatch.await(200, TimeUnit.MILLISECONDS));
502     }
503 
504     @Test
testOfObject_Converter()505     public void testOfObject_Converter() throws Throwable {
506         // Test for PropertyValuesHolder.ofObject(String, TypeConverter<T, V>, Path)
507         // and for PropertyValuesHolder.ofObject(Property, TypeConverter<T, V>, Path)
508         // Create a path that contains two disconnected line segments. Check that the animated
509         // property x and property y always stay on the line segments.
510         Path path = new Path();
511         path.moveTo(LINE1_START, -LINE1_START);
512         path.lineTo(LINE1_END, -LINE1_END);
513         path.moveTo(LINE2_START, LINE2_START);
514         path.lineTo(LINE2_END, LINE2_END);
515         TypeConverter<PointF, Float> converter = new TypeConverter<PointF, Float>(
516                 PointF.class, Float.class) {
517             @Override
518             public Float convert(PointF value) {
519                 return (float) Math.sqrt(value.x * value.x + value.y * value.y);
520             }
521         };
522         final CountDownLatch endLatch = new CountDownLatch(3);
523 
524         // Create three animators. The first one use a converter that converts the point to distance
525         // to  origin. The second one does not have a type converter. The third animator uses a
526         // converter to changes sign of the x, y value of the input pointF.
527         FloatProperty property = new FloatProperty("distance") {
528             @Override
529             public void setValue(Object object, float value) {
530             }
531 
532             @Override
533             public Object get(Object object) {
534                 return null;
535             }
536         };
537         final PropertyValuesHolder pvh1 =
538                 PropertyValuesHolder.ofObject(property, converter, path);
539         final ValueAnimator anim1 = ValueAnimator.ofPropertyValuesHolder(pvh1);
540         anim1.setDuration(100);
541         anim1.setInterpolator(null);
542         anim1.addListener(new AnimatorListenerAdapter() {
543             @Override
544             public void onAnimationEnd(Animator animation) {
545                 endLatch.countDown();
546             }
547         });
548 
549         final PropertyValuesHolder pvh2 =
550                 PropertyValuesHolder.ofObject("position", null, path);
551         final ValueAnimator anim2 = ValueAnimator.ofPropertyValuesHolder(pvh2);
552         anim2.setDuration(100);
553         anim2.setInterpolator(null);
554         anim2.addListener(new AnimatorListenerAdapter() {
555             @Override
556             public void onAnimationEnd(Animator animation) {
557                 endLatch.countDown();
558             }
559         });
560 
561         TypeConverter<PointF, PointF> converter3 = new TypeConverter<PointF, PointF>(
562                 PointF.class, PointF.class) {
563             PointF mValue = new PointF();
564             @Override
565             public PointF convert(PointF value) {
566                 mValue.x = -value.x;
567                 mValue.y = -value.y;
568                 return mValue;
569             }
570         };
571         final PropertyValuesHolder pvh3 =
572                 PropertyValuesHolder.ofObject("position", converter3, path);
573         final ValueAnimator anim3 = ValueAnimator.ofPropertyValuesHolder(pvh3);
574         anim3.setDuration(100);
575         anim3.setInterpolator(null);
576         anim3.addListener(new AnimatorListenerAdapter() {
577             @Override
578             public void onAnimationEnd(Animator animation) {
579                 endLatch.countDown();
580             }
581         });
582 
583         anim3.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
584             // Set the initial value of the distance to the distance between the first point on
585             // the path to the origin.
586             float mLastDistance = (float) (32 * Math.sqrt(2));
587             float mLastFraction = 0f;
588             @Override
589             public void onAnimationUpdate(ValueAnimator animation) {
590                 float fraction = anim1.getAnimatedFraction();
591                 assertEquals(fraction, anim2.getAnimatedFraction(), 0.0f);
592                 assertEquals(fraction, anim3.getAnimatedFraction(), 0.0f);
593                 float distance = (Float) anim1.getAnimatedValue();
594                 PointF position = (PointF) anim2.getAnimatedValue();
595                 PointF positionReverseSign = (PointF) anim3.getAnimatedValue();
596                 assertEquals(position.x, -positionReverseSign.x, 0.0f);
597                 assertEquals(position.y, -positionReverseSign.y, 0.0f);
598 
599                 // Manually calculate the distance for the animator that doesn't have a
600                 // TypeConverter, and expect the result to be the same as the animation value from
601                 // the type converter.
602                 float distanceFromPosition = (float) Math.sqrt(
603                         position.x * position.x + position.y * position.y);
604                 assertEquals(distance, distanceFromPosition, 0.0001f);
605 
606                 if (mLastFraction > 0.75) {
607                     // In the 2nd line segment of the path, distance to origin should be increasing.
608                     assertTrue(distance >= mLastDistance);
609                 } else if (fraction < 0.75) {
610                     assertTrue(distance <= mLastDistance);
611                 }
612                 mLastDistance = distance;
613                 mLastFraction = fraction;
614             }
615         });
616 
617         mActivityRule.runOnUiThread(() -> {
618             anim1.start();
619             anim2.start();
620             anim3.start();
621         });
622 
623         // Wait until both of the animations finish
624         assertTrue(endLatch.await(200, TimeUnit.MILLISECONDS));
625     }
626 
627     @Test
testSetConverter()628     public void testSetConverter() throws Throwable {
629         // Test for PropertyValuesHolder.setConverter()
630         PropertyValuesHolder pvh = PropertyValuesHolder.ofObject("", null, 0f, 1f);
631         // Reverse the sign of the float in the converter, and use that value as the new type
632         // PointF's x value.
633         pvh.setConverter(new TypeConverter<Float, PointF>(Float.class, PointF.class) {
634             PointF mValue = new PointF();
635             @Override
636             public PointF convert(Float value) {
637                 mValue.x = value * (-1f);
638                 mValue.y = 0f;
639                 return mValue;
640             }
641         });
642         final CountDownLatch endLatch = new CountDownLatch(2);
643 
644         final ValueAnimator anim1 = ValueAnimator.ofPropertyValuesHolder(pvh);
645         anim1.setInterpolator(null);
646         anim1.setDuration(100);
647         anim1.addListener(new AnimatorListenerAdapter() {
648             @Override
649             public void onAnimationEnd(Animator animation) {
650                 endLatch.countDown();
651             }
652         });
653 
654         final ValueAnimator anim2 = ValueAnimator.ofFloat(0f, 1f);
655         anim2.setInterpolator(null);
656         anim2.addUpdateListener((ValueAnimator animation) -> {
657             assertEquals(anim1.getAnimatedFraction(), anim2.getAnimatedFraction(), 0.0f);
658             // Check that the pvh with type converter did reverse the sign of float, and set
659             // the x value of the PointF with it.
660             PointF value1 = (PointF) anim1.getAnimatedValue();
661             float value2 = (Float) anim2.getAnimatedValue();
662             assertEquals(value2, -value1.x, 0.0f);
663             assertEquals(0f, value1.y, 0.0f);
664         });
665         anim2.setDuration(100);
666         anim2.addListener(new AnimatorListenerAdapter() {
667             @Override
668             public void onAnimationEnd(Animator animation) {
669                 endLatch.countDown();
670             }
671         });
672 
673         mActivityRule.runOnUiThread(() -> {
674             anim1.start();
675             anim2.start();
676         });
677 
678         // Wait until both of the animations finish
679         assertTrue(endLatch.await(200, TimeUnit.MILLISECONDS));
680     }
681 
682     @Test
testSetProperty()683     public void testSetProperty() throws Throwable {
684         float[] values = {mStartY, mEndY};
685         ShapeHolderYProperty property=new ShapeHolderYProperty(ShapeHolder.class,"y");
686         property.setObject(mObject);
687         PropertyValuesHolder pVHolder = PropertyValuesHolder.ofFloat("", values);
688         pVHolder.setProperty(property);
689         ObjectAnimator objAnimator = ObjectAnimator.ofPropertyValuesHolder(mObject,pVHolder);
690         setAnimatorProperties(objAnimator);
691         startAnimation(objAnimator);
692         assertTrue(objAnimator != null);
693         float[] yArray = getYPosition();
694         assertResults(yArray, mStartY, mEndY);
695     }
696 
697     class ShapeHolderYProperty extends Property {
698         private ShapeHolder shapeHolder ;
699         private Class type = Float.class;
700         private String name = "y";
701         @SuppressWarnings("unchecked")
ShapeHolderYProperty(Class type, String name)702         public ShapeHolderYProperty(Class type, String name) throws Exception {
703             super(Float.class, name );
704             if(!( type.equals(this.type) || ( name.equals(this.name))) ){
705                 throw new Exception("Type or name provided does not match with " +
706                         this.type.getName() + " or " + this.name);
707             }
708         }
709 
setObject(Object object)710         public void setObject(Object object){
711             shapeHolder = (ShapeHolder) object;
712         }
713 
714         @Override
get(Object object)715         public Object get(Object object) {
716             return shapeHolder;
717         }
718 
719         @Override
getName()720         public String getName() {
721             return "y";
722         }
723 
724         @Override
getType()725         public Class getType() {
726             return super.getType();
727         }
728 
729         @Override
isReadOnly()730         public boolean isReadOnly() {
731             return false;
732         }
733 
734         @Override
set(Object object, Object value)735         public void set(Object object, Object value) {
736             shapeHolder.setY((Float)value);
737         }
738 
739     }
740 
741     class ViewColorProperty extends Property {
742         private View view ;
743         private Class type = Integer.class;
744         private String name = "backgroundColor";
745         @SuppressWarnings("unchecked")
ViewColorProperty(Class type, String name)746         public ViewColorProperty(Class type, String name) throws Exception {
747             super(Integer.class, name );
748             if(!( type.equals(this.type) || ( name.equals(this.name))) ){
749                 throw new Exception("Type or name provided does not match with " +
750                         this.type.getName() + " or " + this.name);
751             }
752         }
753 
setObject(Object object)754         public void setObject(Object object){
755             view = (View) object;
756         }
757 
758         @Override
get(Object object)759         public Object get(Object object) {
760             return view;
761         }
762 
763         @Override
getName()764         public String getName() {
765             return name;
766         }
767 
768         @Override
getType()769         public Class getType() {
770             return super.getType();
771         }
772 
773         @Override
isReadOnly()774         public boolean isReadOnly() {
775             return false;
776         }
777 
778         @Override
set(Object object, Object value)779         public void set(Object object, Object value) {
780             view.setBackgroundColor((Integer)value);
781         }
782     }
783 
setAnimatorProperties(ObjectAnimator objAnimator)784     private void setAnimatorProperties(ObjectAnimator objAnimator) {
785         objAnimator.setDuration(mDuration);
786         objAnimator.setRepeatCount(ValueAnimator.INFINITE);
787         objAnimator.setInterpolator(new AccelerateInterpolator());
788         objAnimator.setRepeatMode(ValueAnimator.REVERSE);
789     }
790 
getYPosition()791     public float[] getYPosition() throws Throwable{
792         float[] yArray = new float[3];
793         for(int i = 0; i < 3; i++) {
794             float y = mActivity.view.newBall.getY();
795             yArray[i] = y;
796             SystemClock.sleep(300);
797         }
798         return yArray;
799     }
800 
assertResults(float[] yArray,float startY, float endY)801     public void assertResults(float[] yArray,float startY, float endY) {
802         for(int i = 0; i < 3; i++){
803             float y = yArray[i];
804             assertTrue(y >= startY);
805             assertTrue(y <= endY);
806             if(i < 2) {
807                 float yNext = yArray[i+1];
808                 assertTrue(y != yNext);
809             }
810         }
811     }
812 
startAnimation(final Animator animator)813     private void startAnimation(final Animator animator) throws Throwable {
814         mActivityRule.runOnUiThread(() -> mActivity.startAnimation(animator));
815     }
816 
startAnimation(final ObjectAnimator mObjectAnimator, final ObjectAnimator colorAnimator)817     private void startAnimation(final ObjectAnimator mObjectAnimator,
818             final ObjectAnimator colorAnimator) throws Throwable {
819         mActivityRule.runOnUiThread(() -> mActivity.startAnimation(mObjectAnimator, colorAnimator));
820     }
821 }
822 
823