Merge "Refactoring deferred bind logic" into ub-launcher3-master
diff --git a/res/drawable-hdpi/page_hover_left.9.png b/res/drawable-hdpi/page_hover_left.9.png
deleted file mode 100644
index 3f11d0b..0000000
--- a/res/drawable-hdpi/page_hover_left.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/page_hover_left_active.9.png b/res/drawable-hdpi/page_hover_left_active.9.png
deleted file mode 100644
index abe4c31..0000000
--- a/res/drawable-hdpi/page_hover_left_active.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/page_hover_right.9.png b/res/drawable-hdpi/page_hover_right.9.png
deleted file mode 100644
index 3bcf191..0000000
--- a/res/drawable-hdpi/page_hover_right.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/page_hover_right_active.9.png b/res/drawable-hdpi/page_hover_right_active.9.png
deleted file mode 100644
index 101e4dc..0000000
--- a/res/drawable-hdpi/page_hover_right_active.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/page_hover_left.9.png b/res/drawable-mdpi/page_hover_left.9.png
deleted file mode 100644
index 2b6094c..0000000
--- a/res/drawable-mdpi/page_hover_left.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/page_hover_left_active.9.png b/res/drawable-mdpi/page_hover_left_active.9.png
deleted file mode 100644
index 9eb00a2..0000000
--- a/res/drawable-mdpi/page_hover_left_active.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/page_hover_right.9.png b/res/drawable-mdpi/page_hover_right.9.png
deleted file mode 100644
index c2e59835..0000000
--- a/res/drawable-mdpi/page_hover_right.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/page_hover_right_active.9.png b/res/drawable-mdpi/page_hover_right_active.9.png
deleted file mode 100644
index d2771a1..0000000
--- a/res/drawable-mdpi/page_hover_right_active.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/page_hover_left.9.png b/res/drawable-xhdpi/page_hover_left.9.png
deleted file mode 100644
index dbcc0ab..0000000
--- a/res/drawable-xhdpi/page_hover_left.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/page_hover_left_active.9.png b/res/drawable-xhdpi/page_hover_left_active.9.png
deleted file mode 100644
index 3233efe..0000000
--- a/res/drawable-xhdpi/page_hover_left_active.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/page_hover_right.9.png b/res/drawable-xhdpi/page_hover_right.9.png
deleted file mode 100644
index d82f809..0000000
--- a/res/drawable-xhdpi/page_hover_right.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/page_hover_right_active.9.png b/res/drawable-xhdpi/page_hover_right_active.9.png
deleted file mode 100644
index 819ea19..0000000
--- a/res/drawable-xhdpi/page_hover_right_active.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxhdpi/page_hover_left.9.png b/res/drawable-xxhdpi/page_hover_left.9.png
deleted file mode 100644
index c81f86c..0000000
--- a/res/drawable-xxhdpi/page_hover_left.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxhdpi/page_hover_left_active.9.png b/res/drawable-xxhdpi/page_hover_left_active.9.png
deleted file mode 100644
index 858a3b2..0000000
--- a/res/drawable-xxhdpi/page_hover_left_active.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxhdpi/page_hover_right.9.png b/res/drawable-xxhdpi/page_hover_right.9.png
deleted file mode 100644
index c529770..0000000
--- a/res/drawable-xxhdpi/page_hover_right.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxhdpi/page_hover_right_active.9.png b/res/drawable-xxhdpi/page_hover_right_active.9.png
deleted file mode 100644
index 9900553..0000000
--- a/res/drawable-xxhdpi/page_hover_right_active.9.png
+++ /dev/null
Binary files differ
diff --git a/res/layout-land/launcher.xml b/res/layout-land/launcher.xml
index f431fb1..951a30e 100644
--- a/res/layout-land/launcher.xml
+++ b/res/layout-land/launcher.xml
@@ -50,6 +50,10 @@
             android:layout_gravity="right" />
 
         <include
+            android:id="@+id/app_info_drop_target_bar"
+            layout="@layout/app_info_drop_target_bar" />
+
+        <include
             android:id="@+id/search_drop_target_bar"
             layout="@layout/search_drop_target_bar" />
 
diff --git a/res/layout-port/launcher.xml b/res/layout-port/launcher.xml
index a7f851e..4cb34e9 100644
--- a/res/layout-port/launcher.xml
+++ b/res/layout-port/launcher.xml
@@ -64,6 +64,10 @@
             android:layout_gravity="center_horizontal" />
 
         <include
+            android:id="@+id/app_info_drop_target_bar"
+            layout="@layout/app_info_drop_target_bar" />
+
+        <include
             android:id="@+id/search_drop_target_bar"
             layout="@layout/search_drop_target_bar" />
 
diff --git a/res/layout-sw720dp/launcher.xml b/res/layout-sw720dp/launcher.xml
index 319a493..3228999 100644
--- a/res/layout-sw720dp/launcher.xml
+++ b/res/layout-sw720dp/launcher.xml
@@ -50,6 +50,10 @@
             android:layout_height="match_parent" />
 
         <include
+            android:id="@+id/app_info_drop_target_bar"
+            layout="@layout/app_info_drop_target_bar" />
+
+        <include
             android:id="@+id/search_drop_target_bar"
             layout="@layout/search_drop_target_bar" />
 
diff --git a/res/layout/app_info_drop_target_bar.xml b/res/layout/app_info_drop_target_bar.xml
new file mode 100644
index 0000000..b8f30d0
--- /dev/null
+++ b/res/layout/app_info_drop_target_bar.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2011 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.
+-->
+<com.android.launcher3.AppInfoDropTargetBar xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:focusable="false" >
+
+    <!-- Drag specific targets container -->
+    <LinearLayout
+        android:id="@+id/drag_target_bar"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_gravity="center|bottom" >
+
+        <FrameLayout
+            style="@style/DropTargetButtonContainer"
+            android:layout_weight="1" >
+
+            <!-- Info target -->
+
+            <com.android.launcher3.InfoDropTarget
+                android:id="@+id/info_target_text"
+                style="@style/DropTargetButton"
+                android:text="@string/info_target_label" />
+        </FrameLayout>
+    </LinearLayout>
+
+</com.android.launcher3.AppInfoDropTargetBar>
\ No newline at end of file
diff --git a/res/layout/search_drop_target_bar.xml b/res/layout/search_drop_target_bar.xml
index 4737ee1..724eb94 100644
--- a/res/layout/search_drop_target_bar.xml
+++ b/res/layout/search_drop_target_bar.xml
@@ -43,18 +43,6 @@
             style="@style/DropTargetButtonContainer"
             android:layout_weight="1" >
 
-            <!-- Info target -->
-
-            <com.android.launcher3.InfoDropTarget
-                android:id="@+id/info_target_text"
-                style="@style/DropTargetButton"
-                android:text="@string/info_target_label" />
-        </FrameLayout>
-
-        <FrameLayout
-            style="@style/DropTargetButtonContainer"
-            android:layout_weight="1" >
-
             <!-- Uninstall target -->
 
             <com.android.launcher3.UninstallDropTarget
