Merge "Improved notification scroller animation logic"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
index b72909f..f8aab80 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -695,7 +695,7 @@
 //                        mEdgeGlowBottom.onAbsorb((int) mScroller.getCurrVelocity());
 //                    }
                 }
-                requestChildrenUpdate();
+                updateChildren();
             }
 
             // Keep on drawing until the animation has finished.
@@ -705,7 +705,7 @@
 
     private void customScrollTo(int y) {
         mOwnScrollY = y;
-        requestChildrenUpdate();
+        updateChildren();
     }
 
     @Override
@@ -721,7 +721,7 @@
             if (clampedY) {
                 mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0, getScrollRange());
             }
-            requestChildrenUpdate();
+            updateChildren();
         } else {
             customScrollTo(scrollY);
             scrollTo(scrollX, mScrollY);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java
index 3281b67..2e700aa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java
@@ -132,10 +132,10 @@
         ValueAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_HEIGHT);
         long newDuration = cancelAnimatorAndGetNewDuration(previousAnimator, hasNewEvents);
         if (newDuration <= 0) {
-            if (previousAnimator == null) {
-                // no animation was running, but also no new animation should be performed,
-                // lets just apply the value
-                child.setActualHeight(viewState.height);
+            // no new animation needed, let's just apply the value
+            child.setActualHeight(viewState.height);
+            if (previousAnimator != null && !isRunning()) {
+                onAnimationFinished();
             }
             return;
         }
@@ -158,7 +158,7 @@
                 child.setTag(TAG_END_HEIGHT, null);
             }
         });
-        animator.start();
+        startInstantly(animator);
         child.setTag(TAG_ANIMATOR_HEIGHT, animator);
         child.setTag(TAG_END_HEIGHT, viewState.height);
     }
@@ -173,13 +173,13 @@
         ObjectAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_ALPHA);
         long newDuration = cancelAnimatorAndGetNewDuration(previousAnimator, hasNewEvents);
         if (newDuration <= 0) {
-            if (previousAnimator == null) {
-                // no animation was running, but also no new animation should be performed,
-                // lets just apply the value
-                child.setAlpha(endAlpha);
-                if (endAlpha == 0) {
-                    child.setVisibility(View.INVISIBLE);
-                }
+            // no new animation needed, let's just apply the value
+            child.setAlpha(endAlpha);
+            if (endAlpha == 0) {
+                child.setVisibility(View.INVISIBLE);
+            }
+            if (previousAnimator != null && !isRunning()) {
+                onAnimationFinished();
             }
             return;
         }
@@ -213,6 +213,7 @@
                 mWasCancelled = false;
             }
         });
+        animator.setDuration(newDuration);
         animator.addListener(getGlobalAnimationFinishedListener());
         // remove the tag when the animation is finished
         animator.addListener(new AnimatorListenerAdapter() {
@@ -221,11 +222,90 @@
 
             }
         });
-        animator.start();
+        startInstantly(animator);
         child.setTag(TAG_ANIMATOR_ALPHA, animator);
         child.setTag(TAG_END_ALPHA, endAlpha);
     }
 
