Enabling user folder creation by dropping shortcut onto shortcut

Change-Id: Ib8de001f5003cd44f1524cb7763fc928fa24aaba
diff --git a/src/com/android/launcher2/CellLayout.java b/src/com/android/launcher2/CellLayout.java
index 97518a5..ac9d54d 100644
--- a/src/com/android/launcher2/CellLayout.java
+++ b/src/com/android/launcher2/CellLayout.java
@@ -95,7 +95,7 @@
     private float mGlowBackgroundScale;
     private float mGlowBackgroundAlpha;
 
-    private boolean mAcceptsDrops = false;
+    private boolean mAcceptsDrops = true;
     // If we're actively dragging something over this screen, mIsDragOverlapping is true
     private boolean mIsDragOverlapping = false;
     private boolean mIsDragOccuring = false;
@@ -1010,13 +1010,14 @@
      * @param pixelY The Y location at which you want to search for a vacant area.
      * @param spanX Horizontal span of the object.
      * @param spanY Vertical span of the object.
-     * @param ignoreView Considers space occupied by this view as unoccupied
-     * @param result Previously returned value to possibly recycle.
+     * @param ignoreOccupied If true, the result can be an occupied cell
+     * @param result Array in which to place the result, or null (in which case a new array will
+     *        be allocated)
      * @return The X, Y cell of a vacant area that can contain this object,
      *         nearest the requested location.
      */