diff --git a/src/com/android/launcher3/AppInfoDropTargetBar.java b/src/com/android/launcher3/AppInfoDropTargetBar.java
new file mode 100644
index 0000000..31ff42a
--- /dev/null
+++ b/src/com/android/launcher3/AppInfoDropTargetBar.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2011 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.util.AttributeSet;
+
+import com.android.launcher3.dragndrop.DragController;
+
+public class AppInfoDropTargetBar extends BaseDropTargetBar {
+    private ButtonDropTarget mAppInfoDropTarget;
+
+    public AppInfoDropTargetBar(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public AppInfoDropTargetBar(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+
+        // Get the individual components
+        mAppInfoDropTarget = (ButtonDropTarget) mDropTargetBar.findViewById(R.id.info_target_text);
+
+        mAppInfoDropTarget.setDropTargetBar(this);
+    }
+
+    @Override
+    public void setup(Launcher launcher, DragController dragController) {
+        dragController.addDragListener(this);
+
+        dragController.addDragListener(mAppInfoDropTarget);
+
+        dragController.addDropTarget(mAppInfoDropTarget);
+
+        mAppInfoDropTarget.setLauncher(launcher);
+    }
+
+    @Override
+    public void showDropTarget() {
+        animateDropTargetBarToAlpha(1f, DEFAULT_DRAG_FADE_DURATION);
+    }
+
+    @Override
+    public void hideDropTarget() {
+        animateDropTargetBarToAlpha(0f, DEFAULT_DRAG_FADE_DURATION);
+    }
+
+    private void animateDropTargetBarToAlpha(float alpha, int duration) {
+        animateViewAlpha(mDropTargetBarAnimator, mDropTargetBar, alpha,duration);
+    }
+
+    @Override
+    public void enableAccessibleDrag(boolean enable) {
+        mAppInfoDropTarget.enableAccessibleDrag(enable);
+    }
+}
diff --git a/src/com/android/launcher3/BaseDropTargetBar.java b/src/com/android/launcher3/BaseDropTargetBar.java
new file mode 100644
index 0000000..f478a35
--- /dev/null
+++ b/src/com/android/launcher3/BaseDropTargetBar.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2011 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.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.animation.AccelerateInterpolator;
+import android.widget.FrameLayout;
+
+import com.android.launcher3.dragndrop.DragController;
+
+/**
+ * Base class for drop target bars (where you can drop apps to do actions such as uninstall).
+ */
+public abstract class BaseDropTargetBar extends FrameLayout implements DragController.DragListener {
+    protected static final int DEFAULT_DRAG_FADE_DURATION = 175;
+
+    protected View mDropTargetBar;
+
+    protected LauncherViewPropertyAnimator mDropTargetBarAnimator;
+    protected static final AccelerateInterpolator sAccelerateInterpolator =
+            new AccelerateInterpolator();
+    protected boolean mAccessibilityEnabled = false;
+
+    protected boolean mDeferOnDragEnd;
+
+    public BaseDropTargetBar(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public BaseDropTargetBar(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+
+        mDropTargetBar = findViewById(R.id.drag_target_bar);
+
+        // Create the various fade animations
+        mDropTargetBar.setAlpha(0f);
+        mDropTargetBarAnimator = new LauncherViewPropertyAnimator(mDropTargetBar);
+        mDropTargetBarAnimator.setInterpolator(sAccelerateInterpolator);
+        mDropTargetBarAnimator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationStart(Animator animation) {
+                // Ensure that the view is visible for the animation
+                mDropTargetBar.setVisibility(View.VISIBLE);
+            }
+
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                if (mDropTargetBar != null) {
+                    AlphaUpdateListener.updateVisibility(mDropTargetBar, mAccessibilityEnabled);
+                }
+            }
+        });
+    }
+
+
+    /**
+     * Convenience method to animate the alpha of a view using hardware layers.
+     */
+    protected void animateViewAlpha(LauncherViewPropertyAnimator animator, View v, float alpha,
+                                  int duration) {
+        if (v == null) {
+            return;
+        }
+
+        animator.cancel();
+        if (Float.compare(v.getAlpha(), alpha) != 0) {
+            if (duration > 0) {
+                animator.alpha(alpha).withLayer().setDuration(duration).start();
+            } else {
+                v.setAlpha(alpha);
+                AlphaUpdateListener.updateVisibility(v, mAccessibilityEnabled);
+            }
+        }
+    }
+
+    /*
+     * DragController.DragListener implementation
+     */
+    @Override
+    public void onDragStart(DragSource source, ItemInfo info, int dragAction) {
+        showDropTarget();
+    }
+
+    /**
+     * This is called to defer hiding the delete drop target until the drop animation has completed,
+     * instead of hiding immediately when the drag has ended.
+     */
+    protected void deferOnDragEnd() {
+        mDeferOnDragEnd = true;
+    }
+
+    @Override
+    public void onDragEnd() {
+        if (!mDeferOnDragEnd) {
+            hideDropTarget();
+        } else {
+            mDeferOnDragEnd = false;
+        }
+    }
+
+    public abstract void showDropTarget();
+
+    public abstract void hideDropTarget();
+
+    public abstract void enableAccessibleDrag(boolean enable);
+
+    public abstract void setup(Launcher launcher, DragController dragController);
+}
diff --git a/src/com/android/launcher3/ButtonDropTarget.java b/src/com/android/launcher3/ButtonDropTarget.java
index dc29f7d..703de53 100644
--- a/src/com/android/launcher3/ButtonDropTarget.java
+++ b/src/com/android/launcher3/ButtonDropTarget.java
@@ -49,11 +49,11 @@
 public abstract class ButtonDropTarget extends TextView
         implements DropTarget, DragController.DragListener, OnClickListener {
 
-    private static int DRAG_VIEW_DROP_DURATION = 285;
+    private static final int DRAG_VIEW_DROP_DURATION = 285;
 
     protected Launcher mLauncher;
     private int mBottomDragPadding;
-    protected SearchDropTargetBar mSearchDropTargetBar;
+    protected BaseDropTargetBar mDropTargetBar;
 
     /** Whether this drop target is active for the current drag */
     protected boolean mActive;
@@ -106,8 +106,8 @@
         mLauncher = launcher;
     }
 
-    public void setSearchDropTargetBar(SearchDropTargetBar searchDropTargetBar) {
-        mSearchDropTargetBar = searchDropTargetBar;
+    public void setDropTargetBar(BaseDropTargetBar dropTargetBar) {
+        mDropTargetBar = dropTargetBar;
     }
 
     @Override
@@ -230,13 +230,13 @@
         final Rect to = getIconRect(d.dragView.getMeasuredWidth(), d.dragView.getMeasuredHeight(),
                 width, height);
         final float scale = (float) to.width() / from.width();
-        mSearchDropTargetBar.deferOnDragEnd();
+        mDropTargetBar.deferOnDragEnd();
 
         Runnable onAnimationEndRunnable = new Runnable() {
             @Override
             public void run() {
                 completeDrop(d);
-                mSearchDropTargetBar.onDragEnd();
+                mDropTargetBar.onDragEnd();
                 mLauncher.exitSpringLoadedDragModeDelayed(true, 0, null);
             }
         };
diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java
index 700bf9e..b8b4144 100644
--- a/src/com/android/launcher3/CellLayout.java
+++ b/src/com/android/launcher3/CellLayout.java
@@ -2685,6 +2685,7 @@
             LayoutParams lp = (LayoutParams) child.getLayoutParams();
             lp.dropped = true;
             child.requestLayout();
+            markCellsAsOccupiedForView(child);
         }
     }
 
diff --git a/src/com/android/launcher3/DeleteDropTarget.java b/src/com/android/launcher3/DeleteDropTarget.java
index 9a9b57a..6d8fa6b 100644
--- a/src/com/android/launcher3/DeleteDropTarget.java
+++ b/src/com/android/launcher3/DeleteDropTarget.java
@@ -19,7 +19,6 @@
 import android.animation.TimeInterpolator;
 import android.content.Context;
 import android.graphics.PointF;
-import android.os.AsyncTask;
 import android.util.AttributeSet;
 import android.view.View;
 import android.view.animation.AnimationUtils;
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 6364d90..69ef826 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -372,7 +372,7 @@
      * When {@code true}, hotseat is on the bottom row when in landscape mode.
      * If {@code false}, hotseat is on the right column when in landscape mode.
      */
