am 52f52702: Merge "Realtime drop area" into klp-dev

* commit '52f52702dac55c0fde27d907ac9ebb21fc707c68':
  Realtime drop area
diff --git a/src/com/android/dialer/list/PhoneFavoriteDragAndDropListeners.java b/src/com/android/dialer/list/PhoneFavoriteDragAndDropListeners.java
index a976ead..f0e97ac 100644
--- a/src/com/android/dialer/list/PhoneFavoriteDragAndDropListeners.java
+++ b/src/com/android/dialer/list/PhoneFavoriteDragAndDropListeners.java
@@ -48,6 +48,40 @@
             mTileAdapter = tileAdapter;
         }
 
+        /**
+         * @return The item index in {@link #mTileAdapter} for the given {@link DragEvent}.
+         *     Returns -1 if {@link #mTileAdapter} is not in dragging or index can not be found.
+         */
+        private int getDragItemIndex(DragEvent event) {
+            int itemIndex = -1;
+            if (mTileAdapter != null && mContactTileRow != null
+                    && !mTileAdapter.getInDragging()) {
+                mX = event.getX();
+                mY = event.getY();
+                if (DEBUG) {
+                    Log.v(TAG, String.valueOf(mX) + "; " + String.valueOf(mY));
+                }
+
+                final int[] rowLocation = new int[2];
+                mContactTileRow.getLocationOnScreen(rowLocation);
+
+                final Rect locationRect = new Rect(rowLocation[0], rowLocation[1],
+                        rowLocation[0] + mContactTileRow.getWidth(),
+                        rowLocation[1] + mContactTileRow.getHeight());
+
+                if (locationRect.contains((int) mX, (int) mY)) {
+                    // Finds out which item is being dragged.
+                    // Computes relative coordinates as we get absolute coordinates.
+                    itemIndex = mContactTileRow.getItemIndex(
+                            mX - rowLocation[0], mY - rowLocation[1]);
+                    if (DEBUG) {
+                        Log.v(TAG, "Start dragging " + String.valueOf(itemIndex));
+                    }
+                }
+            }
+            return itemIndex;
+        }
+
         @Override
         public boolean onDrag(View v, DragEvent event) {
             if (DEBUG) {
@@ -56,35 +90,13 @@
             // Handles drag events.
             switch (event.getAction()) {
                 case DragEvent.ACTION_DRAG_STARTED:
-                    if (mTileAdapter != null && mContactTileRow != null
-                            && !mTileAdapter.getInDragging()) {
-                        mX = event.getX();
-                        mY = event.getY();
-                        if (DEBUG) {
-                            Log.v(TAG, String.valueOf(mX) + "; " + String.valueOf(mY));
-                        }
+                    final int itemIndex = getDragItemIndex(event);
+                    if (itemIndex != -1) {
+                        // Indicates a drag has started.
+                        mTileAdapter.setInDragging(true);
 
-                        final int[] rowLocation = new int[2];
-                        mContactTileRow.getLocationOnScreen(rowLocation);
-
-                        final Rect locationRect = new Rect(rowLocation[0], rowLocation[1],
-                                rowLocation[0] + mContactTileRow.getWidth(),
-                                rowLocation[1] + mContactTileRow.getHeight());
-
-                        if (locationRect.contains((int) mX, (int) mY)) {
-                            // Finds out which item is being dragged.
-                            // Computes relative coordinates as we get absolute coordinates.
-                            final int dragIndex = mContactTileRow.getItemIndex(
-                                    mX - rowLocation[0], mY - rowLocation[1]);
-                            if (DEBUG) {
-                                Log.v(TAG, "Start dragging " + String.valueOf(dragIndex));
-                            }
-                            // Indicates a drag has started.
-                            mTileAdapter.setInDragging(true);
-
-                            // Temporarily pops out the Contact entry.
-                            mTileAdapter.popContactEntry(dragIndex);
-                        }
+                        // Temporarily pops out the Contact entry.
+                        mTileAdapter.popContactEntry(itemIndex);
                     }
                     break;
                 case DragEvent.ACTION_DRAG_ENTERED:
@@ -92,33 +104,21 @@
                 case DragEvent.ACTION_DRAG_EXITED:
                     break;
                 case DragEvent.ACTION_DROP:
-                    mX = event.getX();
-                    mY = event.getY();
-                    if (DEBUG) {
-                        Log.v(TAG, String.valueOf(mX) + "; " + String.valueOf(mY));
-                    }
-
                     // Indicates a drag has finished.
                     if (mTileAdapter != null && mContactTileRow != null) {
                         mTileAdapter.setInDragging(false);
-
-                        // Finds out at which position of the list the Contact is being dropped.
-                        final int dropIndex = mContactTileRow.getItemIndex(mX, mY);
-                        if (DEBUG) {
-                            Log.v(TAG, "Stop dragging " + String.valueOf(dropIndex));
-                        }
-
-                        // Adds the dragged contact to the drop position.
-                        mTileAdapter.dropContactEntry(dropIndex);
+                        // The drop to position has been reported to the adapter
+                        // via {@link DragEvent#ACTION_DRAG_LOCATION} events in ListView.
+                        mTileAdapter.handleDrop();
                     }
                     break;
                 case DragEvent.ACTION_DRAG_ENDED:
-                    if (mTileAdapter.getInDragging()) {
+                    if (mTileAdapter != null && mTileAdapter.getInDragging()) {
                         // If the drag and drop ends when the drop happens outside of any rows,
                         // we will end the drag here and put the item back to where it was dragged
                         // from before.
                         mTileAdapter.setInDragging(false);
-                        mTileAdapter.dropToUnsupportedView();
+                        mTileAdapter.handleDrop();
                     }
                     break;
                 case DragEvent.ACTION_DRAG_LOCATION:
diff --git a/src/com/android/dialer/list/PhoneFavoriteListView.java b/src/com/android/dialer/list/PhoneFavoriteListView.java
index b5da054..00f645a 100644
--- a/src/com/android/dialer/list/PhoneFavoriteListView.java
+++ b/src/com/android/dialer/list/PhoneFavoriteListView.java
@@ -25,7 +25,6 @@
 import android.view.DragEvent;
 import android.view.MotionEvent;
 import android.view.View;
-import android.view.View.OnDragListener;
 import android.view.ViewConfiguration;
 import android.widget.ListView;
 
@@ -40,8 +39,7 @@
  * - Swiping, which is borrowed from packages/apps/UnifiedEmail (com.android.mail.ui.Swipeable)
  * - Drag and drop
  */
-public class PhoneFavoriteListView extends ListView implements
-        SwipeHelperCallback, OnDragListener {
+public class PhoneFavoriteListView extends ListView implements SwipeHelperCallback {
 
     public static final String LOG_TAG = PhoneFavoriteListView.class.getSimpleName();
 
@@ -61,6 +59,9 @@
     private final long SCROLL_HANDLER_DELAY_MILLIS = 5;
     private final int DRAG_SCROLL_PX_UNIT = 10;
 
+    private boolean mIsDragScrollerRunning = false;
+    private int mTouchDownForDragStartY;
+
     /**
      * {@link #mTopScrollBound} and {@link mBottomScrollBound} will be
      * offseted to the top / bottom by {@link #getHeight} * {@link #BOUND_GAP_RATIO} pixels.
@@ -94,7 +95,6 @@
         mSwipeHelper = new SwipeHelper(context, SwipeHelper.X, this,
                 mDensityScale, mTouchSlop);
         setItemsCanFocus(true);
-        setOnDragListener(this);
     }
 
     @Override
@@ -123,6 +123,9 @@
 
     @Override
     public boolean onInterceptTouchEvent(MotionEvent ev) {
+        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+            mTouchDownForDragStartY = (int) ev.getY();
+        }
         if (isSwipeEnabled()) {
             return mSwipeHelper.onInterceptTouchEvent(ev) || super.onInterceptTouchEvent(ev);
         } else {
@@ -189,8 +192,7 @@
     }
 
     @Override
-    public void onDragCancelled(View v) {
-    }
+    public void onDragCancelled(View v) {}
 
     @Override
     public void onBeginDrag(View v) {
@@ -205,11 +207,16 @@
     public boolean dispatchDragEvent(DragEvent event) {
         switch (event.getAction()) {
             case DragEvent.ACTION_DRAG_LOCATION:
-                if (mScrollHandler == null) {
-                    mScrollHandler = getHandler();
-                }
                 mLastDragY = (int) event.getY();
-                mScrollHandler.postDelayed(mDragScroller, SCROLL_HANDLER_DELAY_MILLIS);
+                handleDrag((int) event.getX(), mLastDragY);
+                // Kick off {@link #mScrollHandler} if it's not started yet.
+                if (!mIsDragScrollerRunning &&
+                        // And if the distance traveled while dragging exceeds the touch slop
+                        (Math.abs(mLastDragY - mTouchDownForDragStartY) >= 4 * mTouchSlop)) {
+                    mIsDragScrollerRunning = true;
+                    ensureScrollHandler();
+                    mScrollHandler.postDelayed(mDragScroller, SCROLL_HANDLER_DELAY_MILLIS);
+                }
                 break;
             case DragEvent.ACTION_DRAG_ENTERED:
                 final int boundGap = (int) (getHeight() * BOUND_GAP_RATIO);
@@ -218,20 +225,48 @@
                 break;
             case DragEvent.ACTION_DRAG_EXITED:
             case DragEvent.ACTION_DRAG_ENDED:
+            case DragEvent.ACTION_DROP:
+                ensureScrollHandler();
                 mScrollHandler.removeCallbacks(mDragScroller);
+                mIsDragScrollerRunning = false;
                 break;
             case DragEvent.ACTION_DRAG_STARTED:
                 // Not a receiver
-            case DragEvent.ACTION_DROP:
-                // Not a receiver
             default:
                 break;
         }
         return super.dispatchDragEvent(event);
     }
 
-    @Override
-    public boolean onDrag(View v, DragEvent event) {
-        return true;
+    private void ensureScrollHandler() {
+        if (mScrollHandler == null) {
+            mScrollHandler = getHandler();
+        }
+    }
+
+    private void handleDrag(int x, int y) {
+        // find the view under the pointer, accounting for GONE views
+        final int count = getChildCount();
+        View slidingChild;
+        for (int childIdx = 0; childIdx < count; childIdx++) {
+            slidingChild = getChildAt(childIdx);
+            if (slidingChild.getVisibility() == GONE) {
+                continue;
+            }
+            if (y >= slidingChild.getTop() &&
+                    y <= slidingChild.getBottom() &&
+                    slidingChild instanceof ContactTileRow) {
+                final ContactTileRow tile = (ContactTileRow) slidingChild;
+                reportDragEnteredItemIndex(tile.getItemIndex(x, y));
+            }
+        }
+    }
+
+    private void reportDragEnteredItemIndex(int itemIndex) {
+        final PhoneFavoriteMergedAdapter adapter =
+                (PhoneFavoriteMergedAdapter) getAdapter();
+        if (adapter != null) {
+            adapter.reportDragEnteredItemIndex(itemIndex);
+        }
     }
 }
diff --git a/src/com/android/dialer/list/PhoneFavoriteMergedAdapter.java b/src/com/android/dialer/list/PhoneFavoriteMergedAdapter.java
index c7554e2..084a34e 100644
--- a/src/com/android/dialer/list/PhoneFavoriteMergedAdapter.java
+++ b/src/com/android/dialer/list/PhoneFavoriteMergedAdapter.java
@@ -346,4 +346,10 @@
             mOnItemSwipeListener = listener;
         }
     }
+
+    public void reportDragEnteredItemIndex(int itemIndex) {
+        if (mContactTileAdapter != null) {
+            mContactTileAdapter.reportDragEnteredItemIndex(itemIndex);
+        }
+    }
 }
diff --git a/src/com/android/dialer/list/PhoneFavoriteTileView.java b/src/com/android/dialer/list/PhoneFavoriteTileView.java
index 43dbad3..76a0e35 100644
--- a/src/com/android/dialer/list/PhoneFavoriteTileView.java
+++ b/src/com/android/dialer/list/PhoneFavoriteTileView.java
@@ -99,7 +99,11 @@
                     // If the view is regular row, start drag the row view.
                     final View.DragShadowBuilder shadowBuilder =
                             new View.DragShadowBuilder(view.getParentRow());
-                    view.getParentRow().startDrag(data, shadowBuilder, null, 0);
+                    final ContactTileRow parent = (ContactTileRow) view.getParentRow();
+                    // Drag is not available for the item exceeds the PIN_LIMIT.
+                    if (parent.getRegularRowItemIndex() < PhoneFavoritesTileAdapter.PIN_LIMIT) {
+                        view.getParentRow().startDrag(data, shadowBuilder, null, 0);
+                    }
                 } else {
                     // If the view is a tile view, start drag the tile.
                     final View.DragShadowBuilder shadowBuilder = new View.DragShadowBuilder(view);
diff --git a/src/com/android/dialer/list/PhoneFavoritesTileAdapter.java b/src/com/android/dialer/list/PhoneFavoritesTileAdapter.java
index a4d62d8..4eaeaf5 100644
--- a/src/com/android/dialer/list/PhoneFavoritesTileAdapter.java
+++ b/src/com/android/dialer/list/PhoneFavoritesTileAdapter.java
@@ -84,6 +84,8 @@
     private int mDraggedEntryIndex = -1;
     /** New position of the temporarily removed contact in the cache. */
     private int mDropEntryIndex = -1;
+    /** New position of the temporarily entered contact in the cache. */
+    private int mDragEnteredEntryIndex = -1;
     /** Position of the contact pending removal. */
     private int mPotentialRemoveEntryIndex = -1;
     private long mIdToKeepInPlace = -1;
@@ -121,7 +123,7 @@
     /** Indicates whether a drag is in process. */
     private boolean mInDragging = false;
 
-    private static final int PIN_LIMIT = 20;
+    public static final int PIN_LIMIT = 20;
 
     /**
      * The soft limit on how many contact tiles to show.
@@ -569,34 +571,58 @@
      * @param index Position of the contact to be removed.
      */
     public void popContactEntry(int index) {
-        if (index >= 0 && index < mContactEntries.size()) {
+        if (isIndexInBound(index)) {
             mDraggedEntry = mContactEntries.get(index);
-            mContactEntries.set(index, ContactEntry.BLANK_ENTRY);
-            ContactEntry.BLANK_ENTRY.id = mDraggedEntry.id;
             mDraggedEntryIndex = index;
+            mDragEnteredEntryIndex = index;
+            markDropArea(mDragEnteredEntryIndex);
+        }
+    }
+
+    /**
+     * @param itemIndex Position of the contact in {@link #mContactEntries}.
+     * @return True if the given index is valid for {@link #mContactEntries}.
+     */
+    private boolean isIndexInBound(int itemIndex) {
+        return itemIndex >= 0 && itemIndex < mContactEntries.size();
+    }
+
+    /**
+     * Mark the tile as drop area by given the item index in {@link #mContactEntries}.
+     *
+     * @param itemIndex Position of the contact in {@link #mContactEntries}.
+     */
+    private void markDropArea(int itemIndex) {
+        if (isIndexInBound(mDragEnteredEntryIndex) && isIndexInBound(itemIndex)) {
+            // Remove the old placeholder item and place the new placeholder item.
+            mContactEntries.remove(mDragEnteredEntryIndex);
+            mDragEnteredEntryIndex = itemIndex;
+            mContactEntries.add(mDragEnteredEntryIndex, ContactEntry.BLANK_ENTRY);
+            ContactEntry.BLANK_ENTRY.id = mDraggedEntry.id;
             notifyDataSetChanged();
         }
     }
 
     /**
      * Drops the temporarily removed contact to the desired location in the list.
-     *
-     * @param index Location where the contact will be dropped.
      */
-    public void dropContactEntry(int index) {
+    public void handleDrop() {
         boolean changed = false;
         if (mDraggedEntry != null) {
-            if (index >= 0 && index < mContactEntries.size()) {
+            if (isIndexInBound(mDragEnteredEntryIndex)) {
                 // Don't add the ContactEntry here (to prevent a double animation from occuring).
                 // When we receive a new cursor the list of contact entries will automatically be
                 // populated with the dragged ContactEntry at the correct spot.
-                mDropEntryIndex = index;
+                mDropEntryIndex = mDragEnteredEntryIndex;
+                mContactEntries.set(mDropEntryIndex, mDraggedEntry);
                 mIdToKeepInPlace = getAdjustedItemId(mDraggedEntry.id);
                 mDataSetChangedListener.cacheOffsetsForDatasetChange();
                 changed = true;
-            } else if (mDraggedEntryIndex >= 0 && mDraggedEntryIndex <= mContactEntries.size()) {
-                /** If the index is invalid, falls back to the original position of the contact. */
-                mContactEntries.set(mDraggedEntryIndex, mDraggedEntry);
+            } else if (isIndexInBound(mDraggedEntryIndex)) {
+                // If {@link #mDragEnteredEntryIndex} is invalid,
+                // falls back to the original position of the contact.
+                mContactEntries.remove(mDragEnteredEntryIndex);
+                mContactEntries.add(mDraggedEntryIndex, mDraggedEntry);
                 mDropEntryIndex = mDraggedEntryIndex;
                 notifyDataSetChanged();
             }
@@ -605,7 +631,7 @@
                 final ContentValues cv = getReflowedPinnedPositions(mContactEntries, mDraggedEntry,
                         mDraggedEntryIndex, mDropEntryIndex);
                 final Uri pinUri = PinnedPositions.UPDATE_URI.buildUpon().appendQueryParameter(
-                            PinnedPositions.STAR_WHEN_PINNING, "true").build();
+                        PinnedPositions.STAR_WHEN_PINNING, "true").build();
                 // update the database here with the new pinned positions
                 mContext.getContentResolver().update(pinUri, cv, null, null);
             }
@@ -618,7 +644,11 @@
      * contact back to where it was dragged from.
      */
     public void dropToUnsupportedView() {
-        dropContactEntry(-1);
+        if (isIndexInBound(mDragEnteredEntryIndex)) {
+            mContactEntries.remove(mDragEnteredEntryIndex);
+            mContactEntries.add(mDraggedEntryIndex, mDraggedEntry);
+            notifyDataSetChanged();
+        }
     }
 
     /**
@@ -638,7 +668,7 @@
      */
     public boolean removePendingContactEntry() {
         boolean removed = false;
-        if (mPotentialRemoveEntryIndex >= 0 && mPotentialRemoveEntryIndex < mContactEntries.size()) {
+        if (isIndexInBound(mPotentialRemoveEntryIndex)) {
             final ContactEntry entry = mContactEntries.get(mPotentialRemoveEntryIndex);
             unstarAndUnpinContact(entry.lookupKey);
             removed = true;
@@ -661,6 +691,7 @@
     public void cleanTempVariables() {
         mDraggedEntryIndex = -1;
         mDropEntryIndex = -1;
+        mDragEnteredEntryIndex = -1;
         mDraggedEntry = null;
         mPotentialRemoveEntryIndex = -1;
     }
@@ -917,11 +948,20 @@
                 }
             } else {
                 /** If the selected item is one of the rows, compute the index. */
-                return (mPosition - mMaxTiledRows) + mColumnCount * mMaxTiledRows;
+                return getRegularRowItemIndex();
             }
             return -1;
         }
 
+        /**
+         * Gets the index of the regular row item.
+         *
+         * @return Index of the selected item in the cached array.
+         */
+        public int getRegularRowItemIndex() {
+            return (mPosition - mMaxTiledRows) + mColumnCount * mMaxTiledRows;
+        }
+
         public PhoneFavoritesTileAdapter getTileAdapter() {
             return PhoneFavoritesTileAdapter.this;
         }
@@ -1110,32 +1150,12 @@
             ContactEntry entryToPin, int oldPos, int newPinPos) {
 
         final ContentValues cv = new ContentValues();
-        // Add the dragged contact at the user-requested spot.
-        cv.put(String.valueOf(entryToPin.id), newPinPos);
-
-        final int listSize = list.size();
-        if (oldPos < newPinPos && list.get(listSize - 1).pinned == (listSize - 1)) {
-            // The only time we should get here is it we are completely full - i.e. starting
-            // from the newly pinned contact to the end of the list, every single contact
-            // thereafter is pinned, and a contact is being shifted to the right by the user.
-            // Instead of trying to make room to the right, we should thus try to shift contacts
-            // to the left instead, working backwards through the list, starting from the contact
-            // which just got bumped.
-            for (int i = newPinPos; i >= 0; i--) {
-                final ContactEntry entry = list.get(i);
-                // Once we find an unpinned spot(or a blank entry), we can stop pushing contacts
-                // to the left.
-                if (entry.pinned > PIN_LIMIT) break;
-                cv.put(String.valueOf(entry.id), entry.pinned - 1);
-            }
-        } else {
-            // Shift any pinned contacts to the right as necessary, until an unpinned
-            // spot is found
-            for (int i = newPinPos; i < PIN_LIMIT && i < list.size(); i++) {
-                final ContactEntry entry = list.get(i);
-                if (entry.pinned > PIN_LIMIT) break;
-                cv.put(String.valueOf(entry.id), entry.pinned + 1);
-            }
+        final int lowerBound = Math.min(oldPos, newPinPos);
+        final int upperBound = Math.max(oldPos, newPinPos);
+        for (int i = lowerBound; i <= upperBound; i++) {
+            final ContactEntry entry = list.get(i);
+            if (entry.pinned == i) continue;
+            cv.put(String.valueOf(entry.id), i);
         }
         return cv;
     }
@@ -1175,4 +1195,13 @@
     public void setEmptyView(View emptyView) {
         mEmptyView = emptyView;
     }
+
+    public void reportDragEnteredItemIndex(int itemIndex) {
+        if (mInDragging &&
+                mDragEnteredEntryIndex != itemIndex &&
+                isIndexInBound(itemIndex) &&
+                itemIndex < PIN_LIMIT) {
+            markDropArea(itemIndex);
+        }
+    }
 }