Animate items into place when dropping on home screen
diff --git a/src/com/android/launcher2/CellLayout.java b/src/com/android/launcher2/CellLayout.java
index 29ff679..c9be887 100644
--- a/src/com/android/launcher2/CellLayout.java
+++ b/src/com/android/launcher2/CellLayout.java
@@ -119,6 +119,9 @@
 
     private boolean mDragging = false;
 
+    private ObjectAnimator mDropAnim;
+    private TimeInterpolator mEaseOutInterpolator;
+
     public CellLayout(Context context) {
         this(context, null);
     }
@@ -177,7 +180,7 @@
         // Initialize the data structures used for the drag visualization.
 
         mCrosshairsDrawable = res.getDrawable(R.drawable.gardening_crosshairs);
-        TimeInterpolator interp = new DecelerateInterpolator(2.5f); // Quint ease out
+        mEaseOutInterpolator = new DecelerateInterpolator(2.5f); // Quint ease out
 
         // Set up the animation for fading the crosshairs in and out
         int animDuration = res.getInteger(R.integer.config_crosshairsFadeInTime);
@@ -188,7 +191,7 @@
                 CellLayout.this.invalidate();
             }
         });
-        mCrosshairsAnimator.getAnimator().setInterpolator(interp);
+        mCrosshairsAnimator.getAnimator().setInterpolator(mEaseOutInterpolator);
 
         for (int i = 0; i < mDragOutlines.length; i++) {
             mDragOutlines[i] = new Point(-1, -1);
@@ -209,7 +212,7 @@
         for (int i = 0; i < mDragOutlineAnims.length; i++) {
             final InterruptibleInOutAnimator anim =
                 new InterruptibleInOutAnimator(duration, fromAlphaValue, toAlphaValue);
-            anim.getAnimator().setInterpolator(interp);
+            anim.getAnimator().setInterpolator(mEaseOutInterpolator);
             final int thisIndex = i;
             anim.getAnimator().addUpdateListener(new AnimatorUpdateListener() {
                 public void onAnimationUpdate(ValueAnimator animation) {
@@ -245,6 +248,10 @@
             });
             mDragOutlineAnims[i] = anim;
         }
+
+        mDropAnim = new ObjectAnimator();
+        mDropAnim.setInterpolator(mEaseOutInterpolator);
+
         mBackgroundRect = new Rect();
         mHoverRect = new Rect();
         setHoverScale(1.0f);
@@ -752,12 +759,46 @@
         }
     }
 
+    /**
+     * Animate a child of this CellLayout into its current layout position.
+     * The position to animate from is given by the oldX and oldY values in its LayoutParams.
+     */
+    private void animateChildIntoPosition(final View child) {
+        final Resources res = getResources();
+        final ObjectAnimator anim = mDropAnim;
+        final CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
+        final float startX = lp.oldX - lp.x;
+        final float startY = lp.oldY - lp.y;
+
+        // Calculate the duration of the animation based on the object's distance
+        final float dist = (float) Math.sqrt(startX*startX + startY*startY);
+        final float maxDist = (float) res.getInteger(R.integer.config_dropAnimMaxDist);
+        final int duration = (int) (res.getInteger(R.integer.config_dropAnimMaxDuration)
+                * mEaseOutInterpolator.getInterpolation(dist / maxDist));
+
+        anim.cancel(); // Make sure it's not already running
+        anim.setDuration(duration);
+        anim.setTarget(child);
+        anim.setPropertyName("translationX");
+        anim.setFloatValues(startX, 0);
+
+        anim.removeAllUpdateListeners();
+        anim.addUpdateListener(new AnimatorUpdateListener() {
+            public void onAnimationUpdate(ValueAnimator animation) {
+                // Set the value of translationY based on the current x value
+                final float translationX = (Float) anim.getAnimatedValue();
+                child.setTranslationY((startY / startX) * translationX);
+            }
+        });
+        anim.start();
+    }
+
     @Override
     protected void onLayout(boolean changed, int l, int t, int r, int b) {
         int count = getChildCount();
 
         for (int i = 0; i < count; i++) {
-            View child = getChildAt(i);
+            final View child = getChildAt(i);
             if (child.getVisibility() != GONE) {
 
                 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
@@ -774,6 +815,8 @@
                     mWallpaperManager.sendWallpaperCommand(getWindowToken(), "android.home.drop",
                             cellXY[0] + childLeft + lp.width / 2,
                             cellXY[1] + childTop + lp.height / 2, 0, null);
+
+                    animateChildIntoPosition(child);
                 }
             }
         }
