fix 3029324: Implement new drag feedback

Add "shrinkwrap" icon outlines to visualize where the item will land
diff --git a/src/com/android/launcher2/CellLayout.java b/src/com/android/launcher2/CellLayout.java
index d4a12fb..5c1f570 100644
--- a/src/com/android/launcher2/CellLayout.java
+++ b/src/com/android/launcher2/CellLayout.java
@@ -18,6 +18,8 @@
 
 import com.android.launcher.R;
 
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
 import android.animation.TimeInterpolator;
 import android.animation.ValueAnimator;
 import android.animation.ValueAnimator.AnimatorUpdateListener;
@@ -25,13 +27,16 @@
 import android.content.Context;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
+import android.graphics.Bitmap;
 import android.graphics.Canvas;
+import android.graphics.Paint;
 import android.graphics.Point;
 import android.graphics.PointF;
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.graphics.drawable.Drawable;
 import android.util.AttributeSet;
+import android.util.Log;
 import android.view.ContextMenu;
 import android.view.MotionEvent;
 import android.view.View;
@@ -86,17 +91,15 @@
 
     private final Point mDragCenter = new Point();
 
-    private Drawable mDragRectDrawable;
-
     // These arrays are used to implement the drag visualization on x-large screens.
-    // They are used as circular arrays, indexed by mDragRectCurrent.
-    private Rect[] mDragRects = new Rect[8];
-    private int[] mDragRectAlphas = new int[mDragRects.length];
-    private InterruptibleInOutAnimator[] mDragRectAnims =
-            new InterruptibleInOutAnimator[mDragRects.length];
+    // They are used as circular arrays, indexed by mDragOutlineCurrent.
+    private Point[] mDragOutlines = new Point[8];
+    private int[] mDragOutlineAlphas = new int[mDragOutlines.length];
+    private InterruptibleInOutAnimator[] mDragOutlineAnims =
+            new InterruptibleInOutAnimator[mDragOutlines.length];
 
     // Used as an index into the above 3 arrays; indicates which is the most current value.
-    private int mDragRectCurrent = 0;
+    private int mDragOutlineCurrent = 0;
 
     private Drawable mCrosshairsDrawable = null;
     private InterruptibleInOutAnimator mCrosshairsAnimator = null;
@@ -107,6 +110,8 @@
 
     private final WallpaperManager mWallpaperManager;
 
+    private boolean mDragging = false;
+
     public CellLayout(Context context) {
         this(context, null);
     }
@@ -164,7 +169,6 @@
 
         // Initialize the data structures used for the drag visualization.
 
-        mDragRectDrawable = res.getDrawable(R.drawable.rounded_rect_green);
         mCrosshairsDrawable = res.getDrawable(R.drawable.gardening_crosshairs);
         TimeInterpolator interp = new DecelerateInterpolator(2.5f); // Quint ease out
 
@@ -179,8 +183,8 @@
         });
         mCrosshairsAnimator.setInterpolator(interp);
 
