Merge "Several animation calls cleanup" into ub-launcher3-master
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 1fb8e8d..ab430d1 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -51,11 +51,9 @@
     <uses-permission android:name="android.permission.CALL_PHONE" />
     <uses-permission android:name="android.permission.SET_WALLPAPER" />
     <uses-permission android:name="android.permission.SET_WALLPAPER_HINTS" />
-    <uses-permission android:name="android.permission.VIBRATE" />
     <uses-permission android:name="android.permission.BIND_APPWIDGET" />
     <uses-permission android:name="android.permission.GET_ACCOUNTS" />
     <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
-    <uses-permission android:name="android.permission.BROADCAST_STICKY"/>
     <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
     <uses-permission android:name="com.android.launcher.permission.READ_SETTINGS" />
     <uses-permission android:name="com.android.launcher.permission.WRITE_SETTINGS" />
@@ -204,12 +202,6 @@
             </intent-filter>
         </receiver>
 
-        <receiver android:name="com.android.launcher3.StartupReceiver" >
-            <intent-filter>
-                <action android:name="android.intent.action.BOOT_COMPLETED" />
-            </intent-filter>
-        </receiver>
-
         <!-- The settings provider contains Home's data, like the workspace favorites -->
         <provider
             android:name="com.android.launcher3.LauncherProvider"
diff --git a/protos/backup.proto b/protos/backup.proto
index b7ad5ef..6704e08 100644
--- a/protos/backup.proto
+++ b/protos/backup.proto
@@ -18,7 +18,6 @@
 
 package launcher_backup;
 
-option javanano_use_deprecated_package = true;
 option java_package = "com.android.launcher3.backup";
 option java_outer_classname = "BackupProtos";
 
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/CellLayout.java b/src/com/android/launcher3/CellLayout.java
index 078d480..882c93a 100644
--- a/src/com/android/launcher3/CellLayout.java
+++ b/src/com/android/launcher3/CellLayout.java
@@ -212,6 +212,7 @@
 
         mBackground = (TransitionDrawable) res.getDrawable(R.drawable.bg_screenpanel);
         mBackground.setCallback(this);
+        mBackground.setAlpha((int) (mBackgroundAlpha * 255));
 
         mReorderPreviewAnimationMagnitude = (REORDER_PREVIEW_MAGNITUDE *
                 grid.iconSizePx);
@@ -415,7 +416,11 @@
             if (mIsDragOverlapping) {
                 mBackground.startTransition(BACKGROUND_ACTIVATE_DURATION);
             } else {
-                mBackground.reverseTransition(BACKGROUND_ACTIVATE_DURATION);
+                if (mBackgroundAlpha > 0f) {
+                    mBackground.reverseTransition(BACKGROUND_ACTIVATE_DURATION);
+                } else {
+                    mBackground.resetTransition();
+                }
             }
             invalidate();
         }
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 24a2a5d..a4e7ca7 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) {
         mHasDrawn = true;
@@ -205,12 +215,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);
         }
     }
 
@@ -325,11 +335,6 @@
         }
     }
 
