Merge "Adding accessibility action to move item out of a folder" into ub-launcher3-burnaby
diff --git a/res/layout/widget_cell.xml b/res/layout/widget_cell.xml
index cb1c812..ab23b84 100644
--- a/res/layout/widget_cell.xml
+++ b/res/layout/widget_cell.xml
@@ -44,7 +44,7 @@
             android:ellipsize="end"
             android:fadingEdge="horizontal"
 
-            android:textColor="#FFFFFFFF"
+            android:textColor="@color/widgets_view_item_text_color"
             android:textSize="16sp"
             android:textAlignment="viewStart"
             android:fontFamily="sans-serif-condensed"
@@ -61,7 +61,7 @@
             android:layout_marginLeft="5dp"
             android:layout_weight="0"
             android:gravity="start"
-            android:textColor="#FFFFFFFF"
+            android:textColor="@color/widgets_view_item_text_color"
             android:textSize="16sp"
             android:textAlignment="viewStart"
             android:fontFamily="sans-serif-condensed"
diff --git a/res/values/colors.xml b/res/values/colors.xml
index 9695b26..0c3714b 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -42,6 +42,7 @@
     <color name="apps_view_section_text_color">#009688</color>
 
     <!-- Widgetss view -->
-    <color name="widgets_view_section_text_color">#009688</color>
+    <color name="widgets_view_section_text_color">#FFFFFF</color>
+    <color name="widgets_view_item_text_color">#C4C4C4</color>
     <color name="widgets_cell_color">#263238</color>
 </resources>