-        for (int i = 0; i < mDragRects.length; i++) {
-            mDragRects[i] = new Rect();
+        for (int i = 0; i < mDragOutlines.length; i++) {
+            mDragOutlines[i] = new Point(-1, -1);
         }
 
         // When dragging things around the home screens, we show a green outline of
@@ -190,18 +194,45 @@
         final int duration = res.getInteger(R.integer.config_dragOutlineFadeTime);
         final int fromAlphaValue = 0;
         final int toAlphaValue = res.getInteger(R.integer.config_dragOutlineMaxAlpha);
-        for (int i = 0; i < mDragRectAnims.length; i++) {
+
+        for (int i = 0; i < mDragOutlineAlphas.length; i++) {
+            mDragOutlineAlphas[i] = fromAlphaValue;
+        }
+
+        for (int i = 0; i < mDragOutlineAnims.length; i++) {
             final InterruptibleInOutAnimator anim =
                 new InterruptibleInOutAnimator(duration, fromAlphaValue, toAlphaValue);
             anim.setInterpolator(interp);
+
             final int thisIndex = i;
             anim.addUpdateListener(new AnimatorUpdateListener() {
                 public void onAnimationUpdate(ValueAnimator animation) {
-                    mDragRectAlphas[thisIndex] = (Integer) animation.getAnimatedValue();
-                    CellLayout.this.invalidate(mDragRects[thisIndex]);
+                    final Bitmap outline = (Bitmap)anim.getTag();
+
+                    // If an animation is started and then stopped very quickly, we can still
+                    // get spurious updates we've cleared the tag. Guard against this.
+                    if (outline == null) {
+                        // Try to prevent it from continuing to run
+                        animation.cancel();
+                    } else {
+                        mDragOutlineAlphas[thisIndex] = (Integer) animation.getAnimatedValue();
+                        final int left = mDragOutlines[thisIndex].x;
+                        final int top = mDragOutlines[thisIndex].y;
+                        CellLayout.this.invalidate(left, top,
+                                left + outline.getWidth(), top + outline.getHeight());
+                    }
                 }
             });
-            mDragRectAnims[i] = anim;
+            // The animation holds a reference to the drag outline bitmap as long is it's
+            // running. This way the bitmap can be GCed when the animations are complete.
+            anim.addListener(new AnimatorListenerAdapter() {
+                public void onAnimationEnd(Animator animation) {
+                    if ((Integer) anim.getAnimatedValue() == 0) {
+                        anim.setTag(null);
+                    }
+                }
+            });
+            mDragOutlineAnims[i] = anim;
         }
     }
 
@@ -270,14 +301,16 @@
                 }
                 x += mCellWidth + mWidthGap;
             }
+        }
 
-            for (int i = 0; i < mDragRects.length; i++) {
-                int alpha = mDragRectAlphas[i];
-                if (alpha > 0) {
-                    mDragRectDrawable.setAlpha(alpha);
-                    mDragRectDrawable.setBounds(mDragRects[i]);
-                    mDragRectDrawable.draw(canvas);
-                }
+        final Paint paint = new Paint();
+        for (int i = 0; i < mDragOutlines.length; i++) {
+            final int alpha = mDragOutlineAlphas[i];
+            if (alpha > 0) {
+                final Point p = mDragOutlines[i];
+                final Bitmap b = (Bitmap) mDragOutlineAnims[i].getTag();
+                paint.setAlpha(alpha);
+                canvas.drawBitmap(b, p.x, p.y, paint);
             }
         }
     }
@@ -796,35 +829,40 @@
         result[1] = Math.max(0, result[1]); // Snap to top
     }
 