@@ -1176,7 +1219,7 @@
     /**
      * Mark a child as having been dropped.
      * At the beginning of the drag operation, the child may have been on another
-     * screen, but it is reparented before this method is called.
+     * screen, but it is re-parented before this method is called.
      *
      * @param child The child that is being dropped
      */
@@ -1185,13 +1228,17 @@
             LayoutParams lp = (LayoutParams) child.getLayoutParams();
             lp.isDragging = false;
             lp.dropped = true;
+            child.setVisibility(View.VISIBLE);
             child.requestLayout();
         }
     }
 
     void onDropAborted(View child) {
         if (child != null) {
-            ((LayoutParams) child.getLayoutParams()).isDragging = false;
+            LayoutParams lp = (LayoutParams) child.getLayoutParams();
+            lp.isDragging = false;
+            child.setVisibility(View.VISIBLE);
+            animateChildIntoPosition(child);
         }
     }
 
@@ -1203,6 +1250,7 @@
     void onDragChild(View child) {
         LayoutParams lp = (LayoutParams) child.getLayoutParams();
         lp.isDragging = true;
+        child.setVisibility(View.GONE);
     }
 
     /**
@@ -1431,6 +1479,18 @@
         @ViewDebug.ExportedProperty
         int y;
 
+        /**
+         * The old X coordinate of this item, relative to its current parent.
+         * Used to animate the item into its new position.
+         */
+        int oldX;
+
+        /**
+         * The old Y coordinate of this item, relative to its current parent.
+         * Used to animate the item into its new position.
+         */
+        int oldY;
+
         boolean dropped;
 
         public LayoutParams(Context c, AttributeSet attrs) {
diff --git a/src/com/android/launcher2/Workspace.java b/src/com/android/launcher2/Workspace.java
index a82cb7f..dd622a6 100644
--- a/src/com/android/launcher2/Workspace.java
+++ b/src/com/android/launcher2/Workspace.java
@@ -19,11 +19,11 @@
 import com.android.launcher.R;
 
 import android.animation.Animator;
+import android.animation.Animator.AnimatorListener;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
 import android.animation.PropertyValuesHolder;
-import android.animation.Animator.AnimatorListener;
 import android.app.WallpaperManager;
 import android.appwidget.AppWidgetManager;
 import android.appwidget.AppWidgetProviderInfo;
@@ -1031,8 +1031,6 @@
         mDragController.startDrag(b, screenX, screenY, 0, 0, bmpWidth, bmpHeight, this,
                 child.getTag(), DragController.DRAG_ACTION_MOVE, null);
         b.recycle();
-
-        child.setVisibility(View.GONE);
     }
 
     void addApplicationShortcut(ShortcutInfo info, int screen, int cellX, int cellY,
@@ -1048,16 +1046,38 @@
                 cellXY[0], cellXY[1]);
     }
 