-    public void resetLayoutParams() {
-        mOffsetX = mOffsetY = 0;
-        requestLayout();
-    }
-
     /**
      * Move the window containing this view.
      *
@@ -337,8 +342,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;
     }
 
     /**
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index 360e2ba..12e1054 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -27,7 +27,6 @@
 import android.content.Intent;
 import android.content.Intent.ShortcutIconResource;
 import android.content.IntentFilter;
-import android.content.SharedPreferences;
 import android.content.pm.PackageManager;
 import android.content.pm.ProviderInfo;
 import android.content.pm.ResolveInfo;
@@ -1747,8 +1746,7 @@
             final PackageManager manager = context.getPackageManager();
             final boolean isSafeMode = manager.isSafeMode();
             final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
-            final boolean isSdCardReady = context.registerReceiver(null,
-                    new IntentFilter(StartupReceiver.SYSTEM_READY)) != null;
+            final boolean isSdCardReady = Utilities.isBootCompleted();
 
             LauncherAppState app = LauncherAppState.getInstance();
             InvariantDeviceProfile profile = app.getInvariantDeviceProfile();
@@ -1757,6 +1755,8 @@
 
 
             if (MigrateFromRestoreTask.shouldRunTask(mContext)) {
+                long migrationStartTime = System.currentTimeMillis();
+                Log.v(TAG, "Starting workspace migration after restore");
                 try {
                     MigrateFromRestoreTask task = new MigrateFromRestoreTask(mContext);
                     // Clear the flags before starting the task, so that we do not run the task
@@ -1769,6 +1769,8 @@
                     // Clear workspace.
                     mFlags = mFlags | LOADER_FLAG_CLEAR_WORKSPACE;
                 }
+                Log.v(TAG, "Workspace migration completed in "
+                        + (System.currentTimeMillis() - migrationStartTime));
             }
 
             if ((mFlags & LOADER_FLAG_CLEAR_WORKSPACE) != 0) {
@@ -2308,7 +2310,7 @@
 
                 if (!isSdCardReady && !sPendingPackages.isEmpty()) {
                     context.registerReceiver(new AppsAvailabilityCheck(),
-                            new IntentFilter(StartupReceiver.SYSTEM_READY),
+                            new IntentFilter(Intent.ACTION_BOOT_COMPLETED),
                             null, sWorker);
                 }
 
diff --git a/src/com/android/launcher3/StartupReceiver.java b/src/com/android/launcher3/StartupReceiver.java
deleted file mode 100644
index 65f913f..0000000
--- a/src/com/android/launcher3/StartupReceiver.java
+++ /dev/null
@@ -1,15 +0,0 @@
-package com.android.launcher3;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-
-public class StartupReceiver extends BroadcastReceiver {
-
-    static final String SYSTEM_READY = "com.android.launcher3.SYSTEM_READY";
-
-    @Override
-    public void onReceive(Context context, Intent intent) {
-        context.sendStickyBroadcast(new Intent(SYSTEM_READY));
-    }
-}
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index 096edc5..9ae989f 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -60,6 +60,7 @@
 
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
+import java.lang.reflect.Method;
 import java.util.ArrayList;
 import java.util.Locale;
 import java.util.Set;
@@ -711,4 +712,18 @@
     public static String createDbSelectionQuery(String columnName, Iterable<?> values) {
         return String.format(Locale.ENGLISH, "%s IN (%s)", columnName, TextUtils.join(", ", values));
     }
+
+    @SuppressWarnings({"unchecked", "rawtypes"})
+    public static boolean isBootCompleted() {
+        try {
+            Class clazz = Class.forName("android.os.SystemProperties");
+            Method getter = clazz.getDeclaredMethod("get", String.class);
+            String value = (String) getter.invoke(null, "sys.boot_completed");
+            return "1".equals(value);
+        } catch (Exception e) {
+            Log.d(TAG, "Unable to read system properties");
+            // Assume that boot has completed
+            return true;
+        }
+    }
 }
diff --git a/src/com/android/launcher3/compat/AlphabeticIndexCompat.java b/src/com/android/launcher3/compat/AlphabeticIndexCompat.java
index ec1fb66..463278a 100644
--- a/src/com/android/launcher3/compat/AlphabeticIndexCompat.java
+++ b/src/com/android/launcher3/compat/AlphabeticIndexCompat.java
@@ -1,6 +1,7 @@
 package com.android.launcher3.compat;
 
 import android.content.Context;
+
 import com.android.launcher3.Utilities;
 
 import java.lang.reflect.Constructor;
@@ -62,6 +63,7 @@
     private boolean mHasValidAlphabeticIndex;
     private String mDefaultMiscLabel;
 
+    @SuppressWarnings({"unchecked", "rawtypes"})
     public AlphabeticIndexCompat(Context context) {
         super();
         try {
diff --git a/src/com/android/launcher3/model/MigrateFromRestoreTask.java b/src/com/android/launcher3/model/MigrateFromRestoreTask.java
index cb90c8b..8d4472f 100644
--- a/src/com/android/launcher3/model/MigrateFromRestoreTask.java
+++ b/src/com/android/launcher3/model/MigrateFromRestoreTask.java
@@ -38,7 +38,7 @@
 public class MigrateFromRestoreTask {
 
     private static final String TAG = "MigrateFromRestoreTask";
-    private static final boolean DEBUG = false;
+    private static final boolean DEBUG = true;
 
     private static final String KEY_MIGRATION_SOURCE_SIZE = "migration_restore_src_size";
     private static final String KEY_MIGRATION_WIDGET_MINSIZE = "migration_widget_min_size";
diff --git a/src/com/android/launcher3/model/WidgetsAndShortcutNameComparator.java b/src/com/android/launcher3/model/WidgetsAndShortcutNameComparator.java
index 61e8952..b990560 100644
--- a/src/com/android/launcher3/model/WidgetsAndShortcutNameComparator.java
+++ b/src/com/android/launcher3/model/WidgetsAndShortcutNameComparator.java
@@ -1,13 +1,14 @@
 package com.android.launcher3.model;
 
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
-
 import com.android.launcher3.LauncherAppWidgetProviderInfo;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.compat.AppWidgetManagerCompat;
 import com.android.launcher3.compat.UserHandleCompat;
+import com.android.launcher3.util.ComponentKey;
 
 import java.text.Collator;
 import java.util.Comparator;
@@ -16,53 +17,81 @@
 public class WidgetsAndShortcutNameComparator implements Comparator<Object> {
     private final AppWidgetManagerCompat mManager;
     private final PackageManager mPackageManager;
-    private final HashMap<Object, String> mLabelCache;
+    private final HashMap<ComponentKey, String> mLabelCache;
     private final Collator mCollator;
     private final UserHandleCompat mMainHandle;
 
     public WidgetsAndShortcutNameComparator(Context context) {
         mManager = AppWidgetManagerCompat.getInstance(context);
         mPackageManager = context.getPackageManager();
-        mLabelCache = new HashMap<Object, String>();
+        mLabelCache = new HashMap<>();
         mCollator = Collator.getInstance();
         mMainHandle = UserHandleCompat.myUserHandle();
     }
 
+    /**
+     * Resets any stored state.
+     */
+    public void reset() {
+        mLabelCache.clear();
+    }
+
     @Override
