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