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.util.Property;
     42 import android.view.View;
     43 import android.view.animation.AccelerateInterpolator;
     44 import android.view.animation.Interpolator;
     45 
     46 import androidx.test.InstrumentationRegistry;
     47 import androidx.test.filters.MediumTest;
     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 @MediumTest
     60 @RunWith(AndroidJUnit4.class)
     61 public class ObjectAnimatorTest {
     62     private static final float LINE1_START = -32f;
     63     private static final float LINE1_END = -2f;
     64     private static final float LINE1_Y = 0f;
     65     private static final float LINE2_START = 2f;
     66     private static final float LINE2_END = 12f;
     67     private static final float QUADRATIC_CTRL_PT1_X = 0f;
     68     private static final float QUADRATIC_CTRL_PT1_Y = 0f;
     69     private static final float QUADRATIC_CTRL_PT2_X = 50f;
     70     private static final float QUADRATIC_CTRL_PT2_Y = 20f;
     71     private static final float QUADRATIC_CTRL_PT3_X = 100f;
     72     private static final float QUADRATIC_CTRL_PT3_Y = 0f;
     73     private static final float EPSILON = .001f;
     74 
     75     private Instrumentation mInstrumentation;
     76     private AnimationActivity mActivity;
     77     private ObjectAnimator mObjectAnimator;
     78     private long mDuration = 1000;
     79 
     80     @Rule
     81     public ActivityTestRule<AnimationActivity> mActivityRule =
     82             new ActivityTestRule<>(AnimationActivity.class);
     83 
     84     @Before
     85     public void setup() {
     86         mInstrumentation = InstrumentationRegistry.getInstrumentation();
     87         mInstrumentation.setInTouchMode(false);
     88         mActivity = mActivityRule.getActivity();
     89         mObjectAnimator = (ObjectAnimator) mActivity.createAnimatorWithDuration(mDuration);
     90     }
     91 
     92     @Test
     93     public void testDuration() throws Throwable {
     94         final long duration = 2000;
     95         ObjectAnimator objectAnimatorLocal = (ObjectAnimator) mActivity.createAnimatorWithDuration(
     96             duration);
     97         startAnimation(objectAnimatorLocal);
     98         assertEquals(duration, objectAnimatorLocal.getDuration());
     99     }
    100 
    101     @Test
    102     public void testOfFloat() throws Throwable {
    103         Object object = mActivity.view.newBall;
    104         String property = "y";
    105         float startY = mActivity.mStartY;
    106         float endY = mActivity.mStartY + mActivity.mDeltaY;
    107         ObjectAnimator objAnimator = ObjectAnimator.ofFloat(object, property, startY, endY);
    108         assertTrue(objAnimator != null);
    109 
    110         ValueAnimator.AnimatorUpdateListener updateListener = ((animator) -> {
    111             float y = (Float) animator.getAnimatedValue();
    112             assertTrue(y >= startY);
    113             assertTrue(y <= endY);
    114         });
    115         ValueAnimator.AnimatorUpdateListener mockListener =
    116                 mock(ValueAnimator.AnimatorUpdateListener.class);
    117         objAnimator.addUpdateListener(mockListener);
    118         objAnimator.addUpdateListener(updateListener);
    119         objAnimator.setDuration(200);
    120         objAnimator.setRepeatCount(ValueAnimator.INFINITE);
    121         objAnimator.setInterpolator(new AccelerateInterpolator());
    122         objAnimator.setRepeatMode(ValueAnimator.REVERSE);
    123         mActivityRule.runOnUiThread(objAnimator::start);
    124         assertTrue(objAnimator != null);
    125 
    126         verify(mockListener, timeout(2000).atLeast(20)).onAnimationUpdate(objAnimator);
    127         mActivityRule.runOnUiThread(objAnimator::cancel);
    128     }
    129 
    130     @Test
    131     public void testOfFloatBase() throws Throwable {
    132         Object object = mActivity.view.newBall;
    133         String property = "y";
    134         float startY = mActivity.mStartY;
    135         float endY = mActivity.mStartY + mActivity.mDeltaY;
    136         ObjectAnimator animator = ObjectAnimator.ofFloat(object, property, startY, endY);
    137         ObjectAnimator objAnimator = new ObjectAnimator();
    138         objAnimator.setTarget(object);
    139         objAnimator.setPropertyName(property);
    140         assertEquals(animator.getTarget(), objAnimator.getTarget());
    141         assertEquals(animator.getPropertyName(), objAnimator.getPropertyName());
    142     }
    143 
    144     @Test
    145     public void testOfInt() throws Throwable {
    146         Object object = mActivity.view.newBall;
    147         String property = "scrollY";
    148 
    149         final ObjectAnimator intAnimator = ObjectAnimator.ofInt(object, property, 200, 0);
    150         ValueAnimator.AnimatorUpdateListener updateListener = ((animator) -> {
    151             int value = (Integer) intAnimator.getAnimatedValue();
    152             assertTrue(value <= 200);
    153             assertTrue(value >= 0);
    154         });
    155         final Animator.AnimatorListener mockListener = mock(Animator.AnimatorListener.class);
    156         intAnimator.addListener(mockListener);
    157 
    158         intAnimator.addUpdateListener(updateListener);
    159         intAnimator.setDuration(200);
    160         intAnimator.setRepeatCount(1);
    161         intAnimator.setRepeatMode(ValueAnimator.REVERSE);
    162         mActivityRule.runOnUiThread(intAnimator::start);
    163 
    164         verify(mockListener, timeout(400)).onAnimationRepeat(intAnimator);
    165         verify(mockListener, timeout(400)).onAnimationEnd(intAnimator, false);
    166     }
    167 
    168     @Test
    169     public void testOfObject() throws Throwable {
    170         Object object = mActivity.view.newBall;
    171         String property = "backgroundColor";
    172         int startColor = 0xFFFF8080;
    173         int endColor = 0xFF8080FF;
    174 
    175         Object[] values = {new Integer(startColor), new Integer(endColor)};
    176         ArgbEvaluator evaluator = new ArgbEvaluator();
    177         final ObjectAnimator colorAnimator = ObjectAnimator.ofObject(object, property,
    178                 evaluator, values);
    179         ValueAnimator.AnimatorUpdateListener updateListener = ((animator) -> {
    180             int color = (Integer) colorAnimator.getAnimatedValue();
    181             // Check that channel is interpolated separately.
    182             assertEquals(0xFF, Color.alpha(color));
    183             assertTrue(Color.red(color) <= Color.red(startColor));
    184             assertTrue(Color.red(color) >= Color.red(endColor));
    185             assertEquals(0x80, Color.green(color));
    186             assertTrue(Color.blue(color) >= Color.blue(startColor));
    187             assertTrue(Color.blue(color) <= Color.blue(endColor));
    188         });
    189         final Animator.AnimatorListener mockListener = mock(Animator.AnimatorListener.class);
    190         colorAnimator.addListener(mockListener);
    191 
    192         colorAnimator.addUpdateListener(updateListener);
    193         colorAnimator.setDuration(200);
    194         colorAnimator.setRepeatCount(1);
    195         colorAnimator.setRepeatMode(ValueAnimator.REVERSE);
    196         mActivityRule.runOnUiThread(colorAnimator::start);
    197 
    198         verify(mockListener, timeout(400)).onAnimationRepeat(colorAnimator);
    199         verify(mockListener, timeout(400)).onAnimationEnd(colorAnimator, false);
    200     }
    201 
    202     @Test
    203     public void testOfPropertyValuesHolder() throws Throwable {
    204         Object object = mActivity.view.newBall;
    205         String propertyName = "scrollX";
    206         int startValue = 200;
    207         int endValue = 0;
    208         int[] values = {startValue, endValue};
    209         PropertyValuesHolder propertyValuesHolder = PropertyValuesHolder.ofInt(propertyName, values);
    210         final ObjectAnimator intAnimator = ObjectAnimator.ofPropertyValuesHolder(object,
    211             propertyValuesHolder);
    212 
    213         ValueAnimator.AnimatorUpdateListener updateListener = ((animator) -> {
    214             int value = (Integer) intAnimator.getAnimatedValue();
    215             // Check that each channel is interpolated separately.
    216             assertTrue(value <= 200);
    217             assertTrue(value >= 0);
    218         });
    219         final Animator.AnimatorListener mockListener = mock(Animator.AnimatorListener.class);
    220         intAnimator.addListener(mockListener);
    221 
    222         intAnimator.addUpdateListener(updateListener);
    223         intAnimator.setDuration(200);
    224         mActivityRule.runOnUiThread(intAnimator::start);
    225 
    226         verify(mockListener, timeout(400)).onAnimationEnd(intAnimator, false);
    227     }
    228 
    229     @Test
    230     public void testOfArgb() throws Throwable {
    231         Object object = mActivity.view;
    232         String property = "backgroundColor";
    233         int start = 0xffff0000;
    234         int end = 0xff0000ff;
    235         int[] values = {start, end};
    236         int startRed = Color.red(start);
    237         int startBlue = Color.blue(start);
    238         int endRed = Color.red(end);
    239         int endBlue = Color.blue(end);
    240 
    241         ValueAnimator.AnimatorUpdateListener updateListener = ((anim) -> {
    242             Integer animatedValue = (Integer) anim.getAnimatedValue();
    243             int alpha = Color.alpha(animatedValue);
    244             int red = Color.red(animatedValue);
    245             int green = Color.green(animatedValue);
    246             int blue = Color.blue(animatedValue);
    247             assertTrue(red <= startRed);
    248             assertTrue(red >= endRed);
    249             assertTrue(blue >= startBlue);
    250             assertTrue(blue <= endBlue);
    251             assertEquals(255, alpha);
    252             assertEquals(0, green);
    253 
    254         });
    255 
    256         final Animator.AnimatorListener mockListener = mock(Animator.AnimatorListener.class);
    257         final ObjectAnimator animator = ObjectAnimator.ofArgb(object, property, start, end);
    258         animator.setDuration(50);
    259         animator.addListener(mockListener);
    260         animator.addUpdateListener(updateListener);
    261 
    262         mActivityRule.runOnUiThread(animator::start);
    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(400, 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     }
    792 
    793     @Test
    794     public void testSetStartEndValues() throws Throwable {
    795         final float startValue = 100, endValue = 500;
    796         final AnimTarget target = new AnimTarget();
    797         final ObjectAnimator anim1 = ObjectAnimator.ofFloat(target, "testValue", 0);
    798         target.setTestValue(startValue);
    799         anim1.setupStartValues();
    800         target.setTestValue(endValue);
    801         anim1.setupEndValues();
    802         mActivityRule.runOnUiThread(() -> {
    803             anim1.start();
    804             assertEquals(startValue, (float) anim1.getAnimatedValue(), 0.0f);
    805             anim1.setCurrentFraction(1);
    806             assertEquals(endValue, (float) anim1.getAnimatedValue(), 0.0f);
    807             anim1.cancel();
    808         });
    809 
    810         final Property property = AnimTarget.TEST_VALUE;
    811         final ObjectAnimator anim2 = ObjectAnimator.ofFloat(target, AnimTarget.TEST_VALUE, 0);
    812         target.setTestValue(startValue);
    813         final float startValueExpected = (Float) property.get(target);
    814         anim2.setupStartValues();
    815         target.setTestValue(endValue);
    816         final float endValueExpected = (Float) property.get(target);
    817         anim2.setupEndValues();
    818         mActivityRule.runOnUiThread(() -> {
    819             anim2.start();
    820             assertEquals(startValueExpected, (float) anim2.getAnimatedValue(), 0.0f);
    821             anim2.setCurrentFraction(1);
    822             assertEquals(endValueExpected, (float) anim2.getAnimatedValue(), 0.0f);
    823             anim2.cancel();
    824         });
    825 
    826         // This is a test that ensures that the values set on a Property-based animator
    827         // are determined by the property, not by the setter/getter of the target object
    828         final Property doubler = AnimTarget.TEST_DOUBLING_VALUE;
    829         final ObjectAnimator anim3 = ObjectAnimator.ofFloat(target,
    830                 doubler, 0);
    831         target.setTestValue(startValue);
    832         final float startValueExpected3 = (Float) doubler.get(target);
    833         anim3.setupStartValues();
    834         target.setTestValue(endValue);
    835         final float endValueExpected3 = (Float) doubler.get(target);
    836         anim3.setupEndValues();
    837         mActivityRule.runOnUiThread(() -> {
    838             anim3.start();
    839             assertEquals(startValueExpected3, (float) anim3.getAnimatedValue(), 0.0f);
    840             anim3.setCurrentFraction(1);
    841             assertEquals(endValueExpected3, (float) anim3.getAnimatedValue(), 0.0f);
    842             anim3.cancel();
    843         });
    844     }
    845 
    846     @Test
    847     public void testCachedValues() throws Throwable {
    848         final AnimTarget target = new AnimTarget();
    849         final ObjectAnimator anim = ObjectAnimator.ofFloat(target, "testValue", 100);
    850         anim.setDuration(100);
    851         final CountDownLatch twoFramesLatch = new CountDownLatch(2);
    852         mActivityRule.runOnUiThread(() -> {
    853             anim.start();
    854             final View decor = mActivity.getWindow().getDecorView();
    855             decor.postOnAnimation(new Runnable() {
    856                 @Override
    857                 public void run() {
    858                     if (twoFramesLatch.getCount() > 0) {
    859                         twoFramesLatch.countDown();
    860                         if (twoFramesLatch.getCount() > 0) {
    861                             decor.postOnAnimation(this);
    862                         }
    863                     }
    864                 }
    865             });
    866         });
    867 
    868         assertTrue("Animation didn't start in a reasonable time",
    869                 twoFramesLatch.await(800, TimeUnit.MILLISECONDS));
    870 
    871         mActivityRule.runOnUiThread(() -> {
    872             assertTrue("Start value should readjust to current position",
    873                     target.getTestValue() != 0);
    874             anim.cancel();
    875             anim.setupStartValues();
    876             anim.start();
    877             assertTrue("Start value should readjust to current position",
    878                     target.getTestValue() != 0);
    879             anim.cancel();
    880         });
    881     }
    882 
    883     static class AnimTarget {
    884         private float mTestValue = 0;
    885 
    886         public void setTestValue(float value) {
    887             mTestValue = value;
    888         }
    889 
    890         public float getTestValue() {
    891             return mTestValue;
    892         }
    893 
    894         public static final Property<AnimTarget, Float> TEST_VALUE =
    895                 new Property<AnimTarget, Float>(Float.class, "testValue") {
    896                     @Override
    897                     public void set(AnimTarget object, Float value) {
    898                         object.setTestValue(value);
    899                     }
    900 
    901                     @Override
    902                     public Float get(AnimTarget object) {
    903                         return object.getTestValue();
    904                     }
    905                 };
    906         public static final Property<AnimTarget, Float> TEST_DOUBLING_VALUE =
    907                 new Property<AnimTarget, Float>(Float.class, "testValue") {
    908                     @Override
    909                     public void set(AnimTarget object, Float value) {
    910                         object.setTestValue(value);
    911                     }
    912 
    913                     @Override
    914                     public Float get(AnimTarget object) {
    915                         // purposely different from getTestValue, to verify that properties
    916                         // are independent of setters/getters
    917                         return object.getTestValue() * 2;
    918                     }
    919                 };
    920     }
    921 
    922     private void startAnimation(final ObjectAnimator mObjectAnimator) throws Throwable {
    923         mActivityRule.runOnUiThread(() -> mActivity.startAnimation(mObjectAnimator));
    924     }
    925 
    926     private void startAnimation(final ObjectAnimator mObjectAnimator, final
    927             ObjectAnimator colorAnimator) throws Throwable {
    928         mActivityRule.runOnUiThread(() -> mActivity.startAnimation(mObjectAnimator, colorAnimator));
    929     }
    930 }
    931