Merge "Fix behavior change for animators without a start delay"
diff --git a/core/java/android/animation/AnimatorSet.java b/core/java/android/animation/AnimatorSet.java
index 16f825d..d444638 100644
--- a/core/java/android/animation/AnimatorSet.java
+++ b/core/java/android/animation/AnimatorSet.java
@@ -603,7 +603,17 @@
         createDependencyGraph();
 
         // Now that all dependencies are set up, start the animations that should be started.
-        start(mRootNode);
+        boolean setIsEmpty = false;
+        if (mStartDelay > 0) {
+            start(mRootNode);
+        } else if (mNodes.size() > 1) {
+            // No delay, but there are other animators in the set
+            onChildAnimatorEnded(mDelayAnim);
+        } else {
+            // Set is empty, no delay, no other animation. Skip to end in this case
+            setIsEmpty = true;
+        }
+
         if (mListeners != null) {
             ArrayList<AnimatorListener> tmpListeners =
                     (ArrayList<AnimatorListener>) mListeners.clone();
@@ -612,18 +622,9 @@
                 tmpListeners.get(i).onAnimationStart(this);
             }
         }
-        if (mNodes.size() == 0 && mStartDelay == 0) {
-            // Handle unusual case where empty AnimatorSet is started - should send out
-            // end event immediately since the event will not be sent out at all otherwise
-            mStarted = false;
-            if (mListeners != null) {
-                ArrayList<AnimatorListener> tmpListeners =
-                        (ArrayList<AnimatorListener>) mListeners.clone();
-                int numListeners = tmpListeners.size();
-                for (int i = 0; i < numListeners; ++i) {
-                    tmpListeners.get(i).onAnimationEnd(this);
-                }
-            }
+        if (setIsEmpty) {
+            // In the case of empty AnimatorSet, we will trigger the onAnimationEnd() right away.
+            onChildAnimatorEnded(mDelayAnim);
         }
     }
 
@@ -751,44 +752,7 @@
         public void onAnimationEnd(Animator animation) {
             animation.removeListener(this);
             mAnimatorSet.mPlayingSet.remove(animation);
-            Node animNode = mAnimatorSet.mNodeMap.get(animation);
-            animNode.mEnded = true;
-
-            if (!mAnimatorSet.mTerminated) {
-                List<Node> children = animNode.mChildNodes;
-                // Start children animations, if any.
-                int childrenSize = children == null ? 0 : children.size();
-                for (int i = 0; i < childrenSize; i++) {
-                    if (children.get(i).mLatestParent == animNode) {
-                        mAnimatorSet.start(children.get(i));
-                    }
-                }
-                // Listeners are already notified of the AnimatorSet ending in cancel() or
-                // end(); the logic below only kicks in when animations end normally
-                boolean allDone = true;
-                // Traverse the tree and find if there's any unfinished node
-                int size = mAnimatorSet.mNodes.size();
-                for (int i = 0; i < size; i++) {
-                    if (!mAnimatorSet.mNodes.get(i).mEnded) {
-                        allDone = false;
-                        break;
-                    }
-                }
-                if (allDone) {
-                    // If this was the last child animation to end, then notify listeners that this
-                    // AnimatorSet has ended
-                    if (mAnimatorSet.mListeners != null) {
-                        ArrayList<AnimatorListener> tmpListeners =
-                                (ArrayList<AnimatorListener>) mAnimatorSet.mListeners.clone();
-                        int numListeners = tmpListeners.size();
-                        for (int i = 0; i < numListeners; ++i) {
-                            tmpListeners.get(i).onAnimationEnd(mAnimatorSet);
-                        }
-                    }
-                    mAnimatorSet.mStarted = false;
-                    mAnimatorSet.mPaused = false;
-                }
-            }
+            mAnimatorSet.onChildAnimatorEnded(animation);
         }
 
         // Nothing to do
@@ -801,6 +765,47 @@
 
     }
 
+    private void onChildAnimatorEnded(Animator animation) {
+        Node animNode = mNodeMap.get(animation);
+        animNode.mEnded = true;
+
+        if (!mTerminated) {
+            List<Node> children = animNode.mChildNodes;
+            // Start children animations, if any.
+            int childrenSize = children == null ? 0 : children.size();
+            for (int i = 0; i < childrenSize; i++) {
+                if (children.get(i).mLatestParent == animNode) {
+                    start(children.get(i));
+                }
+            }
+            // Listeners are already notified of the AnimatorSet ending in cancel() or
+            // end(); the logic below only kicks in when animations end normally
+            boolean allDone = true;
+            // Traverse the tree and find if there's any unfinished node
+            int size = mNodes.size();
+            for (int i = 0; i < size; i++) {
+                if (!mNodes.get(i).mEnded) {
+                    allDone = false;
+                    break;
+                }
+            }
+            if (allDone) {
+                // If this was the last child animation to end, then notify listeners that this
+                // AnimatorSet has ended
+                if (mListeners != null) {
+                    ArrayList<AnimatorListener> tmpListeners =
+                            (ArrayList<AnimatorListener>) mListeners.clone();
+                    int numListeners = tmpListeners.size();
+                    for (int i = 0; i < numListeners; ++i) {
+                        tmpListeners.get(i).onAnimationEnd(this);
+                    }
+                }
+                mStarted = false;
+                mPaused = false;
+            }
+        }
+    }
+
     /**
      * @hide
      */
diff --git a/core/java/android/animation/ValueAnimator.java b/core/java/android/animation/ValueAnimator.java
index 5af6504..1995ef5 100644
--- a/core/java/android/animation/ValueAnimator.java
+++ b/core/java/android/animation/ValueAnimator.java
@@ -932,6 +932,13 @@
         updateScaledDuration(); // in case the scale factor has changed since creation time
         AnimationHandler animationHandler = AnimationHandler.getInstance();
         animationHandler.addAnimationFrameCallback(this, mStartDelay);
+
+        if (mStartDelay == 0) {
+            // If there's no start delay, init the animation and notify start listeners right away
+            // Otherwise, postpone this until the first frame after the start delay.
+            startAnimation();
+            setCurrentFraction(mSeekFraction == -1 ? 0 : mSeekFraction);
+        }
     }
 
     @Override
@@ -1075,6 +1082,7 @@
         mStartListenersCalled = false;
         mPlayingBackwards = false;
         mReversing = false;
+        mLastFrameTime = 0;
         mCurrentIteration = 0;
         if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
             Trace.asyncTraceEnd(Trace.TRACE_TAG_VIEW, getNameForTrace(),
@@ -1184,12 +1192,13 @@
      * @hide
      */
     public final void doAnimationFrame(long frameTime) {
-        mLastFrameTime = frameTime;
         AnimationHandler handler = AnimationHandler.getInstance();
-        if (!mRunning) {
+        if (mLastFrameTime == 0) {
             // First frame
             handler.addOneShotCommitCallback(this);
-            startAnimation();
+            if (mStartDelay > 0) {
+                startAnimation();
+            }
             if (mSeekFraction < 0) {
                 mStartTime = frameTime;
             } else {
@@ -1199,6 +1208,7 @@
             }
             mStartTimeCommitted = false; // allow start time to be compensated for jank
         }
+        mLastFrameTime = frameTime;
         if (mPaused) {
             if (mPauseTime < 0) {
                 mPauseTime = frameTime;