Merge branch 'readonly-p4-donut' into donut
diff --git a/res/values/colors.xml b/res/values/colors.xml
index 5574944..f9cb0c5 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -24,4 +24,5 @@
     <color name="delete_color_filter">#A5FF0000</color>
 
     <color name="appwidget_error_color">#fccc</color>
+    <color name="snag_callout_color">#f444</color>
 </resources>
diff --git a/src/com/android/launcher/CellLayout.java b/src/com/android/launcher/CellLayout.java
index 91f0420..fe6b193 100644
--- a/src/com/android/launcher/CellLayout.java
+++ b/src/com/android/launcher/CellLayout.java
@@ -174,7 +174,7 @@
                 final int yCount = portrait ? mLongAxisCells : mShortAxisCells;
 
                 final boolean[][] occupied = mOccupied;
-                findOccupiedCells(xCount, yCount, occupied);
+                findOccupiedCells(xCount, yCount, occupied, null);
 
                 cellInfo.cell = null;
                 cellInfo.cellX = cellXY[0];
@@ -215,7 +215,7 @@
             final int yCount = portrait ? mLongAxisCells : mShortAxisCells;
 
             final boolean[][] occupied = mOccupied;
-            findOccupiedCells(xCount, yCount, occupied);
+            findOccupiedCells(xCount, yCount, occupied, null);
 
             findIntersectingVacantCells(info, info.cellX, info.cellY, xCount, yCount, occupied);
 
@@ -315,7 +315,7 @@
         return true;
     }
 
-    CellInfo findAllVacantCells(boolean[] occupiedCells) {
+    CellInfo findAllVacantCells(boolean[] occupiedCells, View ignoreView) {
         final boolean portrait = mPortrait;
         final int xCount = portrait ? mShortAxisCells : mLongAxisCells;
         final int yCount = portrait ? mLongAxisCells : mShortAxisCells;
@@ -329,7 +329,7 @@
                 }
             }
         } else {
-            findOccupiedCells(xCount, yCount, occupied);
+            findOccupiedCells(xCount, yCount, occupied, ignoreView);
         }
 
         CellInfo cellInfo = new CellInfo();
@@ -527,64 +527,72 @@
         super.setChildrenDrawnWithCacheEnabled(enabled);
     }
 
-    boolean acceptChildDrop(int x, int y, int cellHSpan, int cellVSpan, View cell) {
-        int[] cellXY = mCellXY;
-        pointToCellRounded(x, y, cellXY);
-        int cellX = cellXY[0];
-        int cellY = cellXY[1];
-
-        return findCell(cellX, cellY, cellHSpan, cellVSpan, cell) == null;
-    }
-
     /**
-     * Finds the first View intersecting with the specified cell. If the cell is outside
-     * of the layout, this is returned.
-     *
-     * @param cellX The X location of the cell to test.
-     * @param cellY The Y location of the cell to test.
-     * @param cellHSpan The horizontal span of the cell to test.
-     * @param cellVSpan The vertical span of the cell to test.
-     * @param ignoreCell View to ignore during the test.
-     *
-     * @return Returns the first View intersecting with the specified cell, this if the cell
-     *         lies outside of this layout's grid or null if no View was found.
+     * Find a vacant area that will fit the given bounds nearest the requested
+     * cell location. Uses Euclidean distance to score multiple vacant areas.
+     * 
+     * @param cellX The X location of the desired location.
+     * @param cellY The Y location of the desired location.
+     * @param spanX Horizontal span of the object.
+     * @param spanY Vertical span of the object.
+     * @param vacantCells Pre-computed set of vacant cells to search.
+     * @param recycle Previously returned value to possibly recycle.
+     * @return The X, Y cell of a vacant area that can contain this object,
+     *         nearest the requested location.
      */
