Code to enable dragging to System UI shelf.

This CL doesn’t let the new code run, it’s under a compile-time flag,
and the behavior should be same as before.

The change introduces a concept of a DragDriver, which encapsulates
behavior of the existing DND (InternalDragDriver) or the framework-
driven DND (SystemDragDriver).

An instance of DragDriver gets created by DragController for the time
while a DND operation is in progress, and it takes care of translating
DND events for DragController.

Also did some cleanups, like removing meaningless fields etc.

Plans for future: keep working in Burnaby, and:
* Perhaps, better separate DragDriver from DragController
* Detect fling gesture for system DND
* Look at accessibility
* Polish animations and appearance
* Fix dragging from folders
* Support cancelling DND

Then, it will become possible to enable new dragging (based on Android
version).

Bug: 22028725
Change-Id: I41b40e9d512d83937f5b101ac8e3e8e7e807c269
diff --git a/src/com/android/launcher3/AnotherWindowDropTarget.java b/src/com/android/launcher3/AnotherWindowDropTarget.java
new file mode 100644
index 0000000..aff36ae
--- /dev/null
+++ b/src/com/android/launcher3/AnotherWindowDropTarget.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3;
+
+import android.content.Context;
+import android.graphics.PointF;
+import android.graphics.Rect;
+
+/**
+ * Drop target used when another window (i.e. another process) has accepted a global system drag.
+ * If the accepted item was a shortcut, we delete it from Launcher.
+ */
+public class AnotherWindowDropTarget implements DropTarget {
+    final Launcher mLauncher;
+
+    AnotherWindowDropTarget (Context context) { mLauncher = (Launcher) context; }
+
+    @Override
+    public boolean isDropEnabled() { return true; }
+
+    @Override
+    public void onDrop(DragObject dragObject) {
+        dragObject.deferDragViewCleanupPostAnimation = false;
+        LauncherModel.deleteItemFromDatabase(mLauncher, (ShortcutInfo) dragObject.dragInfo);
+    }
+
+    @Override
+    public void onDragEnter(DragObject dragObject) {}
+
+    @Override
+    public void onDragOver(DragObject dragObject) {}
+
+    @Override
+    public void onDragExit(DragObject dragObject) {}
+
+    @Override
+    public void onFlingToDelete(DragObject dragObject, PointF vec) {}
+
+    @Override
+    public boolean acceptDrop(DragObject dragObject) {
+        return dragObject.dragInfo instanceof ShortcutInfo;
+    }
+
+    @Override
+    public void prepareAccessibilityDrop() {}
+
+    // These methods are implemented in Views
+    @Override
+    public void getHitRectRelativeToDragLayer(Rect outRect) {}
+
+    @Override
+    public void getLocationInDragLayer(int[] loc) {}
+
+    @Override
+    public int getLeft() { return 0; }
+
+    @Override
+    public int getTop() { return 0; }
+}
diff --git a/src/com/android/launcher3/DragController.java b/src/com/android/launcher3/DragController.java
index 410271d..53e90fc 100644
--- a/src/com/android/launcher3/DragController.java
+++ b/src/com/android/launcher3/DragController.java
@@ -26,6 +26,7 @@
 import android.os.Handler;
 import android.os.IBinder;
 import android.util.Log;
+import android.view.DragEvent;
 import android.view.HapticFeedbackConstants;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
@@ -73,8 +74,10 @@
     private final int[] mCoordinatesTemp = new int[2];
     private final boolean mIsRtl;
 
-    /** Whether or not we're dragging. */
-    private boolean mDragging;
+    /**
+     * Drag driver for the current drag/drop operation, or null if there is no active DND operation.
+     */
+    private DragDriver mDragDriver = null;
 
     /** Whether or not this is an accessible drag operation */
     private boolean mIsAccessibleDrag;
@@ -161,10 +164,6 @@
         mIsRtl = Utilities.isRtl(r);
     }
 
