Home | History | Annotate | Download | only in cts
      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 
     17 package android.animation.cts;
     18 
     19 import static com.android.compatibility.common.util.CtsMockitoUtils.within;
     20 
     21 import static org.junit.Assert.assertEquals;
     22 import static org.junit.Assert.assertTrue;
     23 import static org.mockito.Mockito.atLeast;
     24 import static org.mockito.Mockito.mock;
     25 import static org.mockito.Mockito.timeout;
     26 import static org.mockito.Mockito.times;
     27 import static org.mockito.Mockito.verify;
     28 
     29 import android.animation.Animator;
     30 import android.animation.AnimatorListenerAdapter;
     31 import android.animation.ArgbEvaluator;
     32 import android.animation.ObjectAnimator;
     33 import android.animation.PropertyValuesHolder;
     34 import android.animation.TypeConverter;
     35 import android.animation.ValueAnimator;
     36 import android.app.Instrumentation;
     37 import android.graphics.Color;
     38 import android.graphics.Path;
     39 import android.graphics.PointF;
     40 import android.os.SystemClock;
     41 import android.support.test.InstrumentationRegistry;
     42 import android.support.test.filters.MediumTest;
     43 import android.support.test.rule.ActivityTestRule;
     44 import android.support.test.runner.AndroidJUnit4;
     45 import android.util.Property;
     46 import android.view.View;
     47 import android.view.animation.AccelerateInterpolator;
     48 import android.view.animation.Interpolator;
     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 @MediumTest
     59 @RunWith(AndroidJUnit4.class)
     60 public class ObjectAnimatorTest {
     61     private static final float LINE1_START = -32f;
     62     private static final float LINE1_END = -2f;
     63     private static final float LINE1_Y = 0f;
     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 ObjectAnimator mObjectAnimator;
     77     private long mDuration = 1000;
     78 
     79     @Rule
     80     public ActivityTestRule<AnimationActivity> mActivityRule =
     81             new ActivityTestRule<>(AnimationActivity.class);
     82 
     83     @Before
     84     public void setup() {
     85         mInstrumentation = InstrumentationRegistry.getInstrumentation();
     86         mInstrumentation.setInTouchMode(false);
     87         mActivity = mActivityRule.getActivity();
     88         mObjectAnimator = (ObjectAnimator) mActivity.createAnimatorWithDuration(mDuration);
     89     }
     90 
     91     @Test
     92     public void testDuration() throws Throwable {
     93         final long duration = 2000;
     94         ObjectAnimator objectAnimatorLocal = (ObjectAnimator) mActivity.createAnimatorWithDuration(
     95             duration);
     96         startAnimation(objectAnimatorLocal);
     97         assertEquals(duration, objectAnimatorLocal.getDuration());
     98     }
     99 
    100     @Test
    101     public void testOfFloat() throws Throwable {
    102         Object object = mActivity.view.newBall;
    103         String property = "y";
    104         float startY = mActivity.mStartY;
    105         float endY = mActivity.mStartY + mActivity.mDeltaY;
    106         ObjectAnimator objAnimator = ObjectAnimator.ofFloat(object, property, startY, endY);
    107         assertTrue(objAnimator != null);
    108 
    109         ValueAnimator.AnimatorUpdateListener updateListener = ((animator) -> {
    110             float y = (Float) animator.getAnimatedValue();
    111             assertTrue(y >= startY);
    112             assertTrue(y <= endY);
    113         });
    114         ValueAnimator.AnimatorUpdateListener mockListener =
    115                 mock(ValueAnimator.AnimatorUpdateListener.class);
    116         objAnimator.addUpdateListener(mockListener);
    117         objAnimator.addUpdateListener(updateListener);
    118         objAnimator.setDuration(200);
    119         objAnimator.setRepeatCount(ValueAnimator.INFINITE);
    120         objAnimator.setInterpolator(new AccelerateInterpolator());
    121         objAnimator.setRepeatMode(ValueAnimator.REVERSE);
    122         mActivityRule.runOnUiThread(objAnimator::start);
    123         assertTrue(objAnimator != null);
    124 
    125         verify(mockListener, timeout(2000).atLeast(20)).onAnimationUpdate(objAnimator);
    126         mActivityRule.runOnUiThread(objAnimator::cancel);
    127     }
    128 
    129     @Test
    130     public void testOfFloatBase() throws Throwable {
    131         Object object = mActivity.view.newBall;
    132         String property = "y";
    133         float startY = mActivity.mStartY;
    134         float endY = mActivity.mStartY + mActivity.mDeltaY;
    135         ObjectAnimator animator = ObjectAnimator.ofFloat(object, property, startY, endY);
    136         ObjectAnimator objAnimator = new ObjectAnimator();
    137         objAnimator.setTarget(object);
    138         objAnimator.setPropertyName(property);
    139         assertEquals(animator.getTarget(), objAnimator.getTarget());
    140         assertEquals(animator.getPropertyName(), objAnimator.getPropertyName());
    141     }
    142 
    143     @Test
    144     public void testOfInt() throws Throwable {
    145         Object object = mActivity.view.newBall;
    146         String property = "scrollY";
    147 
    148         final ObjectAnimator intAnimator = ObjectAnimator.ofInt(object, property, 200, 0);
    149         ValueAnimator.AnimatorUpdateListener updateListener = ((animator) -> {
    150             int value = (Integer) intAnimator.getAnimatedValue();
    151             assertTrue(value <= 200);
    152             assertTrue(value >= 0);
    153         });
    154         final Animator.AnimatorListener mockListener = mock(Animator.AnimatorListener.class);
    155         intAnimator.addListener(mockListener);
    156 
    157         intAnimator.addUpdateListener(updateListener);
    158         intAnimator.setDuration(200);
    159         intAnimator.setRepeatCount(1);
    160         intAnimator.setRepeatMode(ValueAnimator.REVERSE);
    161         mActivityRule.runOnUiThread(intAnimator::start);
    162 
    163         verify(mockListener, timeout(400)).onAnimationRepeat(intAnimator);
    164         verify(mockListener, timeout(400)).onAnimationEnd(intAnimator, false);
    165     }
    166 
    167     @Test
    168     public void testOfObject() throws Throwable {
    169         Object object = mActivity.view.newBall;
    170         String property = "backgroundColor";
    171         int startColor = 0xFFFF8080;
    172         int endColor = 0xFF8080FF;
    173 
    174         Object[] values = {new Integer(startColor), new Integer(endColor)};
    175         ArgbEvaluator evaluator = new ArgbEvaluator();
    176         final ObjectAnimator colorAnimator = ObjectAnimator.ofObject(object, property,
    177                 evaluator, values);
    178         ValueAnimator.AnimatorUpdateListener updateListener = ((animator) -> {
    179             int color = (Integer) colorAnimator.getAnimatedValue();
    180             // Check that channel is interpolated separately.
    181             assertEquals(0xFF, Color.alpha(color));
    182             assertTrue(Color.red(color) <= Color.red(startColor));
    183             assertTrue(Color.red(color) >= Color.red(endColor));
    184             assertEquals(0x80, Color.green(color));
    185             assertTrue(Color.blue(color) >= Color.blue(startColor));
    186             assertTrue(Color.blue(color) <= Color.blue(endColor));
    187         });
    188         final Animator.AnimatorListener mockListener = mock(Animator.AnimatorListener.class);
    189         colorAnimator.addListener(mockListener);
    190 
    191         colorAnimator.addUpdateListener(updateListener);
    192         colorAnimator.setDuration(200);
    193         colorAnimator.setRepeatCount(1);
    194         colorAnimator.setRepeatMode(ValueAnimator.REVERSE);
    195         mActivityRule.runOnUiThread(colorAnimator::start);
    196 
    197         verify(mockListener, timeout(400)).onAnimationRepeat(colorAnimator);
    198         verify(mockListener, timeout(400)).onAnimationEnd(colorAnimator, false);
    199     }
    200 
    201     @Test
    202     public void testOfPropertyValuesHolder() throws Throwable {
    203         Object object = mActivity.view.newBall;
    204         String propertyName = "scrollX";
    205         int startValue = 200;
    206         int endValue = 0;
    207         int[] values = {startValue, endValue};
    208         PropertyValuesHolder propertyValuesHolder = PropertyValuesHolder.ofInt(propertyName, values);
    209         final ObjectAnimator intAnimator = ObjectAnimator.ofPropertyValuesHolder(object,
    210             propertyValuesHolder);
    211 
    212         ValueAnimator.AnimatorUpdateListener updateListener = ((animator) -> {
    213             int value = (Integer) intAnimator.getAnimatedValue();
    214             // Check that each channel is interpolated separately.
    215             assertTrue(value <= 200);
    216             assertTrue(value >= 0);
    217         });
    218         final Animator.AnimatorListener mockListener = mock(Animator.AnimatorListener.class);
    219         intAnimator.addListener(mockListener);
    220 
    221         intAnimator.addUpdateListener(updateListener);
    222         intAnimator.setDuration(200);
    223         mActivityRule.runOnUiThread(intAnimator::start);
    224 
    225         verify(mockListener, timeout(400)).onAnimationEnd(intAnimator, false);
    226     }
    227 
    228     @Test
    229     public void testOfArgb() throws Throwable {
    230         Object object = mActivity.view;
    231         String property = "backgroundColor";
    232         int start = 0xffff0000;
    233         int end = 0xff0000ff;
    234         int[] values = {start, end};
    235         int startRed = Color.red(start);
    236         int startBlue = Color.blue(start);
    237         int endRed = Color.red(end);
    238         int endBlue = Color.blue(end);
    239 
    240         ValueAnimator.AnimatorUpdateListener updateListener = ((anim) -> {
    241             Integer animatedValue = (Integer) anim.getAnimatedValue();
    242             int alpha = Color.alpha(animatedValue);
    243             int red = Color.red(animatedValue);
    244             int green = Color.green(animatedValue);
    245             int blue = Color.blue(animatedValue);
    246             assertTrue(red <= startRed);
    247             assertTrue(red >= endRed);
    248             assertTrue(blue >= startBlue);
    249             assertTrue(blue <= endBlue);
    250             assertEquals(255, alpha);
    251             assertEquals(0, green);
    252 
    253         });
    254 
    255         final Animator.AnimatorListener mockListener = mock(Animator.AnimatorListener.class);
    256         final ObjectAnimator animator = ObjectAnimator.ofArgb(object, property, start, end);
    257         animator.setDuration(200);
    258         animator.addListener(mockListener);
    259         animator.addUpdateListener(updateListener);
    260 
    261         mActivityRule.runOnUiThread(animator::start);
    262         assertTrue(animator.isRunning());
    263 
    264         verify(mockListener, timeout(400)).onAnimationEnd(animator, false);
    265     }
    266 
    267     @Test
    268     public void testNullObject() throws Throwable {
    269         final ObjectAnimator anim = ObjectAnimator.ofFloat(null, "dummyValue", 0f, 1f);
    270         anim.setDuration(300);
    271         final ValueAnimator.AnimatorUpdateListener updateListener =
    272                 mock(ValueAnimator.AnimatorUpdateListener.class);
    273         anim.addUpdateListener(updateListener);
    274         final Animator.AnimatorListener listener = mock(Animator.AnimatorListener.class);
    275         anim.addListener(listener);
    276 
    277         mActivityRule.runOnUiThread(anim::start);
    278         verify(listener, within(500)).onAnimationEnd(anim, false);
    279         // Verify that null target ObjectAnimator didn't get canceled.
    280         verify(listener, times(0)).onAnimationCancel(anim);
    281         // Verify that the update listeners gets called a few times.
    282         verify(updateListener, atLeast(8)).onAnimationUpdate(anim);
    283     }
    284 
    285     @Test
    286     public void testGetPropertyName() throws Throwable {
    287         Object object = mActivity.view.newBall;
    288         String propertyName = "backgroundColor";
    289         int startColor = mActivity.view.RED;
    290         int endColor = mActivity.view.BLUE;
    291         Object[] values = {new Integer(startColor), new Integer(endColor)};
    292         ArgbEvaluator evaluator = new ArgbEvaluator();
    293         ObjectAnimator colorAnimator = ObjectAnimator.ofObject(object, propertyName,
    294                 evaluator, values);
    295         String actualPropertyName = colorAnimator.getPropertyName();
    296         assertEquals(propertyName, actualPropertyName);
    297     }
    298 
    299     @Test
    300     public void testSetFloatValues() throws Throwable {
    301         Object object = mActivity.view.newBall;
    302         String property = "y";
    303         float startY = mActivity.mStartY;
    304         float endY = mActivity.mStartY + mActivity.mDeltaY;
    305         float[] values = {startY, endY};
    306         ObjectAnimator objAnimator = new ObjectAnimator();
    307         ValueAnimator.AnimatorUpdateListener updateListener = ((animator) -> {
    308             float y = (Float) animator.getAnimatedValue();
    309             assertTrue(y >= startY);
    310             assertTrue(y <= endY);
    311         });
    312         ValueAnimator.AnimatorUpdateListener mockListener =
    313                 mock(ValueAnimator.AnimatorUpdateListener.class);
    314         objAnimator.addUpdateListener(mockListener);
    315         objAnimator.addUpdateListener(updateListener);
    316         objAnimator.setTarget(object);
    317         objAnimator.setPropertyName(property);
    318         objAnimator.setFloatValues(values);
    319         objAnimator.setDuration(mDuration);
    320         objAnimator.setRepeatCount(ValueAnimator.INFINITE);
    321         objAnimator.setInterpolator(new AccelerateInterpolator());
    322         objAnimator.setRepeatMode(ValueAnimator.REVERSE);
    323         mActivityRule.runOnUiThread(objAnimator::start);
    324 
    325         verify(mockListener, timeout(2000).atLeast(20)).onAnimationUpdate(objAnimator);
    326         mActivityRule.runOnUiThread(objAnimator::cancel);
    327     }
    328 
    329     @Test
    330     public void testGetTarget() throws Throwable {
    331         Object object = mActivity.view.newBall;
    332         String propertyName = "backgroundColor";
    333         int startColor = mActivity.view.RED;
    334         int endColor = mActivity.view.BLUE;
    335         Object[] values = {new Integer(startColor), new Integer(endColor)};
    336         ArgbEvaluator evaluator = new ArgbEvaluator();
    337         ObjectAnimator colorAnimator = ObjectAnimator.ofObject(object, propertyName,
    338                 evaluator, values);
    339         Object target = colorAnimator.getTarget();
    340         assertEquals(object, target);
    341     }
    342 
    343     @Test
    344     public void testClone() throws Throwable {
    345         Object object = mActivity.view.newBall;
    346         String property = "y";
    347         float startY = mActivity.mStartY;
    348         float endY = mActivity.mStartY + mActivity.mDeltaY;
    349         Interpolator interpolator = new AccelerateInterpolator();
    350         ObjectAnimator objAnimator = ObjectAnimator.ofFloat(object, property, startY, endY);
    351         objAnimator.setDuration(mDuration);
    352         objAnimator.setRepeatCount(ValueAnimator.INFINITE);
    353         objAnimator.setInterpolator(interpolator);
    354         objAnimator.setRepeatMode(ValueAnimator.REVERSE);
    355         ObjectAnimator cloneAnimator = objAnimator.clone();
    356 
    357         assertEquals(mDuration, cloneAnimator.getDuration());
    358         assertEquals(ValueAnimator.INFINITE, cloneAnimator.getRepeatCount());
    359         assertEquals(ValueAnimator.REVERSE, cloneAnimator.getRepeatMode());
    360         assertEquals(object, cloneAnimator.getTarget());
    361         assertEquals(property, cloneAnimator.getPropertyName());
    362         assertEquals(interpolator, cloneAnimator.getInterpolator());
    363     }
    364 
    365     @Test
    366     public void testOfFloat_Path() throws Throwable {
    367         // Test for ObjectAnimator.ofFloat(Object, String, String, Path)
    368         // Create a path that contains two disconnected line segments. Check that the animated
    369         // property x and property y always stay on the line segments.
    370         Path path = new Path();
    371         path.moveTo(LINE1_START, LINE1_Y);
    372         path.lineTo(LINE1_END, LINE1_Y);
    373         path.moveTo(LINE2_START, LINE2_START);
    374         path.lineTo(LINE2_END, LINE2_END);
    375         final double totalLength = (LINE1_END - LINE1_START) + Math.sqrt(
    376                 (LINE2_END - LINE2_START) * (LINE2_END - LINE2_START) +
    377                 (LINE2_END - LINE2_START) * (LINE2_END - LINE2_START));
    378         final double firstSegEndFraction = (LINE1_END - LINE1_START) / totalLength;
    379         final float delta = 0.01f;
    380 
    381         Object target = new Object() {
    382             public void setX(float x) {
    383             }
    384 
    385             public void setY(float y) {
    386             }
    387         };
    388 
    389         final ObjectAnimator anim = ObjectAnimator.ofFloat(target, "x", "y", path);
    390         anim.setDuration(200);
    391         // Linear interpolator
    392         anim.setInterpolator(null);
    393         anim.addUpdateListener((ValueAnimator animation) -> {
    394             float fraction = animation.getAnimatedFraction();
    395             float x = (Float) animation.getAnimatedValue("x");
    396             float y = (Float) animation.getAnimatedValue("y");
    397 
    398             // Check that the point is on the path.
    399             if (x <= 0) {
    400                 // First line segment is a horizontal line.
    401                 assertTrue(x >= LINE1_START);
    402                 assertTrue(x <= LINE1_END);
    403                 assertEquals(LINE1_Y, y, 0.0f);
    404 
    405                 // Check that the time animation stays on the first segment is proportional to
    406                 // the length of the first line segment.
    407                 assertTrue(fraction < firstSegEndFraction + delta);
    408             } else {
    409                 assertTrue(x >= LINE2_START);
    410                 assertTrue(x <= LINE2_END);
    411                 assertEquals(x, y, 0.0f);
    412 
    413                 // Check that the time animation stays on the second segment is proportional to
    414                 // the length of the second line segment.
    415                 assertTrue(fraction > firstSegEndFraction - delta);
    416             }
    417         });
    418         final Animator.AnimatorListener listener = mock(Animator.AnimatorListener.class);
    419         anim.addListener(listener);
    420         mActivityRule.runOnUiThread(anim::start);
    421         verify(listener, within(400)).onAnimationEnd(anim, false);
    422     }
    423 
    424     @Test
    425     public void testOfInt_Path() throws Throwable {
    426         // Test for ObjectAnimator.ofInt(Object, String, String, Path)
    427         // Create a path that contains two disconnected line segments. Check that the animated
    428         // property x and property y always stay on the line segments.
    429         Path path = new Path();
    430         path.moveTo(LINE1_START, -LINE1_START);
    431         path.lineTo(LINE1_END, -LINE1_END);
    432         path.moveTo(LINE2_START, LINE2_START);
    433         path.lineTo(LINE2_END, LINE2_END);
    434 
    435         Object target = new Object() {
    436             public void setX(float x) {
    437             }
    438 
    439             public void setY(float y) {
    440             }
    441         };
    442         final CountDownLatch endLatch = new CountDownLatch(1);
    443         final ObjectAnimator anim = ObjectAnimator.ofInt(target, "x", "y", path);
    444         anim.setDuration(200);
    445 
    446         // Linear interpolator
    447         anim.setInterpolator(null);
    448         anim.addUpdateListener((ValueAnimator animation) -> {
    449             float fraction = animation.getAnimatedFraction();
    450             int x = (Integer) animation.getAnimatedValue("x");
    451             int y = (Integer) animation.getAnimatedValue("y");
    452 
    453             // Check that the point is on the path.
    454             if (x <= 0) {
    455                 // Check that the time animation stays on the first segment is proportional to
    456                 // the length of the first line segment.
    457                 assertTrue(x >= LINE1_START);
    458                 assertTrue(x <= LINE1_END);
    459                 assertEquals(x, -y);
    460 
    461                 // First line segment is 3 times as long as the second line segment, so the
    462                 // 3/4 of the animation duration will be spent on the first line segment.
    463                 assertTrue(fraction <= 0.75f);
    464             } else {
    465                 // Check that the time animation stays on the second segment is proportional to
    466                 // the length of the second line segment.
    467                 assertTrue(x >= LINE2_START);
    468                 assertTrue(x <= LINE2_END);
    469                 assertEquals(x, y);
    470 
    471                 assertTrue(fraction >= 0.75f);
    472             }
    473         });
    474         anim.addListener(new AnimatorListenerAdapter() {
    475             @Override
    476             public void onAnimationEnd(Animator animation) {
    477                 endLatch.countDown();
    478             }
    479         });
    480         mActivityRule.runOnUiThread(anim::start);
    481         assertTrue(endLatch.await(400, TimeUnit.MILLISECONDS));
    482     }
    483 
    484     @Test
    485     public void testOfMultiFloat_Path() throws Throwable {
    486         // Test for ObjectAnimator.ofMultiFloat(Object, String, Path);
    487         // Create a quadratic bezier curve that are symmetric about the vertical line (x = 50).
    488         // Expect when fraction < 0.5, x < 50, otherwise, x >= 50.
    489         Path path = new Path();
    490         path.moveTo(QUADRATIC_CTRL_PT1_X, QUADRATIC_CTRL_PT1_Y);
    491         path.quadTo(QUADRATIC_CTRL_PT2_X, QUADRATIC_CTRL_PT2_Y,
    492                 QUADRATIC_CTRL_PT3_X, QUADRATIC_CTRL_PT3_Y);
    493 
    494         Object target = new Object() {
    495             public void setPosition(float x, float y) {
    496             }
    497         };
    498 
    499         final ObjectAnimator anim = ObjectAnimator.ofMultiFloat(target, "position", path);
    500         // Linear interpolator
    501         anim.setInterpolator(null);
    502         anim.setDuration(200);
    503 
    504         anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    505             float lastFraction = 0;
    506             float lastX = 0;
    507             float lastY = 0;
    508             @Override
    509             public void onAnimationUpdate(ValueAnimator animation) {
    510                 float[] values = (float[]) animation.getAnimatedValue();
    511                 assertEquals(2, values.length);
    512                 float x = values[0];
    513                 float y = values[1];
    514                 float fraction = animation.getAnimatedFraction();
    515                 // Given that the curve is symmetric about the line (x = 50), x should be less than
    516                 // 50 for half of the animation duration.
    517                 if (fraction < 0.5) {
    518                     assertTrue(x < QUADRATIC_CTRL_PT2_X);
    519                 } else {
    520                     assertTrue(x >= QUADRATIC_CTRL_PT2_X);
    521                 }
    522 
    523                 if (lastFraction > 0.5) {
    524                     // x should be increasing, y should be decreasing
    525                     assertTrue(x >= lastX);
    526                     assertTrue(y <= lastY);
    527                 } else if (fraction <= 0.5) {
    528                     // when fraction <= 0.5, both x, y should be increasing
    529                     assertTrue(x >= lastX);
    530                     assertTrue(y >= lastY);
    531                 }
    532                 lastX = x;
    533                 lastY = y;
    534                 lastFraction = fraction;
    535             }
    536         });
    537         final Animator.AnimatorListener listener = mock(Animator.AnimatorListener.class);
    538         anim.addListener(listener);
    539         mActivityRule.runOnUiThread(anim::start);
    540         verify(listener, within(400)).onAnimationEnd(anim, false);
    541     }
    542 
    543     @Test
    544     public void testOfMultiFloat() throws Throwable {
    545         // Test for ObjectAnimator.ofMultiFloat(Object, String, float[][]);
    546         final float[][] data = new float[10][];
    547         for (int i = 0; i < data.length; i++) {
    548             data[i] = new float[3];
    549             data[i][0] = i;
    550             data[i][1] = i * 2;
    551             data[i][2] = 0f;
    552         }
    553 
    554         Object target = new Object() {
    555             public void setPosition(float x, float y, float z) {
    556             }
    557         };
    558         final CountDownLatch endLatch = new CountDownLatch(1);
    559         final ObjectAnimator anim = ObjectAnimator.ofMultiFloat(target, "position", data);
    560         anim.setInterpolator(null);
    561         anim.setDuration(60);
    562         anim.addListener(new AnimatorListenerAdapter() {
    563             @Override
    564             public void onAnimationEnd(Animator animation) {
    565                 endLatch.countDown();
    566             }
    567         });
    568 
    569         anim.addUpdateListener((ValueAnimator animation) -> {
    570             float fraction = animation.getAnimatedFraction();
    571             float[] values = (float[]) animation.getAnimatedValue();
    572             assertEquals(3, values.length);
    573 
    574             float expectedX = fraction * (data.length - 1);
    575 
    576             assertEquals(expectedX, values[0], EPSILON);
    577             assertEquals(expectedX * 2, values[1], EPSILON);
    578             assertEquals(0f, values[2], 0.0f);
    579         });
    580 
    581         mActivityRule.runOnUiThread(anim::start);
    582         assertTrue(endLatch.await(200, TimeUnit.MILLISECONDS));
    583     }
    584 
    585     @Test
    586     public void testOfMultiInt_Path() throws Throwable {
    587         // Test for ObjectAnimator.ofMultiInt(Object, String, Path);
    588         // Create a quadratic bezier curve that are symmetric about the vertical line (x = 50).
    589         // Expect when fraction < 0.5, x < 50, otherwise, x >= 50.
    590         Path path = new Path();
    591         path.moveTo(QUADRATIC_CTRL_PT1_X, QUADRATIC_CTRL_PT1_Y);
    592         path.quadTo(QUADRATIC_CTRL_PT2_X, QUADRATIC_CTRL_PT2_Y,
    593                 QUADRATIC_CTRL_PT3_X, QUADRATIC_CTRL_PT3_Y);
    594 
    595         Object target = new Object() {
    596             public void setPosition(int x, int y) {
    597             }
    598         };
    599 
    600         final ObjectAnimator anim = ObjectAnimator.ofMultiInt(target, "position", path);
    601         // Linear interpolator
    602         anim.setInterpolator(null);
    603         anim.setDuration(200);
    604 
    605         anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    606             float lastFraction = 0;
    607             int lastX = 0;
    608             int lastY = 0;
    609             @Override
    610             public void onAnimationUpdate(ValueAnimator animation) {
    611                 int[] values = (int[]) animation.getAnimatedValue();
    612                 assertEquals(2, values.length);
    613                 int x = values[0];
    614                 int y = values[1];
    615                 float fraction = animation.getAnimatedFraction();
    616                 // Given that the curve is symmetric about the line (x = 50), x should be less than
    617                 // 50 for half of the animation duration.
    618                 if (fraction < 0.5) {
    619                     assertTrue(x < QUADRATIC_CTRL_PT2_X);
    620                 } else {
    621                     assertTrue(x >= QUADRATIC_CTRL_PT2_X);
    622                 }
    623 
    624                 if (lastFraction > 0.5) {
    625                     // x should be increasing, y should be decreasing
    626                     assertTrue(x >= lastX);
    627                     assertTrue(y <= lastY);
    628                 } else if (fraction <= 0.5) {
    629                     // when fraction <= 0.5, both x, y should be increasing
    630                     assertTrue(x >= lastX);
    631                     assertTrue(y >= lastY);
    632                 }
    633                 lastX = x;
    634                 lastY = y;
    635                 lastFraction = fraction;
    636             }
    637         });
    638         final Animator.AnimatorListener listener = mock(Animator.AnimatorListener.class);
    639         anim.addListener(listener);
    640         mActivityRule.runOnUiThread(anim::start);
    641         verify(listener, within(400)).onAnimationEnd(anim, false);
    642     }
    643 
    644     @Test
    645     public void testOfMultiInt() throws Throwable {
    646         // Test for ObjectAnimator.ofMultiFloat(Object, String, int[][]);
    647         final int[][] data = new int[10][];
    648         for (int i = 0; i < data.length; i++) {
    649             data[i] = new int[3];
    650             data[i][0] = i;
    651             data[i][1] = i * 2;
    652             data[i][2] = 0;
    653         }
    654 
    655         Object target = new Object() {
    656             public void setPosition(int x, int y, int z) {
    657             }
    658         };
    659         final CountDownLatch endLatch = new CountDownLatch(1);
    660         final ObjectAnimator anim = ObjectAnimator.ofMultiInt(target, "position", data);
    661         anim.setInterpolator(null);
    662         anim.setDuration(60);
    663         anim.addListener(new AnimatorListenerAdapter() {
    664             @Override
    665             public void onAnimationEnd(Animator animation) {
    666                 endLatch.countDown();
    667             }
    668         });
    669 
    670         anim.addUpdateListener((ValueAnimator animation) -> {
    671             float fraction = animation.getAnimatedFraction();
    672             int[] values = (int[]) animation.getAnimatedValue();
    673             assertEquals(3, values.length);
    674 
    675             int expectedX = Math.round(fraction * (data.length - 1));
    676             int expectedY = Math.round(fraction * (data.length - 1) * 2);
    677 
    678             // Allow a delta of 1 for rounding errors.
    679             assertEquals(expectedX, values[0], 1);
    680             assertEquals(expectedY, values[1], 1);
    681             assertEquals(0, values[2]);
    682         });
    683 
    684         mActivityRule.runOnUiThread(anim::start);
    685         assertTrue(endLatch.await(200, TimeUnit.MILLISECONDS));
    686     }
    687 
    688     @Test
    689     public void testOfObject_Converter() throws Throwable {
    690         // Test for ObjectAnimator.ofObject(Object, String, TypeConverter<T, V>, Path)
    691         // Create a path that contains two disconnected line segments. Check that the animated
    692         // property x and property y always stay on the line segments.
    693         Path path = new Path();
    694         path.moveTo(LINE1_START, -LINE1_START);
    695         path.lineTo(LINE1_END, -LINE1_END);
    696         path.moveTo(LINE2_START, LINE2_START);
    697         path.lineTo(LINE2_END, LINE2_END);
    698 
    699         Object target1 = new Object() {
    700             public void setDistance(float distance) {
    701             }
    702         };
    703         Object target2 = new Object() {
    704             public void setPosition(PointF pos) {
    705             }
    706         };
    707         TypeConverter<PointF, Float> converter = new TypeConverter<PointF, Float>(
    708                 PointF.class, Float.class) {
    709             @Override
    710             public Float convert(PointF value) {
    711                 return (float) Math.sqrt(value.x * value.x + value.y * value.y);
    712             }
    713         };
    714         final CountDownLatch endLatch = new CountDownLatch(2);
    715 
    716         // Create two animators. One use a converter that converts the point to distance to origin.
    717         // The other one does not have a type converter.
    718         final ObjectAnimator anim1 = ObjectAnimator.ofObject(target1, "distance", converter, path);
    719         anim1.setDuration(100);
    720         anim1.setInterpolator(null);
    721         anim1.addListener(new AnimatorListenerAdapter() {
    722             @Override
    723             public void onAnimationEnd(Animator animation) {
    724                 endLatch.countDown();
    725             }
    726         });
    727 
    728         final ObjectAnimator anim2 = ObjectAnimator.ofObject(target2, "position", null, path);
    729         anim2.setDuration(100);
    730         anim2.setInterpolator(null);
    731         anim2.addListener(new AnimatorListenerAdapter() {
    732             @Override
    733             public void onAnimationEnd(Animator animation) {
    734                 endLatch.countDown();
    735             }
    736         });
    737         anim2.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    738             // Set the initial value of the distance to the distance between the first point on
    739             // the path to the origin.
    740             float mLastDistance = (float) (32 * Math.sqrt(2));
    741             float mLastFraction = 0f;
    742             @Override
    743             public void onAnimationUpdate(ValueAnimator animation) {
    744                 float fraction = anim1.getAnimatedFraction();
    745                 assertEquals(fraction, anim2.getAnimatedFraction(), 0.0f);
    746                 float distance = (Float) anim1.getAnimatedValue();
    747                 PointF position = (PointF) anim2.getAnimatedValue();
    748 
    749                 // Manually calculate the distance for the animator that doesn't have a
    750                 // TypeConverter, and expect the result to be the same as the animation value from
    751                 // the type converter.
    752                 float distanceFromPosition = (float) Math.sqrt(
    753                         position.x * position.x + position.y * position.y);
    754                 assertEquals(distance, distanceFromPosition, 0.0001f);
    755 
    756                 if (mLastFraction > 0.75) {
    757                     // In the 2nd line segment of the path, distance to origin should be increasing.
    758                     assertTrue(distance >= mLastDistance);
    759                 } else if (fraction < 0.75) {
    760                     assertTrue(distance <= mLastDistance);
    761                 }
    762                 mLastDistance = distance;
    763                 mLastFraction = fraction;
    764             }
    765         });
    766 
    767         mActivityRule.runOnUiThread(() -> {
    768             anim1.start();
    769             anim2.start();
    770         });
    771 
    772         // Wait until both of the animations finish
    773         assertTrue(endLatch.await(500, TimeUnit.MILLISECONDS));
    774     }
    775 
    776     @Test
    777     public void testIsStarted() throws Throwable {
    778         Object object = mActivity.view.newBall;
    779         String property = "y";
    780         float startY = mActivity.mStartY;
    781         float endY = mActivity.mStartY + mActivity.mDeltaY;
    782         Interpolator interpolator = new AccelerateInterpolator();
    783         ObjectAnimator objAnimator = ObjectAnimator.ofFloat(object, property, startY, endY);
    784         objAnimator.setDuration(mDuration);
    785         objAnimator.setRepeatCount(ValueAnimator.INFINITE);
    786         objAnimator.setInterpolator(interpolator);
    787         objAnimator.setRepeatMode(ValueAnimator.REVERSE);
    788         startAnimation(objAnimator);
    789         SystemClock.sleep(100);
    790         assertTrue(objAnimator.isStarted());
    791         SystemClock.sleep(100);
    792     }
    793 
    794     @Test
    795     public void testSetStartEndValues() throws Throwable {
    796         final float startValue = 100, endValue = 500;
    797         final AnimTarget target = new AnimTarget();
    798         final ObjectAnimator anim1 = ObjectAnimator.ofFloat(target, "testValue", 0);
    799         target.setTestValue(startValue);
    800         anim1.setupStartValues();
    801         target.setTestValue(endValue);
    802         anim1.setupEndValues();
    803         mActivityRule.runOnUiThread(() -> {
    804             anim1.start();
    805             assertEquals(startValue, (float) anim1.getAnimatedValue(), 0.0f);
    806             anim1.setCurrentFraction(1);
    807             assertEquals(endValue, (float) anim1.getAnimatedValue(), 0.0f);
    808             anim1.cancel();
    809         });
    810 
    811         final Property property = AnimTarget.TEST_VALUE;
    812         final ObjectAnimator anim2 = ObjectAnimator.ofFloat(target, AnimTarget.TEST_VALUE, 0);
    813         target.setTestValue(startValue);
    814         final float startValueExpected = (Float) property.get(target);
    815         anim2.setupStartValues();
    816         target.setTestValue(endValue);
    817         final float endValueExpected = (Float) property.get(target);
    818         anim2.setupEndValues();
    819         mActivityRule.runOnUiThread(() -> {
    820             anim2.start();
    821             assertEquals(startValueExpected, (float) anim2.getAnimatedValue(), 0.0f);
    822             anim2.setCurrentFraction(1);
    823             assertEquals(endValueExpected, (float) anim2.getAnimatedValue(), 0.0f);
    824             anim2.cancel();
    825         });
    826 
    827         // This is a test that ensures that the values set on a Property-based animator
    828         // are determined by the property, not by the setter/getter of the target object
    829         final Property doubler = AnimTarget.TEST_DOUBLING_VALUE;
    830         final ObjectAnimator anim3 = ObjectAnimator.ofFloat(target,
    831                 doubler, 0);
    832         target.setTestValue(startValue);
    833         final float startValueExpected3 = (Float) doubler.get(target);
    834         anim3.setupStartValues();
    835         target.setTestValue(endValue);
    836         final float endValueExpected3 = (Float) doubler.get(target);
    837         anim3.setupEndValues();
    838         mActivityRule.runOnUiThread(() -> {
    839             anim3.start();
    840             assertEquals(startValueExpected3, (float) anim3.getAnimatedValue(), 0.0f);
    841             anim3.setCurrentFraction(1);
    842             assertEquals(endValueExpected3, (float) anim3.getAnimatedValue(), 0.0f);
    843             anim3.cancel();
    844         });
    845     }
    846 
    847     @Test
    848     public void testCachedValues() throws Throwable {
    849         final AnimTarget target = new AnimTarget();
    850         final ObjectAnimator anim = ObjectAnimator.ofFloat(target, "testValue", 100);
    851         anim.setDuration(200);
    852         final CountDownLatch twoFramesLatch = new CountDownLatch(2);
    853         mActivityRule.runOnUiThread(() -> {
    854             anim.start();
    855             final View decor = mActivity.getWindow().getDecorView();
    856             decor.postOnAnimation(new Runnable() {
    857                 @Override
    858                 public void run() {
    859                     if (twoFramesLatch.getCount() > 0) {
    860                         twoFramesLatch.countDown();
    861                         decor.postOnAnimation(this);
    862                     }
    863                 }
    864             });
    865         });
    866 
    867         assertTrue("Animation didn't start in a reasonable time",
    868                 twoFramesLatch.await(100, TimeUnit.MILLISECONDS));
    869 
    870         mActivityRule.runOnUiThread(() -> {
    871             assertTrue("Start value should readjust to current position",
    872                     target.getTestValue() != 0);
    873             anim.cancel();
    874             anim.setupStartValues();
    875             anim.start();
    876             assertTrue("Start value should readjust to current position",
    877                     target.getTestValue() != 0);
    878             anim.cancel();
    879         });
    880     }
    881 
    882     static class AnimTarget {
    883         private float mTestValue = 0;
    884 
    885         public void setTestValue(float value) {
    886             mTestValue = value;
    887         }
    888 
    889         public float getTestValue() {
    890             return mTestValue;
    891         }
    892 
    893         public static final Property<AnimTarget, Float> TEST_VALUE =
    894                 new Property<AnimTarget, Float>(Float.class, "testValue") {
    895                     @Override
    896                     public void set(AnimTarget object, Float value) {
    897                         object.setTestValue(value);
    898                     }
    899 
    900                     @Override
    901                     public Float get(AnimTarget object) {
    902                         return object.getTestValue();
    903                     }
    904                 };
    905         public static final Property<AnimTarget, Float> TEST_DOUBLING_VALUE =
    906                 new Property<AnimTarget, Float>(Float.class, "testValue") {
    907                     @Override
    908                     public void set(AnimTarget object, Float value) {
    909                         object.setTestValue(value);
    910                     }
    911 
    912                     @Override
    913                     public Float get(AnimTarget object) {
    914                         // purposely different from getTestValue, to verify that properties
    915                         // are independent of setters/getters
    916                         return object.getTestValue() * 2;
    917                     }
    918                 };
    919     }
    920 
    921     private void startAnimation(final ObjectAnimator mObjectAnimator) throws Throwable {
    922         mActivityRule.runOnUiThread(() -> mActivity.startAnimation(mObjectAnimator));
    923     }
    924 
    925     private void startAnimation(final ObjectAnimator mObjectAnimator, final
    926             ObjectAnimator colorAnimator) throws Throwable {
    927         mActivityRule.runOnUiThread(() -> mActivity.startAnimation(mObjectAnimator, colorAnimator));
    928     }
    929 }
    930