+    private void startZTranslationAnimation(final ExpandableView child,
+            final StackScrollState.ViewState viewState, boolean hasNewEvents) {
+        Float previousEndValue = getChildTag(child,TAG_END_TRANSLATION_Z);
+        if (previousEndValue != null && previousEndValue == viewState.zTranslation) {
+            return;
+        }
+        ObjectAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_TRANSLATION_Z);
+        long newDuration = cancelAnimatorAndGetNewDuration(previousAnimator, hasNewEvents);
+        if (newDuration <= 0) {
+            // no new animation needed, let's just apply the value
+            child.setTranslationZ(viewState.zTranslation);
+
+            if (previousAnimator != null && !isRunning()) {
+                onAnimationFinished();
+            }
+            return;
+        }
+
+        ObjectAnimator animator = ObjectAnimator.ofFloat(child, View.TRANSLATION_Z,
+                child.getTranslationZ(), viewState.zTranslation);
+        animator.setInterpolator(mFastOutSlowInInterpolator);
+        animator.setDuration(newDuration);
+        animator.addListener(getGlobalAnimationFinishedListener());
+        // remove the tag when the animation is finished
+        animator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                child.setTag(TAG_ANIMATOR_TRANSLATION_Z, null);
+                child.setTag(TAG_END_TRANSLATION_Z, null);
+            }
+        });
+        startInstantly(animator);
+        child.setTag(TAG_ANIMATOR_TRANSLATION_Z, animator);
+        child.setTag(TAG_END_TRANSLATION_Z, viewState.zTranslation);
+    }
+
+    private void startYTranslationAnimation(final ExpandableView child,
+            StackScrollState.ViewState viewState, boolean hasNewEvents) {
+        Float previousEndValue = getChildTag(child,TAG_END_TRANSLATION_Y);
+        if (previousEndValue != null && previousEndValue == viewState.yTranslation) {
+            return;
+        }
+        ObjectAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_TRANSLATION_Y);
+        long newDuration = cancelAnimatorAndGetNewDuration(previousAnimator, hasNewEvents);
+        if (newDuration <= 0) {
+            // no new animation needed, let's just apply the value
+            child.setTranslationY(viewState.yTranslation);
+            if (previousAnimator != null && !isRunning()) {
+                onAnimationFinished();
+            }
+            return;
+        }
+
+        ObjectAnimator animator = ObjectAnimator.ofFloat(child, View.TRANSLATION_Y,
+                child.getTranslationY(), viewState.yTranslation);
+        animator.setInterpolator(mFastOutSlowInInterpolator);
+        animator.setDuration(newDuration);
+        animator.addListener(getGlobalAnimationFinishedListener());
+        // remove the tag when the animation is finished
+        animator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                child.setTag(TAG_ANIMATOR_TRANSLATION_Y, null);
+                child.setTag(TAG_END_TRANSLATION_Y, null);
+            }
+        });
+        startInstantly(animator);
+        child.setTag(TAG_ANIMATOR_TRANSLATION_Y, animator);
+        child.setTag(TAG_END_TRANSLATION_Y, viewState.yTranslation);
+    }
+
+    /**
+     * Start an animator instantly instead of waiting on the next synchronization frame
+     */
+    private void startInstantly(ValueAnimator animator) {
+        animator.start();
+        animator.setCurrentPlayTime(0);
+    }
+
     /**
      * @return an adapter which ensures that onAnimationFinished is called once no animation is
      *         running anymore
@@ -259,75 +339,6 @@
                 mWasCancelled = false;
             }
         };
-
-    }
-
-    private void startZTranslationAnimation(final ExpandableView child,
-            final StackScrollState.ViewState viewState, boolean hasNewEvents) {
-        Float previousEndValue = getChildTag(child,TAG_END_TRANSLATION_Z);
-        if (previousEndValue != null && previousEndValue == viewState.zTranslation) {
-            return;
-        }
-        ObjectAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_TRANSLATION_Z);
-        long newDuration = cancelAnimatorAndGetNewDuration(previousAnimator, hasNewEvents);
-        if (newDuration <= 0) {
-            if (previousAnimator == null) {
-                // no animation was running, but also no new animation should be performed,
-                // lets just apply the value
-                child.setTranslationZ(viewState.zTranslation);
-            }
-            return;
-        }
-
-        ObjectAnimator animator = ObjectAnimator.ofFloat(child, View.TRANSLATION_Z,
-                child.getTranslationZ(), viewState.zTranslation);
-        animator.setInterpolator(mFastOutSlowInInterpolator);
-        animator.addListener(getGlobalAnimationFinishedListener());
-        // remove the tag when the animation is finished
-        animator.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                child.setTag(TAG_ANIMATOR_TRANSLATION_Z, null);
-                child.setTag(TAG_END_TRANSLATION_Z, null);
-            }
-        });
-        animator.start();
-        child.setTag(TAG_ANIMATOR_TRANSLATION_Z, animator);
-        child.setTag(TAG_END_TRANSLATION_Z, viewState.zTranslation);
-    }
-
-    private void startYTranslationAnimation(final ExpandableView child,
-            StackScrollState.ViewState viewState, boolean hasNewEvents) {
-        Float previousEndValue = getChildTag(child,TAG_END_TRANSLATION_Y);
-        if (previousEndValue != null && previousEndValue == viewState.yTranslation) {
-            return;
-        }
-        ObjectAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_TRANSLATION_Y);
-        long newDuration = cancelAnimatorAndGetNewDuration(previousAnimator, hasNewEvents);
-        if (newDuration <= 0) {
-            if (previousAnimator == null) {
-                // no animation was running, but also no new animation should be performed,
-                // lets just apply the value
-                child.setTranslationY(viewState.yTranslation);
-            }
-            return;
-        }
-
-        ObjectAnimator animator = ObjectAnimator.ofFloat(child, View.TRANSLATION_Y,
-                child.getTranslationY(), viewState.yTranslation);
-        animator.setInterpolator(mFastOutSlowInInterpolator);
-        animator.addListener(getGlobalAnimationFinishedListener());
-        // remove the tag when the animation is finished
-        animator.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                child.setTag(TAG_ANIMATOR_TRANSLATION_Y, null);
-                child.setTag(TAG_END_TRANSLATION_Y, null);
-            }
-        });
-        animator.start();
-        child.setTag(TAG_ANIMATOR_TRANSLATION_Y, animator);
-        child.setTag(TAG_END_TRANSLATION_Y, viewState.yTranslation);
     }
 
     private <T> T getChildTag(View child, int tag) {
@@ -343,18 +354,19 @@
      */
     private long cancelAnimatorAndGetNewDuration(ValueAnimator previousAnimator,
             boolean hasNewEvents) {
+        long newDuration = ANIMATION_DURATION;
         if (previousAnimator != null) {
-            previousAnimator.cancel();
             if (!hasNewEvents) {
                 // This is only an update, no new event came in. lets just take the remaining
                 // duration as the new duration
-                return (long) ((1.0f - previousAnimator.getAnimatedFraction()) *
-                        previousAnimator.getDuration());
+                newDuration = previousAnimator.getDuration()
+                        - previousAnimator.getCurrentPlayTime();
             }
+            previousAnimator.cancel();
         } else if (!hasNewEvents){
-            return 0;
+            newDuration = 0;
         }
-        return ANIMATION_DURATION;
+        return newDuration;
     }
 
     private void onAnimationFinished() {