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