Add overscroll w/ physics to All Apps.

Bug: 62628421
Bug: 38349031

Change-Id: If3ba6dfbbd3a4b1c87e69df0066f801f963752aa
diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java
index 47b68a2..189b935 100644
--- a/src/com/android/launcher3/allapps/AllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java
@@ -220,7 +220,7 @@
         });
 
         // Load the all apps recycler view
-        mAppsRecyclerView = (AllAppsRecyclerView) findViewById(R.id.apps_list_view);
+        mAppsRecyclerView = findViewById(R.id.apps_list_view);
         mAppsRecyclerView.setApps(mApps);
         mAppsRecyclerView.setLayoutManager(mLayoutManager);
         mAppsRecyclerView.setAdapter(mAdapter);
diff --git a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
index 2b2fddc..a2bd43d 100644
--- a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
@@ -15,12 +15,14 @@
  */
 package com.android.launcher3.allapps;
 
+import android.animation.ObjectAnimator;
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.Canvas;
 import android.graphics.drawable.Drawable;
 import android.support.v7.widget.RecyclerView;
 import android.util.AttributeSet;
+import android.util.Property;
 import android.util.SparseIntArray;
 import android.view.MotionEvent;
 import android.view.View;
@@ -54,6 +56,22 @@
     private int mEmptySearchBackgroundTopOffset;
 
     private SpringAnimationHandler mSpringAnimationHandler;
+    private OverScrollHelper mOverScrollHelper;
+    private VerticalPullDetector mPullDetector;
+
+    private float mContentTranslationY = 0;
+    public static final Property<AllAppsRecyclerView, Float> CONTENT_TRANS_Y =
+            new Property<AllAppsRecyclerView, Float>(Float.class, "appsRecyclerViewContentTransY") {
+                @Override
+                public Float get(AllAppsRecyclerView allAppsRecyclerView) {
+                    return allAppsRecyclerView.getContentTranslationY();
+                }
+
+                @Override
+                public void set(AllAppsRecyclerView allAppsRecyclerView, Float y) {
+                    allAppsRecyclerView.setContentTranslationY(y);
+                }
+            };
 
     public AllAppsRecyclerView(Context context) {
         this(context, null);
@@ -74,6 +92,12 @@
         addOnItemTouchListener(this);
         mEmptySearchBackgroundTopOffset = res.getDimensionPixelSize(
                 R.dimen.all_apps_empty_search_bg_top_offset);
+
+        mOverScrollHelper = new OverScrollHelper();
+        mPullDetector = new VerticalPullDetector(getContext());
+        mPullDetector.setListener(mOverScrollHelper);
+        mPullDetector.setDetectableScrollConditions(VerticalPullDetector.DIRECTION_UP
+                | VerticalPullDetector.DIRECTION_DOWN, true);
     }
 
     public void setSpringAnimationHandler(SpringAnimationHandler springAnimationHandler) {
@@ -81,7 +105,14 @@
     }
 
     @Override
