Improve quick settings fling/expand animation.

Change-Id: I507970ad2d22712a140f2107b62e689c2f3d7d6e
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/FlingAnimationUtils.java b/packages/SystemUI/src/com/android/systemui/statusbar/FlingAnimationUtils.java
new file mode 100644
index 0000000..0606a94
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/FlingAnimationUtils.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.statusbar;
+
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.view.animation.AnimationUtils;
+import android.view.animation.Interpolator;
+import android.view.animation.PathInterpolator;
+
+/**
+ * Utility class to calculate general fling animation when the finger is released.
+ */
+public class FlingAnimationUtils {
+
+    private static final float LINEAR_OUT_SLOW_IN_Y2 = 0.35f;
+    private static final float MAX_LENGTH_SECONDS = 0.4f;
+    private static final float MIN_VELOCITY_DP_PER_SECOND = 250;
+
+    /**
+     * Crazy math. http://en.wikipedia.org/wiki/B%C3%A9zier_curve
+     */
+    private static final float LINEAR_OUT_SLOW_IN_START_GRADIENT = 1/LINEAR_OUT_SLOW_IN_Y2;
+
+    private Interpolator mLinearOutSlowIn;
+    private Interpolator mFastOutSlowIn;
+    private float mMinVelocityPxPerSecond;
+
+    public FlingAnimationUtils(Context ctx) {
+        mLinearOutSlowIn = new PathInterpolator(0, 0, LINEAR_OUT_SLOW_IN_Y2, 1);
+        mFastOutSlowIn
+                = AnimationUtils.loadInterpolator(ctx, android.R.interpolator.fast_out_slow_in);
+        mMinVelocityPxPerSecond
+                = MIN_VELOCITY_DP_PER_SECOND * ctx.getResources().getDisplayMetrics().density;
+    }
+
+    /**
+     * Applies the interpolator and length to the animator, such that the fling animation is
+     * consistent with the finger motion.
+     *
+     * @param animator the animator to apply
+     * @param currValue the current value
+     * @param endValue the end value of the animator
+     * @param velocity the current velocity of the motion
+     */
+    public void apply(ValueAnimator animator, float currValue, float endValue, float velocity) {
+        float diff = Math.abs(endValue - currValue);
+        float velAbs = Math.abs(velocity);
+        float durationSeconds = LINEAR_OUT_SLOW_IN_START_GRADIENT * diff / velAbs;
+        if (durationSeconds <= MAX_LENGTH_SECONDS) {
+            animator.setInterpolator(mLinearOutSlowIn);
+        } else if (velAbs >= mMinVelocityPxPerSecond) {
+
+            // Cross fade between fast-out-slow-in and linear interpolator with current velocity.
+            durationSeconds = MAX_LENGTH_SECONDS;
+            VelocityInterpolator velocityInterpolator
+                    = new VelocityInterpolator(durationSeconds, velAbs, diff);
+            InterpolatorInterpolator superInterpolator = new InterpolatorInterpolator(
+                    velocityInterpolator, mLinearOutSlowIn, mLinearOutSlowIn);
+            animator.setInterpolator(superInterpolator);
+        } else {
+
+            // Just use a normal interpolator which doesn't take the velocity into account.
+            durationSeconds = MAX_LENGTH_SECONDS;
+            animator.setInterpolator(mFastOutSlowIn);
+        }
+        animator.setDuration((long) (durationSeconds * 1000));
+    }
+
+    /**
+     * An interpolator which interpolates two interpolators with an interpolator.
+     */
+    private static final class InterpolatorInterpolator implements Interpolator {
+
+        private Interpolator mInterpolator1;
+        private Interpolator mInterpolator2;
+        private Interpolator mCrossfader;
+
+        InterpolatorInterpolator(Interpolator interpolator1, Interpolator interpolator2,
+                Interpolator crossfader) {
+            mInterpolator1 = interpolator1;
+            mInterpolator2 = interpolator2;
+            mCrossfader = crossfader;
+        }
+
+        @Override
+        public float getInterpolation(float input) {
+            float t = mCrossfader.getInterpolation(input);
+            return (1 - t) * mInterpolator1.getInterpolation(input)
+                    + t * mInterpolator2.getInterpolation(input);
+        }
+    }
+
+    /**
+     * An interpolator which interpolates with a fixed velocity.
+     */
+    private static final class VelocityInterpolator implements Interpolator {
+
+        private float mDurationSeconds;
+        private float mVelocity;
+        private float mDiff;
+
+        private VelocityInterpolator(float durationSeconds, float velocity, float diff) {
+            mDurationSeconds = durationSeconds;
+            mVelocity = velocity;
+            mDiff = diff;
+        }
+
+        @Override
+        public float getInterpolation(float input) {
+            float time = input * mDurationSeconds;
+            return time * mVelocity / mDiff;
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
index 6132ed2..f19ce97 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -32,6 +32,7 @@
 
 import com.android.systemui.R;
 import com.android.systemui.statusbar.ExpandableView;
+import com.android.systemui.statusbar.FlingAnimationUtils;
 import com.android.systemui.statusbar.GestureRecorder;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
@@ -53,8 +54,6 @@
     private int mNotificationTopPadding;
     private boolean mAnimateNextTopPaddingChange;
 
-    private Interpolator mExpansionInterpolator;
-
     private int mTrackingPointer;
     private VelocityTracker mVelocityTracker;
     private boolean mTracking;
@@ -78,6 +77,7 @@
     private int mStackScrollerIntrinsicPadding;
     private boolean mQsExpansionEnabled = true;
     private ValueAnimator mQsExpansionAnimator;
+    private FlingAnimationUtils mFlingAnimationUtils;
 
     public NotificationPanelView(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -115,8 +115,7 @@
         mNotificationTopPadding = getResources().getDimensionPixelSize(
                 R.dimen.notifications_top_padding);
         mMinStackHeight = getResources().getDimensionPixelSize(R.dimen.collapsed_stack_height);
-        mExpansionInterpolator = AnimationUtils.loadInterpolator(
-                getContext(), android.R.interpolator.fast_out_slow_in);
+        mFlingAnimationUtils = new FlingAnimationUtils(getContext());
     }
 
     @Override
@@ -435,13 +434,9 @@
         }
     }
     private void flingSettings(float vel, boolean expand) {
-
-        // TODO: Actually use velocity.
-
         float target = expand ? mQsMaxExpansionHeight : mQsMinExpansionHeight;
         ValueAnimator animator = ValueAnimator.ofFloat(mQsExpansionHeight, target);
-        animator.setDuration(EXPANSION_ANIMATION_LENGTH);
-        animator.setInterpolator(mExpansionInterpolator);
+        mFlingAnimationUtils.apply(animator, mQsExpansionHeight, target, vel);
         animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
             @Override
             public void onAnimationUpdate(ValueAnimator animation) {