-    boolean isVerticalBarLayout() {
+    public boolean isVerticalBarLayout() {
         return isLandscape && transposeLayoutWithOrientation;
     }
 
@@ -397,29 +397,15 @@
 
         // Layout the search bar space
         View searchBar = launcher.getSearchDropTargetBar();
-        lp = (FrameLayout.LayoutParams) searchBar.getLayoutParams();
-        if (hasVerticalBarLayout) {
-            // Vertical search bar space -- The search bar is fixed in the layout to be on the left
-            //                              of the screen regardless of RTL
-            lp.gravity = Gravity.LEFT;
-            lp.width = searchBarSpaceHeightPx;
-
-            LinearLayout targets = (LinearLayout) searchBar.findViewById(R.id.drag_target_bar);
-            targets.setOrientation(LinearLayout.VERTICAL);
-            FrameLayout.LayoutParams targetsLp = (FrameLayout.LayoutParams) targets.getLayoutParams();
-            targetsLp.gravity = Gravity.TOP;
-            targetsLp.height = LayoutParams.WRAP_CONTENT;
-
-        } else {
-            // Horizontal search bar space
-            lp.gravity = Gravity.TOP;
-            lp.height = searchBarSpaceHeightPx;
-
-            LinearLayout targets = (LinearLayout) searchBar.findViewById(R.id.drag_target_bar);
-            targets.getLayoutParams().width = searchBarSpaceWidthPx;
-        }
+        lp = getDropTargetBarLayoutParams(hasVerticalBarLayout, searchBar, Gravity.TOP);
         searchBar.setLayoutParams(lp);
 
+        // Layout the app info bar space
+        View appInfoBar = launcher.getAppInfoDropTargetBar();
+        lp = getDropTargetBarLayoutParams(hasVerticalBarLayout, appInfoBar, Gravity.BOTTOM);
+        lp.bottomMargin = hotseatBarHeightPx;
+        appInfoBar.setLayoutParams(lp);
+
         // Layout the workspace
         PagedView workspace = (PagedView) launcher.findViewById(R.id.workspace);
         lp = (FrameLayout.LayoutParams) workspace.getLayoutParams();
@@ -478,7 +464,6 @@
         // Layout the Overview Mode
         ViewGroup overviewMode = launcher.getOverviewPanel();
         if (overviewMode != null) {
-            int overviewButtonBarHeight = getOverviewModeButtonBarHeight();
             lp = (FrameLayout.LayoutParams) overviewMode.getLayoutParams();
             lp.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
 
@@ -487,7 +472,7 @@
             int maxWidth = totalItemWidth + (visibleChildCount-1) * overviewModeBarSpacerWidthPx;
 
             lp.width = Math.min(availableWidthPx, maxWidth);
-            lp.height = overviewButtonBarHeight;
+            lp.height = getOverviewModeButtonBarHeight();
             overviewMode.setLayoutParams(lp);
 
             if (lp.width > totalItemWidth && visibleChildCount > 1) {
@@ -516,6 +501,31 @@
         }
     }
 
+    private FrameLayout.LayoutParams getDropTargetBarLayoutParams(boolean hasVerticalBarLayout,
+            View dropTargetBar, int verticalGravity) {
+        FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) dropTargetBar.getLayoutParams();
+        if (hasVerticalBarLayout) {
+            // Vertical drop target bar space -- The drop target bar is fixed in the layout to be on
+            //                                   the left of the screen regardless of RTL
+            lp.gravity = Gravity.LEFT;
+            lp.width = searchBarSpaceHeightPx;
+
+            LinearLayout targets = (LinearLayout) dropTargetBar.findViewById(R.id.drag_target_bar);
+            targets.setOrientation(LinearLayout.VERTICAL);
+            FrameLayout.LayoutParams targetsLp = (FrameLayout.LayoutParams) targets.getLayoutParams();
+            targetsLp.gravity = verticalGravity;
+            targetsLp.height = LayoutParams.WRAP_CONTENT;
+        } else {
+            // Horizontal drop target bar space
+            lp.gravity = verticalGravity;
+            lp.height = searchBarSpaceHeightPx;
+
+            LinearLayout targets = (LinearLayout) dropTargetBar.findViewById(R.id.drag_target_bar);
+            targets.getLayoutParams().width = searchBarSpaceWidthPx;
+        }
+        return lp;
+    }
+
     private int getCurrentWidth() {
         return isLandscape
                 ? Math.max(widthPx, heightPx)
diff --git a/src/com/android/launcher3/Folder.java b/src/com/android/launcher3/Folder.java
index e85fce6..ff21be2 100644
--- a/src/com/android/launcher3/Folder.java
+++ b/src/com/android/launcher3/Folder.java
@@ -906,7 +906,7 @@
 
     @Override
     public boolean supportsAppInfoDropTarget() {
-        return false;
+        return true;
     }
 
     @Override
diff --git a/src/com/android/launcher3/InfoDropTarget.java b/src/com/android/launcher3/InfoDropTarget.java
index b4a1445..006ce5d 100644
--- a/src/com/android/launcher3/InfoDropTarget.java
+++ b/src/com/android/launcher3/InfoDropTarget.java
@@ -20,7 +20,7 @@
 import android.content.Context;
 import android.util.AttributeSet;
 
-public class InfoDropTarget extends ButtonDropTarget {
+public class InfoDropTarget extends UninstallDropTarget {
 
     public InfoDropTarget(Context context, AttributeSet attrs) {
         this(context, attrs, 0);
@@ -39,7 +39,10 @@
         setDrawable(R.drawable.ic_info_launcher);
     }
 
-    public static void startDetailsActivityForInfo(ItemInfo info, Launcher launcher) {
+    /**
+     * @return Whether the activity was started.
+     */
+    public static boolean startDetailsActivityForInfo(ItemInfo info, Launcher launcher) {
         ComponentName componentName = null;
         if (info instanceof AppInfo) {
             componentName = ((AppInfo) info).componentName;
@@ -50,7 +53,14 @@
         }
         if (componentName != null) {
             launcher.startApplicationDetailsActivity(componentName, info.user);
+            return true;
         }
+        return false;
+    }
+
+    @Override
+    protected boolean startActivityWithUninstallAffordance(DragObject d) {
+        return startDetailsActivityForInfo(d.dragInfo, mLauncher);
     }
 
     @Override
@@ -59,11 +69,7 @@
     }
 
     public static boolean supportsDrop(Context context, ItemInfo info) {
-        return info instanceof AppInfo || info instanceof PendingAddItemInfo;
-    }
-
-    @Override
-    void completeDrop(DragObject d) {
-        startDetailsActivityForInfo(d.dragInfo, mLauncher);
+        return info instanceof AppInfo || info instanceof ShortcutInfo
+                || info instanceof PendingAddItemInfo;
     }
 }
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 17b72b7..11869d9 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -200,7 +200,8 @@
     public static final String USER_HAS_MIGRATED = "launcher.user_migrated_from_old_data";
 
     /** The different states that Launcher can be in. */
-    enum State { NONE, WORKSPACE, APPS, APPS_SPRING_LOADED, WIDGETS, WIDGETS_SPRING_LOADED }
+    enum State { NONE, WORKSPACE, WORKSPACE_SPRING_LOADED, APPS, APPS_SPRING_LOADED,
+        WIDGETS, WIDGETS_SPRING_LOADED }
 
     @Thunk State mState = State.WORKSPACE;
     @Thunk LauncherStateTransitionAnimation mStateTransitionAnimation;
@@ -247,6 +248,7 @@
     private View mWidgetsButton;
 
     private SearchDropTargetBar mSearchDropTargetBar;
+    private AppInfoDropTargetBar mAppInfoDropTargetBar;
 
     // Main container view for the all apps screen.
     @Thunk AllAppsContainerView mAppsView;
@@ -1361,9 +1363,12 @@
         mWorkspace.setup(dragController);
         dragController.addDragListener(mWorkspace);
 
-        // Get the search/delete bar
+        // Get the search/delete/uninstall bar
         mSearchDropTargetBar = (SearchDropTargetBar)
                 mDragLayer.findViewById(R.id.search_drop_target_bar);
+        // Get the app info bar
+        mAppInfoDropTargetBar = (AppInfoDropTargetBar)
+                mDragLayer.findViewById(R.id.app_info_drop_target_bar);
 
         // Setup Apps and Widgets
         mAppsView = (AllAppsContainerView) findViewById(R.id.apps_view);
@@ -1376,13 +1381,15 @@
 
         // Setup the drag controller (drop targets have to be added in reverse order in priority)
         dragController.setDragScoller(mWorkspace);
-        dragController.setScrollView(mDragLayer);
         dragController.setMoveTarget(mWorkspace);
         dragController.addDropTarget(mWorkspace);
         if (mSearchDropTargetBar != null) {
             mSearchDropTargetBar.setup(this, dragController);
             mSearchDropTargetBar.setQsbSearchBar(getOrCreateQsbBar());
         }
+        if (mAppInfoDropTargetBar != null) {
+            mAppInfoDropTargetBar.setup(this, dragController);
+        }
 
         if (getResources().getBoolean(R.bool.debug_memory_enabled)) {
             Log.v(TAG, "adding WeightWatcher");
@@ -1769,6 +1776,10 @@
         return mSearchDropTargetBar;
     }
 
+    public AppInfoDropTargetBar getAppInfoDropTargetBar() {
+        return mAppInfoDropTargetBar;
+    }
+
     public LauncherAppWidgetHost getAppWidgetHost() {
         return mAppWidgetHost;
     }
@@ -3376,20 +3387,26 @@
 
     public void enterSpringLoadedDragMode() {
         if (LOGD) Log.d(TAG, String.format("enterSpringLoadedDragMode [mState=%s", mState.name()));
-        if (mState == State.WORKSPACE || mState == State.APPS_SPRING_LOADED ||
-                mState == State.WIDGETS_SPRING_LOADED) {
+        if (isStateSpringLoaded()) {
             return;
         }
 
         mStateTransitionAnimation.startAnimationToWorkspace(mState, mWorkspace.getState(),
                 Workspace.State.SPRING_LOADED, true /* animated */,
                 null /* onCompleteRunnable */);
-        mState = isAppsViewVisible() ? State.APPS_SPRING_LOADED : State.WIDGETS_SPRING_LOADED;
+
+        if (isAppsViewVisible()) {
+            mState = State.APPS_SPRING_LOADED;
+        } else if (isWidgetsViewVisible()) {
+            mState = State.WIDGETS_SPRING_LOADED;
+        } else {
+            mState = State.WORKSPACE_SPRING_LOADED;
+        }
     }
 
     public void exitSpringLoadedDragModeDelayed(final boolean successfulDrop, int delay,
             final Runnable onCompleteRunnable) {
-        if (mState != State.APPS_SPRING_LOADED && mState != State.WIDGETS_SPRING_LOADED) return;
+        if (!isStateSpringLoaded()) return;
 
         mHandler.postDelayed(new Runnable() {
             @Override
@@ -3409,12 +3426,19 @@
         }, delay);
     }
 
+    private boolean isStateSpringLoaded() {
+        return mState == State.WORKSPACE_SPRING_LOADED || mState == State.APPS_SPRING_LOADED
+                || mState == State.WIDGETS_SPRING_LOADED;
+    }
+
     void exitSpringLoadedDragMode() {
         if (mState == State.APPS_SPRING_LOADED) {
             showAppsView(true /* animated */, false /* resetListToTop */,
                     false /* updatePredictedApps */, false /* focusSearchBar */);
         } else if (mState == State.WIDGETS_SPRING_LOADED) {
             showWidgetsView(true, false);
+        } else if (mState == State.WORKSPACE_SPRING_LOADED) {
+            showWorkspace(true);
         }
     }
 
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index 3007269..b4095a6 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -34,6 +34,7 @@
 import android.database.Cursor;
 import android.graphics.Bitmap;
 import android.net.Uri;
+import android.os.DeadObjectException;
 import android.os.Environment;
 import android.os.Handler;
 import android.os.HandlerThread;
@@ -166,6 +167,9 @@
     // sBgWidgetProviders is the set of widget providers including custom internal widgets
     public static HashMap<ComponentKey, LauncherAppWidgetProviderInfo> sBgWidgetProviders;
 
+    // sBgShortcutProviders is the set of custom shortcut providers
+    public static List<ResolveInfo> sBgShortcutProviders;
+
     // sPendingPackages is a set of packages which could be on sdcard and are not available yet
     static final HashMap<UserHandleCompat, HashSet<String>> sPendingPackages =
             new HashMap<UserHandleCompat, HashSet<String>>();
@@ -3280,9 +3284,18 @@
                     // Refresh widget list, if there is any newly added widget
                     PackageManager pm = context.getPackageManager();
                     for (String pkg : mPackages) {
-                        needToRefresh |= !pm.queryBroadcastReceivers(
-                                new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE)
-                                    .setPackage(pkg), 0).isEmpty();
+                        try {
+                            List<ResolveInfo> widgets = pm.queryBroadcastReceivers(
+                                    new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE)
+                                            .setPackage(pkg), 0);
+                            needToRefresh |= widgets != null && !widgets.isEmpty();
+                        } catch (RuntimeException e) {
+                            if (ProviderConfig.IS_DOGFOOD_BUILD) {
+                                throw e;
+                            }
+                            // Ignore the crash. We can live with a state widget list.
+                            Log.e(TAG, "PM call failed for " + pkg, e);
+                        }
                     }
                 }
 
