Initial implementation of folder reordering

Change-Id: I9f700e71f46e3b91afa96742d9e3890d519c391d
diff --git a/src/com/android/launcher2/CellLayout.java b/src/com/android/launcher2/CellLayout.java
index e5aa7a9..a9ba88d 100644
--- a/src/com/android/launcher2/CellLayout.java
+++ b/src/com/android/launcher2/CellLayout.java
@@ -20,6 +20,7 @@
 import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
+import android.animation.PropertyValuesHolder;
 import android.animation.TimeInterpolator;
 import android.animation.ValueAnimator;
 import android.animation.ValueAnimator.AnimatorUpdateListener;
@@ -48,6 +49,7 @@
 import com.android.launcher.R;
 
 import java.util.Arrays;
+import java.util.HashMap;
 
 public class CellLayout extends ViewGroup {
     static final String TAG = "CellLayout";
@@ -118,6 +120,9 @@
     private InterruptibleInOutAnimator mCrosshairsAnimator = null;
     private float mCrosshairsVisibility = 0.0f;
 
+    private HashMap<CellLayout.LayoutParams, ObjectAnimator> mReorderAnimators = new
+            HashMap<CellLayout.LayoutParams, ObjectAnimator>();
+
     // When a drag operation is in progress, holds the nearest cell to the touch point
     private final int[] mDragCell = new int[2];
 
@@ -966,6 +971,64 @@
         return mChildren.getChildAt(x, y);
     }
 
+    public boolean animateChildToPosition(final View child, int cellX, int cellY, int duration) {
+        CellLayoutChildren clc = getChildrenLayout();
+        if (clc.indexOfChild(child) != -1 && !mOccupied[cellX][cellY]) {
+            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+            final ItemInfo info = (ItemInfo) child.getTag();
+
+            // We cancel any existing animations
+            if (mReorderAnimators.containsKey(lp)) {
+                mReorderAnimators.get(lp).cancel();
+                mReorderAnimators.remove(lp);
+            }
+
+            int oldX = lp.x;
+            int oldY = lp.y;
+            mOccupied[lp.cellX][lp.cellY] = false;
+            mOccupied[cellX][cellY] = true;
+
+            lp.isLockedToGrid = true;
+            lp.cellX = info.cellX = cellX;
+            lp.cellY = info.cellY = cellY;
+            clc.setupLp(lp);
+            lp.isLockedToGrid = false;
+            int newX = lp.x;
+            int newY = lp.y;
+
+            PropertyValuesHolder x = PropertyValuesHolder.ofInt("x", oldX, newX);
+            PropertyValuesHolder y = PropertyValuesHolder.ofInt("y", oldY, newY);
+            ObjectAnimator oa = ObjectAnimator.ofPropertyValuesHolder(lp, x, y);
+            oa.setDuration(duration);
+            mReorderAnimators.put(lp, oa);
+            oa.addUpdateListener(new AnimatorUpdateListener() {
+                public void onAnimationUpdate(ValueAnimator animation) {
+                    child.requestLayout();
+                }
+            });
+            oa.addListener(new AnimatorListenerAdapter() {
+                boolean cancelled = false;
+                public void onAnimationEnd(Animator animation) {
+                    // If the animation was cancelled, it means that another animation
+                    // has interrupted this one, and we don't want to lock the item into
+                    // place just yet.
+                    if (!cancelled) {
+                        lp.isLockedToGrid = true;
+                    }
+                    if (mReorderAnimators.containsKey(lp)) {
+                        mReorderAnimators.remove(lp);
+                    }
+                }
+                public void onAnimationCancel(Animator animation) {
+                    cancelled = true;
+                }
+            });
+            oa.start();
+            return true;
+        }
+        return false;
+    }
+
     /**
      * Estimate where the top left cell of the dragged item will land if it is dropped.
      *
diff --git a/src/com/android/launcher2/CellLayoutChildren.java b/src/com/android/launcher2/CellLayoutChildren.java
index 59db9c9..6e78885 100644
--- a/src/com/android/launcher2/CellLayoutChildren.java
+++ b/src/com/android/launcher2/CellLayoutChildren.java
@@ -79,6 +79,10 @@
         setMeasuredDimension(widthSpecSize, heightSpecSize);
     }
 
+    public void setupLp(CellLayout.LayoutParams lp) {
+        lp.setup(mCellWidth, mCellHeight, mWidthGap, mHeightGap);
+    }
+
     public void measureChild(View child) {
         final int cellWidth = mCellWidth;
         final int cellHeight = mCellHeight;
diff --git a/src/com/android/launcher2/DragController.java b/src/com/android/launcher2/DragController.java
index 8ad5c7c..5b1b20a 100644
--- a/src/com/android/launcher2/DragController.java
+++ b/src/com/android/launcher2/DragController.java
@@ -284,6 +284,7 @@
 
         mDragging = true;
 
+        mDragObject.dragComplete = false;
         mDragObject.xOffset = mMotionDownX - (screenX + dragRegionLeft);
         mDragObject.yOffset = mMotionDownY - (screenY + dragRegionTop);
         mDragObject.dragSource = source;
@@ -373,6 +374,7 @@
     public void cancelDrag() {
         if (mDragging) {
             // Should we also be calling onDragExit() here?
+            mDragObject.dragComplete = true;
             mDragObject.dragSource.onDropCompleted(null, mDragObject, false);
         }
         endDrag();
@@ -565,6 +567,7 @@
         mDragObject.y = coordinates[1];
         boolean accepted = false;
         if (dropTarget != null) {
+            mDragObject.dragComplete = true;
             dropTarget.onDragExit(mDragObject);
             if (dropTarget.acceptDrop(mDragObject)) {
                 dropTarget.onDrop(mDragObject);
diff --git a/src/com/android/launcher2/DropTarget.java b/src/com/android/launcher2/DropTarget.java
index 9c0faf3..6e18479 100644
--- a/src/com/android/launcher2/DropTarget.java
+++ b/src/com/android/launcher2/DropTarget.java
@@ -34,6 +34,12 @@
         /** Y offset from the upper-left corner of the cell to where we touched.  */
         public int yOffset = -1;
 