+    public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent ev) {
+        mPullDetector.onTouchEvent(ev);
+        return super.onInterceptTouchEvent(rv, ev) || mOverScrollHelper.isInOverScroll();
+    }
+
+    @Override
     public boolean onTouchEvent(MotionEvent e) {
+        mPullDetector.onTouchEvent(e);
         if (FeatureFlags.LAUNCHER3_PHYSICS && mSpringAnimationHandler != null) {
             mSpringAnimationHandler.addMovement(e);
         }
@@ -168,6 +199,8 @@
 
     @Override
     public void onDraw(Canvas c) {
+        c.translate(0, mContentTranslationY);
+
         // Draw the background
         if (mEmptySearchBackground != null && mEmptySearchBackground.getAlpha() > 0) {
             mEmptySearchBackground.draw(c);
@@ -176,6 +209,19 @@
         super.onDraw(c);
     }
 
+    public float getContentTranslationY() {
+        return mContentTranslationY;
+    }
+
+    /**
+     * Use this method instead of calling {@link #setTranslationY(float)}} directly to avoid drawing
+     * on top of other Views.
+     */
+    public void setContentTranslationY(float y) {
+        mContentTranslationY = y;
+        invalidate();
+    }
+
     @Override
     protected boolean verifyDrawable(Drawable who) {
         return who == mEmptySearchBackground || super.verifyDrawable(who);
@@ -434,4 +480,84 @@
                 y + mEmptySearchBackground.getIntrinsicHeight());
     }
 
+    private class OverScrollHelper implements VerticalPullDetector.Listener {
+
+        private static final float MAX_RELEASE_VELOCITY = 5000; // px / s
+        private static final float MAX_OVERSCROLL_PERCENTAGE = 0.07f;
+
+        private boolean mIsInOverScroll;
+
+        @Override
+        public void onDragStart(boolean start) {
+        }
+
+        @Override
+        public boolean onDrag(float displacement, float velocity) {
+            // We are in overscroll iff we are trying to drag further down when we're already at
+            // the bottom of All Apps.
+            mIsInOverScroll = !canScrollVertically(1) && displacement < 0;
+
+            if (mIsInOverScroll) {
+                displacement = getDampedOverScroll(displacement);
+                setContentTranslationY(displacement);
+            }
+            return mIsInOverScroll;
+        }
+
+        @Override
+        public void onDragEnd(float velocity, boolean fling) {
+            float y = getContentTranslationY();
+            if (mIsInOverScroll && Float.compare(y, 0) != 0) {
+                if (FeatureFlags.LAUNCHER3_PHYSICS) {
+                    // We calculate our own velocity to give the springs the desired effect.
+                    velocity = y / getDampedOverScroll(getHeight()) * MAX_RELEASE_VELOCITY;
+                    mSpringAnimationHandler.animateToPositionWithVelocity(0, -velocity);
+                }
+
+                ObjectAnimator.ofFloat(AllAppsRecyclerView.this,
+                        AllAppsRecyclerView.CONTENT_TRANS_Y, 0)
+                        .setDuration(100)
+                        .start();
+            }
+            mIsInOverScroll = false;
+        }
+
+        public boolean isInOverScroll() {
+            return mIsInOverScroll;
+        }
+
+        private float getDampedOverScroll(float y) {
+            return dampedOverScroll(y, getHeight()) * MAX_OVERSCROLL_PERCENTAGE;
+        }
+
+        /**
+         * This curve determines how the effect of scrolling over the limits of the page diminishes
+         * as the user pulls further and further from the bounds
+         *
+         * @param f The percentage of how much the user has overscrolled.
+         * @return A transformed percentage based on the influence curve.
+         */
+        private float overScrollInfluenceCurve(float f) {
+            f -= 1.0f;
+            return f * f * f + 1.0f;
+        }
+
+        /**
+         * @param amount The original amount overscrolled.
+         * @param max The maximum amount that the View can overscroll.
+         * @return The dampened overscroll amount.
+         */
+        private float dampedOverScroll(float amount, float max) {
+            float f = amount / max;
+            if (Float.compare(f, 0) == 0) return 0;
+            f = f / (Math.abs(f)) * (overScrollInfluenceCurve(Math.abs(f)));
+
+            // Clamp this factor, f, to -1 < f < 1
+            if (Math.abs(f) >= 1) {
+                f /= Math.abs(f);
+            }
+
+            return Math.round(f * max);
+        }
+    }
 }
diff --git a/src/com/android/launcher3/anim/SpringAnimationHandler.java b/src/com/android/launcher3/anim/SpringAnimationHandler.java
index 038f826..1efc4e4 100644
--- a/src/com/android/launcher3/anim/SpringAnimationHandler.java
+++ b/src/com/android/launcher3/anim/SpringAnimationHandler.java
@@ -127,6 +127,19 @@
         reset();
     }
 
+    /**
+     * Similar to {@link #animateToFinalPosition(float)}, but used in cases where we want to
+     * manually set the velocity.
+     */
+    public void animateToPositionWithVelocity(float position, float velocity) {
+        if (DEBUG) Log.d(TAG, "animateToPosition#velocity=" + velocity);
+
+        setStartVelocity(velocity);
+        mShouldComputeVelocity = false;
+        animateToFinalPosition(position);
+    }
+
+
     public boolean isRunning() {
         // All the animations run at the same time so we can just check the first one.
         return !mAnimations.isEmpty() && mAnimations.get(0).isRunning();
@@ -153,6 +166,8 @@
     }
 
     private void setStartVelocity(float velocity) {
+        if (DEBUG) Log.d(TAG, "setStartVelocity=" + velocity);
+
         int size = mAnimations.size();
         for (int i = 0; i < size; ++i) {
             mAnimations.get(i).setStartVelocity(velocity);