-    public final int compare(Object a, Object b) {
-        String labelA, labelB;
-        if (mLabelCache.containsKey(a)) {
-            labelA = mLabelCache.get(a);
-        } else {
-            labelA = (a instanceof LauncherAppWidgetProviderInfo)
-                    ? Utilities.trim(mManager.loadLabel((LauncherAppWidgetProviderInfo) a))
-                    : Utilities.trim(((ResolveInfo) a).loadLabel(mPackageManager));
-            mLabelCache.put(a, labelA);
-        }
-        if (mLabelCache.containsKey(b)) {
-            labelB = mLabelCache.get(b);
-        } else {
-            labelB = (b instanceof LauncherAppWidgetProviderInfo)
-                    ? Utilities.trim(mManager.loadLabel((LauncherAppWidgetProviderInfo) b))
-                    : Utilities.trim(((ResolveInfo) b).loadLabel(mPackageManager));
-            mLabelCache.put(b, labelB);
-        }
-
-        // Currently, there is no work profile shortcuts, hence only considering the widget cases.
-
-        boolean aWorkProfile = (a instanceof LauncherAppWidgetProviderInfo) &&
-                !mMainHandle.equals(mManager.getUser((LauncherAppWidgetProviderInfo) a));
-        boolean bWorkProfile = (b instanceof LauncherAppWidgetProviderInfo) &&
-                !mMainHandle.equals(mManager.getUser((LauncherAppWidgetProviderInfo) b));
+    public final int compare(Object objA, Object objB) {
+        ComponentKey keyA = getComponentKey(objA);
+        ComponentKey keyB = getComponentKey(objB);
 
         // Independent of how the labels compare, if only one of the two widget info belongs to
         // work profile, put that one in the back.
+        boolean aWorkProfile = !mMainHandle.equals(keyA.user);
+        boolean bWorkProfile = !mMainHandle.equals(keyB.user);
         if (aWorkProfile && !bWorkProfile) {
             return 1;
         }
         if (!aWorkProfile && bWorkProfile) {
             return -1;
         }
+
+        // Get the labels for comparison
+        String labelA = mLabelCache.get(keyA);
+        String labelB = mLabelCache.get(keyB);
+        if (labelA == null) {
+            labelA = getLabel(objA);
+            mLabelCache.put(keyA, labelA);
+        }
+        if (labelB == null) {
+            labelB = getLabel(objB);
+            mLabelCache.put(keyB, labelB);
+        }
         return mCollator.compare(labelA, labelB);
     }
+
+    /**
+     * @return a component key for the given widget or shortcut info.
+     */
+    private ComponentKey getComponentKey(Object o) {
+        if (o instanceof LauncherAppWidgetProviderInfo) {
+            LauncherAppWidgetProviderInfo widgetInfo = (LauncherAppWidgetProviderInfo) o;
+            return new ComponentKey(widgetInfo.provider, mManager.getUser(widgetInfo));
+        } else {
+            ResolveInfo shortcutInfo = (ResolveInfo) o;
+            ComponentName cn = new ComponentName(shortcutInfo.activityInfo.packageName,
+                    shortcutInfo.activityInfo.name);
+            // Currently, there are no work profile shortcuts
+            return new ComponentKey(cn, UserHandleCompat.myUserHandle());
+        }
+    }
+
+    /**
+     * @return the label for the given widget or shortcut info.  This may be an expensive call.
+     */
+    private String getLabel(Object o) {
+        if (o instanceof LauncherAppWidgetProviderInfo) {
+            LauncherAppWidgetProviderInfo widgetInfo = (LauncherAppWidgetProviderInfo) o;
+            return Utilities.trim(mManager.loadLabel(widgetInfo));
+        } else {
+            ResolveInfo shortcutInfo = (ResolveInfo) o;
+            return Utilities.trim(shortcutInfo.loadLabel(mPackageManager));
+        }
+    }
 };
