Fixing issue with touch scrolling the prediction bar.

Change-Id: Ie15ca96e5ea33a54508285aa76fb6aea81b2376d
diff --git a/src/com/android/launcher3/AppsContainerView.java b/src/com/android/launcher3/AppsContainerView.java
index 692c23f..57ccff0 100644
--- a/src/com/android/launcher3/AppsContainerView.java
+++ b/src/com/android/launcher3/AppsContainerView.java
@@ -34,7 +34,6 @@
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputMethodManager;
 import android.widget.FrameLayout;
-import android.widget.LinearLayout;
 import android.widget.TextView;
 import com.android.launcher3.util.Thunk;
 
@@ -82,8 +81,12 @@
 
     private int mNumAppsPerRow;
     private int mNumPredictedAppsPerRow;
-    private Point mLastTouchDownPos = new Point(-1, -1);
-    private Point mLastTouchPos = new Point();
+    // This coordinate is relative to this container view
+    private Point mBoundsCheckLastTouchDownPos = new Point(-1, -1);
+    // This coordinate is relative to its parent
+    private Point mIconLastTouchPos = new Point();
+    // This coordinate is used to proxy click and long-click events
+    private Point mPredictionIconTouchDownPos = new Point();
     private int mContentMarginStart;
     // Normal container insets
     private int mContainerInset;
@@ -91,6 +94,9 @@
     // RecyclerView scroll position
     @Thunk int mRecyclerViewScrollY;
 
+    private CheckLongPressHelper mPredictionIconCheckForLongPress;
+    private View mPredictionIconUnderTouch;
+
     public AppsContainerView(Context context) {
         this(context, null);
     }
@@ -283,9 +289,6 @@
                 // Otherwise, inflate a new icon
                 icon = (BubbleTextView) mLayoutInflater.inflate(
                         R.layout.apps_prediction_bar_icon_view, mPredictionBarView, false);
-                icon.setOnTouchListener(this);
-                icon.setOnClickListener(mLauncher);
-                icon.setOnLongClickListener(this);
                 icon.setFocusable(true);
                 mPredictionBarView.addView(icon);
             }
@@ -401,7 +404,7 @@
         switch (ev.getAction()) {
             case MotionEvent.ACTION_DOWN:
             case MotionEvent.ACTION_MOVE:
-                mLastTouchPos.set((int) ev.getX(), (int) ev.getY());
+                mIconLastTouchPos.set((int) ev.getX(), (int) ev.getY());
                 break;
         }
         return false;
@@ -427,7 +430,7 @@
         if (!mLauncher.isDraggingEnabled()) return false;
 
         // Start the drag
-        mLauncher.getWorkspace().beginDragShared(v, mLastTouchPos, this, false);
+        mLauncher.getWorkspace().beginDragShared(v, mIconLastTouchPos, this, false);
         // Enter spring loaded mode
         mLauncher.enterSpringLoadedDragMode();
 
@@ -626,6 +629,16 @@
         mPredictionBarView.setTranslationY(-mRecyclerViewScrollY + mAppsRecyclerView.getPaddingTop());
     }
 