+        /** This indicates whether a drag is in final stages, either drop or cancel. It
+         * differentiates onDragExit, since this is called when the drag is ending, above
+         * the current drag target, or when the drag moves off the current drag object.
+         */
+        public boolean dragComplete = false;
+
         /** The view that moves around while you drag.  */
         public DragView dragView = null;
 
diff --git a/src/com/android/launcher2/Folder.java b/src/com/android/launcher2/Folder.java
index d6be307..d81183c 100644
--- a/src/com/android/launcher2/Folder.java
+++ b/src/com/android/launcher2/Folder.java
@@ -23,6 +23,7 @@
 import android.animation.ValueAnimator;
 import android.animation.ValueAnimator.AnimatorUpdateListener;
 import android.content.Context;
+import android.graphics.Color;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.util.AttributeSet;
@@ -54,11 +55,6 @@
     protected Launcher mLauncher;
 
     protected FolderInfo mInfo;
-    
-    /**
-     * Which item is being dragged
-     */
-    protected ShortcutInfo mDragItem;
 
     private static final String TAG = "Launcher.Folder";
 
@@ -75,6 +71,8 @@
     private int[] mDragItemPosition = new int[2];
     private static final int FULL_GROW = 0;
     private static final int PARTIAL_GROW = 1;
+    private static final int REORDER_ANIMATION_DURATION = 230;
+    private static final int ON_EXIT_CLOSE_DELAY = 800;
     private int mMode = PARTIAL_GROW;
     private boolean mRearrangeOnClose = false;
     private FolderIcon mFolderIcon;
@@ -84,7 +82,14 @@
     private ArrayList<View> mItemsInReadingOrder = new ArrayList<View>();
     private Drawable mIconDrawable;
     boolean mItemsInvalidated = false;