diff --git a/src/com/android/launcher3/AlphabeticalAppsList.java b/src/com/android/launcher3/AlphabeticalAppsList.java
index 2ee5a62..c7ee2e9 100644
--- a/src/com/android/launcher3/AlphabeticalAppsList.java
+++ b/src/com/android/launcher3/AlphabeticalAppsList.java
@@ -254,7 +254,7 @@
         int length = apps.size();
         for (int i = 0; i < length; ++i) {
             AppInfo info = apps.get(i);
-            if (info.user.equals(info.user)
+            if (info.user.equals(targetInfo.user)
                     && info.intent.getComponent().equals(targetComponent)) {
                 return i;
             }
diff --git a/src/com/android/launcher3/AppsContainerView.java b/src/com/android/launcher3/AppsContainerView.java
index b247145..f7adaf8 100644
--- a/src/com/android/launcher3/AppsContainerView.java
+++ b/src/com/android/launcher3/AppsContainerView.java
@@ -269,19 +269,8 @@
 
         // Start the drag
         mLauncher.getWorkspace().beginDragShared(v, mLastTouchPos, this, false);
-
-        // We delay entering spring-loaded mode slightly to make sure the UI
-        // thready is free of any work.
-        postDelayed(new Runnable() {
-            @Override
-            public void run() {
-                // We don't enter spring-loaded mode if the drag has been cancelled
-                if (mLauncher.getDragController().isDragging()) {
-                    // Go into spring loaded mode (must happen before we startDrag())
-                    mLauncher.enterSpringLoadedDragMode();
-                }
-            }
-        }, 150);
+        // Enter spring loaded mode
+        mLauncher.enterSpringLoadedDragMode();
 
         return false;
     }
diff --git a/src/com/android/launcher3/AppsGridAdapter.java b/src/com/android/launcher3/AppsGridAdapter.java
index 954c59f..5bc3981 100644
--- a/src/com/android/launcher3/AppsGridAdapter.java
+++ b/src/com/android/launcher3/AppsGridAdapter.java
@@ -33,13 +33,13 @@
      */
     public static class ViewHolder extends RecyclerView.ViewHolder {
         public View mContent;
-        public boolean mIsSectionRow;
+        public boolean mIsSectionHeader;
         public boolean mIsEmptyRow;
 
-        public ViewHolder(View v, boolean isSectionRow, boolean isEmptyRow) {
+        public ViewHolder(View v, boolean isSectionHeader, boolean isEmptyRow) {
             super(v);
             mContent = v;
-            mIsSectionRow = isSectionRow;
+            mIsSectionHeader = isSectionHeader;
             mIsEmptyRow = isEmptyRow;
         }
     }
@@ -72,36 +72,26 @@
         @Override
         public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
             List<AlphabeticalAppsList.AdapterItem> items = mApps.getAdapterItems();
-            if (items.isEmpty()) {
-                return;
-            }
-
             for (int i = 0; i < parent.getChildCount(); i++) {
                 View child = parent.getChildAt(i);
                 ViewHolder holder = (ViewHolder) parent.getChildViewHolder(child);
-                if (holder != null) {
-                    GridLayoutManager.LayoutParams lp = (GridLayoutManager.LayoutParams)
-                            child.getLayoutParams();
-                    if (!holder.mIsSectionRow && !holder.mIsEmptyRow && !lp.isItemRemoved()) {
-                        if (items.get(holder.getPosition() - 1).isSectionHeader) {
-                            // Draw at the parent
-                            AlphabeticalAppsList.AdapterItem item =
-                                    items.get(holder.getPosition());
-                            String section = item.sectionName;
-                            mSectionTextPaint.getTextBounds(section, 0, section.length(),
-                                    mTmpBounds);
-                            if (mIsRtl) {
-                                int left = parent.getWidth() - mPaddingStart - mStartMargin;
-                                c.drawText(section, left + (mStartMargin - mTmpBounds.width()) / 2,
-                                        child.getTop() + (2 * child.getPaddingTop()) +
-                                                mTmpBounds.height(), mSectionTextPaint);
-                            } else {
-                                int left = mPaddingStart;
-                                c.drawText(section, left + (mStartMargin - mTmpBounds.width()) / 2,
-                                    child.getTop() + (2 * child.getPaddingTop()) +
-                                            mTmpBounds.height(), mSectionTextPaint);
-                            }
-                        }
+                if (shouldDrawItemSection(holder, child, items)) {
+                    // Draw at the parent
+                    AlphabeticalAppsList.AdapterItem item =
+                            items.get(holder.getPosition());
+                    String section = item.sectionName;
+                    mSectionTextPaint.getTextBounds(section, 0, section.length(),
+                            mTmpBounds);
+                    if (mIsRtl) {
+                        int left = parent.getWidth() - mPaddingStart - mStartMargin;
+                        c.drawText(section, left + (mStartMargin - mTmpBounds.width()) / 2,
+                                child.getTop() + (2 * child.getPaddingTop()) +
+                                        mTmpBounds.height(), mSectionTextPaint);
+                    } else {
+                        int left = mPaddingStart;
+                        c.drawText(section, left + (mStartMargin - mTmpBounds.width()) / 2,
+                            child.getTop() + (2 * child.getPaddingTop()) +
+                                    mTmpBounds.height(), mSectionTextPaint);
                     }
                 }
             }
@@ -112,6 +102,31 @@
                 RecyclerView.State state) {
             // Do nothing
         }
+
+        private boolean shouldDrawItemSection(ViewHolder holder, View child,
+                List<AlphabeticalAppsList.AdapterItem> items) {
+            // Ensure item is not already removed
+            GridLayoutManager.LayoutParams lp = (GridLayoutManager.LayoutParams)
+                    child.getLayoutParams();
+            if (lp.isItemRemoved()) {
+                return false;
+            }
+            // Ensure we have a valid holder
+            if (holder == null) {
+                return false;
+            }
+            // Ensure it's not an empty row
+            if (holder.mIsEmptyRow) {
+                return false;
+            }
+            // Ensure we have a holder position
+            int pos = holder.getPosition();
+            if (pos <= 0 || pos >= items.size()) {
+                return false;
+            }
+            // Only draw the first item in the section (the first one after the section header)
+            return items.get(pos - 1).isSectionHeader && !items.get(pos).isSectionHeader;
+        }
     }
 
     private LayoutInflater mLayoutInflater;
diff --git a/src/com/android/launcher3/ButtonDropTarget.java b/src/com/android/launcher3/ButtonDropTarget.java
index 7cf002e..683c511 100644
--- a/src/com/android/launcher3/ButtonDropTarget.java
+++ b/src/com/android/launcher3/ButtonDropTarget.java
@@ -117,7 +117,7 @@
     }
 
     @Override
-    public void onFlingToDelete(DragObject d, int x, int y, PointF vec) { }
+    public void onFlingToDelete(DragObject d, PointF vec) { }
 
     @Override
     public final void onDragEnter(DragObject d) {
diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java
index 94f2275..65c6702 100644
--- a/src/com/android/launcher3/CellLayout.java
+++ b/src/com/android/launcher3/CellLayout.java
@@ -310,7 +310,6 @@
     @TargetApi(Build.VERSION_CODES.LOLLIPOP)
     public void enableAccessibleDrag(boolean enable, int dragType) {
         mUseTouchHelper = enable;
-        Log.e("HIGHRES", getParent() + "  " + enable + "  " + dragType, new Exception());
         if (!enable) {
             ViewCompat.setAccessibilityDelegate(this, null);
             setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
diff --git a/src/com/android/launcher3/DeleteDropTarget.java b/src/com/android/launcher3/DeleteDropTarget.java
index e741b97..08186f5 100644
--- a/src/com/android/launcher3/DeleteDropTarget.java
+++ b/src/com/android/launcher3/DeleteDropTarget.java
@@ -17,30 +17,18 @@
 package com.android.launcher3;
 
 import android.animation.TimeInterpolator;
-import android.animation.ValueAnimator;
-import android.animation.ValueAnimator.AnimatorUpdateListener;
 import android.content.Context;
 import android.graphics.PointF;
-import android.graphics.Rect;
 import android.os.AsyncTask;
 import android.util.AttributeSet;
 import android.view.View;
-import android.view.ViewConfiguration;
 import android.view.animation.AnimationUtils;
-import android.view.animation.DecelerateInterpolator;
 
+import com.android.launcher3.util.FlingAnimation;
 import com.android.launcher3.util.Thunk;
-import com.android.launcher3.widget.WidgetsContainerView;
 
 public class DeleteDropTarget extends ButtonDropTarget {
 
-    private static int FLING_DELETE_ANIMATION_DURATION = 350;
-    private static float FLING_TO_DELETE_FRICTION = 0.035f;
-    private static int MODE_FLING_DELETE_TO_TRASH = 0;
-    private static int MODE_FLING_DELETE_ALONG_VECTOR = 1;
-
-    private final int mFlingDeleteMode = MODE_FLING_DELETE_ALONG_VECTOR;
-
     public DeleteDropTarget(Context context, AttributeSet attrs) {
         this(context, attrs, 0);
     }
@@ -119,146 +107,19 @@
         return true;
     }
 
-    /**
-     * Creates an animation from the current drag view to the delete trash icon.
-     */
-    private AnimatorUpdateListener createFlingToTrashAnimatorListener(final DragLayer dragLayer,
-            DragObject d, PointF vel, ViewConfiguration config) {
-
-        int width = mDrawable.getIntrinsicWidth();
-        int height = mDrawable.getIntrinsicHeight();
-        final Rect to = getIconRect(d.dragView.getMeasuredWidth(), d.dragView.getMeasuredHeight(),
-                width, height);
-        final Rect from = new Rect();
-        dragLayer.getViewRectRelativeToSelf(d.dragView, from);
-
-        // Calculate how far along the velocity vector we should put the intermediate point on
-        // the bezier curve
-        float velocity = Math.abs(vel.length());
-        float vp = Math.min(1f, velocity / (config.getScaledMaximumFlingVelocity() / 2f));
-        int offsetY = (int) (-from.top * vp);
-        int offsetX = (int) (offsetY / (vel.y / vel.x));
-        final float y2 = from.top + offsetY;                        // intermediate t/l
-        final float x2 = from.left + offsetX;
-        final float x1 = from.left;                                 // drag view t/l
-        final float y1 = from.top;
-        final float x3 = to.left;                                   // delete target t/l
-        final float y3 = to.top;
-
-        final TimeInterpolator scaleAlphaInterpolator = new TimeInterpolator() {
-            @Override
-            public float getInterpolation(float t) {
-                return t * t * t * t * t * t * t * t;
-            }
-        };
-        return new AnimatorUpdateListener() {
-            @Override
-            public void onAnimationUpdate(ValueAnimator animation) {
-                final DragView dragView = (DragView) dragLayer.getAnimatedView();
-                float t = ((Float) animation.getAnimatedValue()).floatValue();
-                float tp = scaleAlphaInterpolator.getInterpolation(t);
-                float initialScale = dragView.getInitialScale();
-                float finalAlpha = 0.5f;
-                float scale = dragView.getScaleX();
-                float x1o = ((1f - scale) * dragView.getMeasuredWidth()) / 2f;
-                float y1o = ((1f - scale) * dragView.getMeasuredHeight()) / 2f;
-                float x = (1f - t) * (1f - t) * (x1 - x1o) + 2 * (1f - t) * t * (x2 - x1o) +
-                        (t * t) * x3;
-                float y = (1f - t) * (1f - t) * (y1 - y1o) + 2 * (1f - t) * t * (y2 - x1o) +
-                        (t * t) * y3;
-
-                dragView.setTranslationX(x);
-                dragView.setTranslationY(y);
-                dragView.setScaleX(initialScale * (1f - tp));
-                dragView.setScaleY(initialScale * (1f - tp));
-                dragView.setAlpha(finalAlpha + (1f - finalAlpha) * (1f - tp));
-            }
-        };
-    }
-
-    /**
-     * Creates an animation from the current drag view along its current velocity vector.
-     * For this animation, the alpha runs for a fixed duration and we update the position
-     * progressively.
-     */
-    private static class FlingAlongVectorAnimatorUpdateListener implements AnimatorUpdateListener {
-        private DragLayer mDragLayer;
-        private PointF mVelocity;
-        private Rect mFrom;
-        private long mPrevTime;
-        private boolean mHasOffsetForScale;
-        private float mFriction;
-
-        private final TimeInterpolator mAlphaInterpolator = new DecelerateInterpolator(0.75f);
-
-        public FlingAlongVectorAnimatorUpdateListener(DragLayer dragLayer, PointF vel, Rect from,
-                long startTime, float friction) {
-            mDragLayer = dragLayer;
-            mVelocity = vel;
-            mFrom = from;
-            mPrevTime = startTime;
-            mFriction = 1f - (dragLayer.getResources().getDisplayMetrics().density * friction);
-        }
-
-        @Override
-        public void onAnimationUpdate(ValueAnimator animation) {
-            final DragView dragView = (DragView) mDragLayer.getAnimatedView();
-            float t = ((Float) animation.getAnimatedValue()).floatValue();
-            long curTime = AnimationUtils.currentAnimationTimeMillis();
-
-            if (!mHasOffsetForScale) {
-                mHasOffsetForScale = true;
-                float scale = dragView.getScaleX();
-                float xOffset = ((scale - 1f) * dragView.getMeasuredWidth()) / 2f;
-                float yOffset = ((scale - 1f) * dragView.getMeasuredHeight()) / 2f;
-
-                mFrom.left += xOffset;
-                mFrom.top += yOffset;
-            }
-
-            mFrom.left += (mVelocity.x * (curTime - mPrevTime) / 1000f);
-            mFrom.top += (mVelocity.y * (curTime - mPrevTime) / 1000f);
-
-            dragView.setTranslationX(mFrom.left);
-            dragView.setTranslationY(mFrom.top);
-            dragView.setAlpha(1f - mAlphaInterpolator.getInterpolation(t));
-
-            mVelocity.x *= mFriction;
-            mVelocity.y *= mFriction;
-            mPrevTime = curTime;
-        }
-    };
-    private AnimatorUpdateListener createFlingAlongVectorAnimatorListener(final DragLayer dragLayer,
-            DragObject d, PointF vel, final long startTime, final int duration,
-            ViewConfiguration config) {
-        final Rect from = new Rect();
-        dragLayer.getViewRectRelativeToSelf(d.dragView, from);
-
-        return new FlingAlongVectorAnimatorUpdateListener(dragLayer, vel, from, startTime,
-                FLING_TO_DELETE_FRICTION);
-    }
-
-    public void onFlingToDelete(final DragObject d, int x, int y, PointF vel) {
-        final boolean isWidgets = d.dragSource instanceof WidgetsContainerView;
-        final boolean isAllapps = d.dragSource instanceof AppsContainerView;
-
+    @Override
+    public void onFlingToDelete(final DragObject d, PointF vel) {
         // Don't highlight the icon as it's animating
         d.dragView.setColor(0);
         d.dragView.updateInitialScaleToCurrentScale();
-        // Don't highlight the target if we are flinging from AllApps
-        if (isWidgets || isAllapps) {
-            resetHoverColor();
-        }
 
-        if (mFlingDeleteMode == MODE_FLING_DELETE_TO_TRASH) {
-            // Defer animating out the drop target if we are animating to it
-            mSearchDropTargetBar.deferOnDragEnd();
-            mSearchDropTargetBar.finishAnimations();
-        }
-
-        final ViewConfiguration config = ViewConfiguration.get(mLauncher);
         final DragLayer dragLayer = mLauncher.getDragLayer();
-        final int duration = FLING_DELETE_ANIMATION_DURATION;
+        FlingAnimation fling = new FlingAnimation(d, vel,
+                getIconRect(d.dragView.getMeasuredWidth(), d.dragView.getMeasuredHeight(),
+                        mDrawable.getIntrinsicWidth(), mDrawable.getIntrinsicHeight()),
+                        dragLayer);
+
+        final int duration = fling.getDuration();
         final long startTime = AnimationUtils.currentAnimationTimeMillis();
 
         // NOTE: Because it takes time for the first frame of animation to actually be
@@ -282,27 +143,17 @@
                 return Math.min(1f, mOffset + t);
             }
         };
-        AnimatorUpdateListener updateCb = null;
-        if (mFlingDeleteMode == MODE_FLING_DELETE_TO_TRASH) {
-            updateCb = createFlingToTrashAnimatorListener(dragLayer, d, vel, config);
-        } else if (mFlingDeleteMode == MODE_FLING_DELETE_ALONG_VECTOR) {
-            updateCb = createFlingAlongVectorAnimatorListener(dragLayer, d, vel, startTime,
-                    duration, config);
-        }
 
         Runnable onAnimationEndRunnable = new Runnable() {
             @Override
             public void run() {
-                // If we are dragging from AllApps, then we allow AppsCustomizePagedView to clean up
-                // itself, otherwise, complete the drop to initiate the deletion process
-                if (!isWidgets || !isAllapps) {
-                    mLauncher.exitSpringLoadedDragMode();
-                    completeDrop(d);
-                }
+                mLauncher.exitSpringLoadedDragMode();
+                completeDrop(d);
                 mLauncher.getDragController().onDeferredEndFling(d);
             }
         };
-        dragLayer.animateView(d.dragView, updateCb, duration, tInterpolator, onAnimationEndRunnable,
+
+        dragLayer.animateView(d.dragView, fling, duration, tInterpolator, onAnimationEndRunnable,
                 DragLayer.ANIMATION_END_DISAPPEAR, null);
     }
 
diff --git a/src/com/android/launcher3/DragController.java b/src/com/android/launcher3/DragController.java
index a896099..5fea9d8 100644
--- a/src/com/android/launcher3/DragController.java
+++ b/src/com/android/launcher3/DragController.java
@@ -710,8 +710,7 @@
         mDragObject.dragComplete = true;
         mFlingToDeleteDropTarget.onDragExit(mDragObject);
         if (mFlingToDeleteDropTarget.acceptDrop(mDragObject)) {
-            mFlingToDeleteDropTarget.onFlingToDelete(mDragObject, mDragObject.x, mDragObject.y,
-                    vel);
+            mFlingToDeleteDropTarget.onFlingToDelete(mDragObject, vel);
             accepted = true;
         }
         mDragObject.dragSource.onDropCompleted((View) mFlingToDeleteDropTarget, mDragObject, true,
diff --git a/src/com/android/launcher3/DropTarget.java b/src/com/android/launcher3/DropTarget.java
index 3628e57..a3828c1 100644
--- a/src/com/android/launcher3/DropTarget.java
+++ b/src/com/android/launcher3/DropTarget.java
@@ -29,7 +29,7 @@
 
     public static final String TAG = "DropTarget";
 
-    class DragObject {
+    public static class DragObject {
         public int x = -1;
         public int y = -1;
 
@@ -164,7 +164,7 @@
      * of onDrop().  (This is only called on objects that are set as the DragController's
      * fling-to-delete target.
      */
-    void onFlingToDelete(DragObject dragObject, int x, int y, PointF vec);
+    void onFlingToDelete(DragObject dragObject, PointF vec);
 
     /**
      * Check if a drop action can occur at, or near, the requested location.
diff --git a/src/com/android/launcher3/Folder.java b/src/com/android/launcher3/Folder.java
index e0aecea..a282805 100644
--- a/src/com/android/launcher3/Folder.java
+++ b/src/com/android/launcher3/Folder.java
@@ -704,9 +704,15 @@
         if (mInfo.opened) {
             mLauncher.closeFolder();
             mRearrangeOnClose = true;
+        } else if (mState == STATE_ANIMATING) {
+            mRearrangeOnClose = true;
         } else {
             rearrangeChildren();
+            clearDragInfo();
         }
+    }
+
+    private void clearDragInfo() {
         mCurrentDragInfo = null;
         mCurrentDragView = null;
         mSuppressOnAdd = false;
@@ -835,7 +841,8 @@
         return true;
     }
 
-    public void onFlingToDelete(DragObject d, int x, int y, PointF vec) {
+    @Override
+    public void onFlingToDelete(DragObject d, PointF vec) {
         // Do nothing
     }
 
@@ -1037,6 +1044,7 @@
             }
         }
         mSuppressFolderDeletion = false;
+        clearDragInfo();
     }
 
     @Thunk void replaceFolderWithFinalItem() {
diff --git a/src/com/android/launcher3/FolderInfo.java b/src/com/android/launcher3/FolderInfo.java
index 69b2ae7..80b1564 100644
--- a/src/com/android/launcher3/FolderInfo.java
+++ b/src/com/android/launcher3/FolderInfo.java
@@ -33,7 +33,6 @@
 
     /**
      * The folder is locked in sorted mode
-     * @deprecated
      */
     public static final int FLAG_ITEMS_SORTED = 0x00000001;
 
diff --git a/src/com/android/launcher3/InstallShortcutReceiver.java b/src/com/android/launcher3/InstallShortcutReceiver.java
index 27dda64..23bcc85 100644
--- a/src/com/android/launcher3/InstallShortcutReceiver.java
+++ b/src/com/android/launcher3/InstallShortcutReceiver.java
@@ -146,10 +146,13 @@
             return;
         }
 
-        if (DBG) Log.d(TAG, "Got INSTALL_SHORTCUT: " + data.toUri(0));
         PendingInstallShortcutInfo info = new PendingInstallShortcutInfo(data, context);
-        info = convertToLauncherActivityIfPossible(info);
+        if (info.launchIntent == null || info.label == null) {
+            if (DBG) Log.e(TAG, "Invalid install shortcut intent");
+            return;
+        }
 
+        info = convertToLauncherActivityIfPossible(info);
         queuePendingShortcutInfo(info, context);
     }
 
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 2ef5d0a..4533089 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -522,13 +522,19 @@
         mLauncherCallbacks.setLauncherAppsCallback(new Launcher.LauncherAppsCallbacks() {
             @Override
             public void onAllAppsBoundsChanged(Rect bounds) {
+                if (LOGD) {
+                    Log.d(TAG, "onAllAppsBoundsChanged(Rect): " + bounds);
+                }
                 mAppsView.setFixedBounds(Launcher.this, bounds);
             }
 
             @Override
             public void dismissAllApps() {
-                showWorkspace(WorkspaceStateTransitionAnimation.SCROLL_TO_CURRENT_PAGE, true, null,
-                        false /* notifyLauncherCallbacks */);
+                // Dismiss All Apps if we aren't already paused/invisible
+                if (!mPaused) {
+                    showWorkspace(WorkspaceStateTransitionAnimation.SCROLL_TO_CURRENT_PAGE, true,
+                            null /* onCompleteRunnable */, false /* notifyLauncherCallbacks */);
+                }
             }
         });
         return true;
@@ -1012,6 +1018,13 @@
         }
         mOnResumeState = State.NONE;
 
+        // Restore the apps state if we are in all apps
+        if (mState == State.APPS) {
+            if (mLauncherCallbacks != null) {
+                mLauncherCallbacks.onAllAppsShown();
+            }
+        }
+
         // Background was set to gradient in onPause(), restore to black if in all apps.
         setWorkspaceBackground(mState == State.WORKSPACE);
 
@@ -1072,7 +1085,7 @@
                 mWorkspace.getCustomContentCallbacks().onShow(true);
             }
         }
-        mWorkspace.updateInteractionForState();
+        updateInteraction(Workspace.State.NORMAL, mWorkspace.getState());
         mWorkspace.onResume();
 
         if (!isWorkspaceLoading()) {
@@ -1675,9 +1688,6 @@
                 mModel.startLoader(false, PagedView.INVALID_RESTORE_PAGE,
                         LauncherModel.LOADER_FLAG_CLEAR_WORKSPACE
                                 | LauncherModel.LOADER_FLAG_MIGRATE_SHORTCUTS);
-            } else if (LauncherAppsCompat.ACTION_MANAGED_PROFILE_ADDED.equals(action)
-                    || LauncherAppsCompat.ACTION_MANAGED_PROFILE_REMOVED.equals(action)) {
-                getModel().forceReload();
             }
         }
     };
@@ -1691,8 +1701,6 @@
         filter.addAction(Intent.ACTION_SCREEN_OFF);
         filter.addAction(Intent.ACTION_USER_PRESENT);
         // For handling managed profiles
-        filter.addAction(LauncherAppsCompat.ACTION_MANAGED_PROFILE_ADDED);
-        filter.addAction(LauncherAppsCompat.ACTION_MANAGED_PROFILE_REMOVED);
         if (ENABLE_DEBUG_INTENTS) {
             filter.addAction(DebugIntents.DELETE_DATABASE);
             filter.addAction(DebugIntents.MIGRATE_DATABASE);
@@ -2101,8 +2109,6 @@
     public void startSearch(String initialQuery, boolean selectInitialQuery,
             Bundle appSearchData, boolean globalSearch) {
 
-        showWorkspace(true);
-
         if (initialQuery == null) {
             // Use any text typed in the launcher as the initial query
             initialQuery = getTypedText();
@@ -2121,6 +2127,9 @@
         if (clearTextImmediately) {
             clearTypedText();
         }
+
+        // We need to show the workspace after starting the search
+        showWorkspace(true);
     }
 
     /**
@@ -2866,6 +2875,21 @@
         }
     }
 
+    /** Updates the interaction state. */
+    public void updateInteraction(Workspace.State fromState, Workspace.State toState) {
+        // Only update the interacting state if we are transitioning to/from a view without an
+        // overlay
+        boolean fromStateWithoutOverlay = fromState != Workspace.State.NORMAL &&
+                fromState != Workspace.State.NORMAL_HIDDEN;
+        boolean toStateWithoutOverlay = toState != Workspace.State.NORMAL &&
+                toState != Workspace.State.NORMAL_HIDDEN;
+        if (toStateWithoutOverlay) {
+            onInteractionBegin();
+        } else if (fromStateWithoutOverlay) {
+            onInteractionEnd();
+        }
+    }
+
     void startApplicationDetailsActivity(ComponentName componentName, UserHandleCompat user) {
         try {
             LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(this);
@@ -3170,7 +3194,6 @@
 
         if (v instanceof Workspace) {
             if (!mWorkspace.isInOverviewMode()) {
-
                 if (!mWorkspace.isTouchActive()) {
                     showOverviewMode(true);
                     mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS,
@@ -3342,8 +3365,12 @@
             // Send an accessibility event to announce the context change
             getWindow().getDecorView()
                     .sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
-
-            onWorkspaceShown(animated);
+            if (notifyLauncherCallbacks) {
+                // Dismiss all apps when the workspace is shown
+                if (mLauncherCallbacks != null) {
+                    mLauncherCallbacks.onAllAppsHidden();
+                }
+            }
         }
     }
 
@@ -3353,10 +3380,6 @@
                 WorkspaceStateTransitionAnimation.SCROLL_TO_CURRENT_PAGE, animated,
                 null /* onCompleteRunnable */);
         mState = State.WORKSPACE;
-        onWorkspaceShown(animated);
-    }
-
-    public void onWorkspaceShown(boolean animated) {
     }
 
     /**
@@ -3416,6 +3439,18 @@
                 .sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
     }
 
+    /**
+     * Updates the workspace and interaction state on state change, and return the animation to this
+     * new state.
+     */
+    public Animator startWorkspaceStateChangeAnimation(Workspace.State toState, int toPage,
+            boolean animated, HashMap<View, Integer> layerViews) {
+        Workspace.State fromState = mWorkspace.getState();
+        Animator anim = mWorkspace.setStateWithAnimation(toState, toPage, animated, layerViews);
+        updateInteraction(fromState, toState);
+        return anim;
+    }
+
     public void enterSpringLoadedDragMode() {
         Log.d(TAG, String.format("enterSpringLoadedDragMode [mState=%s",
                 mState.name()));
@@ -3434,6 +3469,14 @@
             final Runnable onCompleteRunnable) {
         if (mState != State.APPS_SPRING_LOADED && mState != State.WIDGETS_SPRING_LOADED) return;
 
+        if (successfulDrop) {
+            // We need to trigger all apps hidden to notify search to update itself before the
+            // delayed call to showWorkspace below
+            if (mLauncherCallbacks != null) {
+                mLauncherCallbacks.onAllAppsHidden();
+            }
+        }
+
         mHandler.postDelayed(new Runnable() {
             @Override
             public void run() {
@@ -3454,13 +3497,10 @@
 
     void exitSpringLoadedDragMode() {
         if (mState == State.APPS_SPRING_LOADED) {
-            mStateTransitionAnimation.startAnimationToAllApps(true /* animated */);
-            mState = State.APPS;
+            showAppsView(true, false);
         } else if (mState == State.WIDGETS_SPRING_LOADED) {
-            mStateTransitionAnimation.startAnimationToWidgets(true /* animated */);
-            mState = State.WIDGETS;
+            showWidgetsView(true, false);
         }
-        // Otherwise, we are not in spring loaded mode, so don't do anything.
     }
 
     void lockAllApps() {
@@ -3584,14 +3624,13 @@
      * in onResume.
      *
      * This needs to be called from incoming places where resources might have been loaded
-     * while we are paused.  That is becaues the Configuration might be wrong
-     * when we're not running, and if it comes back to what it was when we
-     * were paused, we are not restarted.
+     * while the activity is paused. That is because the Configuration (e.g., rotation)  might be
+     * wrong when we're not running, and if the activity comes back to what the configuration was
+     * when we were paused, activity is not restarted.
      *
      * Implementation of the method from LauncherModel.Callbacks.
      *
-     * @return true if we are currently paused.  The caller might be able to
-     * skip some work in that case since we will come back again.
+     * @return {@code true} if we are currently paused. The caller might be able to skip some work
      */
     private boolean waitUntilResume(Runnable run, boolean deletePreviousRunnables) {
         if (mPaused) {
@@ -4135,10 +4174,6 @@
         if (mAppsView != null) {
             mAppsView.setApps(apps);
         }
-        if (mWidgetsView != null) {
-            mWidgetsView.addWidgets(LauncherModel.getSortedWidgetsAndShortcuts(this, false),
-                    getPackageManager());
-        }
         if (mLauncherCallbacks != null) {
             mLauncherCallbacks.bindAllApplications(apps);
         }
@@ -4274,26 +4309,23 @@
         }
     }
 
-    /**
-     * A number of packages were updated.
-     */
     @Thunk ArrayList<Object> mWidgetsAndShortcuts;
     private Runnable mBindPackagesUpdatedRunnable = new Runnable() {
             public void run() {
-                bindPackagesUpdated(mWidgetsAndShortcuts);
-                mWidgetsAndShortcuts = null;
+                bindAllPackages(mWidgetsAndShortcuts);
             }
         };
 
-    public void bindPackagesUpdated(final ArrayList<Object> widgetsAndShortcuts) {
+    @Override
+    public void bindAllPackages(final ArrayList<Object> widgetsAndShortcuts) {
         if (waitUntilResume(mBindPackagesUpdatedRunnable, true)) {
             mWidgetsAndShortcuts = widgetsAndShortcuts;
             return;
         }
 
-        if (mWidgetsView != null) {
-            mWidgetsView.addWidgets(LauncherModel.getSortedWidgetsAndShortcuts(this, false),
-                    getPackageManager());
+        if (mWidgetsView != null && widgetsAndShortcuts != null) {
+            mWidgetsView.addWidgets(widgetsAndShortcuts, getPackageManager());
+            mWidgetsAndShortcuts = null;
         }
     }
 
diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java
index 7f31e49..d51df32 100644
--- a/src/com/android/launcher3/LauncherAppState.java
+++ b/src/com/android/launcher3/LauncherAppState.java
@@ -112,6 +112,10 @@
         filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
         filter.addAction(SearchManager.INTENT_GLOBAL_SEARCH_ACTIVITY_CHANGED);
         filter.addAction(SearchManager.INTENT_ACTION_SEARCHABLES_CHANGED);
+        // For handling managed profiles
+        filter.addAction(LauncherAppsCompat.ACTION_MANAGED_PROFILE_ADDED);
+        filter.addAction(LauncherAppsCompat.ACTION_MANAGED_PROFILE_REMOVED);
+
         sContext.registerReceiver(mModel, filter);
     }
 
diff --git a/src/com/android/launcher3/LauncherAppWidgetHost.java b/src/com/android/launcher3/LauncherAppWidgetHost.java
index e32e0d9..c274f2e 100644
--- a/src/com/android/launcher3/LauncherAppWidgetHost.java
+++ b/src/com/android/launcher3/LauncherAppWidgetHost.java
@@ -79,7 +79,8 @@
     }
 
     protected void onProvidersChanged() {
-        mLauncher.getModel().loadAndBindWidgetsAndShortcuts(mLauncher, mLauncher);
+        mLauncher.getModel().loadAndBindWidgetsAndShortcuts(mLauncher, mLauncher,
+                true /* refresh */);
         if (!mProviderChangeListeners.isEmpty()) {
             for (Runnable callback : new ArrayList<>(mProviderChangeListeners)) {
                 callback.run();
diff --git a/src/com/android/launcher3/LauncherCallbacks.java b/src/com/android/launcher3/LauncherCallbacks.java
index 2fee81c..25c86c9 100644
--- a/src/com/android/launcher3/LauncherCallbacks.java
+++ b/src/com/android/launcher3/LauncherCallbacks.java
@@ -51,6 +51,7 @@
     public void finishBindingItems(final boolean upgradePath);
     public void onClickAllAppsButton(View v);
     public void onAllAppsShown();
+    public void onAllAppsHidden();
     public void bindAllApplications(ArrayList<AppInfo> apps);
     public void onClickFolderIcon(View v);
     public void onClickAppShortcut(View v);
diff --git a/src/com/android/launcher3/LauncherExtension.java b/src/com/android/launcher3/LauncherExtension.java
index e4fdbbc..8174af0 100644
--- a/src/com/android/launcher3/LauncherExtension.java
+++ b/src/com/android/launcher3/LauncherExtension.java
@@ -128,6 +128,10 @@
         }
 
         @Override
+        public void onAllAppsHidden() {
+        }
+
+        @Override
         public void bindAllApplications(ArrayList<AppInfo> apps) {
         }
 
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index 97a2830..f283c2f 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -197,7 +197,7 @@
         public void bindRestoreItemsChange(HashSet<ItemInfo> updates);
         public void bindComponentsRemoved(ArrayList<String> packageNames,
                         ArrayList<AppInfo> appInfos, UserHandleCompat user, int reason);
-        public void bindPackagesUpdated(ArrayList<Object> widgetsAndShortcuts);
+        public void bindAllPackages(ArrayList<Object> widgetsAndShortcuts);
         public void bindSearchablesChanged();
         public boolean isAllAppsButtonRank(int rank);
         public void onPageBoundSynchronously(int page);
@@ -881,22 +881,22 @@
      * TODO: Throw exception is above condition is not met.
      */
     @Thunk static boolean shortcutExists(Context context, Intent intent, UserHandleCompat user) {
-        final Intent intentWithPkg, intentWithoutPkg;
+        final String intentWithPkg, intentWithoutPkg;
         final String packageName;
         if (intent.getComponent() != null) {
             // If component is not null, an intent with null package will produce
             // the same result and should also be a match.
             packageName = intent.getComponent().getPackageName();
             if (intent.getPackage() != null) {
-                intentWithPkg = intent;
-                intentWithoutPkg = new Intent(intent).setPackage(null);
+                intentWithPkg = intent.toUri(0);
+                intentWithoutPkg = new Intent(intent).setPackage(null).toUri(0);
             } else {
-                intentWithPkg = new Intent(intent).setPackage(packageName);
-                intentWithoutPkg = intent;
+                intentWithPkg = new Intent(intent).setPackage(packageName).toUri(0);
+                intentWithoutPkg = intent.toUri(0);
             }
         } else {
-            intentWithPkg = intent;
-            intentWithoutPkg = intent;
+            intentWithPkg = intent.toUri(0);
+            intentWithoutPkg = intent.toUri(0);
             packageName = intent.getPackage();
         }
 
@@ -904,9 +904,11 @@
             for (ItemInfo item : sBgItemsIdMap) {
                 if (item instanceof ShortcutInfo) {
                     ShortcutInfo info = (ShortcutInfo) item;
-                    if (intentWithPkg.equals(info.getIntent())
-                            || intentWithoutPkg.equals(info.getIntent())) {
-                        return true;
+                    if (info.getIntent() != null && info.user.equals(user)) {
+                        String s = info.getIntent().toUri(0);
+                        if (intentWithPkg.equals(s) || intentWithoutPkg.equals(s)) {
+                            return true;
+                        }
                     }
                 }
             }
@@ -1277,6 +1279,9 @@
             if (callbacks != null) {
                 callbacks.bindSearchablesChanged();
             }
+        } else if (LauncherAppsCompat.ACTION_MANAGED_PROFILE_ADDED.equals(action)
+                || LauncherAppsCompat.ACTION_MANAGED_PROFILE_REMOVED.equals(action)) {
+            forceReload();
         }
     }
 
@@ -1594,9 +1599,6 @@
                 if (DEBUG_LOADERS) Log.d(TAG, "step 2: loading all apps");
                 loadAndBindAllApps();
 
-                // Remove entries for packages which changed while the launcher was dead.
-                LauncherAppState.getInstance().getWidgetCache().removeObsoletePreviews();
-
                 // Restore the default thread priority after we are done loading items
                 synchronized (mLock) {
                     android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
@@ -2865,6 +2867,7 @@
                     final Callbacks callbacks = tryGetCallbacks(oldCallbacks);
                     if (callbacks != null) {
                         callbacks.bindAllApplications(added);
+                        loadAndBindWidgetsAndShortcuts(mContext, callbacks, true /* refresh */);
                         if (DEBUG_LOADERS) {
                             Log.d(TAG, "bound " + added.size() + " apps in "
                                 + (SystemClock.uptimeMillis() - bindTime) + "ms");
@@ -3225,9 +3228,10 @@
                     }
                 });
             }
-            if (Build.VERSION.SDK_INT < 17) {
-                loadAndBindWidgetsAndShortcuts(context, callbacks);
-            }
+
+            // onProvidersChanged method (API >= 17) already refreshed the widget list
+            loadAndBindWidgetsAndShortcuts(context, callbacks, Build.VERSION.SDK_INT < 17);
+
             // Write all the logs to disk
             mHandler.post(new Runnable() {
                 public void run() {
@@ -3276,33 +3280,38 @@
         }
     }
 
-    public void loadAndBindWidgetsAndShortcuts(final Context context, final Callbacks callbacks) {
+    public void loadAndBindWidgetsAndShortcuts(final Context context, final Callbacks callbacks,
+            final boolean refresh) {
         runOnWorkerThread(new Runnable(){
             @Override
             public void run() {
-                final ArrayList<Object> list =
-                        getSortedWidgetsAndShortcuts(context, true /* refresh */);
+                final ArrayList<Object> list = getWidgetsAndShortcuts(context, refresh);
                 mHandler.post(new Runnable() {
                     @Override
                     public void run() {
                         Callbacks cb = getCallback();
                         if (callbacks == cb && cb != null) {
-                            callbacks.bindPackagesUpdated(list);
+                            callbacks.bindAllPackages(list);
                         }
                     }
                 });
+                // update the Widget entries inside DB on the worker thread.
+                LauncherAppState.getInstance().getWidgetCache().removeObsoletePreviews(list);
             }
         });
     }
 
-    // Returns a list of ResolveInfos/AppWidgetInfos in sorted order
-    public static ArrayList<Object> getSortedWidgetsAndShortcuts(Context context, boolean refresh) {
+    /**
+     *  Returns a list of ResolveInfos/AppWidgetInfos.
+     *
+     *  @see #loadAndBindWidgetsAndShortcuts
+     */
+    private ArrayList<Object> getWidgetsAndShortcuts(Context context, boolean refresh) {
         PackageManager packageManager = context.getPackageManager();
         final ArrayList<Object> widgetsAndShortcuts = new ArrayList<Object>();
         widgetsAndShortcuts.addAll(getWidgetProviders(context, refresh));
         Intent shortcutsIntent = new Intent(Intent.ACTION_CREATE_SHORTCUT);
         widgetsAndShortcuts.addAll(packageManager.queryIntentActivities(shortcutsIntent, 0));
-        Collections.sort(widgetsAndShortcuts, new WidgetAndShortcutNameComparator(context));
         return widgetsAndShortcuts;
     }
 
diff --git a/src/com/android/launcher3/LauncherStateTransitionAnimation.java b/src/com/android/launcher3/LauncherStateTransitionAnimation.java
index 9e005f2..51f84bf 100644
--- a/src/com/android/launcher3/LauncherStateTransitionAnimation.java
+++ b/src/com/android/launcher3/LauncherStateTransitionAnimation.java
@@ -249,8 +249,8 @@
 
         // Create the workspace animation.
         // NOTE: this call apparently also sets the state for the workspace if !animated
-        Animator workspaceAnim = mLauncher.getWorkspace().setStateWithAnimation(
-                toWorkspaceState, -1, animated, layerViews);
+        Animator workspaceAnim = mLauncher.startWorkspaceStateChangeAnimation(toWorkspaceState, -1,
+                animated, layerViews);
 
         if (animated && initialized) {
             mStateAnimation = LauncherAnimUtils.createAnimatorSet();
@@ -546,8 +546,8 @@
 
         // Create the workspace animation.
         // NOTE: this call apparently also sets the state for the workspace if !animated
-        Animator workspaceAnim = mLauncher.getWorkspace().setStateWithAnimation(
-                toWorkspaceState, toWorkspacePage, animated, layerViews);
+        Animator workspaceAnim = mLauncher.startWorkspaceStateChangeAnimation(toWorkspaceState,
+                toWorkspacePage, animated, layerViews);
 
         if (animated && initialized) {
             mStateAnimation = LauncherAnimUtils.createAnimatorSet();
diff --git a/src/com/android/launcher3/SearchDropTargetBar.java b/src/com/android/launcher3/SearchDropTargetBar.java
index 44a76b7..4cdf1ca 100644
--- a/src/com/android/launcher3/SearchDropTargetBar.java
+++ b/src/com/android/launcher3/SearchDropTargetBar.java
@@ -180,9 +180,6 @@
         prepareStartAnimation(mDropTargetBar);
         mShowDropTargetBarAnim.start();
         hideSearchBar(true);
-        if (mQSBSearchBar != null) {
-            mQSBSearchBar.setVisibility(View.GONE);
-        }
     }
 
     /**
@@ -193,9 +190,6 @@
         prepareStartAnimation(mDropTargetBar);
         mShowDropTargetBarAnim.reverse();
         showSearchBar(true);
-        if (mQSBSearchBar != null) {
-            mQSBSearchBar.setVisibility(View.VISIBLE);
-        }
     }
 
     /*
diff --git a/src/com/android/launcher3/WidgetPreviewLoader.java b/src/com/android/launcher3/WidgetPreviewLoader.java
index 412cbcd..93bfeaf 100644
--- a/src/com/android/launcher3/WidgetPreviewLoader.java
+++ b/src/com/android/launcher3/WidgetPreviewLoader.java
@@ -24,6 +24,7 @@
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
 import android.os.AsyncTask;
+import android.os.Process;
 import android.util.Log;
 import android.util.LongSparseArray;
 
@@ -34,6 +35,9 @@
 import com.android.launcher3.util.Thunk;
 import com.android.launcher3.widget.WidgetCell;
 
+import junit.framework.Assert;
+
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -202,11 +206,14 @@
      *   2. Any preview for an absent package is removed
      * This ensures that we remove entries for packages which changed while the launcher was dead.
      */
-    public void removeObsoletePreviews() {
+    public void removeObsoletePreviews(ArrayList<Object> list) {
+        // This method should always be called from the worker thread.
+        Assert.assertTrue(LauncherModel.sWorkerThread.getThreadId() == Process.myTid());
+
         LongSparseArray<UserHandleCompat> userIdCache = new LongSparseArray<>();
         LongSparseArray<HashSet<String>> validPackages = new LongSparseArray<>();
 
-        for (Object obj : LauncherModel.getSortedWidgetsAndShortcuts(mContext, false)) {
+        for (Object obj : list) {
             final UserHandleCompat user;
             final String pkg;
             if (obj instanceof ResolveInfo) {
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 07d1c98..e7a41e0 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -2047,14 +2047,6 @@
         return -offsetFromTopEdge + mInsets.top + offsetToCenterInOverview;
     }
 
-    public void updateInteractionForState() {
-        if (mState != State.NORMAL) {
-            mLauncher.onInteractionBegin();
-        } else {
-            mLauncher.onInteractionEnd();
-        }
-    }
-
     /**
      * Sets the current workspace {@link State}, returning an animation transitioning the workspace
      * to that new state.
@@ -2067,7 +2059,6 @@
 
         // Update the current state
         mState = toState;
-        updateInteractionForState();
         updateAccessibilityFlags();
 
         return workspaceAnim;
@@ -3917,7 +3908,7 @@
     }
 
     @Override
-    public void onFlingToDelete(DragObject d, int x, int y, PointF vec) {
+    public void onFlingToDelete(DragObject d, PointF vec) {
         // Do nothing
     }
 
diff --git a/src/com/android/launcher3/util/FlingAnimation.java b/src/com/android/launcher3/util/FlingAnimation.java
new file mode 100644
index 0000000..55c5d7d
--- /dev/null
+++ b/src/com/android/launcher3/util/FlingAnimation.java
@@ -0,0 +1,104 @@
+package com.android.launcher3.util;
+
+import android.animation.TimeInterpolator;
+import android.animation.ValueAnimator;
+import android.animation.ValueAnimator.AnimatorUpdateListener;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.view.animation.DecelerateInterpolator;
+
+import com.android.launcher3.DragLayer;
+import com.android.launcher3.DragView;
+import com.android.launcher3.DropTarget.DragObject;
+
+public class FlingAnimation implements AnimatorUpdateListener {
+
+    /**
+     * Maximum acceleration in one dimension (pixels per milliseconds)
+     */
+    private static final float MAX_ACCELERATION = 0.5f;
+    private static final int DRAG_END_DELAY = 300;
+
+    protected final DragObject mDragObject;
+    protected final Rect mIconRect;
+    protected final DragLayer mDragLayer;
+    protected final Rect mFrom;
+    protected final int mDuration;
+    protected final float mUX, mUY;
+    protected final float mAnimationTimeFraction;
+    protected final TimeInterpolator mAlphaInterpolator = new DecelerateInterpolator(0.75f);
+
+    protected float mAX, mAY;
+
+    /**
+     * @param vel initial fling velocity in pixels per second.
+     */
+    public FlingAnimation(DragObject d, PointF vel, Rect iconRect, DragLayer dragLayer) {
+        mDragObject = d;
+        mUX = vel.x / 1000;
+        mUY = vel.y / 1000;
+        mIconRect = iconRect;
+
+        mDragLayer = dragLayer;
+        mFrom = new Rect();
+        dragLayer.getViewRectRelativeToSelf(d.dragView, mFrom);
+
+        float scale = d.dragView.getScaleX();
+        float xOffset = ((scale - 1f) * d.dragView.getMeasuredWidth()) / 2f;
+        float yOffset = ((scale - 1f) * d.dragView.getMeasuredHeight()) / 2f;
+        mFrom.left += xOffset;
+        mFrom.right -= xOffset;
+        mFrom.top += yOffset;
+        mFrom.bottom -= yOffset;
+
+        mDuration = initDuration();
+        mAnimationTimeFraction = ((float) mDuration) / (mDuration + DRAG_END_DELAY);
+    }
+
+    /**
+     * The fling animation is based on the following system
+     *   - Apply a constant force in the y direction to causing the fling to decelerate.
+     *   - The animation runs for the time taken by the object to go out of the screen.
+     *   - Calculate a constant acceleration in x direction such that the object reaches
+     *     {@link #mIconRect} in the given time.
+     */
+    protected int initDuration() {
+        float sY = -mFrom.bottom;
+
+        float d = mUY * mUY + 2 * sY * MAX_ACCELERATION;
+        if (d >= 0) {
+            // sY can be reached under the MAX_ACCELERATION. Use MAX_ACCELERATION for y direction.
+            mAY = MAX_ACCELERATION;
+        } else {
+            // sY is not reachable, decrease the acceleration so that sY is almost reached.
+            d = 0;
+            mAY = mUY * mUY / (2 * -sY);
+        }
+        double t = (-mUY - Math.sqrt(d)) / mAY;
+
+        float sX = -mFrom.exactCenterX() + mIconRect.exactCenterX();
+
+        // Find horizontal acceleration such that: u*t + a*t*t/2 = s
+        mAX = (float) ((sX - t * mUX) * 2 / (t * t));
+        return (int) Math.round(t);
+    }
+
+    public final int getDuration() {
+        return mDuration + DRAG_END_DELAY;
+    }
+
+    @Override
+    public void onAnimationUpdate(ValueAnimator animation) {
+        float t = animation.getAnimatedFraction();
+        if (t > mAnimationTimeFraction) {
+            t = 1;
+        } else {
+            t = t / mAnimationTimeFraction;
+        }
+        final DragView dragView = (DragView) mDragLayer.getAnimatedView();
+        final float time = t * mDuration;
+        dragView.setTranslationX(time * mUX + mFrom.left + mAX * time * time / 2);
+        dragView.setTranslationY(time * mUY + mFrom.top + mAY * time * time / 2);
+        dragView.setAlpha(1f - mAlphaInterpolator.getInterpolation(t));
+    }
+}
diff --git a/src/com/android/launcher3/widget/WidgetsContainerView.java b/src/com/android/launcher3/widget/WidgetsContainerView.java
index 8090c88..22e29f3 100644
--- a/src/com/android/launcher3/widget/WidgetsContainerView.java
+++ b/src/com/android/launcher3/widget/WidgetsContainerView.java
@@ -24,6 +24,7 @@
 import android.graphics.drawable.Drawable;
 import android.support.v7.widget.LinearLayoutManager;
 import android.support.v7.widget.RecyclerView;
+import android.support.v7.widget.RecyclerView.State;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.View;
@@ -62,6 +63,9 @@
 
     private static final int SPRING_MODE_DELAY_MS = 150;
 
+    /* Coefficient multiplied to the screen height for preloading widgets. */
+    private static final int PRELOAD_SCREEN_HEIGHT_MULTIPLE = 1;
+
     /* Global instances that are used inside this container. */
     private Launcher mLauncher;
     private DragController mDragController;
@@ -114,8 +118,15 @@
         }
         mView = (RecyclerView) findViewById(R.id.widgets_list_view);
         mView.setAdapter(mAdapter);
-        mView.setLayoutManager(new LinearLayoutManager(getContext()));
 
+        // This extends the layout space so that preloading happen for the {@link RecyclerView}
+        mView.setLayoutManager(new LinearLayoutManager(getContext()) {
+            @Override
+            protected int getExtraLayoutSpace(State state) {
+                return super.getExtraLayoutSpace(state)
+                        + WidgetsContainerView.this.getHeight() * PRELOAD_SCREEN_HEIGHT_MULTIPLE;
+            }
+        });
         mPadding.set(getPaddingLeft(), getPaddingTop(), getPaddingRight(),
                 getPaddingBottom());
     }
diff --git a/src/com/android/launcher3/widget/WidgetsListAdapter.java b/src/com/android/launcher3/widget/WidgetsListAdapter.java
index 051a3ab..a7728a1 100644
--- a/src/com/android/launcher3/widget/WidgetsListAdapter.java
+++ b/src/com/android/launcher3/widget/WidgetsListAdapter.java
@@ -161,10 +161,6 @@
 
     @Override
     public void onViewRecycled(WidgetsRowViewHolder holder) {
-        if (DEBUG) {
-            Log.v(TAG, String.format("onViewDetachedFromWindow, [pos=%d]",
-                    holder.getAdapterPosition()));
-        }
         ViewGroup row = ((ViewGroup) holder.getContent().findViewById(R.id.widgets_cell_list));
 
         for (int i = 0; i < row.getChildCount(); i++) {