All apps container should not intercept touch during animation.

b/28917826
> When all apps container is settling near either the bottom or top
end, touch event should not be intercepted.

Change-Id: I8aee6cea52e844c4615b92b15f8ef7247236a25b
diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
index d70ee47..7f047d5 100644
--- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java
+++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
@@ -54,6 +54,8 @@
     private float mProgressTransY;   // numerator
     private float mTranslation = -1; // denominator
 
+    private static final float RECATCH_REJECTION_FRACTION = .0875f;
+
     private long mAnimationDuration;
     private float mCurY;
 
@@ -73,19 +75,19 @@
             mNoIntercept = false;
             if (mLauncher.getWorkspace().isInOverviewMode() || mLauncher.isWidgetsViewVisible()) {
                 mNoIntercept = true;
-            }
-            if (mLauncher.isAllAppsVisible() &&
+            } else if (mLauncher.isAllAppsVisible() &&
                     !mAppsView.shouldContainerScroll(ev.getX(), ev.getY())) {
                 mNoIntercept = true;
+            } else {
+                mDetector.setDetectableScrollConditions(mLauncher.isAllAppsVisible() /* down */,
+                        isInDisallowRecatchTopZone(), isInDisallowRecatchBottomZone());
             }
         }
         if (mNoIntercept) {
             return false;
-        } else {
-            mDetector.setScrollDirectionDown(mLauncher.isAllAppsVisible());
         }
         mDetector.onTouchEvent(ev);
-        return mDetector.mScrolling;
+        return mDetector.shouldIntercept();
     }
 
     @Override
@@ -93,6 +95,14 @@
         return mDetector.onTouchEvent(ev);
     }
 