-    View findCell(int cellX, int cellY, int cellHSpan, int cellVSpan, View ignoreCell) {
-        if (cellX < 0 || cellX + cellHSpan > (mPortrait ? mShortAxisCells : mLongAxisCells) ||
-                cellY < 0 || cellY + cellVSpan > (mPortrait ? mLongAxisCells : mShortAxisCells)) {
-            return this;
+    int[] findNearestVacantArea(int pixelX, int pixelY, int spanX, int spanY,
+            CellInfo vacantCells, int[] recycle) {
+        
+        // Keep track of best-scoring drop area
+        final int[] bestXY = recycle != null ? recycle : new int[2];
+        final int[] cellXY = mCellXY;
+        double bestDistance = Double.MAX_VALUE;
+        
+        // Bail early if vacant cells aren't valid
+        if (!vacantCells.valid) {
+            return null;
         }
 
-        final int count = getChildCount();
-        for (int i = 0; i < count; i++) {
-            final View view = getChildAt(i);
-            if (view == ignoreCell) {
+        // Look across all vacant cells for best fit
+        final int size = vacantCells.vacantCells.size();
+        for (int i = 0; i < size; i++) {
+            final CellInfo.VacantCell cell = vacantCells.vacantCells.get(i);
+            
+            // Reject if vacant cell isn't our exact size
+            if (cell.spanX != spanX || cell.spanY != spanY) {
                 continue;
             }
-
-            final LayoutParams lp = (LayoutParams) view.getLayoutParams();
-            if (cellX < lp.cellX + lp.cellHSpan && lp.cellX < cellX + cellHSpan &&
-                    cellY < lp.cellY + lp.cellVSpan && lp.cellY < cellY + cellVSpan) {
-                return view;
+            
+            // Score is center distance from requested pixel
+            cellToPoint(cell.cellX, cell.cellY, cellXY);
+            
+            double distance = Math.sqrt(Math.pow(cellXY[0] - pixelX, 2) +
+                    Math.pow(cellXY[1] - pixelY, 2));
+            if (distance <= bestDistance) {
+                bestDistance = distance;
+                bestXY[0] = cell.cellX;
+                bestXY[1] = cell.cellY;
             }
         }
 
-        return null;
+        // Return null if no suitable location found 
+        if (bestDistance < Double.MAX_VALUE) {
+            return bestXY;
+        } else {
+            return null;
+        }
     }
-
+    
     /**
      * Drop a child at the specified position
      *
      * @param child The child that is being dropped
-     * @param cellX The child's new x location
-     * @param cellY The child's new y location
+     * @param targetXY Destination area to move to
      */
-    void onDropChild(View child, int cellX, int cellY) {
-        int[] cellXY = mCellXY;
-        pointToCellRounded(cellX, cellY, cellXY);
+    void onDropChild(View child, int[] targetXY) {
         LayoutParams lp = (LayoutParams) child.getLayoutParams();
-        lp.cellX = cellXY[0];
-        lp.cellY = cellXY[1];
+        lp.cellX = targetXY[0];
+        lp.cellY = targetXY[1];
         lp.isDragging = false;
         mDragRect.setEmpty();
         child.requestLayout();
@@ -688,7 +696,7 @@
         final int yCount = portrait ? mLongAxisCells : mShortAxisCells;
         final boolean[][] occupied = mOccupied;
 
-        findOccupiedCells(xCount, yCount, occupied);
+        findOccupiedCells(xCount, yCount, occupied, null);
 
         return findVacantCell(vacant, spanX, spanY, xCount, yCount, occupied);
     }
@@ -723,7 +731,7 @@
         final int yCount = portrait ? mLongAxisCells : mShortAxisCells;
         final boolean[][] occupied = mOccupied;
 
-        findOccupiedCells(xCount, yCount, occupied);
+        findOccupiedCells(xCount, yCount, occupied, null);
 
         final boolean[] flat = new boolean[xCount * yCount];
         for (int y = 0; y < yCount; y++) {
@@ -735,7 +743,7 @@
         return flat;
     }
 
-    private void findOccupiedCells(int xCount, int yCount, boolean[][] occupied) {
+    private void findOccupiedCells(int xCount, int yCount, boolean[][] occupied, View ignoreView) {
         for (int x = 0; x < xCount; x++) {
             for (int y = 0; y < yCount; y++) {
                 occupied[x][y] = false;
@@ -745,7 +753,7 @@
         int count = getChildCount();
         for (int i = 0; i < count; i++) {
             View child = getChildAt(i);
-            if (child instanceof Folder) {
+            if (child instanceof Folder || child.equals(ignoreView)) {
                 continue;
             }
             LayoutParams lp = (LayoutParams) child.getLayoutParams();
diff --git a/src/com/android/launcher/DeleteZone.java b/src/com/android/launcher/DeleteZone.java
index 7f92c23..02e8011 100644
--- a/src/com/android/launcher/DeleteZone.java
+++ b/src/com/android/launcher/DeleteZone.java
@@ -19,6 +19,7 @@
 import android.widget.ImageView;
 import android.content.Context;
 import android.content.res.TypedArray;
+import android.graphics.Rect;
 import android.util.AttributeSet;
 import android.view.View;
 import android.view.animation.TranslateAnimation;
@@ -77,6 +78,10 @@
             Object dragInfo) {
         return true;
     }
+    
+    public Rect estimateDropLocation(DragSource source, int x, int y, int xOffset, int yOffset, Object dragInfo, Rect recycle) {
+        return null;
+    }
 
     public void onDrop(DragSource source, int x, int y, int xOffset, int yOffset, Object dragInfo) {
         final ItemInfo item = (ItemInfo) dragInfo;
diff --git a/src/com/android/launcher/DragLayer.java b/src/com/android/launcher/DragLayer.java
index b542de6..b5b84b8 100644
--- a/src/com/android/launcher/DragLayer.java
+++ b/src/com/android/launcher/DragLayer.java
@@ -113,8 +113,25 @@
     private DropTarget mLastDropTarget;
 
     private final Paint mTrashPaint = new Paint();
+    private final Paint mEstimatedPaint = new Paint();
     private Paint mDragPaint;
 
+    /**
+     * If true, draw a "snag" showing where the object currently being dragged
+     * would end up if dropped from current location.
+     */
+    private static final boolean DRAW_TARGET_SNAG = false;
+
+    private Rect mEstimatedRect = new Rect();
+    private float[] mDragCenter = new float[2];
+    private float[] mEstimatedCenter = new float[2];
+    private boolean mDrawEstimated = false;
+
+    private int mTriggerWidth = -1;
+    private int mTriggerHeight = -1;
+
+    private static final int DISTANCE_DRAW_SNAG = 20;
+
     private static final int ANIMATION_STATE_STARTING = 1;
     private static final int ANIMATION_STATE_RUNNING = 2;
     private static final int ANIMATION_STATE_DONE = 3;
@@ -141,6 +158,13 @@
 
         final int srcColor = context.getResources().getColor(R.color.delete_color_filter);
         mTrashPaint.setColorFilter(new PorterDuffColorFilter(srcColor, PorterDuff.Mode.SRC_ATOP));
+
+        // Make estimated paint area in gray
+        int snagColor = context.getResources().getColor(R.color.snag_callout_color);
+        mEstimatedPaint.setColor(snagColor);
+        mEstimatedPaint.setStrokeWidth(3);
+        mEstimatedPaint.setAntiAlias(true);
+
     }
 
     public void startDrag(View v, DragSource source, Object dragInfo, int dragAction) {
@@ -177,6 +201,9 @@
         int width = viewBitmap.getWidth();
         int height = viewBitmap.getHeight();
 
+        mTriggerWidth = width * 2 / 3;
+        mTriggerHeight = height * 2 / 3;
+
         Matrix scale = new Matrix();
         float scaleFactor = v.getWidth();
         scaleFactor = (scaleFactor + DRAG_SCALE) /scaleFactor;
@@ -252,6 +279,13 @@
                         break;
                 }
             } else {
+                // Only draw estimate drop "snag" when requested
+                if (DRAW_TARGET_SNAG && mDrawEstimated) {
+                    canvas.drawLine(mDragCenter[0], mDragCenter[1], mEstimatedCenter[0], mEstimatedCenter[1], mEstimatedPaint);
+                    canvas.drawCircle(mEstimatedCenter[0], mEstimatedCenter[1], 8, mEstimatedPaint);
+                }
+
+                // Draw actual icon being dragged
                 canvas.drawBitmap(mDragBitmap,
                         mScrollX + mLastMotionX - mTouchOffsetX - mBitmapOffsetX,
                         mScrollY + mLastMotionY - mTouchOffsetY - mBitmapOffsetY, mDragPaint);
@@ -355,8 +389,16 @@
             left = (int) (scrollX + x - touchX - offsetX);
             top = (int) (scrollY + y - touchY - offsetY);
 
+            // Invalidate current icon position
             rect.union(left - 1, top - 1, left + width + 1, top + height + 1);
-            invalidate(rect);
+
+            mDragCenter[0] = rect.centerX();
+            mDragCenter[1] = rect.centerY();
+
+            // Invalidate any old estimated location
+            if (DRAW_TARGET_SNAG && mDrawEstimated) {
+                rect.union(mEstimatedRect);
+            }
 
             final int[] coordinates = mDropCoordinates;
             DropTarget dropTarget = findDropTarget((int) x, (int) y, coordinates);
@@ -378,6 +420,33 @@
                         (int) mTouchOffsetX, (int) mTouchOffsetY, mDragInfo);
                 }
             }
+
+            // Render estimated drop "snag" only outside of width
+            mDrawEstimated = false;
+            if (DRAW_TARGET_SNAG && dropTarget != null) {
+                Rect foundEstimate = dropTarget.estimateDropLocation(mDragSource,
+                        (int) (scrollX + mLastMotionX), (int) (scrollY + mLastMotionY),
+                        (int) mTouchOffsetX, (int) mTouchOffsetY, mDragInfo, mEstimatedRect);
+
+                if (foundEstimate != null) {
+                    mEstimatedCenter[0] = foundEstimate.centerX();
+                    mEstimatedCenter[1] = foundEstimate.centerY();
+
+                    int deltaX = (int) Math.abs(mEstimatedCenter[0] - mDragCenter[0]);
+                    int deltaY = (int) Math.abs(mEstimatedCenter[1] - mDragCenter[1]);
+
+                    if (deltaX > mTriggerWidth || deltaY > mTriggerHeight) {
+                        mDrawEstimated = true;
+                    }
+                }
+            }
+
+            // Include new estimated area in invalidated rectangle
+            if (DRAW_TARGET_SNAG && mDrawEstimated) {
+                rect.union(mEstimatedRect);
+            }
+            invalidate(rect);
+
             mLastDropTarget = dropTarget;
 
             boolean inDragRegion = false;
@@ -478,9 +547,15 @@
                     }
                     if (target == null) {
                         if (child instanceof DropTarget) {
-                            dropCoordinates[0] = x;
-                            dropCoordinates[1] = y;
-                            return (DropTarget) child;
+                            // Only consider this child if they will accept
+                            DropTarget childTarget = (DropTarget) child;
+                            if (childTarget.acceptDrop(mDragSource, x, y, 0, 0, mDragInfo)) {
+                                dropCoordinates[0] = x;
+                                dropCoordinates[1] = y;
+                                return (DropTarget) child;
+                            } else {
+                                return null;
+                            }
                         }
                     } else {
                         return target;
@@ -531,6 +606,7 @@
 
         public void run() {
             if (mDragScroller != null) {
+                mDrawEstimated = false;
                 if (mDirection == SCROLL_LEFT) {
                     mDragScroller.scrollLeft();
                 } else {
diff --git a/src/com/android/launcher/DropTarget.java b/src/com/android/launcher/DropTarget.java
index 8129089..4835323 100644
--- a/src/com/android/launcher/DropTarget.java
+++ b/src/com/android/launcher/DropTarget.java
@@ -16,6 +16,8 @@
 
 package com.android.launcher;
 
+import android.graphics.Rect;
+
 /**
  * Interface defining an object that can receive a drag.
  *
@@ -42,18 +44,38 @@
     void onDragExit(DragSource source, int x, int y, int xOffset, int yOffset, Object dragInfo);
 
     /**
-     * Indicates whether a drop action can occur at the specified location. The method
-     * {@link #onDrop(DragSource, int, int, int, int, Object)} will be invoked on this
-     * drop target only if this method returns true. 
-     *
+     * Check if a drop action can occur at, or near, the requested location.
+     * This may be called repeatedly during a drag, so any calls should return
+     * quickly.
+     * 
      * @param source DragSource where the drag started
      * @param x X coordinate of the drop location
      * @param y Y coordinate of the drop location
-     * @param xOffset Horizontal offset with the object being dragged where the original touch happened
-     * @param yOffset Vertical offset with the object being dragged where the original touch happened
+     * @param xOffset Horizontal offset with the object being dragged where the
+     *            original touch happened
+     * @param yOffset Vertical offset with the object being dragged where the
+     *            original touch happened
      * @param dragInfo Data associated with the object being dragged
-     *
-     * return True if the drop is accepted, false otherwise.
+     * @return True if the drop will be accepted, false otherwise.
      */
     boolean acceptDrop(DragSource source, int x, int y, int xOffset, int yOffset, Object dragInfo);
+
+    /**
+     * Estimate the surface area where this object would land if dropped at the
+     * given location.
+     * 
+     * @param source DragSource where the drag started
+     * @param x X coordinate of the drop location
+     * @param y Y coordinate of the drop location
+     * @param xOffset Horizontal offset with the object being dragged where the
+     *            original touch happened
+     * @param yOffset Vertical offset with the object being dragged where the
+     *            original touch happened
+     * @param dragInfo Data associated with the object being dragged
+     * @param recycle {@link Rect} object to be possibly recycled.
+     * @return Estimated area that would be occupied if object was dropped at
+     *         the given location. Should return null if no estimate is found,
+     *         or if this target doesn't provide estimations.
+     */
+    Rect estimateDropLocation(DragSource source, int x, int y, int xOffset, int yOffset, Object dragInfo, Rect recycle);
 }
diff --git a/src/com/android/launcher/FolderIcon.java b/src/com/android/launcher/FolderIcon.java
index 667f92e..a56101d 100644
--- a/src/com/android/launcher/FolderIcon.java
+++ b/src/com/android/launcher/FolderIcon.java
@@ -18,6 +18,7 @@
 
 import android.content.Context;
 import android.content.res.Resources;
+import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.util.AttributeSet;
 import android.view.LayoutInflater;
@@ -69,6 +70,10 @@
                 && item.container != mInfo.id;
     }
 
+    public Rect estimateDropLocation(DragSource source, int x, int y, int xOffset, int yOffset, Object dragInfo, Rect recycle) {
+        return null;
+    }
+
     public void onDrop(DragSource source, int x, int y, int xOffset, int yOffset, Object dragInfo) {
         final ApplicationInfo item = (ApplicationInfo) dragInfo;
         // TODO: update open folder that is looking at this data
diff --git a/src/com/android/launcher/UserFolder.java b/src/com/android/launcher/UserFolder.java
index 1044e96..6cdfed9 100644
--- a/src/com/android/launcher/UserFolder.java
+++ b/src/com/android/launcher/UserFolder.java
@@ -1,6 +1,7 @@
 package com.android.launcher;
 
 import android.content.Context;
+import android.graphics.Rect;
 import android.util.AttributeSet;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -33,6 +34,10 @@
         return (itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION ||
                 itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) && item.container != mInfo.id;
     }
+    
+    public Rect estimateDropLocation(DragSource source, int x, int y, int xOffset, int yOffset, Object dragInfo, Rect recycle) {
+        return null;
+    }
 
     public void onDrop(DragSource source, int x, int y, int xOffset, int yOffset, Object dragInfo) {
         final ApplicationInfo item = (ApplicationInfo) dragInfo;
diff --git a/src/com/android/launcher/Workspace.java b/src/com/android/launcher/Workspace.java
index c76fb7e..d474efa 100644
--- a/src/com/android/launcher/Workspace.java
+++ b/src/com/android/launcher/Workspace.java
@@ -48,7 +48,7 @@
  */
 public class Workspace extends ViewGroup implements DropTarget, DragSource, DragScroller {
     private static final int INVALID_SCREEN = -1;
-
+    
     /**
      * The velocity at which a fling gesture will cause us to snap to the next screen
      */
@@ -75,6 +75,11 @@
      * CellInfo for the cell that is currently being dragged
      */
     private CellLayout.CellInfo mDragInfo;
+    
+    /**
+     * Target drop area calculated during last acceptDrop call.
+     */
+    private int[] mTargetCell = null;
 
     private float mLastMotionX;
     private float mLastMotionY;
@@ -88,8 +93,14 @@
 
     private Launcher mLauncher;
     private DragController mDragger;
-
+    
+    /**
+     * Cache of vacant cells, used during drag events and invalidated as needed.
+     */
+    private CellLayout.CellInfo mVacantCache = null;
+    
     private int[] mTempCell = new int[2];
+    private int[] mTempEstimate = new int[2];
 
     private boolean mAllowLongPress;
     private boolean mLocked;
@@ -363,7 +374,7 @@
     CellLayout.CellInfo findAllVacantCells(boolean[] occupied) {
         CellLayout group = (CellLayout) getChildAt(mCurrentScreen);
         if (group != null) {
-            return group.findAllVacantCells(occupied);
+            return group.findAllVacantCells(occupied, null);
         }
         return null;
     }
@@ -890,7 +901,7 @@
     }
 
     public void onDrop(DragSource source, int x, int y, int xOffset, int yOffset, Object dragInfo) {
-        final CellLayout cellLayout = (CellLayout) getChildAt(mCurrentScreen);
+        final CellLayout cellLayout = getCurrentDropLayout();
         if (source != this) {
             onDropExternal(x - xOffset, y - yOffset, dragInfo, cellLayout);
         } else {
@@ -902,7 +913,9 @@
                     originalCellLayout.removeView(cell);
                     cellLayout.addView(cell);
                 }
-                cellLayout.onDropChild(cell, x - xOffset, y - yOffset);
+                mTargetCell = estimateDropCell(source, x - xOffset, y - yOffset,
+                        mDragInfo.spanX, mDragInfo.spanY, cell, cellLayout, mTargetCell);
+                cellLayout.onDropChild(cell, mTargetCell);
 
                 final ItemInfo info = (ItemInfo)cell.getTag();
                 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) cell.getLayoutParams();
@@ -914,6 +927,7 @@
 
     public void onDragEnter(DragSource source, int x, int y, int xOffset, int yOffset,
             Object dragInfo) {
+        mVacantCache = null;
     }
 
     public void onDragOver(DragSource source, int x, int y, int xOffset, int yOffset,
@@ -922,6 +936,7 @@
 
     public void onDragExit(DragSource source, int x, int y, int xOffset, int yOffset,
             Object dragInfo) {
+        mVacantCache = null;
     }
 
     private void onDropExternal(int x, int y, Object dragInfo, CellLayout cellLayout) {
@@ -955,7 +970,8 @@
 
         cellLayout.addView(view, insertAtFirst ? 0 : -1);
         view.setOnLongClickListener(mLongClickListener);
-        cellLayout.onDropChild(view, x, y);
+        mTargetCell = estimateDropCell(null, x, y, 1, 1, view, cellLayout, mTargetCell);
+        cellLayout.onDropChild(view, mTargetCell);
         CellLayout.LayoutParams lp = (CellLayout.LayoutParams) view.getLayoutParams();
 
         final LauncherModel model = Launcher.getModel();
@@ -963,18 +979,73 @@
         LauncherModel.addOrMoveItemInDatabase(mLauncher, info,
                 LauncherSettings.Favorites.CONTAINER_DESKTOP, mCurrentScreen, lp.cellX, lp.cellY);
     }
-
-    public boolean acceptDrop(DragSource source, int x, int y, int xOffset, int yOffset,
-            Object dragInfo) {
-
-        final CellLayout.CellInfo cellInfo = mDragInfo;
-        int cellHSpan = cellInfo == null ? 1 : cellInfo.spanX;
-        int cellVSpan = cellInfo == null ? 1 : cellInfo.spanY;
-
-        return ((CellLayout) getChildAt(mCurrentScreen)).acceptChildDrop(x - xOffset, y - yOffset,
-                cellHSpan, cellVSpan, cellInfo == null ? null : cellInfo.cell);
+    
+    /**
+     * Return the current {@link CellLayout}, correctly picking the destination
+     * screen while a scroll is in progress.
+     */
+    private CellLayout getCurrentDropLayout() {
+        int index = mScroller.isFinished() ? mCurrentScreen : mNextScreen;
+        return (CellLayout) getChildAt(index);
     }
 
+    /**
+     * {@inheritDoc}
+     */
+    public boolean acceptDrop(DragSource source, int x, int y,
+            int xOffset, int yOffset, Object dragInfo) {
+        // Workspaces accept everything
+        return true;
+    }
+    
+    /**
+     * {@inheritDoc}
+     */
+    public Rect estimateDropLocation(DragSource source, int x, int y,
+            int xOffset, int yOffset, Object dragInfo, Rect recycle) {
+        final CellLayout layout = getCurrentDropLayout();
+        
+        final CellLayout.CellInfo cellInfo = mDragInfo;
+        final int spanX = cellInfo == null ? 1 : cellInfo.spanX;
+        final int spanY = cellInfo == null ? 1 : cellInfo.spanY;
+        final View ignoreView = cellInfo == null ? null : cellInfo.cell;
+        
+        final Rect location = recycle != null ? recycle : new Rect();
+        
+        // Find drop cell and convert into rectangle
+        int[] dropCell = estimateDropCell(source, x - xOffset, y - yOffset,
+                spanX, spanY, ignoreView, layout, mTempCell);
+        
+        if (dropCell == null) {
+            return null;
+        }
+        
+        layout.cellToPoint(dropCell[0], dropCell[1], mTempEstimate);
+        location.left = mTempEstimate[0];
+        location.top = mTempEstimate[1];
+        
+        layout.cellToPoint(dropCell[0] + spanX, dropCell[1] + spanY, mTempEstimate);
+        location.right = mTempEstimate[0];
+        location.bottom = mTempEstimate[1];
+        
+        return location;
+    }
+
+    /**
+     * Calculate the nearest cell where the given object would be dropped.
+     */
+    private int[] estimateDropCell(DragSource source, int pixelX, int pixelY,
+            int spanX, int spanY, View ignoreView, CellLayout layout, int[] recycle) {
+        // Create vacant cell cache if none exists
+        if (mVacantCache == null) {
+            mVacantCache = layout.findAllVacantCells(null, ignoreView);
+        }
+
+        // Find the best target drop location
+        return layout.findNearestVacantArea(pixelX, pixelY,
+                spanX, spanY, mVacantCache, recycle);
+    }
+    
     void setLauncher(Launcher launcher) {
         mLauncher = launcher;
     }
@@ -1002,12 +1073,14 @@
     }
 
     public void scrollLeft() {
+        mVacantCache = null;
         if (mNextScreen == INVALID_SCREEN && mCurrentScreen > 0 && mScroller.isFinished()) {
             snapToScreen(mCurrentScreen - 1);
         }
     }
 
     public void scrollRight() {
+        mVacantCache = null;
         if (mNextScreen == INVALID_SCREEN && mCurrentScreen < getChildCount() -1 &&
                 mScroller.isFinished()) {
             snapToScreen(mCurrentScreen + 1);