-    int[] findNearestVacantArea(
-            int pixelX, int pixelY, int spanX, int spanY, View ignoreView, int[] result) {
+    int[] findNearestArea(int pixelX, int pixelY, int spanX, int spanY, View ignoreView,
+            boolean ignoreOccupied, int[] result) {
         // mark space take by ignoreView as available (method checks if ignoreView is null)
         markCellsAsUnoccupiedForView(ignoreView);
 
@@ -1031,13 +1032,15 @@
         for (int y = 0; y < countY - (spanY - 1); y++) {
             inner:
             for (int x = 0; x < countX - (spanX - 1); x++) {
-                for (int i = 0; i < spanX; i++) {
-                    for (int j = 0; j < spanY; j++) {
-                        if (occupied[x + i][y + j]) {
-                            // small optimization: we can skip to after the column we just found
-                            // an occupied cell
-                            x += i;
-                            continue inner;
+                if (ignoreOccupied) {
+                    for (int i = 0; i < spanX; i++) {
+                        for (int j = 0; j < spanY; j++) {
+                            if (occupied[x + i][y + j]) {
+                                // small optimization: we can skip to after the column we
+                                // just found an occupied cell
+                                x += i;
+                                continue inner;
+                            }
                         }
                     }
                 }
@@ -1064,6 +1067,42 @@
         }
     }
 
+    /**
+     * Find a vacant area that will fit the given bounds nearest the requested
+     * cell location. Uses Euclidean distance to score multiple vacant areas.
+     *
+     * @param pixelX The X location at which you want to search for a vacant area.
+     * @param pixelY The Y location at which you want to search for a vacant area.
+     * @param spanX Horizontal span of the object.
+     * @param spanY Vertical span of the object.
+     * @param ignoreView Considers space occupied by this view as unoccupied
+     * @param result Previously returned value to possibly recycle.
+     * @return The X, Y cell of a vacant area that can contain this object,
+     *         nearest the requested location.
+     */
+    int[] findNearestVacantArea(
+            int pixelX, int pixelY, int spanX, int spanY, View ignoreView, int[] result) {
+        return findNearestArea(pixelX, pixelY, spanX, spanY, ignoreView, true, result);
+    }
+
+    /**
+     * Find a starting cell position that will fit the given bounds nearest the requested
+     * cell location. Uses Euclidean distance to score multiple vacant areas.
+     *
+     * @param pixelX The X location at which you want to search for a vacant area.
+     * @param pixelY The Y location at which you want to search for a vacant area.
+     * @param spanX Horizontal span of the object.
+     * @param spanY Vertical span of the object.
+     * @param ignoreView Considers space occupied by this view as unoccupied
+     * @param result Previously returned value to possibly recycle.
+     * @return The X, Y cell of a vacant area that can contain this object,
+     *         nearest the requested location.
+     */
+    int[] findNearestArea(
+            int pixelX, int pixelY, int spanX, int spanY, int[] result) {
+        return findNearestArea(pixelX, pixelY, spanX, spanY, null, false, result);
+    }
+
     boolean existsEmptyCell() {
         return findCellForSpan(null, 1, 1);
     }
diff --git a/src/com/android/launcher2/FolderIcon.java b/src/com/android/launcher2/FolderIcon.java
index bbc3409..a64b550 100644
--- a/src/com/android/launcher2/FolderIcon.java
+++ b/src/com/android/launcher2/FolderIcon.java
@@ -77,6 +77,11 @@
                 && item.container != mInfo.id;
     }
 
+    public void addItem(ShortcutInfo item) {
+        mInfo.add(item);
+        LauncherModel.addOrMoveItemInDatabase(mLauncher, item, mInfo.id, 0, 0, 0);
+    }
+
     public void onDrop(DragSource source, int x, int y, int xOffset, int yOffset,
             DragView dragView, Object dragInfo) {
         ShortcutInfo item;
@@ -86,8 +91,7 @@
         } else {
             item = (ShortcutInfo)dragInfo;
         }
-        mInfo.add(item);
-        LauncherModel.addOrMoveItemInDatabase(mLauncher, item, mInfo.id, 0, 0, 0);
+        addItem(item);
     }
 
     public void onDragEnter(DragSource source, int x, int y, int xOffset, int yOffset,
diff --git a/src/com/android/launcher2/Launcher.java b/src/com/android/launcher2/Launcher.java
index a68bd9b..bb58081 100644
--- a/src/com/android/launcher2/Launcher.java
+++ b/src/com/android/launcher2/Launcher.java
@@ -107,7 +107,6 @@
 import java.util.HashMap;
 import java.util.List;
 
-
 /**
  * Default launcher application.
  */
@@ -315,7 +314,6 @@
             // share the same customization workspace across all the tabs
             mCustomizePagedView = (CustomizePagedView) findViewById(
                     R.id.customization_drawer_tab_contents);
-
         }
         setupViews();
 
@@ -1803,7 +1801,7 @@
         }
     }
 
-    void addFolder(int screen, int intersectCellX, int intersectCellY) {
+    FolderIcon addFolder(int screen, int intersectCellX, int intersectCellY) {
         UserFolderInfo folderInfo = new UserFolderInfo();
         folderInfo.title = getText(R.string.folder_name);
 
@@ -1811,7 +1809,7 @@
         final int[] cellXY = mTmpAddItemCellCoordinates;
         if (!layout.findCellForSpanThatIntersects(cellXY, 1, 1, intersectCellX, intersectCellY)) {
             showOutOfSpaceMessage();
-            return;
+            return null;
         }
 
         // Update the model
@@ -1825,6 +1823,7 @@
                 (ViewGroup) mWorkspace.getChildAt(mWorkspace.getCurrentPage()),
                 folderInfo, mIconCache);
         mWorkspace.addInScreen(newFolder, screen, cellXY[0], cellXY[1], 1, 1, isWorkspaceLocked());
+        return newFolder;
     }
 
     void removeFolder(FolderInfo folder) {
@@ -3377,6 +3376,8 @@
      */
     public void startBinding() {
         final Workspace workspace = mWorkspace;
+
+        mWorkspace.clearDropTargets();
         int count = workspace.getChildCount();
         for (int i = 0; i < count; i++) {
             // Use removeAllViewsInLayout() to avoid an extra requestLayout() and invalidate().
diff --git a/src/com/android/launcher2/Workspace.java b/src/com/android/launcher2/Workspace.java
index 7e17a54..a156d0d 100644
--- a/src/com/android/launcher2/Workspace.java
+++ b/src/com/android/launcher2/Workspace.java
@@ -2333,13 +2333,39 @@
 
             if (dropTargetLayout != null) {
                 // Move internally
-                mTargetCell = findNearestVacantArea(originX, originY,
-                        mDragInfo.spanX, mDragInfo.spanY, cell, dropTargetLayout,
+
+                // First we find the cell nearest to point at which the item is dropped, without
+                // any consideration to whether there is an item there.
+                mTargetCell = findNearestArea(originX, originY,
+                        mDragInfo.spanX, mDragInfo.spanY, dropTargetLayout,
                         mTargetCell);
 
                 final int screen = (mTargetCell == null) ?
                         mDragInfo.screen : indexOfChild(dropTargetLayout);
 
+                View v = dropTargetLayout.getChildAt(mTargetCell[0], mTargetCell[1]);
+                boolean hasMoved = !(mDragInfo.cellX == mTargetCell[0] &&
+                        mDragInfo.cellY == mTargetCell[1]);
+
+                // If the item being dropped is a shortcut and the nearest drop cell also contains
+                // a shortcut, then create a folder with the two shortcuts.
+                if (v != null && (v.getTag() instanceof ShortcutInfo) &&
+                        dragInfo instanceof ShortcutInfo && hasMoved) {
+                    ShortcutInfo info1 = (ShortcutInfo) v.getTag();
+                    ShortcutInfo info2 = (ShortcutInfo) dragInfo;
+                    dropTargetLayout.removeView(v);
+                    FolderIcon fi = mLauncher.addFolder(screen, mTargetCell[0], mTargetCell[1]);
+                    fi.addItem(info1);
+                    fi.addItem(info2);
+                    return;
+                }
+
+                // Aside from the special case where we're dropping a shortcut onto a shortcut,
+                // we need to find the nearest cell location that is vacant
+                mTargetCell = findNearestVacantArea(originX, originY,
+                        mDragInfo.spanX, mDragInfo.spanY, cell, dropTargetLayout,
+                        mTargetCell);
+
                 if (screen != mCurrentPage) {
                     snapToPage(screen);
                 }
@@ -2984,6 +3010,20 @@
                 localPixelX, localPixelY, spanX, spanY, ignoreView, recycle);
     }
 
+    /**
+     * Calculate the nearest cell where the given object would be dropped.
+     */
+    private int[] findNearestArea(int pixelX, int pixelY,
+            int spanX, int spanY,CellLayout layout, int[] recycle) {
+
+        int localPixelX = pixelX - (layout.getLeft() - mScrollX);
+        int localPixelY = pixelY - (layout.getTop() - mScrollY);
+
+        // Find the best target drop location
+        return layout.findNearestArea(
+                localPixelX, localPixelY, spanX, spanY, recycle);
+    }
+
     void setLauncher(Launcher launcher) {
         mLauncher = launcher;
         mSpringLoadedDragController = new SpringLoadedDragController(mLauncher);
@@ -3134,6 +3174,21 @@
         return null;
     }
 
+    void clearDropTargets() {
+        final int screenCount = getChildCount();
+
+        for (int i = 0; i < screenCount; i++) {
+            final CellLayout layoutParent = (CellLayout) getChildAt(i);
+            final ViewGroup layout = layoutParent.getChildrenLayout();
+            int childCount = layout.getChildCount();
+            for (int j = 0; j < childCount; j++) {
+                View v = layout.getChildAt(j);
+                if (v instanceof DropTarget) {
+                    mDragController.removeDropTarget((DropTarget) v);
+                }
+            }
+        }
+    }
 
     void removeItems(final ArrayList<ApplicationInfo> apps) {
         final int screenCount = getChildCount();