+    private boolean isInDisallowRecatchTopZone() {
+        return mProgressTransY / mTranslation < RECATCH_REJECTION_FRACTION;
+    }
+
+    private boolean isInDisallowRecatchBottomZone() {
+        return mProgressTransY / mTranslation > 1 - RECATCH_REJECTION_FRACTION;
+    }
+
     private void init() {
         if (mAppsView != null) {
             return;
@@ -267,7 +277,7 @@
         if ((mAppsView = mLauncher.getAppsView()) == null || animationOut == null){
             return;
         }
-        if (!mDetector.mScrolling) {
+        if (mDetector.isRestingState()) {
             preparePull(true);
             mAnimationDuration = duration;
         }
@@ -311,7 +321,7 @@
         if ((mAppsView = mLauncher.getAppsView()) == null || animationOut == null){
             return;
         }
-        if(!mDetector.mScrolling) {
+        if(mDetector.isRestingState()) {
             preparePull(true);
             mAnimationDuration = duration;
         }
diff --git a/src/com/android/launcher3/allapps/VerticalPullDetector.java b/src/com/android/launcher3/allapps/VerticalPullDetector.java
index 0c3698d..7df63e0 100644
--- a/src/com/android/launcher3/allapps/VerticalPullDetector.java
+++ b/src/com/android/launcher3/allapps/VerticalPullDetector.java
@@ -15,6 +15,8 @@
 
     private float mTouchSlop;
     private boolean mScrollDown; // if false, only scroll up will be reported.
+    private boolean mDisallowRecatchFromTop;
+    private boolean mDisallowRecatchFromBottom;
 
     /**
      * The minimum release velocity in pixels per millisecond that triggers fling..
@@ -28,7 +30,23 @@
     public static final float SCROLL_VELOCITY_DAMPENING_RC = 1000f / (2f * (float) Math.PI * 10);
 
     /* Scroll state, this is set to true during dragging and animation. */
-    boolean mScrolling;
+    private State mState = State.NONE;
+    enum State {NONE, DRAG, SCROLLING};
+
+    private void setState(State newState) {
+        if (DBG) {
+            Log.d(TAG, mState + "->" + newState);
+        }
+        mState = newState;
+    }
+
+    public boolean shouldIntercept() {
+        return mState == State.DRAG;
+    }
+
+    public boolean isRestingState() {
+        return mState == State.NONE;
+    }
 
     private float mDownX;
     private float mDownY;
@@ -53,9 +71,6 @@
     }
 
     interface Listener{
-        /**
-         * @param start when should
-         */
         void onScrollStart(boolean start);
         boolean onScroll(float displacement, float velocity);
         void onScrollEnd(float velocity, boolean fling);
@@ -65,8 +80,11 @@
         mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
     }
 
-    public void setScrollDirectionDown(boolean scrollDown) {
+    public void setDetectableScrollConditions(boolean scrollDown, boolean disallowRecatchFromTop,
+            boolean disallowRecatchFromBottom) {
         mScrollDown = scrollDown;
+        mDisallowRecatchFromTop = disallowRecatchFromTop;
+        mDisallowRecatchFromBottom = disallowRecatchFromBottom;
     }
 
     private boolean shouldScrollStart() {
@@ -85,6 +103,21 @@
         return false;
     }
 
+    private boolean shouldRecatchScrollStart() {
+        if (!mDisallowRecatchFromBottom && !mDisallowRecatchFromTop) {
+            return true;
+        }
+        if (mDisallowRecatchFromTop && mDisplacementY > mTouchSlop) {
+            mDisallowRecatchFromTop = false;
+            return true;
+        }
+        if (mDisallowRecatchFromBottom && mDisplacementY < -mTouchSlop) {
+            mDisallowRecatchFromBottom = false;
+            return true;
+        }
+        return false;
+    }
+
     public boolean onTouchEvent(MotionEvent ev) {
         switch (ev.getAction()) {
             case MotionEvent.ACTION_DOWN:
@@ -93,8 +126,7 @@
                 mDownY = ev.getY();
                 mLastDisplacement = 0;
                 mVelocity = 0;
-
-                if (mScrolling) {
+                if (mState == State.SCROLLING && shouldRecatchScrollStart()){
                     reportScrollStart(true /* recatch */);
                 }
                 break;
@@ -103,18 +135,22 @@
                 mDisplacementY = ev.getY() - mDownY;
                 mVelocity = computeVelocity(ev, mVelocity);
 
-                if (!mScrolling && shouldScrollStart()) {
-                    mScrolling = true;
+                if (mState == State.SCROLLING && shouldRecatchScrollStart()){
+                    setState(State.DRAG);
+                    reportScrollStart(true /* recatch */);
+                }
+                if (mState == State.NONE && shouldScrollStart()) {
+                    setState(State.DRAG);
                     reportScrollStart(false /* recatch */);
                 }
-                if (mScrolling && mListener != null) {
+                if (mState == State.DRAG && mListener != null) {
                     reportScroll();
                 }
                 break;
             case MotionEvent.ACTION_CANCEL:
             case MotionEvent.ACTION_UP:
                 // These are synthetic events and there is no need to update internal values.
-                if (mScrolling && mListener != null) {
+                if (mState == State.DRAG && mListener != null) {
                     reportScrollEnd();
                 }
                 break;
@@ -132,7 +168,7 @@
     }
 
     public void finishedScrolling() {
-        mScrolling = false;
+        setState(State.NONE);
     }
 
     private boolean reportScrollStart(boolean recatch) {
@@ -170,6 +206,7 @@
                     mDisplacementY, mVelocity));
         }
         mListener.onScrollEnd(mVelocity, Math.abs(mVelocity) > RELEASE_VELOCITY_PX_MS);
+        setState(State.SCROLLING);
     }
     /**
      * Computes the damped velocity using the two motion events and the previous velocity.
@@ -187,10 +224,6 @@
         return interpolate(previousVelocity, velocity, alpha);
     }
 
-    private float computeDisplacement(MotionEvent to) {
-        return to.getY() - mDownY;
-    }
-
     private float computeDelta(MotionEvent to) {
         return to.getY() - mLastY;
     }