diff --git a/src/com/android/launcher3/model/WidgetsModel.java b/src/com/android/launcher3/model/WidgetsModel.java
index 185dfca..cabff14 100644
--- a/src/com/android/launcher3/model/WidgetsModel.java
+++ b/src/com/android/launcher3/model/WidgetsModel.java
@@ -39,7 +39,7 @@
     private ArrayList<Object> mRawList;
 
     private final AppWidgetManagerCompat mAppWidgetMgr;
-    private final Comparator mWidgetAndShortcutNameComparator;
+    private final WidgetsAndShortcutNameComparator mWidgetAndShortcutNameComparator;
     private final Comparator mAppNameComparator;
     private final IconCache mIconCache;
     private final AppFilter mAppFilter;
@@ -103,6 +103,7 @@
         // clear the lists.
         mWidgetsList.clear();
         mPackageItemInfos.clear();
+        mWidgetAndShortcutNameComparator.reset();
 
         // add and update.
         for (Object o: rawWidgetsShortcuts) {
@@ -139,7 +140,7 @@
             if (widgetsShortcutsList != null) {
                 widgetsShortcutsList.add(o);
             } else {
-                widgetsShortcutsList = new ArrayList<Object>();
+                widgetsShortcutsList = new ArrayList<>();
                 widgetsShortcutsList.add(o);
                 pInfo = new PackageItemInfo(packageName);
                 mIconCache.getTitleAndIconForApp(packageName, userHandle,