Move the drag thing into its own window that goes around on top of everything else.
diff --git a/src/com/android/launcher2/DragController.java b/src/com/android/launcher2/DragController.java
index f8c8077..da2df5c 100644
--- a/src/com/android/launcher2/DragController.java
+++ b/src/com/android/launcher2/DragController.java
@@ -16,14 +16,108 @@
 
 package com.android.launcher2;
 
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.os.IBinder;
+import android.os.Handler;
+import android.os.Vibrator;
+import android.os.SystemClock;
+import android.util.AttributeSet;
+import android.util.Log;
 import android.view.View;
+import android.view.ViewGroup;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+
+import java.util.ArrayList;
 
 /**
- * Interface for initiating a drag within a view or across multiple views.
- *
+ * Class for initiating a drag within a view or across multiple views.
  */
-public interface DragController {
-    
+public class DragController {
+    /** Indicates the drag is a move.  */
+    public static int DRAG_ACTION_MOVE = 0;
+
+    /** Indicates the drag is a copy.  */
+    public static int DRAG_ACTION_COPY = 1;
+
+    private static final int SCROLL_DELAY = 600;
+    private static final int SCROLL_ZONE = 20;
+    private static final int VIBRATE_DURATION = 35;
+
+    private static final boolean PROFILE_DRAWING_DURING_DRAG = false;
+
+    private static final int SCROLL_OUTSIDE_ZONE = 0;
+    private static final int SCROLL_WAITING_IN_ZONE = 1;
+
+    private static final int SCROLL_LEFT = 0;
+    private static final int SCROLL_RIGHT = 1;
+
+    private Context mContext;
+    private Handler mHandler;
+    private final Vibrator mVibrator = new Vibrator();
+
+    // temporaries to avoid gc thrash
+    private Rect mRectTemp = new Rect();
+    private final int[] mCoordinatesTemp = new int[2];
+
+    /** Whether or not we're dragging. */
+    private boolean mDragging;
+
+    /** X coordinate of the down event. */
+    private float mMotionDownX;
+
+    /** Y coordinate of the down event. */
+    private float mMotionDownY;
+
+    /** Original view that is being dragged.  */
+    private View mOriginator;
+
+    /** The contents of mOriginator with no scaling.  */
+    private Bitmap mDragBitmap;
+
+    /** X offset from the upper-left corner of the cell to where we touched.  */
+    private float mTouchOffsetX;
+
+    /** Y offset from the upper-left corner of the cell to where we touched.  */
+    private float mTouchOffsetY;
+
+    /** Where the drag originated */
+    private DragSource mDragSource;
+
+    /** The data associated with the object being dragged */
+    private Object mDragInfo;
+
+    /** The view that moves around while you drag.  */
+    private DragView mDragView;
+
+    /** Who can receive drop events */
+    private ArrayList<DropTarget> mDropTargets = new ArrayList<DropTarget>();
+
+    private DragListener mListener;
+
+    /** The window token used as the parent for the DragView. */
+    private IBinder mWindowToken;
+
+    /** The view that will be scrolled when dragging to the left and right edges of the screen. */
+    private View mScrollView;
+
+    private DragScroller mDragScroller;
+    private int mScrollState = SCROLL_OUTSIDE_ZONE;
+    private ScrollRunnable mScrollRunnable = new ScrollRunnable();
+
+    private RectF mDeleteRegion;
+    private DropTarget mLastDropTarget;
+
+    private InputMethodManager mInputMethodManager;
+
     /**
      * Interface to receive notifications when a drag starts or stops
      */
@@ -47,14 +141,15 @@
     }
     
     /**
-     * Indicates the drag is a move.
+     * Used to create a new DragLayer from XML.
+     *
+     * @param context The application's context.
+     * @param attrs The attribtues set containing the Workspace's customization values.
      */
-    public static int DRAG_ACTION_MOVE = 0;
-
-    /**
-     * Indicates the drag is a copy.
-     */
-    public static int DRAG_ACTION_COPY = 1;
+    public DragController(Context context) {
+        mContext = context;
+        mHandler = new Handler();
+    }
 
     /**
      * Starts a drag
@@ -65,15 +160,355 @@
      * @param dragAction The drag action: either {@link #DRAG_ACTION_MOVE} or
      *        {@link #DRAG_ACTION_COPY}
      */
-    void startDrag(View v, DragSource source, Object info, int dragAction);
-    
+    public void startDrag(View v, DragSource source, Object dragInfo, int dragAction) {
+        if (PROFILE_DRAWING_DURING_DRAG) {
+            android.os.Debug.startMethodTracing("Launcher");
+        }
+
+        // Hide soft keyboard, if visible
+        if (mInputMethodManager == null) {
+            mInputMethodManager = (InputMethodManager)
+                    mContext.getSystemService(Context.INPUT_METHOD_SERVICE);
+        }
+        mInputMethodManager.hideSoftInputFromWindow(mWindowToken, 0);
+
+        if (mListener != null) {
+            mListener.onDragStart(v, source, dragInfo, dragAction);
+        }
+
+        int[] loc = mCoordinatesTemp;
+        v.getLocationOnScreen(loc);
+        int screenX = loc[0];
+        int screenY = loc[1];
+
+        int registrationX = ((int)mMotionDownX) - screenX;
+        int registrationY = ((int)mMotionDownY) - screenY;
+
+        mTouchOffsetX = mMotionDownX - screenX;
+        mTouchOffsetY = mMotionDownY - screenY;
+
+        mDragging = true;
+        mOriginator = v;
+        mDragSource = source;
+        mDragInfo = dragInfo;
+
+        mVibrator.vibrate(VIBRATE_DURATION);
+
+        mDragBitmap = getViewBitmap(v);
+        DragView dragView = mDragView = new DragView(mContext, mDragBitmap,
+                registrationX, registrationY);
+        dragView.show(mWindowToken, (int)mMotionDownX, (int)mMotionDownY);
+
+        if (dragAction == DRAG_ACTION_MOVE) {
+            v.setVisibility(View.GONE);
+        }
+    }
+
+    /**
+     * Draw the view into a bitmap.
+     */
+    private Bitmap getViewBitmap(View v) {
+        v.clearFocus();
+        v.setPressed(false);
+
+        boolean willNotCache = v.willNotCacheDrawing();
+        v.setWillNotCacheDrawing(false);
+
+        // Reset the drawing cache background color to fully transparent
+        // for the duration of this operation
+        int color = v.getDrawingCacheBackgroundColor();
+        v.setDrawingCacheBackgroundColor(0);
+
+        if (color != 0) {
+            v.destroyDrawingCache();
+        }
+        v.buildDrawingCache();
+        Bitmap cacheBitmap = v.getDrawingCache();
+
+        Bitmap bitmap = Bitmap.createBitmap(cacheBitmap);
+
+        // Restore the view
+        v.destroyDrawingCache();
+        v.setWillNotCacheDrawing(willNotCache);
+        v.setDrawingCacheBackgroundColor(color);
+
+        return bitmap;
+    }
+
+    /**
+     * Call this from a drag source view like this:
+     *
+     * <pre>
+     *  @Override
+     *  public boolean dispatchKeyEvent(KeyEvent event) {
+     *      return mDragController.dispatchKeyEvent(this, event)
+     *              || super.dispatchKeyEvent(event);
+     * </pre>
+     */
+    public boolean dispatchKeyEvent(KeyEvent event) {
+        return mDragging;
+    }
+
+    private void endDrag() {
+        if (mDragging) {
+            mDragging = false;
+            if (mOriginator != null) {
+                mOriginator.setVisibility(View.VISIBLE);
+            }
+            if (mListener != null) {
+                mListener.onDragEnd();
+            }
+            if (mDragView != null) {
+                mDragView.remove();
+                mDragView = null;
+            }
+            if (mDragBitmap != null) {
+                mDragBitmap.recycle();
+                mDragBitmap = null;
+            }
+        }
+    }
+
+    /**
+     * Call this from a drag source view.
+     */
+    public boolean onInterceptTouchEvent(MotionEvent ev) {
+        final int action = ev.getAction();
+
+        final float screenX = ev.getRawX();
+        final float screenY = ev.getRawY();
+
+        switch (action) {
+            case MotionEvent.ACTION_MOVE:
+                break;
+
+            case MotionEvent.ACTION_DOWN:
+                // Remember location of down touch
+                mMotionDownX = screenX;
+                mMotionDownY = screenY;
+                mLastDropTarget = null;
+                break;
+
+            case MotionEvent.ACTION_CANCEL:
+            case MotionEvent.ACTION_UP:
+                if (mDragging) {
+                    drop(screenX, screenY);
+                }
+                endDrag();
+                break;
+        }
+
+        return mDragging;
+    }
+
+    /**
+     * Call this from a drag source view.
+     */
+    public boolean onTouchEvent(MotionEvent ev) {
+        View scrollView = mScrollView;
+
+        if (!mDragging) {
+            return false;
+        }
+
+        final int action = ev.getAction();
+        final float x = ev.getRawX();
+        final float y = ev.getRawY();
+
+        switch (action) {
+        case MotionEvent.ACTION_DOWN:
+
+            // Remember where the motion event started
+            mMotionDownX = x;
+            mMotionDownY = y;
+
+            if ((x < SCROLL_ZONE) || (x > scrollView.getWidth() - SCROLL_ZONE)) {
+                mScrollState = SCROLL_WAITING_IN_ZONE;
+                mHandler.postDelayed(mScrollRunnable, SCROLL_DELAY);
+            } else {
+                mScrollState = SCROLL_OUTSIDE_ZONE;
+            }
+
+            break;
+        case MotionEvent.ACTION_MOVE:
+            // Update the drag view.
+            mDragView.move((int)ev.getRawX(), (int)ev.getRawY());
+
+            // Drop on someone?
+            final int[] coordinates = mCoordinatesTemp;
+            DropTarget dropTarget = findDropTarget((int) x, (int) y, coordinates);
+            if (dropTarget != null) {
+                if (mLastDropTarget == dropTarget) {
+                    dropTarget.onDragOver(mDragSource, coordinates[0], coordinates[1],
+                        (int) mTouchOffsetX, (int) mTouchOffsetY, mDragView, mDragInfo);
+                } else {
+                    if (mLastDropTarget != null) {
+                        mLastDropTarget.onDragExit(mDragSource, coordinates[0], coordinates[1],
+                            (int) mTouchOffsetX, (int) mTouchOffsetY, mDragView, mDragInfo);
+                    }
+                    dropTarget.onDragEnter(mDragSource, coordinates[0], coordinates[1],
+                        (int) mTouchOffsetX, (int) mTouchOffsetY, mDragView, mDragInfo);
+                }
+            } else {
+                if (mLastDropTarget != null) {
+                    mLastDropTarget.onDragExit(mDragSource, coordinates[0], coordinates[1],
+                        (int) mTouchOffsetX, (int) mTouchOffsetY, mDragView, mDragInfo);
+                }
+            }
+            mLastDropTarget = dropTarget;
+
+            // Scroll, maybe, but not if we're in the delete region.
+            boolean inDeleteRegion = false;
+            if (mDeleteRegion != null) {
+                inDeleteRegion = mDeleteRegion.contains(ev.getRawX(), ev.getRawY());
+            }
+            if (!inDeleteRegion && x < SCROLL_ZONE) {
+                if (mScrollState == SCROLL_OUTSIDE_ZONE) {
+                    mScrollState = SCROLL_WAITING_IN_ZONE;
+                    mScrollRunnable.setDirection(SCROLL_LEFT);
+                    mHandler.postDelayed(mScrollRunnable, SCROLL_DELAY);
+                }
+            } else if (!inDeleteRegion && x > scrollView.getWidth() - SCROLL_ZONE) {
+                if (mScrollState == SCROLL_OUTSIDE_ZONE) {
+                    mScrollState = SCROLL_WAITING_IN_ZONE;
+                    mScrollRunnable.setDirection(SCROLL_RIGHT);
+                    mHandler.postDelayed(mScrollRunnable, SCROLL_DELAY);
+                }
+            } else {
+                if (mScrollState == SCROLL_WAITING_IN_ZONE) {
+                    mScrollState = SCROLL_OUTSIDE_ZONE;
+                    mScrollRunnable.setDirection(SCROLL_RIGHT);
+                    mHandler.removeCallbacks(mScrollRunnable);
+                }
+            }
+
+            break;
+        case MotionEvent.ACTION_UP:
+            mHandler.removeCallbacks(mScrollRunnable);
+            if (mDragging) {
+                drop(x, y);
+            }
+            endDrag();
+
+            break;
+        case MotionEvent.ACTION_CANCEL:
+            endDrag();
+        }
+
+        return true;
+    }
+
+    private boolean drop(float x, float y) {
+        final int[] coordinates = mCoordinatesTemp;
+        DropTarget dropTarget = findDropTarget((int) x, (int) y, coordinates);
+
+        if (dropTarget != null) {
+            dropTarget.onDragExit(mDragSource, coordinates[0], coordinates[1],
+                    (int) mTouchOffsetX, (int) mTouchOffsetY, mDragView, mDragInfo);
+            if (dropTarget.acceptDrop(mDragSource, coordinates[0], coordinates[1],
+                    (int) mTouchOffsetX, (int) mTouchOffsetY, mDragView, mDragInfo)) {
+                dropTarget.onDrop(mDragSource, coordinates[0], coordinates[1],
+                        (int) mTouchOffsetX, (int) mTouchOffsetY, mDragView, mDragInfo);
+                mDragSource.onDropCompleted((View) dropTarget, true);
+                return true;
+            } else {
+                mDragSource.onDropCompleted((View) dropTarget, false);
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private DropTarget findDropTarget(int x, int y, int[] dropCoordinates) {
+        final Rect r = mRectTemp;
+
+        final ArrayList<DropTarget> dropTargets = mDropTargets;
+        final int count = dropTargets.size();
+        for (int i=count-1; i>=0; i--) {
+            final DropTarget target = dropTargets.get(i);
+            target.getHitRect(r);
+            target.getLocationOnScreen(dropCoordinates);
+            r.offset(dropCoordinates[0] - target.getLeft(), dropCoordinates[1] - target.getTop());
+            if (r.contains(x, y)) {
+                dropCoordinates[0] = x - dropCoordinates[0];
+                dropCoordinates[1] = y - dropCoordinates[1];
+                return target;
+            }
+        }
+        return null;
+    }
+
+    public void setDragScoller(DragScroller scroller) {
+        mDragScroller = scroller;
+    }
+
+    public void setWindowToken(IBinder token) {
+        mWindowToken = token;
+    }
+
     /**
      * Sets the drag listner which will be notified when a drag starts or ends.
      */
-    void setDragListener(DragListener l);
-    
+    public void setDragListener(DragListener l) {
+        mListener = l;
+    }
+
     /**
      * Remove a previously installed drag listener.
      */
-    void removeDragListener(DragListener l);
+    public void removeDragListener(DragListener l) {
+        mListener = null;
+    }
+
+    /**
+     * Add a DropTarget to the list of potential places to receive drop events.
+     */
+    public void addDropTarget(DropTarget target) {
+        mDropTargets.add(target);
+    }
+
+    /**
+     * Don't send drop events to <em>target</em> any more.
+     */
+    public void removeDropTarget(DropTarget target) {
+        mDropTargets.remove(target);
+    }
+
+    /**
+     * Set which view scrolls for touch events near the edge of the screen.
+     */
+    public void setScrollView(View v) {
+        mScrollView = v;
+    }
+
+    /**
+     * Specifies the delete region.  We won't scroll on touch events over the delete region.
+     *
+     * @param region The rectangle in screen coordinates of the delete region.
+     */
+    void setDeleteRegion(RectF region) {
+        mDeleteRegion = region;
+    }
+
+    private class ScrollRunnable implements Runnable {
+        private int mDirection;
+
+        ScrollRunnable() {
+        }
+
+        public void run() {
+            if (mDragScroller != null) {
+                if (mDirection == SCROLL_LEFT) {
+                    mDragScroller.scrollLeft();
+                } else {
+                    mDragScroller.scrollRight();
+                }
+                mScrollState = SCROLL_OUTSIDE_ZONE;
+            }
+        }
+
+        void setDirection(int direction) {
+            mDirection = direction;
+        }
+    }
 }