@@ -3334,7 +3347,9 @@
                 return results;
             }
         } catch (Exception e) {
-            if (e.getCause() instanceof TransactionTooLargeException) {
+            if (!ProviderConfig.IS_DOGFOOD_BUILD &&
+                    (e.getCause() instanceof TransactionTooLargeException ||
+                    e.getCause() instanceof DeadObjectException)) {
                 // the returned value may be incomplete and will not be refreshed until the next
                 // time Launcher starts.
                 // TODO: after figuring out a repro step, introduce a dirty bit to check when
@@ -3394,8 +3409,29 @@
         PackageManager packageManager = mApp.getContext().getPackageManager();
         final ArrayList<Object> widgetsAndShortcuts = new ArrayList<Object>();
         widgetsAndShortcuts.addAll(getWidgetProviders(mApp.getContext(), refresh));
-        Intent shortcutsIntent = new Intent(Intent.ACTION_CREATE_SHORTCUT);
-        widgetsAndShortcuts.addAll(packageManager.queryIntentActivities(shortcutsIntent, 0));
+
+        // Update shortcut providers
+        synchronized (sBgLock) {
+            try {
+                Intent shortcutsIntent = new Intent(Intent.ACTION_CREATE_SHORTCUT);
+                List<ResolveInfo> providers = packageManager.queryIntentActivities(shortcutsIntent, 0);
+                sBgShortcutProviders = providers;
+            } catch (RuntimeException e) {
+                if (!LauncherAppState.isDogfoodBuild() &&
+                        (e.getCause() instanceof TransactionTooLargeException ||
+                                e.getCause() instanceof DeadObjectException)) {
+                    /**
+                     * Ignore exception and use the cached list if available.
+                     * Refer to {@link #getWidgetProviders(Context, boolean}} for more info.
+                     */
+                } else {
+                    throw e;
+                }
+            }
+            if (sBgShortcutProviders != null) {
+                widgetsAndShortcuts.addAll(sBgShortcutProviders);
+            }
+        }
         mBgWidgetsModel.setWidgetsAndShortcuts(widgetsAndShortcuts);
     }
 
diff --git a/src/com/android/launcher3/LauncherStateTransitionAnimation.java b/src/com/android/launcher3/LauncherStateTransitionAnimation.java
index 0e20fab..d3af19a 100644
--- a/src/com/android/launcher3/LauncherStateTransitionAnimation.java
+++ b/src/com/android/launcher3/LauncherStateTransitionAnimation.java
@@ -176,7 +176,7 @@
     }
 
     /**
-     * Starts and animation to the workspace from the current overlay view.
+     * Starts an animation to the workspace from the current overlay view.
      */
     public void startAnimationToWorkspace(final Launcher.State fromState,
             final Workspace.State fromWorkspaceState, final Workspace.State toWorkspaceState,
@@ -190,9 +190,13 @@
         if (fromState == Launcher.State.APPS || fromState == Launcher.State.APPS_SPRING_LOADED) {
             startAnimationToWorkspaceFromAllApps(fromWorkspaceState, toWorkspaceState,
                     animated, onCompleteRunnable);
-        } else {
+        } else if (fromState == Launcher.State.WIDGETS ||
+                fromState == Launcher.State.WIDGETS_SPRING_LOADED) {
             startAnimationToWorkspaceFromWidgets(fromWorkspaceState, toWorkspaceState,
                     animated, onCompleteRunnable);
+        } else {
+            startAnimationToNewWorkspaceState(fromWorkspaceState, toWorkspaceState,
+                    animated, onCompleteRunnable);
         }
     }
 
@@ -403,7 +407,7 @@
     }
 
     /**
-     * Starts and animation to the workspace from the apps view.
+     * Starts an animation to the workspace from the apps view.
      */
     private void startAnimationToWorkspaceFromAllApps(final Workspace.State fromWorkspaceState,
             final Workspace.State toWorkspaceState, final boolean animated, 
@@ -448,10 +452,10 @@
     }
 
     /**
-     * Starts and animation to the workspace from the widgets view.
+     * Starts an animation to the workspace from the widgets view.
      */
     private void startAnimationToWorkspaceFromWidgets(final Workspace.State fromWorkspaceState,
-            final Workspace.State toWorkspaceState, final boolean animated, 
+            final Workspace.State toWorkspaceState, final boolean animated,
             final Runnable onCompleteRunnable) {
         final WidgetsContainerView widgetsView = mLauncher.getWidgetsView();
         PrivateTransitionCallbacks cb = new PrivateTransitionCallbacks() {
@@ -477,6 +481,95 @@
     }
 
     /**
+     * Starts an animation to the workspace from another workspace state, e.g. normal to overview.
+     */
+    private void startAnimationToNewWorkspaceState(final Workspace.State fromWorkspaceState,
+            final Workspace.State toWorkspaceState, final boolean animated,
+            final Runnable onCompleteRunnable) {
+        final View fromWorkspace = mLauncher.getWorkspace();
+        final HashMap<View, Integer> layerViews = new HashMap<>();
+        final AnimatorSet animation = LauncherAnimUtils.createAnimatorSet();
+        final int revealDuration = mLauncher.getResources()
+                .getInteger(R.integer.config_overlayRevealTime);
+
+        // Cancel the current animation
+        cancelAnimation();
+
+        // Create the workspace animation.
+        // NOTE: this call apparently also sets the state for the workspace if !animated
+        Animator workspaceAnim = mLauncher.startWorkspaceStateChangeAnimation(toWorkspaceState,
+                animated, layerViews);
+
+        startWorkspaceSearchBarAnimation(animation, fromWorkspaceState, toWorkspaceState,
+                animated ? revealDuration : 0, null);
+
+        if (animated) {
+            if (workspaceAnim != null) {
+                animation.play(workspaceAnim);
+            }
+            dispatchOnLauncherTransitionPrepare(fromWorkspace, animated, true);
+
+            final AnimatorSet stateAnimation = animation;
+            final Runnable startAnimRunnable = new Runnable() {
+                @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+                public void run() {
+                    // Check that mCurrentAnimation hasn't changed while
+                    // we waited for a layout/draw pass
+                    if (mCurrentAnimation != stateAnimation)
+                        return;
+
+                    dispatchOnLauncherTransitionStart(fromWorkspace, animated, true);
+
+                    // Enable all necessary layers
+                    for (View v : layerViews.keySet()) {
+                        if (layerViews.get(v) == BUILD_AND_SET_LAYER) {
+                            v.setLayerType(View.LAYER_TYPE_HARDWARE, null);
+                        }
+                        if (Utilities.ATLEAST_LOLLIPOP && v.isAttachedToWindow()) {
+                            v.buildLayer();
+                        }
+                    }
+                    stateAnimation.start();
+                }
+            };
+            animation.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    dispatchOnLauncherTransitionEnd(fromWorkspace, animated, true);
+
+                    // Run any queued runnables
+                    if (onCompleteRunnable != null) {
+                        onCompleteRunnable.run();
+                    }
+
+                    // Disable all necessary layers
+                    for (View v : layerViews.keySet()) {
+                        if (layerViews.get(v) == BUILD_AND_SET_LAYER) {
+                            v.setLayerType(View.LAYER_TYPE_NONE, null);
+                        }
+                    }
+
+                    // This can hold unnecessary references to views.
+                    cleanupAnimation();
+                }
+            });
+            fromWorkspace.post(startAnimRunnable);
+            mCurrentAnimation = animation;
+        } else /* if (!animated) */ {
+            dispatchOnLauncherTransitionPrepare(fromWorkspace, animated, true);
+            dispatchOnLauncherTransitionStart(fromWorkspace, animated, true);
+            dispatchOnLauncherTransitionEnd(fromWorkspace, animated, true);
+
+            // Run any queued runnables
+            if (onCompleteRunnable != null) {
+                onCompleteRunnable.run();
+            }
+
+            mCurrentAnimation = null;
+        }
+    }
+
+    /**
      * Creates and starts a new animation to the workspace.
      */
     private AnimatorSet startAnimationToWorkspaceFromOverlay(final Workspace.State fromWorkspaceState,
@@ -683,7 +776,7 @@
             fromView.post(startAnimRunnable);
 
             return animation;
-        } else {
+        } else /* if (!(animated && initialized)) */ {
             fromView.setVisibility(View.GONE);
             dispatchOnLauncherTransitionPrepare(fromView, animated, true);
             dispatchOnLauncherTransitionStart(fromView, animated, true);
diff --git a/src/com/android/launcher3/SearchDropTargetBar.java b/src/com/android/launcher3/SearchDropTargetBar.java
index fdcad82..878a474 100644
--- a/src/com/android/launcher3/SearchDropTargetBar.java
+++ b/src/com/android/launcher3/SearchDropTargetBar.java
@@ -23,17 +23,15 @@
 import android.util.AttributeSet;
 import android.view.View;
 import android.view.accessibility.AccessibilityManager;
-import android.view.animation.AccelerateInterpolator;
-import android.widget.FrameLayout;
 
 import com.android.launcher3.dragndrop.DragController;
 import com.android.launcher3.util.Thunk;
 
 /*
- * Ths bar will manage the transition between the QSB search bar and the delete drop
- * targets so that each of the individual IconDropTargets don't have to.
+ * This bar will manage the transition between the QSB search bar and the delete/uninstall drop
+ * targets so that each of the individual ButtonDropTargets don't have to.
  */
-public class SearchDropTargetBar extends FrameLayout implements DragController.DragListener {
+public class SearchDropTargetBar extends BaseDropTargetBar {
 
     /** The different states that the search bar space can be in. */
     public enum State {
@@ -58,21 +56,13 @@
         }
     }
 
-    private static int DEFAULT_DRAG_FADE_DURATION = 175;
 
-    private LauncherViewPropertyAnimator mDropTargetBarAnimator;
     private LauncherViewPropertyAnimator mQSBSearchBarAnimator;
-    private static final AccelerateInterpolator sAccelerateInterpolator =
-            new AccelerateInterpolator();
 
     private State mState = State.SEARCH_BAR;
     @Thunk View mQSB;
-    @Thunk View mDropTargetBar;
-    private boolean mDeferOnDragEnd = false;
-    @Thunk boolean mAccessibilityEnabled = false;
 
     // Drop targets
-    private ButtonDropTarget mInfoDropTarget;
     private ButtonDropTarget mDeleteDropTarget;
     private ButtonDropTarget mUninstallDropTarget;
 
@@ -84,61 +74,48 @@
         super(context, attrs, defStyle);
     }
 
-    public void setup(Launcher launcher, DragController dragController) {
-        dragController.addDragListener(this);
-        dragController.setFlingToDeleteDropTarget(mDeleteDropTarget);
-
-        dragController.addDragListener(mInfoDropTarget);
-        dragController.addDragListener(mDeleteDropTarget);
-        dragController.addDragListener(mUninstallDropTarget);
-
-        dragController.addDropTarget(mInfoDropTarget);
-        dragController.addDropTarget(mDeleteDropTarget);
-        dragController.addDropTarget(mUninstallDropTarget);
-
-        mInfoDropTarget.setLauncher(launcher);
-        mDeleteDropTarget.setLauncher(launcher);
-        mUninstallDropTarget.setLauncher(launcher);
-    }
-
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
 
         // Get the individual components
-        mDropTargetBar = findViewById(R.id.drag_target_bar);
-        mInfoDropTarget = (ButtonDropTarget) mDropTargetBar.findViewById(R.id.info_target_text);
         mDeleteDropTarget = (ButtonDropTarget) mDropTargetBar.findViewById(R.id.delete_target_text);
-        mUninstallDropTarget = (ButtonDropTarget) mDropTargetBar.findViewById(R.id.uninstall_target_text);
+        mUninstallDropTarget = (ButtonDropTarget) mDropTargetBar
+                .findViewById(R.id.uninstall_target_text);
 
-        mInfoDropTarget.setSearchDropTargetBar(this);
-        mDeleteDropTarget.setSearchDropTargetBar(this);
-        mUninstallDropTarget.setSearchDropTargetBar(this);
+        mDeleteDropTarget.setDropTargetBar(this);
+        mUninstallDropTarget.setDropTargetBar(this);
+    }
 