+    @Override
+    public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
+        // If we were waiting for long-click, cancel the request once a child has started handling
+        // the scrolling
+        if (mPredictionIconCheckForLongPress != null) {
+            mPredictionIconCheckForLongPress.cancelLongPress();
+        }
+        super.requestDisallowInterceptTouchEvent(disallowIntercept);
+    }
+
     /**
      * Handles the touch events to dismiss all apps when clicking outside the bounds of the
      * recycler view.
@@ -633,31 +646,44 @@
     private boolean handleTouchEvent(MotionEvent ev) {
         LauncherAppState app = LauncherAppState.getInstance();
         DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
+        int x = (int) ev.getX();
+        int y = (int) ev.getY();
 
         switch (ev.getAction()) {
             case MotionEvent.ACTION_DOWN:
+                // We workaround the fact that the recycler view needs the touches for the scroll
+                // and we want to intercept it for clicks in the prediction bar by handling clicks
+                // and long clicks in the prediction bar ourselves.
+                mPredictionIconTouchDownPos.set(x, y);
+                mPredictionIconUnderTouch = findPredictedAppAtCoordinate(x, y);
+                if (mPredictionIconUnderTouch != null) {
+                    mPredictionIconCheckForLongPress =
+                            new CheckLongPressHelper(mPredictionIconUnderTouch, this);
+                    mPredictionIconCheckForLongPress.postCheckForLongPress();
+                }
+
                 if (!mFixedBounds.isEmpty()) {
                     // Outset the fixed bounds and check if the touch is outside all apps
                     Rect tmpRect = new Rect(mFixedBounds);
                     tmpRect.inset(-grid.allAppsIconSizePx / 2, 0);
                     if (ev.getX() < tmpRect.left || ev.getX() > tmpRect.right) {
-                        mLastTouchDownPos.set((int) ev.getX(), (int) ev.getY());
+                        mBoundsCheckLastTouchDownPos.set(x, y);
                         return true;
                     }
                 } else {
                     // Check if the touch is outside all apps
                     if (ev.getX() < getPaddingLeft() ||
                             ev.getX() > (getWidth() - getPaddingRight())) {
-                        mLastTouchDownPos.set((int) ev.getX(), (int) ev.getY());
+                        mBoundsCheckLastTouchDownPos.set(x, y);
                         return true;
                     }
                 }
                 break;
             case MotionEvent.ACTION_UP:
-                if (mLastTouchDownPos.x > -1) {
+                if (mBoundsCheckLastTouchDownPos.x > -1) {
                     ViewConfiguration viewConfig = ViewConfiguration.get(getContext());
-                    float dx = ev.getX() - mLastTouchDownPos.x;
-                    float dy = ev.getY() - mLastTouchDownPos.y;
+                    float dx = ev.getX() - mBoundsCheckLastTouchDownPos.x;
+                    float dy = ev.getY() - mBoundsCheckLastTouchDownPos.y;
                     float distance = (float) Math.hypot(dx, dy);
                     if (distance < viewConfig.getScaledTouchSlop()) {
                         // The background was clicked, so just go home
@@ -666,15 +692,49 @@
                         return true;
                     }
                 }
+
+                // Trigger the click on the prediction bar icon if that's where we touched
+                if (mPredictionIconUnderTouch != null &&
+                        !mPredictionIconCheckForLongPress.hasPerformedLongPress()) {
+                    mLauncher.onClick(mPredictionIconUnderTouch);
+                }
+
                 // Fall through
             case MotionEvent.ACTION_CANCEL:
-                mLastTouchDownPos.set(-1, -1);
+                mBoundsCheckLastTouchDownPos.set(-1, -1);
+                mPredictionIconTouchDownPos.set(-1, -1);
+
+                // On touch up/cancel, cancel the long press on the prediction bar icon if it has
+                // not yet been performed
+                if (mPredictionIconCheckForLongPress != null) {
+                    mPredictionIconCheckForLongPress.cancelLongPress();
+                    mPredictionIconCheckForLongPress = null;
+                }
+                mPredictionIconUnderTouch = null;
+
                 break;
         }
         return false;
     }
 
     /**
+     * Returns the predicted app in the prediction bar given a set of local coordinates.
+     */
+    private View findPredictedAppAtCoordinate(int x, int y) {
+        int[] coord = {x, y};
+        Rect hitRect = new Rect();
+        Utilities.mapCoordInSelfToDescendent(mPredictionBarView, this, coord);
+        for (int i = 0; i < mPredictionBarView.getChildCount(); i++) {
+            View child = mPredictionBarView.getChildAt(i);
+            child.getHitRect(hitRect);
+            if (hitRect.contains(coord[0], coord[1])) {
+                return child;
+            }
+        }
+        return null;
+    }
+
+    /**
      * Shows the search field.
      */
     private void showSearchField() {
diff --git a/src/com/android/launcher3/CheckLongPressHelper.java b/src/com/android/launcher3/CheckLongPressHelper.java
index 10ca6a3..381b678 100644
--- a/src/com/android/launcher3/CheckLongPressHelper.java
+++ b/src/com/android/launcher3/CheckLongPressHelper.java
@@ -22,6 +22,7 @@
 
 public class CheckLongPressHelper {
     @Thunk View mView;
+    @Thunk View.OnLongClickListener mListener;
     @Thunk boolean mHasPerformedLongPress;
     private CheckForLongPress mPendingCheckForLongPress;
 
@@ -29,7 +30,13 @@
         public void run() {
             if ((mView.getParent() != null) && mView.hasWindowFocus()
                     && !mHasPerformedLongPress) {
-                if (mView.performLongClick()) {
+                boolean handled;
+                if (mListener != null) {
+                    handled = mListener.onLongClick(mView);
+                } else {
+                    handled = mView.performLongClick();
+                }
+                if (handled) {
                     mView.setPressed(false);
                     mHasPerformedLongPress = true;
                 }
@@ -41,6 +48,11 @@
         mView = v;
     }
 
+    public CheckLongPressHelper(View v, View.OnLongClickListener listener) {
+        mView = v;
+        mListener = listener;
+    }
+
     public void postCheckForLongPress() {
         mHasPerformedLongPress = false;
 
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index dc6de07..890b9c9 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -443,7 +443,7 @@
         int availableAppsWidthPx = (containerWidth > 0) ? containerWidth : availableWidthPx;
         int numAppsCols = (availableAppsWidthPx - appsViewLeftMarginPx) /
                 (allAppsCellWidthPx + 2 * allAppsCellPaddingPx);
-        int numPredictiveAppCols = isPhone() ? numColumns : numAppsCols;
+        int numPredictiveAppCols = Math.max(numColumns, numAppsCols);
         if ((numAppsCols != appsViewNumCols) ||
                 (numPredictiveAppCols != appsViewNumPredictiveCols)) {
             appsViewNumCols = numAppsCols;