Add overscroll to the top of All Apps.

* Overscroll at the top of all apps will occur when the user scrolls
  up, hits the top, and continues to scroll up.
* Fixed bug where All Apps jumps when the user enters overscroll
  from a scroll that doesn't start at the bottom.
* Fix bug where AllAppsRecyclerView stays translated even after
  the user has finished dragging.

Bug: 62628421
Change-Id: Ia1d230a7cc07a7cf8c1a7c5211a025034ae5f6df
diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java
index 4fb0b86..ccef4f2 100644
--- a/src/com/android/launcher3/allapps/AllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java
@@ -410,22 +410,19 @@
 
         @Override
         public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
-            if (mScrollState == RecyclerView.SCROLL_STATE_DRAGGING
-                    || (dx == 0 && dy == 0)) {
+            if (mScrollState == RecyclerView.SCROLL_STATE_DRAGGING || (dx == 0 && dy == 0)) {
                 if (mSpringAnimationHandler.isRunning()){
                     mSpringAnimationHandler.skipToEnd();
                 }
                 return;
             }
 
-            int first = mLayoutManager.findFirstVisibleItemPosition();
-            int last = mLayoutManager.findLastVisibleItemPosition();
-
-            // We only show the spring animation when at the top or bottom, so we wait until the
-            // first or last row is visible to ensure that all animations run in sync.
-            boolean scrollUp = dy < 0;
-            if ((first == 0 && scrollUp) || (last == mAdapter.getItemCount() - 1 && dy > 0)) {
-                mSpringAnimationHandler.animateToFinalPosition(0, scrollUp ? 1 : -1);
+            // We only start the spring animation when we fling and hit the top/bottom, to ensure
+            // that all of the animations start at the same time.
+            if (dy < 0 && !mAppsRecyclerView.canScrollVertically(-1)) {
+                mSpringAnimationHandler.animateToFinalPosition(0, 1);
+            } else if (dy > 0 && !mAppsRecyclerView.canScrollVertically(1)) {
+                mSpringAnimationHandler.animateToFinalPosition(0, -1);
             }
         }
 
diff --git a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
index ff8de88..fb785fb 100644
--- a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
@@ -96,8 +96,7 @@
         mOverScrollHelper = new OverScrollHelper();
         mPullDetector = new VerticalPullDetector(getContext());
         mPullDetector.setListener(mOverScrollHelper);
-        mPullDetector.setDetectableScrollConditions(VerticalPullDetector.DIRECTION_UP
-                | VerticalPullDetector.DIRECTION_DOWN, true);
+        mPullDetector.setDetectableScrollConditions(VerticalPullDetector.DIRECTION_BOTH, true);
     }
 
     public void setSpringAnimationHandler(SpringAnimationHandler springAnimationHandler) {
@@ -487,28 +486,53 @@
 
         private boolean mIsInOverScroll;
 
+        // We use this value to calculate the actual amount the user has overscrolled.
+        private float mFirstDisplacement = 0;
+
+        private boolean mAlreadyScrollingUp;
+        private int mFirstScrollYOnScrollUp;
+
         @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
-                    && !mScrollbar.isDraggingThumb();
+            boolean isScrollingUp = displacement > 0;
+            if (isScrollingUp) {
+                if (!mAlreadyScrollingUp) {
+                    mFirstScrollYOnScrollUp = getCurrentScrollY();
+                    mAlreadyScrollingUp = true;
+                }
+            } else {
+                mAlreadyScrollingUp = false;
+            }
+
+            // Only enter overscroll if the user is interacting with the RecyclerView directly
+            // and if one of the following criteria are met:
+            // - User scrolls down when they're already at the bottom.
+            // - User starts scrolling up, hits the top, and continues scrolling up.
+            mIsInOverScroll = !mScrollbar.isDraggingThumb() &&
+                    ((!canScrollVertically(1) && displacement < 0) ||
+                    (!canScrollVertically(-1) && isScrollingUp && mFirstScrollYOnScrollUp != 0));
 
             if (mIsInOverScroll) {
-                displacement = getDampedOverScroll(displacement);
-                setContentTranslationY(displacement);
+                if (Float.compare(mFirstDisplacement, 0) == 0) {
+                    // Because users can scroll before entering overscroll, we need to
+                    // subtract the amount where the user was not in overscroll.
+                    mFirstDisplacement = displacement;
+                }
+                float overscrollY = displacement - mFirstDisplacement;
+                setContentTranslationY(getDampedOverScroll(overscrollY));
             }
+
             return mIsInOverScroll;
         }
 
         @Override
         public void onDragEnd(float velocity, boolean fling) {
             float y = getContentTranslationY();
-            if (mIsInOverScroll && Float.compare(y, 0) != 0) {
+            if (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;
@@ -523,6 +547,9 @@
                         .start();
             }
             mIsInOverScroll = false;
+            mFirstDisplacement = 0;
+            mFirstScrollYOnScrollUp = 0;
+            mAlreadyScrollingUp = false;
         }
 
         public boolean isInOverScroll() {