-        // Create the various fade animations
-        mDropTargetBar.setAlpha(0f);
-        mDropTargetBarAnimator = new LauncherViewPropertyAnimator(mDropTargetBar);
-        mDropTargetBarAnimator.setInterpolator(sAccelerateInterpolator);
-        mDropTargetBarAnimator.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationStart(Animator animation) {
-                // Ensure that the view is visible for the animation
-                mDropTargetBar.setVisibility(View.VISIBLE);
-            }
+    @Override
+    public void setup(Launcher launcher, DragController dragController) {
+        dragController.addDragListener(this);
+        dragController.setFlingToDeleteDropTarget(mDeleteDropTarget);
 
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                if (mDropTargetBar != null) {
-                    AlphaUpdateListener.updateVisibility(mDropTargetBar, mAccessibilityEnabled);
-                }
-            }
-        });
+        dragController.addDragListener(mDeleteDropTarget);
+        dragController.addDragListener(mUninstallDropTarget);
+
+        dragController.addDropTarget(mDeleteDropTarget);
+        dragController.addDropTarget(mUninstallDropTarget);
+
+        mDeleteDropTarget.setLauncher(launcher);
+        mUninstallDropTarget.setLauncher(launcher);
+    }
+
+    @Override
+    public void showDropTarget() {
+        animateToState(State.DROP_TARGET, DEFAULT_DRAG_FADE_DURATION);
+    }
+
+    @Override
+    public void hideDropTarget() {
+        animateToState(State.SEARCH_BAR, DEFAULT_DRAG_FADE_DURATION);
     }
 
     public void setQsbSearchBar(View qsb) {
         mQSB = qsb;
         if (mQSB != null) {
-            // Update the search ber animation
+            // Update the search bar animation
             mQSBSearchBarAnimator = new LauncherViewPropertyAnimator(mQSB);
             mQSBSearchBarAnimator.setInterpolator(sAccelerateInterpolator);
             mQSBSearchBarAnimator.addListener(new AnimatorListenerAdapter() {
@@ -183,51 +160,6 @@
     }
 
     /**
-     * Convenience method to animate the alpha of a view using hardware layers.
-     */
-    private void animateViewAlpha(LauncherViewPropertyAnimator animator, View v, float alpha,
-            int duration) {
-        if (v == null) {
-            return;
-        }
-
-        animator.cancel();
-        if (Float.compare(v.getAlpha(), alpha) != 0) {
-            if (duration > 0) {
-                animator.alpha(alpha).withLayer().setDuration(duration).start();
-            } else {
-                v.setAlpha(alpha);
-                AlphaUpdateListener.updateVisibility(v, mAccessibilityEnabled);
-            }
-        }
-    }
-
-    /*
-     * DragController.DragListener implementation
-     */
-    @Override
-    public void onDragStart(DragSource source, ItemInfo info, int dragAction) {
-        animateToState(State.DROP_TARGET, DEFAULT_DRAG_FADE_DURATION);
-    }
-
-    /**
-     * This is called to defer hiding the delete drop target until the drop animation has completed,
-     * instead of hiding immediately when the drag has ended.
-     */
-    public void deferOnDragEnd() {
-        mDeferOnDragEnd = true;
-    }
-
-    @Override
-    public void onDragEnd() {
-        if (!mDeferOnDragEnd) {
-            animateToState(State.SEARCH_BAR, DEFAULT_DRAG_FADE_DURATION);
-        } else {
-            mDeferOnDragEnd = false;
-        }
-    }
-
-    /**
      * @return the bounds of the QSB search bar.
      */
     public Rect getSearchBarBounds() {
@@ -246,11 +178,11 @@
         }
     }
 
+    @Override
     public void enableAccessibleDrag(boolean enable) {
         if (mQSB != null) {
             mQSB.setVisibility(enable ? View.GONE : View.VISIBLE);
         }
-        mInfoDropTarget.enableAccessibleDrag(enable);
         mDeleteDropTarget.enableAccessibleDrag(enable);
         mUninstallDropTarget.enableAccessibleDrag(enable);
     }
diff --git a/src/com/android/launcher3/UninstallDropTarget.java b/src/com/android/launcher3/UninstallDropTarget.java
index 9ed4fb6..b69c79d 100644
--- a/src/com/android/launcher3/UninstallDropTarget.java
+++ b/src/com/android/launcher3/UninstallDropTarget.java
@@ -82,7 +82,7 @@
     void completeDrop(final DragObject d) {
         final Pair<ComponentName, Integer> componentInfo = getAppInfoFlags(d.dragInfo);
         final UserHandleCompat user = d.dragInfo.user;
-        if (startUninstallActivity(mLauncher, d.dragInfo)) {
+        if (startActivityWithUninstallAffordance(d)) {
 
             final Runnable checkIfUninstallWasSuccess = new Runnable() {
                 @Override
@@ -99,6 +99,10 @@
         }
     }
 
+    protected boolean startActivityWithUninstallAffordance(DragObject d) {
+        return startUninstallActivity(mLauncher, d.dragInfo);
+    }
+
     public static boolean startUninstallActivity(Launcher launcher, ItemInfo info) {
         final Pair<ComponentName, Integer> componentInfo = getAppInfoFlags(info);
         final UserHandleCompat user = info.user;
@@ -115,7 +119,7 @@
     /**
      * Interface defining an object that can provide uninstallable drag objects.
      */
-    public static interface UninstallSource {
+    public interface UninstallSource {
 
         /**
          * A pending uninstall operation was complete.
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 1539658..b59d85d 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -1582,6 +1582,7 @@
             setOnClickListener(mLauncher);
         }
         mLauncher.getSearchDropTargetBar().enableAccessibleDrag(enable);
+        mLauncher.getAppInfoDropTargetBar().enableAccessibleDrag(enable);
         mLauncher.getHotseat().getLayout()
             .enableAccessibleDrag(enable, CellLayout.WORKSPACE_ACCESSIBILITY_DRAG);
     }
@@ -1998,6 +1999,10 @@
         return -workspaceOffsetTopEdge + overviewOffsetTopEdge;
     }
 
+    int getSpringLoadedTranslationY() {
+        return getOverviewModeTranslationY();
+    }
+
     /**
      * Sets the current workspace {@link State}, returning an animation transitioning the workspace
      * to that new state.
@@ -2341,6 +2346,8 @@
         dv.setIntrinsicIconScaleFactor(source.getIntrinsicIconScaleFactor());
 
         b.recycle();
+
+        mLauncher.enterSpringLoadedDragMode();
     }
 
     public void beginExternalDragShared(View child, DragSource source) {
@@ -2389,6 +2396,8 @@
 
         // Recycle temporary bitmaps
         tmpB.recycle();
+
+        mLauncher.enterSpringLoadedDragMode();
     }
 
     public boolean transitionStateShouldAllowDrop() {
@@ -2846,10 +2855,6 @@
         CellLayout layout = getCurrentDropLayout();
         setCurrentDropLayout(layout);
         setCurrentDragOverlappingLayout(layout);
-
-        if (!workspaceInModalState()) {
-            mLauncher.getDragLayer().showPageHints();
-        }
     }
 
     @Override
@@ -2884,8 +2889,6 @@
         setCurrentDragOverlappingLayout(null);
 
         mSpringLoadedDragController.cancel();
-
-        mLauncher.getDragLayer().hidePageHints();
     }
 
     private void enfoceDragParity(String event, int update, int expectedValue) {
@@ -3720,6 +3723,9 @@
         }
         mDragOutline = null;
         mDragInfo = null;
+
+        mLauncher.exitSpringLoadedDragModeDelayed(success,
+                Launcher.EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, null);
     }
 
     /**
@@ -3809,7 +3815,7 @@
 
     @Override
     public boolean supportsAppInfoDropTarget() {
-        return false;
+        return true;
     }
 
     @Override
diff --git a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
index 011f4a2..d32ce73 100644
--- a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
+++ b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
@@ -285,8 +285,13 @@
         float finalHotseatAndPageIndicatorAlpha = (states.stateIsNormal || states.stateIsSpringLoaded) ?
                 1f : 0f;
         float finalOverviewPanelAlpha = states.stateIsOverview ? 1f : 0f;
-        float finalWorkspaceTranslationY = states.stateIsOverview || states.stateIsOverviewHidden ?
-                mWorkspace.getOverviewModeTranslationY() : 0;
+
+        float finalWorkspaceTranslationY = 0;
+        if (states.stateIsOverview || states.stateIsOverviewHidden) {
+            finalWorkspaceTranslationY = mWorkspace.getOverviewModeTranslationY();
+        } else if (states.stateIsSpringLoaded) {
+            finalWorkspaceTranslationY = mWorkspace.getSpringLoadedTranslationY();
+        }
 
         final int childCount = mWorkspace.getChildCount();
         final int customPageCount = mWorkspace.numCustomPages();
diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java
index 41b4e04..979a733 100644
--- a/src/com/android/launcher3/allapps/AllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java
@@ -477,8 +477,6 @@
 
         // Start the drag
         mLauncher.getWorkspace().beginDragShared(v, mIconLastTouchPos, this, false);
-        // Enter spring loaded mode
-        mLauncher.enterSpringLoadedDragMode();
 
         return false;
     }
diff --git a/src/com/android/launcher3/dragndrop/DragController.java b/src/com/android/launcher3/dragndrop/DragController.java
index 8777dc6..4a39e6b 100644
--- a/src/com/android/launcher3/dragndrop/DragController.java
+++ b/src/com/android/launcher3/dragndrop/DragController.java
@@ -42,7 +42,6 @@
 import com.android.launcher3.Launcher;
 import com.android.launcher3.PagedView;
 import com.android.launcher3.ShortcutInfo;
-import com.android.launcher3.Utilities;
 import com.android.launcher3.util.Thunk;
 
 import com.android.launcher3.R;
@@ -82,7 +81,6 @@
     // temporaries to avoid gc thrash
     private Rect mRectTemp = new Rect();
     private final int[] mCoordinatesTemp = new int[2];
-    private final boolean mIsRtl;
 
     /**
      * Drag driver for the current drag/drop operation, or null if there is no active DND operation.
@@ -99,11 +97,6 @@
     /** Y coordinate of the down event. */
     private int mMotionDownY;
 
-    /** the area at the edge of the screen that makes the workspace go left
-     *   or right while you're dragging.
-     */
-    private final int mScrollZone;
-
     private DropTarget.DragObject mDragObject;
 
     /** Who can receive drop events */
@@ -114,9 +107,6 @@
     /** The window token used as the parent for the DragView. */
     private IBinder mWindowToken;
 
-    /** The view that will be scrolled when dragging to the left and right edges of the screen. */
-    private View mScrollView;
-
     private View mMoveTarget;
 
     @Thunk DragScroller mDragScroller;
@@ -129,7 +119,6 @@
 
     @Thunk int mLastTouch[] = new int[2];
     @Thunk long mLastTouchUpTime = -1;
-    @Thunk int mDistanceSinceScroll = 0;
 
     private int mTmpPoint[] = new int[2];
     private Rect mDragLayerRect = new Rect();
@@ -159,19 +148,15 @@
 
     /**
      * Used to create a new DragLayer from XML.
-     *
-     * @param context The application's context.
      */
     public DragController(Launcher launcher) {
         Resources r = launcher.getResources();
         mLauncher = launcher;
         mHandler = new Handler();
-        mScrollZone = r.getDimensionPixelSize(R.dimen.scroll_zone);
         mVelocityTracker = VelocityTracker.obtain();
 
         mFlingToDeleteThresholdVelocity =
                 r.getDimensionPixelSize(R.dimen.drag_flingToDeleteMinVelocity);
-        mIsRtl = Utilities.isRtl(r);
     }
 
     /**
@@ -559,7 +544,6 @@
             mScrollState = SCROLL_OUTSIDE_ZONE;
             mScrollRunnable.setDirection(SCROLL_RIGHT);
             mDragScroller.onExitScrollArea();
-            mLauncher.getDragLayer().onExitScrollArea();
         }
     }
 
@@ -573,11 +557,8 @@
         mDragObject.y = coordinates[1];
         checkTouchMove(dropTarget);
 
-        // Check if we are hovering over the scroll areas
-        mDistanceSinceScroll += Math.hypot(mLastTouch[0] - x, mLastTouch[1] - y);
         mLastTouch[0] = x;
         mLastTouch[1] = y;
-        checkScrollState(x, y);
     }
 
     public void forceTouchMove() {
@@ -605,36 +586,6 @@
         mLastDropTarget = dropTarget;
     }
 
-    @Thunk void checkScrollState(int x, int y) {
-        final int slop = ViewConfiguration.get(mLauncher).getScaledWindowTouchSlop();
-        final int delay = mDistanceSinceScroll < slop ? RESCROLL_DELAY : SCROLL_DELAY;
-        final DragLayer dragLayer = mLauncher.getDragLayer();
-        final int forwardDirection = mIsRtl ? SCROLL_RIGHT : SCROLL_LEFT;
-        final int backwardsDirection = mIsRtl ? SCROLL_LEFT : SCROLL_RIGHT;
-
-        if (x < mScrollZone) {
-            if (mScrollState == SCROLL_OUTSIDE_ZONE) {
-                mScrollState = SCROLL_WAITING_IN_ZONE;
-                if (mDragScroller.onEnterScrollArea(x, y, forwardDirection)) {
-                    dragLayer.onEnterScrollArea(forwardDirection);
-                    mScrollRunnable.setDirection(forwardDirection);
-                    mHandler.postDelayed(mScrollRunnable, delay);
-                }
-            }
-        } else if (x > mScrollView.getWidth() - mScrollZone) {
-            if (mScrollState == SCROLL_OUTSIDE_ZONE) {
-                mScrollState = SCROLL_WAITING_IN_ZONE;
-                if (mDragScroller.onEnterScrollArea(x, y, backwardsDirection)) {
-                    dragLayer.onEnterScrollArea(backwardsDirection);
-                    mScrollRunnable.setDirection(backwardsDirection);
-                    mHandler.postDelayed(mScrollRunnable, delay);
-                }
-            }
-        } else {
-            clearScrollRunnable();
-        }
-    }
-
     /**
      * Call this from a drag source view.
      */
@@ -656,13 +607,6 @@
                 // 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;
-                }
                 break;
             case MotionEvent.ACTION_UP:
             case MotionEvent.ACTION_CANCEL:
@@ -712,21 +656,29 @@
 
         ViewConfiguration config = ViewConfiguration.get(mLauncher);
         mVelocityTracker.computeCurrentVelocity(1000, config.getScaledMaximumFlingVelocity());
-
+        PointF vel = new PointF(mVelocityTracker.getXVelocity(), mVelocityTracker.getYVelocity());
+        float theta = MAX_FLING_DEGREES + 1;
         if (mVelocityTracker.getYVelocity() < mFlingToDeleteThresholdVelocity) {
             // Do a quick dot product test to ensure that we are flinging upwards
-            PointF vel = new PointF(mVelocityTracker.getXVelocity(),
-                    mVelocityTracker.getYVelocity());
             PointF upVec = new PointF(0f, -1f);
-            float theta = (float) Math.acos(((vel.x * upVec.x) + (vel.y * upVec.y)) /
-                    (vel.length() * upVec.length()));
-            if (theta <= Math.toRadians(MAX_FLING_DEGREES)) {
-                return vel;
-            }
+            theta = getAngleBetweenVectors(vel, upVec);
+        } else if (mLauncher.getDeviceProfile().isVerticalBarLayout() &&
+                mVelocityTracker.getXVelocity() < mFlingToDeleteThresholdVelocity) {
+            // Remove icon is on left side instead of top, so check if we are flinging to the left.
+            PointF leftVec = new PointF(-1f, 0f);
+            theta = getAngleBetweenVectors(vel, leftVec);
+        }
+        if (theta <= Math.toRadians(MAX_FLING_DEGREES)) {
+            return vel;
         }
         return null;
     }
 
+    private float getAngleBetweenVectors(PointF vec1, PointF vec2) {
+        return (float) Math.acos(((vec1.x * vec2.x) + (vec1.y * vec2.y)) /
+                (vec1.length() * vec2.length()));
+    }
+
     void drop(DropTarget dropTarget, float x, float y, PointF flingVel) {
         final int[] coordinates = mCoordinatesTemp;
 
@@ -847,13 +799,6 @@
         }
     }
 
-    /**
-     * Set which view scrolls for touch events near the edge of the screen.
-     */
-    public void setScrollView(View v) {
-        mScrollView = v;
-    }
-
     private class ScrollRunnable implements Runnable {
         private int mDirection;
 
@@ -868,14 +813,7 @@
                     mDragScroller.scrollRight();
                 }
                 mScrollState = SCROLL_OUTSIDE_ZONE;
-                mDistanceSinceScroll = 0;
                 mDragScroller.onExitScrollArea();
-                mLauncher.getDragLayer().onExitScrollArea();
-
-                if (isDragging()) {
-                    // Check the scroll again so that we can requeue the scroller if necessary
-                    checkScrollState(mLastTouch[0], mLastTouch[1]);
-                }
             }
         }
 
diff --git a/src/com/android/launcher3/dragndrop/DragLayer.java b/src/com/android/launcher3/dragndrop/DragLayer.java
index 741d5e6..adcc12c 100644
--- a/src/com/android/launcher3/dragndrop/DragLayer.java
+++ b/src/com/android/launcher3/dragndrop/DragLayer.java
@@ -102,15 +102,6 @@
     // Darkening scrim
     private float mBackgroundAlpha = 0;
 
-    // Related to adjacent page hints
-    private final Rect mScrollChildPosition = new Rect();
-    private boolean mInScrollArea;
-    private boolean mShowPageHints;
-    private Drawable mLeftHoverDrawable;
-    private Drawable mRightHoverDrawable;
-    private Drawable mLeftHoverDrawableActive;
-    private Drawable mRightHoverDrawableActive;
-
     private boolean mBlockTouches = false;
 
     /**
@@ -126,12 +117,7 @@
         setMotionEventSplittingEnabled(false);
         setChildrenDrawingOrderEnabled(true);
 
-        final Resources res = getResources();
-        mLeftHoverDrawable = res.getDrawable(R.drawable.page_hover_left);
-        mRightHoverDrawable = res.getDrawable(R.drawable.page_hover_right);
-        mLeftHoverDrawableActive = res.getDrawable(R.drawable.page_hover_left_active);
-        mRightHoverDrawableActive = res.getDrawable(R.drawable.page_hover_right_active);
-        mIsRtl = Utilities.isRtl(res);
+        mIsRtl = Utilities.isRtl(getResources());
     }
 
     public void setup(Launcher launcher, DragController controller) {
@@ -179,6 +165,11 @@
         if (mHitRect.contains((int) ev.getX(), (int) ev.getY())) {
             return true;
         }
+
+        getDescendantRectRelativeToSelf(mLauncher.getAppInfoDropTargetBar(), mHitRect);
+        if (mHitRect.contains((int) ev.getX(), (int) ev.getY())) {
+            return true;
+        }
         return false;
     }
 
@@ -338,6 +329,7 @@
 
             if (isInAccessibleDrag()) {
                 childrenForAccessibility.add(mLauncher.getSearchDropTargetBar());
+                childrenForAccessibility.add(mLauncher.getAppInfoDropTargetBar());
             }
         } else {
             super.addChildrenForAccessibility(childrenForAccessibility);
@@ -895,29 +887,6 @@
         }
     }
 
-    void onEnterScrollArea(int direction) {
-        mInScrollArea = true;
-        invalidate();
-    }
-
-    void onExitScrollArea() {
-        mInScrollArea = false;
-        invalidate();
-    }
-
-    public void showPageHints() {
-        mShowPageHints = true;
-        Workspace workspace = mLauncher.getWorkspace();
-        getDescendantRectRelativeToSelf(workspace.getChildAt(workspace.numCustomPages()),
-                mScrollChildPosition);
-        invalidate();
-    }
-
-    public void hidePageHints() {
-        mShowPageHints = false;
-        invalidate();
-    }
-
     @Override
     protected void dispatchDraw(Canvas canvas) {
         // Draw the background below children.
@@ -929,41 +898,6 @@
         super.dispatchDraw(canvas);
     }
 
-    private void drawPageHints(Canvas canvas) {
-        if (mShowPageHints) {
-            Workspace workspace = mLauncher.getWorkspace();
-            int width = getMeasuredWidth();
-            int page = workspace.getNextPage();
-            CellLayout leftPage = (CellLayout) workspace.getChildAt(mIsRtl ? page + 1 : page - 1);
-            CellLayout rightPage = (CellLayout) workspace.getChildAt(mIsRtl ? page - 1 : page + 1);
-
-            if (leftPage != null && leftPage.isDragTarget()) {
-                Drawable left = mInScrollArea && leftPage.getIsDragOverlapping() ?
-                        mLeftHoverDrawableActive : mLeftHoverDrawable;
-                left.setBounds(0, mScrollChildPosition.top,
-                        left.getIntrinsicWidth(), mScrollChildPosition.bottom);
-                left.draw(canvas);
-            }
-            if (rightPage != null && rightPage.isDragTarget()) {
-                Drawable right = mInScrollArea && rightPage.getIsDragOverlapping() ?
-                        mRightHoverDrawableActive : mRightHoverDrawable;
-                right.setBounds(width - right.getIntrinsicWidth(),
-                        mScrollChildPosition.top, width, mScrollChildPosition.bottom);
-                right.draw(canvas);
-            }
-        }
-    }
-
-    protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
-        boolean ret = super.drawChild(canvas, child, drawingTime);
-
-        // We want to draw the page hints above the workspace, but below the drag view.
-        if (child instanceof Workspace) {
-            drawPageHints(canvas);
-        }
-        return ret;
-    }
-
     public void setBackgroundAlpha(float alpha) {
         if (alpha != mBackgroundAlpha) {
             mBackgroundAlpha = alpha;
diff --git a/src/com/android/launcher3/util/FlingAnimation.java b/src/com/android/launcher3/util/FlingAnimation.java
index f82038b..da8bae7 100644
--- a/src/com/android/launcher3/util/FlingAnimation.java
+++ b/src/com/android/launcher3/util/FlingAnimation.java
@@ -51,7 +51,7 @@
         mFrom.top += yOffset;
         mFrom.bottom -= yOffset;
 
-        mDuration = initDuration();
+        mDuration = Math.abs(vel.y) > Math.abs(vel.x) ? initFlingUpDuration() : initFlingLeftDuration();
         mAnimationTimeFraction = ((float) mDuration) / (mDuration + DRAG_END_DELAY);
     }
 
@@ -62,7 +62,7 @@
      *   - Calculate a constant acceleration in x direction such that the object reaches
      *     {@link #mIconRect} in the given time.
      */
-    protected int initDuration() {
+    protected int initFlingUpDuration() {
         float sY = -mFrom.bottom;
 
         float d = mUY * mUY + 2 * sY * MAX_ACCELERATION;
@@ -83,6 +83,34 @@
         return (int) Math.round(t);
     }
 
+    /**
+     * The fling animation is based on the following system
+     *   - Apply a constant force in the x 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 y direction such that the object reaches
+     *     {@link #mIconRect} in the given time.
+     */
+    protected int initFlingLeftDuration() {
+        float sX = -mFrom.right;
+
+        float d = mUX * mUX + 2 * sX * MAX_ACCELERATION;
+        if (d >= 0) {
+            // sX can be reached under the MAX_ACCELERATION. Use MAX_ACCELERATION for x direction.
+            mAX = MAX_ACCELERATION;
+        } else {
+            // sX is not reachable, decrease the acceleration so that sX is almost reached.
+            d = 0;
+            mAX = mUX * mUX / (2 * -sX);
+        }
+        double t = (-mUX - Math.sqrt(d)) / mAX;
+
+        float sY = -mFrom.exactCenterY() + mIconRect.exactCenterY();
+
+        // Find vertical acceleration such that: u*t + a*t*t/2 = s
+        mAY = (float) ((sY - t * mUY) * 2 / (t * t));
+        return (int) Math.round(t);
+    }
+
     public final int getDuration() {
         return mDuration + DRAG_END_DELAY;
     }