Merge "A new test for AnimatorSet's clone"
diff --git a/core/tests/coretests/src/android/animation/AnimatorSetActivityTest.java b/core/tests/coretests/src/android/animation/AnimatorSetActivityTest.java
index c66fd15..922bc59 100644
--- a/core/tests/coretests/src/android/animation/AnimatorSetActivityTest.java
+++ b/core/tests/coretests/src/android/animation/AnimatorSetActivityTest.java
@@ -11,6 +11,7 @@
 
 public class AnimatorSetActivityTest extends ActivityInstrumentationTestCase2<AnimatorSetActivity> {
 
+    private static final long POLL_INTERVAL = 100; // ms
     private AnimatorSetActivity mActivity;
     private ObjectAnimator a1,a2,a3;
     private ValueAnimator a4,a5;
@@ -481,6 +482,154 @@
         });
     }
 
+    @SmallTest
+    public void testClone() throws Throwable {
+        // Set up an AnimatorSet and two clones, add one listener to each. When the clones animate,
+        // listeners of both the clone and the animator being cloned should receive animation
+        // lifecycle events.
+        final AnimatorSet s1 = getSequentialSet();
+
+        // Record animators that called their listeners for the corresponding event.
+        final ArrayList<Animator> startedAnimators = new ArrayList<>();
+        final ArrayList<Animator> canceledAnimators = new ArrayList<>();
+        final ArrayList<Animator> endedAnimators = new ArrayList<>();
+
+        final MyListener l1 = new MyListener() {
+            @Override
+            public void onAnimationStart(Animator anim) {
+                super.onAnimationStart(anim);
+                startedAnimators.add(anim);
+            }
+
+            @Override
+            public void onAnimationCancel(Animator anim) {
+                super.onAnimationCancel(anim);
+                canceledAnimators.add(anim);
+            }
+
+            @Override
+            public void onAnimationEnd(Animator anim) {
+                super.onAnimationEnd(anim);
+                endedAnimators.add(anim);
+            }
+
+        };
+        s1.addListener(l1);
+
+        // Start the animation, and make the first clone during its run and the second clone once
+        // it ends.
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                assertFalse(l1.startIsCalled);
+                assertFalse(l1.endIsCalled);
+
+                s1.start();
+            }
+        });
+
+        // Make the first clone, during the animation's run.
+        assertTrue(s1.isStarted());
+        final AnimatorSet s2 = s1.clone();
+        final MyListener l2 = new MyListener();
+        s2.addListener(l2);
+
+        Thread.sleep(POLL_INTERVAL);
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                s1.end();
+            }
+        });
+
+        Thread.sleep(POLL_INTERVAL);
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                assertTrue(l1.startIsCalled);
+                assertTrue(l1.endIsCalled);
+            }
+        });
+        Thread.sleep(POLL_INTERVAL);
+
+        // Make the second clone now.
+        final AnimatorSet s3 = s1.clone();
+        final MyListener l3 = new MyListener();
+        s3.addListener(l3);
+
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                // Checking the fields before animations start.
+                assertFalse(l2.startIsCalled);
+                assertFalse(l2.cancelIsCalled);
+                assertFalse(l2.endIsCalled);
+                assertFalse(l3.startIsCalled);
+                assertFalse(l3.cancelIsCalled);
+                assertFalse(l3.endIsCalled);
+
+                s2.start();
+                s3.start();
+            }
+        });
+
+        Thread.sleep(POLL_INTERVAL);
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                // Make sure the listeners receive the callbacks
+                // At this time only onAnimationStart() should be called.
+                assertTrue(l2.startIsCalled);
+                assertTrue(l3.startIsCalled);
+                assertFalse(l2.endIsCalled);
+                assertFalse(l3.endIsCalled);
+                assertFalse(l2.cancelIsCalled);
+                assertFalse(l3.cancelIsCalled);
+
+                s2.end();
+                s3.cancel();
+            }
+        });
+        Thread.sleep(POLL_INTERVAL);
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                // Check that the new listeners for the new animations gets called for the events.
+                assertTrue(l2.startIsCalled);
+                assertFalse(l2.cancelIsCalled);
+                assertTrue(l2.endIsCalled);
+                assertTrue(l3.startIsCalled);
+                assertTrue(l3.cancelIsCalled);
+                assertTrue(l3.endIsCalled);
+
+                // Check that the listener on the animation that was being clone receive the
+                // animation lifecycle events for the clones.
+                assertTrue(onlyContains(startedAnimators, s1, s2, s3));
+                assertTrue(onlyContains(canceledAnimators, s3));
+                assertTrue(onlyContains(endedAnimators, s1, s2, s3));
+            }
+        });
+
+    }
+
+    /**
+     * Check that the animator list contains exactly the given animators and nothing else.
+     */
+    private boolean onlyContains(ArrayList<Animator> animators, AnimatorSet... sets) {
+        if (sets.length != animators.size()) {
+            return false;
+        }
+
+        for (int i = 0; i < sets.length; i++) {
+            AnimatorSet set = sets[i];
+            if (!animators.contains(set)) {
+                return false;
+            }
+        }
+        return true;
+
+    }
+
     // Create an AnimatorSet with all the animators running sequentially
     private AnimatorSet getSequentialSet() {
         AnimatorSet set = new AnimatorSet();