-    ShortcutInfo mCurrentDragInfo;
+    private ShortcutInfo mCurrentDragInfo;
+    private View mCurrentDragView;
+    boolean mSuppressOnAdd = false;
+    private int[] mTargetCell = new int[2];
+    private int[] mPreviousTargetCell = new int[2];
+    private int[] mEmptyCell = new int[2];
+    private Alarm mReorderAlarm = new Alarm();
+    private Alarm mOnExitAlarm = new Alarm();
 
     /**
      * Used to inflate the Workspace from XML.
@@ -136,8 +141,6 @@
     public boolean onLongClick(View v) {
         Object tag = v.getTag();
         if (tag instanceof ShortcutInfo) {
-            mLauncher.closeFolder(this);
-
             ShortcutInfo item = (ShortcutInfo) tag;
             if (!v.isInTouchMode()) {
                 return false;
@@ -150,10 +153,11 @@
             mIconDrawable = ((TextView) v).getCompoundDrawables()[1];
 
             mCurrentDragInfo = item;
-            mItemsInvalidated = true;
-            mInfo.itemsChanged();
-
-            mDragItem = item;
+            mEmptyCell[0] = item.cellX;
+            mEmptyCell[1] = item.cellY;
+            mCurrentDragView = v;
+            mContent.removeView(mCurrentDragView);
+            mInfo.remove(item);
         } else {
             mLauncher.closeFolder(this);
             mLauncher.showRenameDialog(mInfo);
@@ -182,8 +186,6 @@
 
         mDragController.startDrag(view, this, app, DragController.DRAG_ACTION_COPY);
         mLauncher.closeFolder(this);
-        mDragItem = app;
-
         return true;
     }
 
@@ -382,25 +384,6 @@
                     !isFull());
     }
 
-    public void onDrop(DragObject d) {
-        ShortcutInfo item;
-        if (d.dragInfo instanceof ApplicationInfo) {
-            // Came from all apps -- make a copy
-            item = ((ApplicationInfo) d.dragInfo).makeShortcut();
-            item.spanX = 1;
-            item.spanY = 1;
-        } else {
-            item = (ShortcutInfo) d.dragInfo;
-        }
-
-        // Dragged from self onto self
-        if (item == mCurrentDragInfo) {
-            mInfo.remove(item);
-        }
-
-        mInfo.add(item);
-    }
-
     protected boolean findAndSetEmptyCells(ShortcutInfo item) {
         int[] emptyCell = new int[2];
         if (mContent.findCellForSpan(emptyCell, item.spanX, item.spanY)) {
@@ -430,46 +413,135 @@
     }
 
     public void onDragEnter(DragObject d) {
+        mPreviousTargetCell[0] = -1;
+        mPreviousTargetCell[1] = -1;
         mContent.onDragEnter();
+        mOnExitAlarm.cancelAlarm();
+    }
+
+    OnAlarmListener mReorderAlarmListener = new OnAlarmListener() {
+        public void onAlarm(Alarm alarm) {
+            realTimeReorder(mEmptyCell, mTargetCell);
+        }
+    };
+
+    boolean readingOrderGreaterThan(int[] v1, int[] v2) {
+        if (v1[1] > v2[1] || (v1[1] == v2[1] && v1[0] > v2[0])) {
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    private void realTimeReorder(int[] empty, int[] target) {
+        boolean wrap;
+        int startX;
+        int endX;
+        int startY;
+        if (readingOrderGreaterThan(target, empty)) {
+            wrap = empty[0] >= mContent.getCountX() - 1;
+            startY = wrap ? empty[1] + 1 : empty[1];
+            for (int y = startY; y <= target[1]; y++) {
+                startX = y == empty[1] ? empty[0] + 1 : 0;
+                endX = y < target[1] ? mContent.getCountX() - 1 : target[0];
+                for (int x = startX; x <= endX; x++) {
+                    View v = mContent.getChildAt(x,y);
+                    if (mContent.animateChildToPosition(v, empty[0], empty[1],
+                            REORDER_ANIMATION_DURATION)) {
+                        empty[0] = x;
+                        empty[1] = y;
+                    }
+                }
+            }
+        } else {
+            wrap = empty[0] == 0;
+            startY = wrap ? empty[1] - 1 : empty[1];
+            for (int y = startY; y >= target[1]; y--) {
+                startX = y == empty[1] ? empty[0] - 1 : mContent.getCountX() - 1;
+                endX = y > target[1] ? 0 : target[0];
+                for (int x = startX; x >= endX; x--) {
+                    View v = mContent.getChildAt(x,y);
+                    if (mContent.animateChildToPosition(v, empty[0], empty[1],
+                            REORDER_ANIMATION_DURATION)) {
+                        empty[0] = x;
+                        empty[1] = y;
+                    }
+                }
+            }
+        }
     }
 
     public void onDragOver(DragObject d) {
-        float[] r = mapPointFromScreenToContent(d.x, d.y, null);
-        mContent.visualizeDropLocation(null, null, (int) r[0], (int) r[1], 1, 1);
+        float[] r = getDragViewVisualCenter(d.x, d.y, d.xOffset, d.yOffset, d.dragView, null);
+        mTargetCell = mContent.findNearestArea((int) r[0], (int) r[1], 1, 1, mTargetCell);
+
+        if (mTargetCell[0] != mPreviousTargetCell[0] || mTargetCell[1] != mPreviousTargetCell[1]) {
+            mReorderAlarm.cancelAlarm();
+            mReorderAlarm.setOnAlarmListener(mReorderAlarmListener);
+            mReorderAlarm.setAlarm(150);
+            mPreviousTargetCell[0] = mTargetCell[0];
+            mPreviousTargetCell[1] = mTargetCell[1];
+        }
     }
 
+    // This is used to compute the visual center of the dragView. The idea is that
+    // the visual center represents the user's interpretation of where the item is, and hence
+    // is the appropriate point to use when determining drop location.
+    private float[] getDragViewVisualCenter(int x, int y, int xOffset, int yOffset,
+            DragView dragView, float[] recycle) {
+        float res[];
+        if (recycle == null) {
+            res = new float[2];
+        } else {
+            res = recycle;
+        }
+
+        // These represent the visual top and left of drag view if a dragRect was provided.
+        // If a dragRect was not provided, then they correspond to the actual view left and
+        // top, as the dragRect is in that case taken to be the entire dragView.
+        // R.dimen.dragViewOffsetY.
+        int left = x - xOffset;
+        int top = y - yOffset;
+
+        // In order to find the visual center, we shift by half the dragRect
+        res[0] = left + dragView.getDragRegion().width() / 2;
+        res[1] = top + dragView.getDragRegion().height() / 2;
+
+        return res;
+    }
+
+    OnAlarmListener mOnExitAlarmListener = new OnAlarmListener() {
+        public void onAlarm(Alarm alarm) {
+            mLauncher.closeFolder(Folder.this);
+            mCurrentDragInfo = null;
+            mCurrentDragView = null;
+            mSuppressOnAdd = false;
+            mRearrangeOnClose = true;
+        }
+    };
+
     public void onDragExit(DragObject d) {
+        // We only close the folder if this is a true drag exit, ie. not because a drop
+        // has occurred above the folder.
+        if (!d.dragComplete) {
+            mOnExitAlarm.setOnAlarmListener(mOnExitAlarmListener);
+            mOnExitAlarm.setAlarm(ON_EXIT_CLOSE_DELAY);
+        }
+        mReorderAlarm.cancelAlarm();
         mContent.onDragExit();
     }
 
-    public float[] mapPointFromScreenToContent(int x, int y, float[] r) {
-        if (r == null) {
-            r = new float[2];
-        }
-
-        int[] screenLocation = new int[2];
-        mContent.getLocationOnScreen(screenLocation);
-
-        r[0] = x - screenLocation[0];
-        r[1] = y - screenLocation[1];
-        return r;
-    }
-
     public void onDropCompleted(View target, DragObject d, boolean success) {
+        mCurrentDragInfo = null;
+        mCurrentDragView = null;
+        mSuppressOnAdd = false;
         if (!success) {
             if (d.dragView != null) {
                 if (target instanceof CellLayout) {
                     // TODO: we should animate the item back to the folder in this case
                 }
             }
-            mCurrentDragInfo = null;
-            mItemsInvalidated = true;
-            mInfo.itemsChanged();
-        } else {
-            if (target != this) {
-                mInfo.remove(mCurrentDragInfo);
-                mCurrentDragInfo = null;
-            }
+            // TODO: if the drag fails, we need to re-add the item
         }
     }
 
@@ -594,18 +666,6 @@
         mItemsInvalidated = true;
     }
 
-    public void onAdd(ShortcutInfo item) {
-        mItemsInvalidated = true;
-        if (!findAndSetEmptyCells(item)) {
-            // The current layout is full, can we expand it?
-            setupContentForNumItems(getItemCount() + 1);
-            findAndSetEmptyCells(item);
-        }
-        createAndAddShortcut(item);
-        LauncherModel.addOrMoveItemInDatabase(
-                mLauncher, item, mInfo.id, 0, item.cellX, item.cellY);
-    }
-
     public int getItemCount() {
         return mContent.getChildrenLayout().getChildCount();
     }
@@ -621,8 +681,46 @@
         }
     }
 
+    public void onDrop(DragObject d) {
+        ShortcutInfo item;
+        if (d.dragInfo instanceof ApplicationInfo) {
+            // Came from all apps -- make a copy
+            item = ((ApplicationInfo) d.dragInfo).makeShortcut();
+            item.spanX = 1;
+            item.spanY = 1;
+        } else {
+            item = (ShortcutInfo) d.dragInfo;
+        }
+        // Dragged from self onto self
+        if (item == mCurrentDragInfo) {
+            ShortcutInfo si = (ShortcutInfo) mCurrentDragView.getTag();
+            CellLayout.LayoutParams lp = (CellLayout.LayoutParams) mCurrentDragView.getLayoutParams();
+            si.cellX = lp.cellX = mEmptyCell[0];
+            si.cellX = lp.cellY = mEmptyCell[1];
+            mContent.addViewToCellLayout(mCurrentDragView, -1, (int)item.id, lp, true);
+            mSuppressOnAdd = true;
+            mItemsInvalidated = true;
+            setupContentDimension(getItemCount());
+        }
+        mInfo.add(item);
+    }
+
+    public void onAdd(ShortcutInfo item) {
+        mItemsInvalidated = true;
+        if (mSuppressOnAdd) return;
+        if (!findAndSetEmptyCells(item)) {
+            // The current layout is full, can we expand it?
+            setupContentForNumItems(getItemCount() + 1);
+            findAndSetEmptyCells(item);
+        }
+        createAndAddShortcut(item);
+        LauncherModel.addOrMoveItemInDatabase(
+                mLauncher, item, mInfo.id, 0, item.cellX, item.cellY);
+    }
+
     public void onRemove(ShortcutInfo item) {
         mItemsInvalidated = true;
+        if (item == mCurrentDragInfo) return;
         View v = mContent.getChildAt(mDragItemPosition[0], mDragItemPosition[1]);
         mContent.removeView(v);
         if (mState == STATE_ANIMATING) {
diff --git a/src/com/android/launcher2/Workspace.java b/src/com/android/launcher2/Workspace.java
index ff2dede..1e01254 100644
--- a/src/com/android/launcher2/Workspace.java
+++ b/src/com/android/launcher2/Workspace.java
@@ -2555,42 +2555,6 @@
     }
 
     public DropTarget getDropTargetDelegate(DragObject d) {
-
-        if (mIsSmall || mIsInUnshrinkAnimation) {
-            // If we're shrunken, don't let anyone drag on folders/etc that are on the mini-screens
-            return null;
-        }
-        // 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) d.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 = d.x - d.xOffset;
-            dragPointY = d.y - d.yOffset;
-        } else {
-            // Otherwise, use the exact drag coordinates
-            dragPointX = d.x;
-            dragPointY = d.y;
-        }
-        dragPointX += mScrollX - currentLayout.getLeft();
-        dragPointY += mScrollY - currentLayout.getTop();
-
-        // 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(d)) {
-                return target;
-            }
-        }
         return null;
     }
 
@@ -3559,5 +3523,4 @@
     @Override
     public void syncPageItems(int page) {
     }
-
 }