-    void visualizeDropLocation(View view, int originX, int originY, int spanX, int spanY) {
-        final int[] nearest = findNearestVacantArea(originX, originY, spanX, spanY, view, mDragCell);
-        mDragCenter.set(originX + (view.getWidth() / 2), originY + (view.getHeight() / 2));
+    void visualizeDropLocation(
+            View v, Bitmap dragOutline, int originX, int originY, int spanX, int spanY) {
+
+        final int[] nearest = findNearestVacantArea(originX, originY, spanX, spanY, v, mDragCell);
+        mDragCenter.set(originX + (v.getWidth() / 2), originY + (v.getHeight() / 2));
 
         if (nearest != null) {
             // Find the top left corner of the rect the object will occupy
             final int[] topLeft = mTmpPoint;
             cellToPoint(nearest[0], nearest[1], topLeft);
 
-            // Need to copy these, because the next call to cellToPoint will overwrite them
-            final int left = topLeft[0];
-            final int top = topLeft[1];
+            int left = topLeft[0];
+            int top = topLeft[1];
 
-            final Rect dragRect = mDragRects[mDragRectCurrent];
+            if (v.getParent() instanceof CellLayout) {
+                LayoutParams lp = (LayoutParams) v.getLayoutParams();
+                left += lp.leftMargin;
+                top += lp.topMargin;
+            }
 
-            if (dragRect.isEmpty() || left != dragRect.left || top != dragRect.top) {
-                // Now find the bottom right
-                final int[] bottomRight = mTmpPoint;
-                cellToPoint(nearest[0] + spanX - 1, nearest[1] + spanY - 1, bottomRight);
-                bottomRight[0] += mCellWidth;
-                bottomRight[1] += mCellHeight;
+            // Offsets due to the size difference between the View and the dragOutline
+            left += (v.getWidth() - dragOutline.getWidth()) / 2;
+            top += (v.getHeight() - dragOutline.getHeight()) / 2;
 
-                final int oldIndex = mDragRectCurrent;
-                mDragRectCurrent = (oldIndex + 1) % mDragRects.length;
+            final int oldIndex = mDragOutlineCurrent;
+            final Point lastPoint = mDragOutlines[oldIndex];
+            if (lastPoint.x != left || lastPoint.y != top) {
+                mDragOutlineCurrent = (oldIndex + 1) % mDragOutlines.length;
 
-                mDragRects[mDragRectCurrent].set(left, top, bottomRight[0], bottomRight[1]);
+                mDragOutlines[mDragOutlineCurrent].set(left, top);
 
-                mDragRectAnims[oldIndex].animateOut();
-                mDragRectAnims[mDragRectCurrent].animateIn();
+                mDragOutlineAnims[oldIndex].animateOut();
+                mDragOutlineAnims[mDragOutlineCurrent].setTag(dragOutline);
+                mDragOutlineAnims[mDragOutlineCurrent].animateIn();
             }
         }
 
@@ -1035,20 +1073,29 @@
      * Called when drag has left this CellLayout or has been completed (successfully or not)
      */
     void onDragExit() {
-        // Invalidate the drag data
-        mDragCell[0] = -1;
-        mDragCell[1] = -1;
+        // This can actually be called when we aren't in a drag, e.g. when adding a new
+        // item to this layout via the customize drawer.
+        // Guard against that case.
+        if (mDragging) {
+            mDragging = false;
 
-        setHover(false);
+            // Invalidate the drag data
+            mDragCell[0] = -1;
+            mDragCell[1] = -1;
 
-        // Fade out the drag indicators
-        if (mCrosshairsAnimator != null) {
-            mCrosshairsAnimator.animateOut();
+            setHover(false);
+
+            // Fade out the drag indicators
+            if (mCrosshairsAnimator != null) {
+                mCrosshairsAnimator.animateOut();
+            }
+
+            final int prev = mDragOutlineCurrent;
+            mDragOutlineAnims[prev].animateOut();
+            mDragOutlineCurrent = (prev + 1) % mDragOutlines.length;
+            mDragOutlines[mDragOutlineCurrent].set(-1, -1);
+            mDragOutlineAlphas[mDragOutlineCurrent] = 0;
         }
-
-        mDragRectAnims[mDragRectCurrent].animateOut();
-        mDragRectCurrent = (mDragRectCurrent + 1) % mDragRects.length;
-        mDragRects[mDragRectCurrent].setEmpty();
     }
 
     /**
@@ -1091,6 +1138,11 @@
      * or it may have begun on another layout.
      */
     void onDragEnter(View dragView) {
+        if (mDragging) {
+//            Log.d(TAG, "Received onDragEnter while drag still active");
+        }
+        mDragging = true;
+
         // Fade in the drag indicators
         if (mCrosshairsAnimator != null) {
             mCrosshairsAnimator.animateIn();
diff --git a/src/com/android/launcher2/HolographicOutlineHelper.java b/src/com/android/launcher2/HolographicOutlineHelper.java
index 9e61b79..ea4b01a 100644
--- a/src/com/android/launcher2/HolographicOutlineHelper.java
+++ b/src/com/android/launcher2/HolographicOutlineHelper.java
@@ -19,26 +19,42 @@
 import android.graphics.Bitmap;
 import android.graphics.BlurMaskFilter;
 import android.graphics.Canvas;
+import android.graphics.MaskFilter;
 import android.graphics.Paint;
 import android.graphics.PorterDuff;
 import android.graphics.PorterDuffXfermode;
+import android.graphics.Rect;
+import android.graphics.TableMaskFilter;
 
 public class HolographicOutlineHelper {
     private final Paint mHolographicPaint = new Paint();
-    private final Paint mExpensiveBlurPaint = new Paint();
+    private final Paint mBlurPaint = new Paint();
     private final Paint mErasePaint = new Paint();
 
-    private static final BlurMaskFilter mThickOuterBlurMaskFilter = new BlurMaskFilter(6.0f,
-            BlurMaskFilter.Blur.OUTER);
-    private static final BlurMaskFilter mThinOuterBlurMaskFilter = new BlurMaskFilter(1.0f,
-            BlurMaskFilter.Blur.OUTER);
-    private static final BlurMaskFilter mThickInnerBlurMaskFilter = new BlurMaskFilter(4.0f,
-            BlurMaskFilter.Blur.NORMAL);
+    private static final BlurMaskFilter sLargeGlowBlurMaskFilter = new BlurMaskFilter(
+            10.0f, BlurMaskFilter.Blur.OUTER);
+    private static final BlurMaskFilter sThickOuterBlurMaskFilter = new BlurMaskFilter(
+            6.0f, BlurMaskFilter.Blur.OUTER);
+    private static final BlurMaskFilter sMediumOuterBlurMaskFilter = new BlurMaskFilter(
+            2.0f, BlurMaskFilter.Blur.OUTER);
+    private static final BlurMaskFilter sThinOuterBlurMaskFilter = new BlurMaskFilter(
+            1.0f, BlurMaskFilter.Blur.OUTER);
+
+    private static final BlurMaskFilter sThickInnerBlurMaskFilter = new BlurMaskFilter(
+            4.0f, BlurMaskFilter.Blur.NORMAL);
+    private static final BlurMaskFilter sThinInnerBlurMaskFilter = new BlurMaskFilter(
+            1.0f, BlurMaskFilter.Blur.INNER);
+
+    private static final MaskFilter sFineClipTable = TableMaskFilter.CreateClipTable(0, 20);
+    private static final MaskFilter sCoarseClipTable = TableMaskFilter.CreateClipTable(0, 200);
+
+    private int[] mTempOffset = new int[2];
 
     HolographicOutlineHelper() {
         mHolographicPaint.setFilterBitmap(true);
         mHolographicPaint.setAntiAlias(true);
-        mExpensiveBlurPaint.setFilterBitmap(true);
+        mBlurPaint.setFilterBitmap(true);
+        mBlurPaint.setAntiAlias(true);
         mErasePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
         mErasePaint.setFilterBitmap(true);
         mErasePaint.setAntiAlias(true);
@@ -65,6 +81,51 @@
         }
     }
 
+    void applyGlow(Bitmap bitmap, Canvas canvas, int color) {
+        mBlurPaint.setMaskFilter(sThickOuterBlurMaskFilter);
+        Bitmap glow = bitmap.extractAlpha(mBlurPaint, mTempOffset);
+
+        // Use the clip table to make the glow heavier closer to the outline
+        mHolographicPaint.setMaskFilter(sCoarseClipTable);
+        mHolographicPaint.setAlpha(150);
+        mHolographicPaint.setColor(color);
+        canvas.drawBitmap(glow, mTempOffset[0], mTempOffset[1], mHolographicPaint);
+        glow.recycle();
+    }
+
+    /**
+     * Draws a solid outline around a bitmap, erasing the original pixels.
+     *
+     * @param bitmap The bitmap to modify
+     * @param canvas A canvas on the bitmap
+     * @param color The color to draw the outline and glow in
+     * @param removeOrig If true, punch out the original pixels to just leave the outline
+     */
+    void applyExpensiveOuterOutline(Bitmap bitmap, Canvas canvas, int color, boolean removeOrig) {
+        Bitmap originalImage = null;
+        if (removeOrig) {
+            originalImage = bitmap.extractAlpha();
+        }
+
+        // Compute an outer blur on the original bitmap
+        mBlurPaint.setMaskFilter(sMediumOuterBlurMaskFilter);
+        Bitmap outline = bitmap.extractAlpha(mBlurPaint, mTempOffset);
+
+        // Paint the blurred bitmap back into the canvas. Using the clip table causes any alpha
+        // pixels above a certain threshold to be rounded up to be fully opaque. This gives the
+        // effect of a thick outline, with a slight blur on the edge
+        mHolographicPaint.setColor(color);
+        mHolographicPaint.setMaskFilter(sFineClipTable);
+        canvas.drawBitmap(outline, mTempOffset[0], mTempOffset[1], mHolographicPaint);
+        outline.recycle();
+
+        if (removeOrig) {
+            // Finally, punch out the original pixels, leaving just the outline
+            canvas.drawBitmap(originalImage, 0, 0, mErasePaint);
+            originalImage.recycle();
+        }
+    }
+
     /**
      * Applies a more expensive and accurate outline to whatever is currently drawn in a specified
      * bitmap.
@@ -72,18 +133,18 @@
     void applyExpensiveOutlineWithBlur(Bitmap srcDst, Canvas srcDstCanvas, int color,
             int outlineColor) {
         // calculate the outer blur first
-        mExpensiveBlurPaint.setMaskFilter(mThickOuterBlurMaskFilter);
+        mBlurPaint.setMaskFilter(sThickOuterBlurMaskFilter);
         int[] outerBlurOffset = new int[2];
-        Bitmap thickOuterBlur = srcDst.extractAlpha(mExpensiveBlurPaint, outerBlurOffset);
-        mExpensiveBlurPaint.setMaskFilter(mThinOuterBlurMaskFilter);
+        Bitmap thickOuterBlur = srcDst.extractAlpha(mBlurPaint, outerBlurOffset);
+        mBlurPaint.setMaskFilter(sThinOuterBlurMaskFilter);
         int[] thinOuterBlurOffset = new int[2];
-        Bitmap thinOuterBlur = srcDst.extractAlpha(mExpensiveBlurPaint, thinOuterBlurOffset);
+        Bitmap thinOuterBlur = srcDst.extractAlpha(mBlurPaint, thinOuterBlurOffset);
 
         // calculate the inner blur
         srcDstCanvas.drawColor(0xFF000000, PorterDuff.Mode.SRC_OUT);
-        mExpensiveBlurPaint.setMaskFilter(mThickInnerBlurMaskFilter);
+        mBlurPaint.setMaskFilter(sThickInnerBlurMaskFilter);
         int[] thickInnerBlurOffset = new int[2];
-        Bitmap thickInnerBlur = srcDst.extractAlpha(mExpensiveBlurPaint, thickInnerBlurOffset);
+        Bitmap thickInnerBlur = srcDst.extractAlpha(mBlurPaint, thickInnerBlurOffset);
 
         // mask out the inner blur
         srcDstCanvas.setBitmap(thickInnerBlur);
diff --git a/src/com/android/launcher2/InterruptibleInOutAnimator.java b/src/com/android/launcher2/InterruptibleInOutAnimator.java
index fb07284..920aa43 100644
--- a/src/com/android/launcher2/InterruptibleInOutAnimator.java
+++ b/src/com/android/launcher2/InterruptibleInOutAnimator.java
@@ -16,6 +16,8 @@
 
 package com.android.launcher2;
 
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
 import android.animation.ValueAnimator;
 import android.util.Log;
 
@@ -31,6 +33,10 @@
     private Object mOriginalFromValue;
     private Object mOriginalToValue;
 
+    private boolean mFirstRun = true;
+
+    private Object mTag = null;
+
     public InterruptibleInOutAnimator(long duration, Object fromValue, Object toValue) {
         super(duration, fromValue, toValue);
         mOriginalDuration = duration;
@@ -38,14 +44,17 @@
         mOriginalToValue = toValue;
     }
 
-    private void animate(Object fromValue, Object toValue) {
+    private void animateTo(Object toValue) {
         // This only makes sense when it's running in the opposite direction, or stopped.
         setDuration(mOriginalDuration - getCurrentPlayTime());
 
-        final Object startValue = isRunning() ? getAnimatedValue() : fromValue;
+        final Object startValue = mFirstRun ? mOriginalFromValue : getAnimatedValue();
         cancel();
-        setValues(startValue, toValue);
-        start();
+        if (startValue != toValue) {
+            setValues(startValue, toValue);
+            start();
+            mFirstRun = false;
+        }
     }
 
     /**
@@ -54,7 +63,7 @@
      * direction and animate for a correspondingly shorter duration.
      */
     public void animateIn() {
-        animate(mOriginalFromValue, mOriginalToValue);
+        animateTo(mOriginalToValue);
     }
 
     /**
@@ -64,6 +73,14 @@
      * direction and animate for a correspondingly shorter duration.
      */
     public void animateOut() {
-        animate(mOriginalToValue, mOriginalFromValue);
+        animateTo(mOriginalFromValue);
+    }
+
+    public void setTag(Object tag) {
+        mTag = tag;
+    }
+
+    public Object getTag() {
+        return mTag;
     }
 }
diff --git a/src/com/android/launcher2/Workspace.java b/src/com/android/launcher2/Workspace.java
index 0d07048..75e39e0 100644
--- a/src/com/android/launcher2/Workspace.java
+++ b/src/com/android/launcher2/Workspace.java
@@ -33,9 +33,12 @@
 import android.content.pm.ProviderInfo;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
+import android.graphics.Bitmap;
 import android.graphics.Canvas;
+import android.graphics.Color;
 import android.graphics.Matrix;
 import android.graphics.Rect;
+import android.graphics.Region.Op;
 import android.graphics.drawable.Drawable;
 import android.net.Uri;
 import android.os.IBinder;
@@ -136,6 +139,11 @@
 
     private boolean mInScrollArea = false;
 
+    private HolographicOutlineHelper mOutlineHelper = new HolographicOutlineHelper();
+    private Bitmap mDragOutline = null;
+    private Rect mTempRect = new Rect();
+    private int[] mTempXY = new int[2];
+
     /**
      * Used to inflate the Workspace from XML.
      *
@@ -921,8 +929,70 @@
         }
     }
 
+    /**
+     * Draw the View v into the given Canvas.
+     *
+     * @param v the view to draw
+     * @param destCanvas the canvas to draw on
+     * @param padding the horizontal and vertical padding to use when drawing
+     */
+    private void drawDragView(View v, Canvas destCanvas, int padding) {
+        final Rect clipRect = mTempRect;
+        v.getDrawingRect(clipRect);
+
+        // For a TextView, adjust the clip rect so that we don't include the text label
+        if (v instanceof TextView) {
+            final int iconHeight = ((TextView)v).getCompoundPaddingTop() - v.getPaddingTop();
+            clipRect.bottom = clipRect.top + iconHeight;
+        }
+
+        // Draw the View into the bitmap.
+        // The translate of scrollX and scrollY is necessary when drawing TextViews, because
+        // they set scrollX and scrollY to large values to achieve centered text
+
+        destCanvas.save();
+        destCanvas.translate(-v.getScrollX() + padding / 2, -v.getScrollY() + padding / 2);
+        destCanvas.clipRect(clipRect, Op.REPLACE);
+        v.draw(destCanvas);
+        destCanvas.restore();
+    }
+
+    /**
+     * Returns a new bitmap to be used as the object outline, e.g. to visualize the drop location.
+     * Responsibility for the bitmap is transferred to the caller.
+     */
+    private Bitmap createDragOutline(View v, Canvas canvas, int padding) {
+        final int outlineColor = getResources().getColor(R.color.drag_outline_color);
+        final Bitmap b = Bitmap.createBitmap(
+                v.getWidth() + padding, v.getHeight() + padding, Bitmap.Config.ARGB_8888);
+
+        canvas.setBitmap(b);
+        drawDragView(v, canvas, padding);
+        mOutlineHelper.applyExpensiveOuterOutline(b, canvas, outlineColor, true);
+
+        return b;
+    }
+
+    /**
+     * Returns a new bitmap to show when the given View is being dragged around.
+     * Responsibility for the bitmap is transferred to the caller.
+     */
+    private Bitmap createDragBitmap(View v, Canvas canvas, int padding) {
+        final int outlineColor = getResources().getColor(R.color.drag_outline_color);
+        final Bitmap b = Bitmap.createBitmap(
+                mDragOutline.getWidth(), mDragOutline.getHeight(), Bitmap.Config.ARGB_8888);
+
+        canvas.setBitmap(b);
+        canvas.drawBitmap(mDragOutline, 0, 0, null);
+        drawDragView(v, canvas, padding);
+        mOutlineHelper.applyGlow(b, canvas, outlineColor);
+
+        return b;
+    }
+
     void startDrag(CellLayout.CellInfo cellInfo) {
         View child = cellInfo.cell;
+        final int blurPadding = 40;
 
         // Make sure the drag was started by a long press as opposed to a long click.
         if (!child.isInTouchMode()) {
@@ -935,9 +1005,29 @@
         CellLayout current = ((CellLayout) getChildAt(mCurrentPage));
 
         current.onDragChild(child);
-        mDragController.startDrag(child, this, child.getTag(), DragController.DRAG_ACTION_MOVE);
+
+        child.clearFocus();
+        child.setPressed(false);
+
+        final Canvas canvas = new Canvas();
+
+        // The outline is used to visualize where the item will land if dropped
+        mDragOutline = createDragOutline(child, canvas, blurPadding);
+
+        // The drag bitmap follows the touch point around on the screen
+        final Bitmap b = createDragBitmap(child, canvas, blurPadding);
+
+        final int bmpWidth = b.getWidth();
+        final int bmpHeight = b.getHeight();
+        child.getLocationOnScreen(mTempXY);
+        final int screenX = (int) mTempXY[0] + (child.getWidth() - bmpWidth) / 2;
+        final int screenY = (int) mTempXY[1] + (child.getHeight() - bmpHeight) / 2;
+        mDragController.startDrag(b, screenX, screenY, 0, 0, bmpWidth, bmpHeight, this,
+                child.getTag(), DragController.DRAG_ACTION_MOVE, null);
+        b.recycle();
+
         current.onDragEnter(child);
-        invalidate();
+        child.setVisibility(View.GONE);
     }
 
     void addApplicationShortcut(ShortcutInfo info, int screen, int cellX, int cellY,
@@ -1280,7 +1370,7 @@
                 int localOriginX = originX - (mDragTargetLayout.getLeft() - mScrollX);
                 int localOriginY = originY - (mDragTargetLayout.getTop() - mScrollY);
                 mDragTargetLayout.visualizeDropLocation(
-                        child, localOriginX, localOriginY, item.spanX, item.spanY);
+                        child, mDragOutline, localOriginX, localOriginY, item.spanX, item.spanY);
             }
         }
     }
@@ -1320,6 +1410,8 @@
     }
 
     // Drag from somewhere else
+    // NOTE: This can also be called when we are outside of a drag event, when we want
+    // to add an item to one of the workspace screens.
     private void onDropExternal(int x, int y, Object dragInfo,
             CellLayout cellLayout, boolean insertAtFirst) {
         int screen = indexOfChild(cellLayout);
@@ -1481,6 +1573,10 @@
             }
         }
 
+        if (mDragInfo != null) {
+            mDragInfo.cell.setVisibility(View.VISIBLE);
+        }
+        mDragOutline = null;
         mDragInfo = null;
     }