+    private void setPositionForDropAnimation(
+            View dragView, int dragViewX, int dragViewY, View parent, View child) {
+        final CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
+
+        // Based on the position of the drag view, find the top left of the original view
+        int viewX = dragViewX + (dragView.getWidth() - child.getWidth()) / 2;
+        int viewY = dragViewY + (dragView.getHeight() - child.getHeight()) / 2;
+        viewX -= getResources().getInteger(R.integer.config_dragViewOffsetX);
+        viewY -= getResources().getInteger(R.integer.config_dragViewOffsetY);
+
+        // Set its old pos (in the new parent's coordinates); the CellLayout will
+        // animate it from this position during the next layout pass
+        lp.oldX = viewX - (parent.getLeft() - mScrollX);
+        lp.oldY = viewY - (parent.getTop() - mScrollY);
+    }
 
     public void onDrop(DragSource source, int x, int y, int xOffset, int yOffset,
             DragView dragView, Object dragInfo) {
-        if (mDragTargetLayout == null) {
-            // cancel the drag if we're not over a screen at time of drop
-            // TODO: maybe add a nice fade here?
-            return;
-        }
+
         int originX = x - xOffset;
         int originY = y - yOffset;
+
+        if (mDragTargetLayout == null) {
+            // Cancel the drag if we're not over a screen at time of drop
+            if (mDragInfo != null) {
+                // Set its position so the parent can animate it back
+                final View parent = getChildAt(mDragInfo.screen);
+                setPositionForDropAnimation(dragView, originX, originY, parent, mDragInfo.cell);
+            }
+            return;
+        }
+
         if (mIsSmall || mIsInUnshrinkAnimation) {
             // get originX and originY in the local coordinate system of the screen
             mTempOriginXY[0] = originX;
@@ -1069,37 +1089,38 @@
 
         if (source != this) {
             onDropExternal(originX, originY, dragInfo, mDragTargetLayout);
-        } else {
+        } else if (mDragInfo != null) {
             // Move internally
-            if (mDragInfo != null) {
-                final View cell = mDragInfo.cell;
+            final View cell = mDragInfo.cell;
+            mTargetCell = findNearestVacantArea(originX, originY,
+                    mDragInfo.spanX, mDragInfo.spanY, cell, mDragTargetLayout,
+                    mTargetCell);
 
-                mTargetCell = findNearestVacantArea(originX, originY,
-                        mDragInfo.spanX, mDragInfo.spanY, cell, mDragTargetLayout,
-                        mTargetCell);
-
-                int screen = indexOfChild(mDragTargetLayout);
-                if (screen != mDragInfo.screen) {
-                    final CellLayout originalCellLayout = (CellLayout) getChildAt(mDragInfo.screen);
-                    originalCellLayout.removeView(cell);
-                    addInScreen(cell, screen, mTargetCell[0], mTargetCell[1],
-                            mDragInfo.spanX, mDragInfo.spanY);
-                }
-                mDragTargetLayout.onDropChild(cell);
-
-                // update the item's position after drop
-                final ItemInfo info = (ItemInfo) cell.getTag();
-                CellLayout.LayoutParams lp = (CellLayout.LayoutParams) cell.getLayoutParams();
-                mDragTargetLayout.onMove(cell, mTargetCell[0], mTargetCell[1]);
-                lp.cellX = mTargetCell[0];
-                lp.cellY = mTargetCell[1];
-                cell.setId(LauncherModel.getCellLayoutChildId(-1, mDragInfo.screen,
-                        mTargetCell[0], mTargetCell[1], mDragInfo.spanX, mDragInfo.spanY));
-
-                LauncherModel.moveItemInDatabase(mLauncher, info,
-                        LauncherSettings.Favorites.CONTAINER_DESKTOP, screen,
-                        lp.cellX, lp.cellY);
+            int screen = indexOfChild(mDragTargetLayout);
+            if (screen != mDragInfo.screen) {
+                final CellLayout originalCellLayout = (CellLayout) getChildAt(mDragInfo.screen);
+                originalCellLayout.removeView(cell);
+                addInScreen(cell, screen, mTargetCell[0], mTargetCell[1],
+                        mDragInfo.spanX, mDragInfo.spanY);
             }
+            mDragTargetLayout.onDropChild(cell);
+
+            // update the item's position after drop
+            final ItemInfo info = (ItemInfo) cell.getTag();
+            CellLayout.LayoutParams lp = (CellLayout.LayoutParams) cell.getLayoutParams();
+            mDragTargetLayout.onMove(cell, mTargetCell[0], mTargetCell[1]);
+            lp.cellX = mTargetCell[0];
+            lp.cellY = mTargetCell[1];
+            cell.setId(LauncherModel.getCellLayoutChildId(-1, mDragInfo.screen,
+                    mTargetCell[0], mTargetCell[1], mDragInfo.spanX, mDragInfo.spanY));
+
+            LauncherModel.moveItemInDatabase(mLauncher, info,
+                    LauncherSettings.Favorites.CONTAINER_DESKTOP, screen,
+                    lp.cellX, lp.cellY);
+
+            // Prepare it to be animated into its new position
+            // This must be called after the view has been re-parented
+            setPositionForDropAnimation(dragView, originX, originY, mDragTargetLayout, cell);
         }
     }
 
@@ -1562,16 +1583,10 @@
                 }
                 // final Object tag = mDragInfo.cell.getTag();
             }
-        } else {
-            if (mDragInfo != null) {
-                final CellLayout cellLayout = (CellLayout) getChildAt(mDragInfo.screen);
-                cellLayout.onDropAborted(mDragInfo.cell);
-            }
+        } else if (mDragInfo != null) {
+            ((CellLayout) getChildAt(mDragInfo.screen)).onDropAborted(mDragInfo.cell);
         }
 
-        if (mDragInfo != null) {
-            mDragInfo.cell.setVisibility(View.VISIBLE);
-        }
         mDragOutline = null;
         mDragInfo = null;
     }