-    public boolean dragging() {
-        return mDragging;
-    }
-
     /**
      * Starts a drag.
      *
@@ -234,8 +233,8 @@
         final int dragRegionLeft = dragRegion == null ? 0 : dragRegion.left;
         final int dragRegionTop = dragRegion == null ? 0 : dragRegion.top;
 
-        mDragging = true;
         mIsAccessibleDrag = accessible;
+        mLastDropTarget = null;
 
         mDragObject = new DropTarget.DragObject();
 
@@ -256,6 +255,8 @@
         final DragView dragView = mDragObject.dragView = new DragView(mLauncher, b, registrationX,
                 registrationY, 0, 0, b.getWidth(), b.getHeight(), initialDragViewScale);
 
+        mDragDriver = DragDriver.create(this, dragInfo, dragView);
+
         if (dragOffset != null) {
             dragView.setDragVisualizeOffset(new Point(dragOffset));
         }
@@ -318,18 +319,18 @@
      * </pre>
      */
     public boolean dispatchKeyEvent(KeyEvent event) {
-        return mDragging;
+        return mDragDriver != null;
     }
 
     public boolean isDragging() {
-        return mDragging;
+        return mDragDriver != null;
     }
 
     /**
      * Stop dragging without dropping.
      */
     public void cancelDrag() {
-        if (mDragging) {
+        if (mDragDriver != null) {
             if (mLastDropTarget != null) {
                 mLastDropTarget.onDragExit(mDragObject);
             }
@@ -363,8 +364,8 @@
     }
 
     private void endDrag() {
-        if (mDragging) {
-            mDragging = false;
+        if (mDragDriver != null) {
+            mDragDriver = null;
             mIsAccessibleDrag = false;
             clearScrollRunnable();
             boolean isDeferred = false;
@@ -416,7 +417,7 @@
     }
 
     long getLastGestureUpTime() {
-        if (mDragging) {
+        if (mDragDriver != null) {
             return System.currentTimeMillis();
         } else {
             return mLastTouchUpTime;
@@ -428,14 +429,53 @@
     }
 
     /**
+     * Call this from the drag driver.
+     */
+    public void onDriverDragMove(float x, float y) {
+        final int[] dragLayerPos = getClampedDragLayerPos(x, y);
+
+        handleMoveEvent(dragLayerPos[0], dragLayerPos[1]);
+    }
+
+    /**
+     * Call this from the drag driver.
+     */
+    public void onDriverDragEnd(float x, float y, DropTarget dropTargetOverride) {
+        final int[] dragLayerPos = getClampedDragLayerPos(x, y);
+        final int dragLayerX = dragLayerPos[0];
+        final int dragLayerY = dragLayerPos[1];
+
+        DropTarget dropTarget;
+        PointF vec = null;
+
+        if (dropTargetOverride != null) {
+            dropTarget = dropTargetOverride;
+        } else {
+            vec = isFlingingToDelete(mDragObject.dragSource);
+            if (!DeleteDropTarget.supportsDrop(mDragObject.dragInfo)) {
+                vec = null;
+            }
+            if (vec != null) {
+                dropTarget = mFlingToDeleteDropTarget;
+            } else {
+                dropTarget = findDropTarget((int) x, (int) y, mCoordinatesTemp);
+            }
+        }
+
+        drop(dropTarget, x, y, vec);
+
+        endDrag();
+    }
+
+    /**
      * Call this from a drag source view.
      */
     public boolean onInterceptTouchEvent(MotionEvent ev) {
         @SuppressWarnings("all") // suppress dead code warning
         final boolean debug = false;
         if (debug) {
-            Log.d(Launcher.TAG, "DragController.onInterceptTouchEvent " + ev + " mDragging="
-                    + mDragging);
+            Log.d(Launcher.TAG, "DragController.onInterceptTouchEvent " + ev + " Dragging="
+                    + (mDragDriver != null));
         }
 
         if (mIsAccessibleDrag) {
@@ -451,35 +491,33 @@
         final int dragLayerY = dragLayerPos[1];
 
         switch (action) {
-            case MotionEvent.ACTION_MOVE:
-                break;
             case MotionEvent.ACTION_DOWN:
                 // Remember location of down touch
                 mMotionDownX = dragLayerX;
                 mMotionDownY = dragLayerY;
-                mLastDropTarget = null;
                 break;
             case MotionEvent.ACTION_UP:
                 mLastTouchUpTime = System.currentTimeMillis();
-                if (mDragging) {
-                    PointF vec = isFlingingToDelete(mDragObject.dragSource);
-                    if (!DeleteDropTarget.supportsDrop(mDragObject.dragInfo)) {
-                        vec = null;
-                    }
-                    if (vec != null) {
-                        dropOnFlingToDeleteTarget(dragLayerX, dragLayerY, vec);
-                    } else {
-                        drop(dragLayerX, dragLayerY);
-                    }
-                }
-                endDrag();
-                break;
-            case MotionEvent.ACTION_CANCEL:
-                cancelDrag();
                 break;
         }
 
-        return mDragging;
+        return mDragDriver != null && mDragDriver.onInterceptTouchEvent(ev);
+    }
+
+    /**
+     * Call this from a drag source view.
+     */
+    public boolean onDragEvent(DragEvent event) {
+        return mDragDriver != null && mDragDriver.onDragEvent(event);
+    }
+
+    /**
+     * Call this from a drag view.
+     */
+    public void onDragViewAnimationEnd() {
+        if (mDragDriver != null ) {
+            mDragDriver.onDragViewAnimationEnd();
+        }
     }
 
     /**
@@ -579,7 +617,7 @@
      * Call this from a drag source view.
      */
     public boolean onTouchEvent(MotionEvent ev) {
-        if (!mDragging || mIsAccessibleDrag) {
+        if (mDragDriver == null || mIsAccessibleDrag) {
             return false;
         }
 
@@ -592,47 +630,25 @@
         final int dragLayerY = dragLayerPos[1];
 
         switch (action) {
-        case MotionEvent.ACTION_DOWN:
-            // Remember where the motion event started
-            mMotionDownX = dragLayerX;
-            mMotionDownY = dragLayerY;
+            case MotionEvent.ACTION_DOWN:
+                // Remember where the motion event started
+                mMotionDownX = dragLayerX;
+                mMotionDownY = dragLayerY;
 
-            if ((dragLayerX < mScrollZone) || (dragLayerX > mScrollView.getWidth() - mScrollZone)) {
-                mScrollState = SCROLL_WAITING_IN_ZONE;
-                mHandler.postDelayed(mScrollRunnable, SCROLL_DELAY);
-            } else {
-                mScrollState = SCROLL_OUTSIDE_ZONE;
-            }
-            handleMoveEvent(dragLayerX, dragLayerY);
-            break;
-        case MotionEvent.ACTION_MOVE:
-            handleMoveEvent(dragLayerX, dragLayerY);
-            break;
-        case MotionEvent.ACTION_UP:
-            // Ensure that we've processed a move event at the current pointer location.
-            handleMoveEvent(dragLayerX, dragLayerY);
-            mHandler.removeCallbacks(mScrollRunnable);
-
-            if (mDragging) {
-                PointF vec = isFlingingToDelete(mDragObject.dragSource);
-                if (!DeleteDropTarget.supportsDrop(mDragObject.dragInfo)) {
-                    vec = null;
-                }
-                if (vec != null) {
-                    dropOnFlingToDeleteTarget(dragLayerX, dragLayerY, vec);
+                if ((dragLayerX < mScrollZone) || (dragLayerX > mScrollView.getWidth() - mScrollZone)) {
+                    mScrollState = SCROLL_WAITING_IN_ZONE;
+                    mHandler.postDelayed(mScrollRunnable, SCROLL_DELAY);
                 } else {
-                    drop(dragLayerX, dragLayerY);
+                    mScrollState = SCROLL_OUTSIDE_ZONE;
                 }
-            }
-            endDrag();
-            break;
-        case MotionEvent.ACTION_CANCEL:
-            mHandler.removeCallbacks(mScrollRunnable);
-            cancelDrag();
-            break;
+                break;
+            case MotionEvent.ACTION_UP:
+            case MotionEvent.ACTION_CANCEL:
+                mHandler.removeCallbacks(mScrollRunnable);
+                break;
         }
 
-        return true;
+        return mDragDriver.onTouchEvent(ev);
     }
 
     /**
@@ -642,7 +658,6 @@
     public void prepareAccessibleDrag(int x, int y) {
         mMotionDownX = x;
         mMotionDownY = y;
-        mLastDropTarget = null;
     }
 
     /**
@@ -660,7 +675,7 @@
 
         dropTarget.prepareAccessibilityDrop();
         // Perform the drop
-        drop(location[0], location[1]);
+        drop(dropTarget, location[0], location[1], null);
         endDrag();
     }
 
@@ -690,49 +705,41 @@
         return null;
     }
 
-    private void dropOnFlingToDeleteTarget(float x, float y, PointF vel) {
+    void drop(DropTarget dropTarget, float x, float y, PointF flingVel) {
         final int[] coordinates = mCoordinatesTemp;
 
         mDragObject.x = coordinates[0];
         mDragObject.y = coordinates[1];
 
-        // Clean up dragging on the target if it's not the current fling delete target otherwise,
-        // start dragging to it.
-        if (mLastDropTarget != null && mFlingToDeleteDropTarget != mLastDropTarget) {
-            mLastDropTarget.onDragExit(mDragObject);
+        // Move dragging to the final target.
+        if (dropTarget != mLastDropTarget) {
+            if (mLastDropTarget != null) {
+                mLastDropTarget.onDragExit(mDragObject);
+            }
+            mLastDropTarget = dropTarget;
+            if (dropTarget != null) {
+                dropTarget.onDragEnter(mDragObject);
+            }
         }
 
-        // Drop onto the fling-to-delete target
-        boolean accepted = false;
-        mFlingToDeleteDropTarget.onDragEnter(mDragObject);
-        // We must set dragComplete to true _only_ after we "enter" the fling-to-delete target for
-        // "drop"
         mDragObject.dragComplete = true;
-        mFlingToDeleteDropTarget.onDragExit(mDragObject);
-        if (mFlingToDeleteDropTarget.acceptDrop(mDragObject)) {
-            mFlingToDeleteDropTarget.onFlingToDelete(mDragObject, vel);
-            accepted = true;
-        }
-        mDragObject.dragSource.onDropCompleted((View) mFlingToDeleteDropTarget, mDragObject, true,
-                accepted);
-    }
 
-    private void drop(float x, float y) {
-        final int[] coordinates = mCoordinatesTemp;
-        final DropTarget dropTarget = findDropTarget((int) x, (int) y, coordinates);
-
-        mDragObject.x = coordinates[0];
-        mDragObject.y = coordinates[1];
+        // Drop onto the target.
         boolean accepted = false;
         if (dropTarget != null) {
-            mDragObject.dragComplete = true;
             dropTarget.onDragExit(mDragObject);
             if (dropTarget.acceptDrop(mDragObject)) {
-                dropTarget.onDrop(mDragObject);
+                if (flingVel != null) {
+                    dropTarget.onFlingToDelete(mDragObject, flingVel);
+                } else {
+                    dropTarget.onDrop(mDragObject);
+                }
                 accepted = true;
             }
         }
-        mDragObject.dragSource.onDropCompleted((View) dropTarget, mDragObject, false, accepted);
+        final View dropTargetAsView = dropTarget instanceof View ? (View) dropTarget : null;
+        mDragObject.dragSource.onDropCompleted(
+                dropTargetAsView, mDragObject, flingVel != null, accepted);
     }
 
     private DropTarget findDropTarget(int x, int y, int[] dropCoordinates) {
diff --git a/src/com/android/launcher3/DragDriver.java b/src/com/android/launcher3/DragDriver.java
new file mode 100644
index 0000000..56b7632
--- /dev/null
+++ b/src/com/android/launcher3/DragDriver.java
@@ -0,0 +1,235 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3;
+
+import android.content.ClipData;
+import android.content.Intent;
+import android.graphics.Canvas;
+import android.graphics.Point;
+import android.view.DragEvent;
+import android.view.MotionEvent;
+import android.view.View;
+
+/**
+ * Base class for driving a drag/drop operation.
+ */
+public abstract class DragDriver {
+    protected final DragController mDragController;
+
+    public DragDriver(DragController dragController) {
+        mDragController = dragController;
+    }
+
+    /**
+     * Handles ending of the DragView animation.
+     */
+    public abstract void onDragViewAnimationEnd();
+
+    public abstract boolean onTouchEvent(MotionEvent ev);
+
+    public abstract boolean onDragEvent (DragEvent event);
+
+    public abstract boolean onInterceptTouchEvent(MotionEvent ev);
+
+    public static DragDriver create(
+            DragController dragController, ItemInfo dragInfo, DragView dragView) {
+        // TODO: Replace the hardcoded constant with looking at the API version.
+        final boolean useSystemDrag = false;
+
+        if (useSystemDrag) {
+            return new SystemDragDriver(dragController, dragInfo.getIntent(), dragView);
+        } else {
+            return new InternalDragDriver(dragController);
+        }
+    }
+
+};
+
+/**
+ * Class for driving a system (i.e. framework) drag/drop operation.
+ */
+class SystemDragDriver extends DragDriver {
+    /** Intent associated with the drag operation, or null is there no associated intent.  */
+    private final Intent mDragIntent;
+
+    private final DragView mDragView;
+    boolean mDragging = false;
+    boolean mReceivedDropEvent = false;
+    float mLastX = 0;
+    float mLastY = 0;
+
+    public SystemDragDriver(DragController dragController, Intent dragIntent, DragView dragView) {
+        super(dragController);
+        mDragIntent = dragIntent;
+        mDragView = dragView;
+    }
+
+    private static class ShadowBuilder extends View.DragShadowBuilder {
+        final DragView mDragView;
+
+        public ShadowBuilder(DragView dragView) {
+            mDragView = dragView;
+        }
+
+        @Override
+        public void onProvideShadowMetrics (Point size, Point touch) {
+            mDragView.provideDragShadowMetrics(size, touch);
+        }
+
+        @Override
+        public void onDrawShadow(Canvas canvas) {
+            mDragView.drawDragShadow(canvas);
+        }
+    };
+
+    @Override
+    public void onDragViewAnimationEnd() {
+        // Clip data for the drag operation. If there is an intent, create an intent-based ClipData,
+        // which will be passed to a global DND.
+        // If there is no intent, craft a fake ClipData and start a local DND operation; this
+        // ClipData will be ignored.
+        final ClipData dragData = mDragIntent != null ?
+                ClipData.newIntent("", mDragIntent) :
+                ClipData.newPlainText("", "");
+
+        View.DragShadowBuilder shadowBuilder = new ShadowBuilder(mDragView);
+        // TODO: DND flags are in flux, once settled, use the appropriate constant.
+        final int flags = mDragIntent != null ? 1 : 0;
+
+        mDragging = true;
+
+        if (!mDragView.startDrag(dragData, shadowBuilder, null, flags)) {
+            mDragging = false;
+            mDragController.cancelDrag();
+            return;
+        }
+
+        // Starting from this point, the driver takes over showing the drag shadow, so hiding the
+        // drag view.
+        mDragView.setVisibility(View.INVISIBLE);
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent ev) {
+        return false;
+    }
+
+    @Override
+    public boolean onInterceptTouchEvent(MotionEvent ev) {
+        return false;
+    }
+
+    @Override
+    public boolean onDragEvent (DragEvent event) {
+        if (!mDragging) {
+            // We are interested only in drag events started by this driver.
+            return false;
+        }
+
+        final int action = event.getAction();
+
+        switch (action) {
+            case DragEvent.ACTION_DRAG_STARTED:
+                return true;
+
+            case DragEvent.ACTION_DRAG_ENTERED:
+                return true;
+
+            case DragEvent.ACTION_DRAG_LOCATION:
+                mLastX = event.getX();
+                mLastY = event.getY();
+                mDragController.onDriverDragMove(event.getX(), event.getY());
+                return true;
+
+            case DragEvent.ACTION_DROP:
+                mReceivedDropEvent = true;
+                return true;
+
+            case DragEvent.ACTION_DRAG_EXITED:
+                return true;
+
+            case DragEvent.ACTION_DRAG_ENDED:
+                final boolean dragAccepted = event.getResult();
+                final boolean acceptedByAnotherWindow = dragAccepted && !mReceivedDropEvent;
+
+                // When the system drag ends, its drag shadow disappears. Resume showing the drag
+                // view for the possible final animation.
+                mDragView.setVisibility(View.VISIBLE);
+
+                final DropTarget dropTargetOverride = acceptedByAnotherWindow ?
+                        new AnotherWindowDropTarget(mDragView.getContext()) : null;
+
+                mDragController.onDriverDragEnd(mLastX, mLastY, dropTargetOverride);
+                mDragging = false;
+                return true;
+
+            default:
+                return false;
+        }
+    }
+};
+
+/**
+ * Class for driving an internal (i.e. not using framework) drag/drop operation.
+ */
+class InternalDragDriver extends DragDriver {
+    public InternalDragDriver(DragController dragController) {
+        super(dragController);
+    }
+
+    @Override
+    public void onDragViewAnimationEnd() {}
+
+    @Override
+    public boolean onTouchEvent(MotionEvent ev) {
+        final int action = ev.getAction();
+
+        switch (action) {
+            case MotionEvent.ACTION_MOVE:
+                mDragController.onDriverDragMove(ev.getX(), ev.getY());
+                break;
+            case MotionEvent.ACTION_UP:
+                mDragController.onDriverDragMove(ev.getX(), ev.getY());
+                mDragController.onDriverDragEnd(ev.getX(), ev.getY(), null);
+                break;
+            case MotionEvent.ACTION_CANCEL:
+                mDragController.cancelDrag();
+                break;
+        }
+
+        return true;
+    }
+
+    @Override
+    public boolean onInterceptTouchEvent(MotionEvent ev) {
+        final int action = ev.getAction();
+
+        switch (action) {
+            case MotionEvent.ACTION_UP:
+                mDragController.onDriverDragEnd(ev.getX(), ev.getY(), null);
+                break;
+            case MotionEvent.ACTION_CANCEL:
+                mDragController.cancelDrag();
+                break;
+        }
+
+        return true;
+    }
+
+    @Override
+    public boolean onDragEvent (DragEvent event) { return false; }
+};
diff --git a/src/com/android/launcher3/DragLayer.java b/src/com/android/launcher3/DragLayer.java
index aaa14e6..8445cca 100644
--- a/src/com/android/launcher3/DragLayer.java
+++ b/src/com/android/launcher3/DragLayer.java
@@ -28,6 +28,7 @@
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.util.AttributeSet;
+import android.view.DragEvent;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.view.View;
@@ -375,6 +376,11 @@
         return mDragController.onTouchEvent(ev);
     }
 
+    @Override
+    public boolean onDragEvent (DragEvent event) {
+        return mDragController.onDragEvent(event);
+    }
+
     /**
      * Determine the rect of the descendant in this DragLayer's coordinates
      *
@@ -764,7 +770,7 @@
         // Show the drop view if it was previously hidden
         mDropView = view;
         mDropView.cancelAnimation();
-        mDropView.resetLayoutParams();
+        mDropView.requestLayout();
 
         // Set the anchor view if the page is scrolling
         if (anchorView != null) {
diff --git a/src/com/android/launcher3/DragView.java b/src/com/android/launcher3/DragView.java
index dfa8202..10809b5 100644
--- a/src/com/android/launcher3/DragView.java
+++ b/src/com/android/launcher3/DragView.java
@@ -16,6 +16,8 @@
 
 package com.android.launcher3;
 
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
 import android.animation.FloatArrayEvaluator;
 import android.animation.ValueAnimator;
 import android.animation.ValueAnimator.AnimatorUpdateListener;
@@ -45,18 +47,17 @@
     private Bitmap mBitmap;
     private Bitmap mCrossFadeBitmap;
     @Thunk Paint mPaint;
-    private int mRegistrationX;
-    private int mRegistrationY;
+    private final int mRegistrationX;
+    private final int mRegistrationY;
 
     private Point mDragVisualizeOffset = null;
     private Rect mDragRegion = null;
-    private DragLayer mDragLayer = null;
+    private final DragLayer mDragLayer;
+    private final DragController mDragController;
     private boolean mHasDrawn = false;
     @Thunk float mCrossFadeProgress = 0f;
 
     ValueAnimator mAnim;
-    @Thunk float mOffsetX = 0.0f;
-    @Thunk float mOffsetY = 0.0f;
     private float mInitialScale = 1f;
     // The intrinsic icon scale factor is the scale factor for a drag icon over the workspace
     // size.  This is ignored for non-icons.
@@ -81,6 +82,7 @@
             int left, int top, int width, int height, final float initialScale) {
         super(launcher);
         mDragLayer = launcher.getDragLayer();
+        mDragController = launcher.getDragController();
         mInitialScale = initialScale;
 
         final Resources res = getResources();
@@ -99,11 +101,6 @@
             public void onAnimationUpdate(ValueAnimator animation) {
                 final float value = (Float) animation.getAnimatedValue();
 
-                final int deltaX = (int) (-mOffsetX);
-                final int deltaY = (int) (-mOffsetY);
-
-                mOffsetX += deltaX;
-                mOffsetY += deltaY;
                 setScaleX(initialScale + (value * (scale - initialScale)));
                 setScaleY(initialScale + (value * (scale - initialScale)));
                 if (sDragAlpha != 1f) {
@@ -112,13 +109,17 @@
 
                 if (getParent() == null) {
                     animation.cancel();
-                } else {
-                    setTranslationX(getTranslationX() + deltaX);
-                    setTranslationY(getTranslationY() + deltaY);
                 }
             }
         });
 
+        mAnim.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                mDragController.onDragViewAnimationEnd();
+            }
+        });
+
         mBitmap = Bitmap.createBitmap(bitmap, left, top, width, height);
         setDragRegion(new Rect(0, 0, width, height));
 
@@ -145,10 +146,6 @@
         return mIntrinsicIconScale;
     }
 
-    public float getOffsetY() {
-        return mOffsetY;
-    }
-
     public int getDragRegionLeft() {
         return mDragRegion.left;
     }
@@ -181,10 +178,6 @@
         return mDragRegion;
     }
 
-    public float getInitialScale() {
-        return mInitialScale;
-    }
-
     public void updateInitialScaleToCurrentScale() {
         mInitialScale = getScaleX();
     }
@@ -194,6 +187,23 @@
         setMeasuredDimension(mBitmap.getWidth(), mBitmap.getHeight());
     }
 
+    // Draws drag shadow for system DND.
+    public void drawDragShadow(Canvas canvas) {
+        final int saveCount = canvas.save(Canvas.MATRIX_SAVE_FLAG);
+        canvas.scale(getScaleX(), getScaleY());
+        onDraw(canvas);
+        canvas.restoreToCount(saveCount);
+    }
+
+    // Provides drag shadow metrics for system DND.
+    public void provideDragShadowMetrics(Point size, Point touch) {
+        size.set((int)(mBitmap.getWidth() * getScaleX()), (int)(mBitmap.getHeight() * getScaleY()));
+
+        final int xGrowth = (int)(mBitmap.getWidth() * (getScaleX() - mInitialScale));
+        final int yGrowth = (int)(mBitmap.getHeight() * (getScaleY() - mInitialScale));
+        touch.set(mRegistrationX + xGrowth / 2, mRegistrationY + yGrowth / 2);
+    }
+
     @Override
     protected void onDraw(Canvas canvas) {
         @SuppressWarnings("all") // suppress dead code warning
@@ -214,12 +224,12 @@
         canvas.drawBitmap(mBitmap, 0.0f, 0.0f, mPaint);
         if (crossFade) {
             mPaint.setAlpha((int) (255 * mCrossFadeProgress));
-            canvas.save();
+            final int saveCount = canvas.save(Canvas.MATRIX_SAVE_FLAG);
             float sX = (mBitmap.getWidth() * 1.0f) / mCrossFadeBitmap.getWidth();
             float sY = (mBitmap.getHeight() * 1.0f) / mCrossFadeBitmap.getHeight();
             canvas.scale(sX, sY);
             canvas.drawBitmap(mCrossFadeBitmap, 0.0f, 0.0f, mPaint);
-            canvas.restore();
+            canvas.restoreToCount(saveCount);
         }
     }
 
@@ -333,11 +343,6 @@
         }
     }
 
-    public void resetLayoutParams() {
-        mOffsetX = mOffsetY = 0;
-        requestLayout();
-    }
-
     /**
      * Move the window containing this view.
      *
@@ -345,8 +350,8 @@
      * @param touchY the y coordinate the user touched in DragLayer coordinates
      */
     void move(int touchX, int touchY) {
-        setTranslationX(touchX - mRegistrationX + (int) mOffsetX);
-        setTranslationY(touchY - mRegistrationY + (int) mOffsetY);
+        setTranslationX(touchX - mRegistrationX);
+        setTranslationY(touchY - mRegistrationY);
     }
 
     void remove() {
diff --git a/src/com/android/launcher3/ItemInfo.java b/src/com/android/launcher3/ItemInfo.java
index 4bc5019..956bb8c 100644
--- a/src/com/android/launcher3/ItemInfo.java
+++ b/src/com/android/launcher3/ItemInfo.java
@@ -134,7 +134,7 @@
     }
 
     public Intent getIntent() {
-        throw new RuntimeException("Unexpected Intent");
+        return null;
     }
 
     /**