Fix bug with drag visualization and UserFolders.

When dragging an app shortcut, it was possible that we'd show a red rectangle
around a cell occupied by a UserFolder. This shouldn't be possible -- as soon
as that cell becomes the target drop cell, the folder should start handling
the drag and drop events.

Change-Id: I1b7a8b1aa9aeb7e2f1bd51ce8d947c06455e988f
diff --git a/src/com/android/launcher2/CellLayout.java b/src/com/android/launcher2/CellLayout.java
index 9cc370f..3f60d50 100644
--- a/src/com/android/launcher2/CellLayout.java
+++ b/src/com/android/launcher2/CellLayout.java
@@ -677,6 +677,20 @@
         return true;
     }
 
+    public View getChildAt(int x, int y) {
+        final int count = getChildCount();
+        for (int i = 0; i < count; i++) {
+            View child = getChildAt(i);
+            LayoutParams lp = (LayoutParams) child.getLayoutParams();
+
+            if ((lp.cellX <= x) && (x < lp.cellX + lp.cellHSpan) &&
+                    (lp.cellY <= y) && (y < lp.cellY + lp.cellHSpan)) {
+                return child;
+            }
+        }
+        return null;
+    }
+
     /**
      * Estimate where the top left cell of the dragged item will land if it is dropped.
      *
@@ -690,7 +704,7 @@
         final int countX = getCountX();
         final int countY = getCountY();
 
-        pointToCellRounded(originX, originY, result);
+        pointToCellRounded(originX + (mCellWidth / 2), originY + (mCellHeight / 2), result);
 
         // If the item isn't fully on this screen, snap to the edges
         int rightOverhang = result[0] + spanX - countX;
diff --git a/src/com/android/launcher2/DeleteZone.java b/src/com/android/launcher2/DeleteZone.java
index 3a6c63d..b11b1dd 100644
--- a/src/com/android/launcher2/DeleteZone.java
+++ b/src/com/android/launcher2/DeleteZone.java
@@ -254,4 +254,10 @@
             return false;
         }
     }
+
+    @Override
+    public DropTarget getDropTargetDelegate(DragSource source, int x, int y, int xOffset, int yOffset,
+            DragView dragView, Object dragInfo) {
+        return null;
+    }
 }
diff --git a/src/com/android/launcher2/DragController.java b/src/com/android/launcher2/DragController.java
index b4f972b..f2fad9a 100644
--- a/src/com/android/launcher2/DragController.java
+++ b/src/com/android/launcher2/DragController.java
@@ -394,6 +394,12 @@
             // Drop on someone?
             final int[] coordinates = mCoordinatesTemp;
             DropTarget dropTarget = findDropTarget(screenX, screenY, coordinates);
+            DropTarget delegate = dropTarget.getDropTargetDelegate(
+                    mDragSource, coordinates[0], coordinates[1],
+                    (int) mTouchOffsetX, (int) mTouchOffsetY, mDragView, mDragInfo);
+            if (delegate != null) {
+                dropTarget = delegate;
+            }
             if (dropTarget != null) {
                 if (mLastDropTarget == dropTarget) {
                     dropTarget.onDragOver(mDragSource, coordinates[0], coordinates[1],
@@ -482,13 +488,25 @@
         final ArrayList<DropTarget> dropTargets = mDropTargets;
         final int count = dropTargets.size();
         for (int i=count-1; i>=0; i--) {
-            final DropTarget target = dropTargets.get(i);
+            DropTarget target = dropTargets.get(i);
             target.getHitRect(r);
+
+            // Convert the hit rect to screen coordinates
             target.getLocationOnScreen(dropCoordinates);
             r.offset(dropCoordinates[0] - target.getLeft(), dropCoordinates[1] - target.getTop());
+
             if (r.contains(x, y)) {
+                DropTarget delegate = target.getDropTargetDelegate(mDragSource,
+                        x, y, (int)mTouchOffsetX, (int)mTouchOffsetY, mDragView, mDragInfo);
+                if (delegate != null) {
+                    target = delegate;
+                    target.getLocationOnScreen(dropCoordinates);
+                }
+
+                // Make dropCoordinates relative to the DropTarget
                 dropCoordinates[0] = x - dropCoordinates[0];
                 dropCoordinates[1] = y - dropCoordinates[1];
+
                 return target;
             }
         }
diff --git a/src/com/android/launcher2/DropTarget.java b/src/com/android/launcher2/DropTarget.java
index 72eb330..7e54231 100644
--- a/src/com/android/launcher2/DropTarget.java
+++ b/src/com/android/launcher2/DropTarget.java
@@ -51,6 +51,26 @@
             DragView dragView, Object dragInfo);
 
     /**
+     * Allows a DropTarget to delegate drag and drop events to another object.
+     *
+     * Most subclasses will should just return null from this method.
+     *
+     * @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 dragView The DragView that's being dragged around on screen.
+     * @param dragInfo Data associated with the object being dragged
+     *
+     * @return The DropTarget to delegate to, or null to not delegate to another object.
+     */
+    DropTarget getDropTargetDelegate(DragSource source, int x, int y, int xOffset, int yOffset,
+            DragView dragView, Object dragInfo);
+
+    /**
      * 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.
diff --git a/src/com/android/launcher2/FolderIcon.java b/src/com/android/launcher2/FolderIcon.java
index 0013644..a09b6e0 100644
--- a/src/com/android/launcher2/FolderIcon.java
+++ b/src/com/android/launcher2/FolderIcon.java
@@ -102,4 +102,10 @@
             DragView dragView, Object dragInfo) {
         setCompoundDrawablesWithIntrinsicBounds(null, mCloseIcon, null, null);
     }
+
+    @Override
+    public DropTarget getDropTargetDelegate(DragSource source, int x, int y, int xOffset, int yOffset,
+            DragView dragView, Object dragInfo) {
+        return null;
+    }
 }
diff --git a/src/com/android/launcher2/UserFolder.java b/src/com/android/launcher2/UserFolder.java
index 255099f..b46e924 100644
--- a/src/com/android/launcher2/UserFolder.java
+++ b/src/com/android/launcher2/UserFolder.java
@@ -89,4 +89,10 @@
         super.onOpen();
         requestFocus();
     }
+
+    @Override
+    public DropTarget getDropTargetDelegate(DragSource source, int x, int y, int xOffset, int yOffset,
+            DragView dragView, Object dragInfo) {
+        return null;
+    }
 }
diff --git a/src/com/android/launcher2/Workspace.java b/src/com/android/launcher2/Workspace.java
index 67b543e..b4986ad 100644
--- a/src/com/android/launcher2/Workspace.java
+++ b/src/com/android/launcher2/Workspace.java
@@ -1219,11 +1219,45 @@
         clearVacantCache();
     }
 
+    public DropTarget getDropTargetDelegate(DragSource source, int x, int y, int xOffset, int yOffset,
+            DragView dragView, Object dragInfo) {
+
+        // We may need to delegate the drag to a child view. If a 1x1 item
+        // would land in a cell occupied by a DragTarget (e.g. a Folder),
+        // then drag events should be handled by that child.
+
+        ItemInfo item = (ItemInfo)dragInfo;
+        CellLayout currentLayout = getCurrentDropLayout();
+
+        int dragPointX, dragPointY;
+        if (item.spanX == 1 && item.spanY == 1) {
+            // For a 1x1, calculate the drop cell exactly as in onDragOver
+            dragPointX = x - xOffset;
+            dragPointY = y - yOffset;
+        } else {
+            // Otherwise, use the exact drag coordinates
+            dragPointX = x;
+            dragPointY = y;
+        }
+
+        // If we are dragging over a cell that contains a DropTarget that will
+        // accept the drop, delegate to that DropTarget.
+        final int[] cellXY = mTempCell;
+        currentLayout.estimateDropCell(dragPointX, dragPointY, item.spanX, item.spanY, cellXY);
+        View child = currentLayout.getChildAt(cellXY[0], cellXY[1]);
+        if (child instanceof DropTarget) {
+            DropTarget target = (DropTarget)child;
+            if (target.acceptDrop(source, x, y, xOffset, yOffset, dragView, dragInfo)) {
+                return target;
+            }
+        }
+        return null;
+    }
+
     public void onDragOver(DragSource source, int x, int y, int xOffset, int yOffset,
             DragView dragView, Object dragInfo) {
 
         ItemInfo item = (ItemInfo)dragInfo;
-
         CellLayout currentLayout = getCurrentDropLayout();
 
         if (dragInfo instanceof LauncherAppWidgetInfo) {