Add user event logging for tap on search b/31772967
am: d8fcffb8fe

Change-Id: I67e36fb3167a40c24957b4ec3bb7ed08d9966e62
diff --git a/build.gradle b/build.gradle
index e103d79..0c00da9 100644
--- a/build.gradle
+++ b/build.gradle
@@ -3,7 +3,7 @@
         mavenCentral()
     }
     dependencies {
-        classpath 'com.android.tools.build:gradle:2.1.3'
+        classpath 'com.android.tools.build:gradle:2.2.0'
         classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.0'
     }
 }
diff --git a/res/layout-port/launcher.xml b/res/layout-port/launcher.xml
index a2e2f9b..dd981dd 100644
--- a/res/layout-port/launcher.xml
+++ b/res/layout-port/launcher.xml
@@ -77,8 +77,7 @@
             android:id="@+id/apps_view"
             android:layout_width="match_parent"
             android:layout_height="match_parent"
-            android:visibility="invisible"
-            launcher:layout_ignoreInsets="true" />
+            android:visibility="invisible" />
     </com.android.launcher3.dragndrop.DragLayer>
 
 </com.android.launcher3.LauncherRootView>
diff --git a/res/layout-sw720dp/launcher.xml b/res/layout-sw720dp/launcher.xml
index 12c01b7..06cb550 100644
--- a/res/layout-sw720dp/launcher.xml
+++ b/res/layout-sw720dp/launcher.xml
@@ -76,8 +76,7 @@
             android:id="@+id/apps_view"
             android:layout_width="match_parent"
             android:layout_height="match_parent"
-            android:visibility="invisible"
-           launcher:layout_ignoreInsets="true" />
+            android:visibility="invisible" />
     </com.android.launcher3.dragndrop.DragLayer>
 
 </com.android.launcher3.LauncherRootView>
diff --git a/res/layout/all_apps.xml b/res/layout/all_apps.xml
index 4909eb3..9268839 100644
--- a/res/layout/all_apps.xml
+++ b/res/layout/all_apps.xml
@@ -46,10 +46,10 @@
         <!-- DO NOT CHANGE THE ID -->
         <com.android.launcher3.allapps.AllAppsRecyclerView
             android:id="@+id/apps_list_view"
+            android:layout_below="@+id/search_container"
             android:layout_width="match_parent"
             android:layout_height="match_parent"
             android:layout_gravity="center_horizontal|top"
-            android:layout_marginTop="@dimen/all_apps_search_bar_height"
             android:clipToPadding="false"
             android:descendantFocusability="afterDescendants"
             android:focusable="true"
@@ -60,7 +60,6 @@
             android:layout_width="match_parent"
             android:layout_height="@dimen/all_apps_search_bar_height"
             android:layout_gravity="center|top"
-            android:paddingTop="@dimen/all_apps_search_bar_margin_top"
             android:gravity="center|bottom"
             android:orientation="horizontal"
             android:saveEnabled="false">
@@ -68,8 +67,9 @@
             <com.android.launcher3.ExtendedEditText
                 android:id="@+id/search_box_input"
                 android:layout_width="match_parent"
-                android:layout_height="match_parent"
+                android:layout_height="@dimen/all_apps_search_bar_field_height"
                 android:background="@android:color/transparent"
+                android:layout_gravity="bottom"
                 android:focusableInTouchMode="true"
                 android:gravity="center"
                 android:imeOptions="actionSearch|flagNoExtractUi"
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index c112949..12e902a 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -64,8 +64,8 @@
     <dimen name="all_apps_grid_view_start_margin">0dp</dimen>
     <dimen name="all_apps_grid_section_y_offset">8dp</dimen>
     <dimen name="all_apps_grid_section_text_size">24sp</dimen>
+    <dimen name="all_apps_search_bar_field_height">48dp</dimen>
     <dimen name="all_apps_search_bar_height">60dp</dimen>
-    <dimen name="all_apps_search_bar_margin_top">12dp</dimen>
     <dimen name="all_apps_search_bar_icon_margin_right">4dp</dimen>
     <dimen name="all_apps_search_bar_icon_margin_top">1dp</dimen>
     <dimen name="all_apps_list_bottom_padding">8dp</dimen>
@@ -164,7 +164,7 @@
     <dimen name="bg_pill_height">48dp</dimen>
     <dimen name="bg_pill_radius">24dp</dimen>
     <dimen name="deep_shortcuts_spacing">4dp</dimen>
-    <dimen name="deferred_drag_view_scale">6dp</dimen>
+    <dimen name="pre_drag_view_scale">6dp</dimen>
     <!-- an icon with shortcuts must be dragged this far before the container is removed. -->
     <dimen name="deep_shortcuts_start_drag_threshold">16dp</dimen>
     <dimen name="deep_shortcut_icon_size">36dp</dimen>
@@ -182,6 +182,9 @@
          also happens to equal 19dp-->
     <dimen name="deep_shortcuts_arrow_horizontal_offset">19dp</dimen>
 
+<!-- Touch handling -->
+    <dimen name="edge_of_screen_threshold">8dp</dimen>
+
 <!-- Other -->
     <!-- Approximates the system status bar height. Not guaranteed to be always be correct. -->
     <dimen name="status_bar_height">24dp</dimen>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 60a37e5..a9c970d 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -37,6 +37,10 @@
     <string name="safemode_widget_error">Widgets disabled in Safe mode</string>
     <!-- Message shown when a shortcut is not available. It could have been temporarily disabled and may start working again after some time. -->
     <string name="shortcut_not_available">Shortcut isn\'t available</string>
+    <!-- User visible name for the launcher/home screen. [CHAR_LIMIT=30] -->
+    <string name="home_screen">Home screen</string>
+    <!-- Label for showing custom action list of a shortcut or widget. [CHAR_LIMIT=30] -->
+    <string name="custom_actions">Custom actions</string>
 
     <!-- Widgets -->
     <!-- Message to tell the user to press and hold on a widget to add it [CHAR_LIMIT=50] -->
diff --git a/src/com/android/launcher3/AllAppsList.java b/src/com/android/launcher3/AllAppsList.java
index c431593..c427ddc 100644
--- a/src/com/android/launcher3/AllAppsList.java
+++ b/src/com/android/launcher3/AllAppsList.java
@@ -23,7 +23,7 @@
 import com.android.launcher3.compat.LauncherAppsCompat;
 import com.android.launcher3.compat.UserHandleCompat;
 import com.android.launcher3.util.FlagOp;
-import com.android.launcher3.util.StringFilter;
+import com.android.launcher3.util.ItemInfoMatcher;
 
 import java.util.ArrayList;
 import java.util.HashSet;
@@ -121,14 +121,13 @@
     }
 
     /**
-     * Updates the apps for the given packageName and user based on {@param op}.
+     * Updates the disabled flags of apps matching {@param matcher} based on {@param op}.
      */
-    public void updatePackageFlags(StringFilter pkgFilter, UserHandleCompat user, FlagOp op) {
+    public void updateDisabledFlags(ItemInfoMatcher matcher, FlagOp op) {
         final List<AppInfo> data = this.data;
         for (int i = data.size() - 1; i >= 0; i--) {
             AppInfo info = data.get(i);
-            final ComponentName component = info.intent.getComponent();
-            if (info.user.equals(user) && pkgFilter.matches(component.getPackageName())) {
+            if (matcher.matches(info, info.intent.getComponent())) {
                 info.isDisabled = op.apply(info.isDisabled);
                 modified.add(info);
             }
diff --git a/src/com/android/launcher3/AppWidgetResizeFrame.java b/src/com/android/launcher3/AppWidgetResizeFrame.java
index daeca3b..f0ec503 100644
--- a/src/com/android/launcher3/AppWidgetResizeFrame.java
+++ b/src/com/android/launcher3/AppWidgetResizeFrame.java
@@ -15,14 +15,17 @@
 import android.graphics.Rect;
 import android.view.Gravity;
 import android.view.KeyEvent;
+import android.view.MotionEvent;
 import android.view.View;
 import android.widget.FrameLayout;
 import android.widget.ImageView;
 
 import com.android.launcher3.accessibility.DragViewStateAnnouncer;
 import com.android.launcher3.util.FocusLogic;
+import com.android.launcher3.util.TouchController;
 
-public class AppWidgetResizeFrame extends FrameLayout implements View.OnKeyListener {
+public class AppWidgetResizeFrame extends FrameLayout
+        implements View.OnKeyListener, TouchController {
     private static final int SNAP_DURATION = 150;
     private static final float DIMMED_HANDLE_ALPHA = 0f;
     private static final float RESIZE_THRESHOLD = 0.66f;
@@ -76,6 +79,8 @@
     private int mTopTouchRegionAdjustment = 0;
     private int mBottomTouchRegionAdjustment = 0;
 
+    private int mXDown, mYDown;
+
     public AppWidgetResizeFrame(Context context,
             LauncherAppWidgetHostView widgetView, CellLayout cellLayout, DragLayer dragLayer) {
 
@@ -205,7 +210,7 @@
         }
     }
 
-    public void visualizeResizeForDelta(int deltaX, int deltaY) {
+    private void visualizeResizeForDelta(int deltaX, int deltaY) {
         visualizeResizeForDelta(deltaX, deltaY, false);
     }
 
@@ -398,7 +403,7 @@
         requestLayout();
     }
 
-    public void onTouchUp() {
+    private void onTouchUp() {
         int xThreshold = mCellLayout.getCellWidth() + mCellLayout.getWidthGap();
         int yThreshold = mCellLayout.getCellHeight() + mCellLayout.getHeightGap();
 
@@ -493,10 +498,56 @@
     public boolean onKey(View v, int keyCode, KeyEvent event) {
         // Clear the frame and give focus to the widget host view when a directional key is pressed.
         if (FocusLogic.shouldConsume(keyCode)) {
-            mDragLayer.clearAllResizeFrames();
+            mDragLayer.clearResizeFrame();
             mWidgetView.requestFocus();
             return true;
         }
         return false;
     }
+
+    private boolean handleTouchDown(MotionEvent ev) {
+        Rect hitRect = new Rect();
+        int x = (int) ev.getX();
+        int y = (int) ev.getY();
+
+        getHitRect(hitRect);
+        if (hitRect.contains(x, y)) {
+            if (beginResizeIfPointInRegion(x - getLeft(), y - getTop())) {
+                mXDown = x;
+                mYDown = y;
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public boolean onControllerTouchEvent(MotionEvent ev) {
+        int action = ev.getAction();
+        int x = (int) ev.getX();
+        int y = (int) ev.getY();
+
+        switch (action) {
+            case MotionEvent.ACTION_DOWN:
+                return handleTouchDown(ev);
+            case MotionEvent.ACTION_MOVE:
+                visualizeResizeForDelta(x - mXDown, y - mYDown);
+                break;
+            case MotionEvent.ACTION_CANCEL:
+            case MotionEvent.ACTION_UP:
+                visualizeResizeForDelta(x - mXDown, y - mYDown);
+                onTouchUp();
+                mXDown = mYDown = 0;
+                break;
+        }
+        return true;
+    }
+
+    @Override
+    public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
+        if (ev.getAction() == MotionEvent.ACTION_DOWN && handleTouchDown(ev)) {
+            return true;
+        }
+        return false;
+    }
 }
diff --git a/src/com/android/launcher3/AutoInstallsLayout.java b/src/com/android/launcher3/AutoInstallsLayout.java
index d5309b4..8b5a8a8 100644
--- a/src/com/android/launcher3/AutoInstallsLayout.java
+++ b/src/com/android/launcher3/AutoInstallsLayout.java
@@ -38,6 +38,7 @@
 import com.android.launcher3.LauncherProvider.SqlArguments;
 import com.android.launcher3.LauncherSettings.Favorites;
 import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.graphics.LauncherIcons;
 import com.android.launcher3.util.Thunk;
 
 import org.xmlpull.v1.XmlPullParser;
@@ -436,7 +437,7 @@
                 return -1;
             }
 
-            ItemInfo.writeBitmap(mValues, Utilities.createIconBitmap(icon, mContext));
+            ItemInfo.writeBitmap(mValues, LauncherIcons.createIconBitmap(icon, mContext));
             mValues.put(Favorites.ICON_PACKAGE, mIconRes.getResourcePackageName(iconId));
             mValues.put(Favorites.ICON_RESOURCE, mIconRes.getResourceName(iconId));
 
diff --git a/src/com/android/launcher3/BaseContainerView.java b/src/com/android/launcher3/BaseContainerView.java
index 96942ee..b7321d9 100644
--- a/src/com/android/launcher3/BaseContainerView.java
+++ b/src/com/android/launcher3/BaseContainerView.java
@@ -16,17 +16,24 @@
 
 package com.android.launcher3;
 
+import android.annotation.SuppressLint;
 import android.content.Context;
 import android.content.res.TypedArray;
+import android.graphics.Point;
+import android.graphics.PointF;
+import android.graphics.Rect;
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.InsetDrawable;
 import android.util.AttributeSet;
+import android.view.MotionEvent;
 import android.view.View;
+import android.view.ViewConfiguration;
 import android.widget.FrameLayout;
 
 import com.android.launcher3.allapps.AllAppsContainerView;
 import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.util.TransformingTouchDelegate;
 
 /**
  * A base container view, which supports resizing.
@@ -39,12 +46,16 @@
     protected int mContainerPaddingTop;
     protected int mContainerPaddingBottom;
 
-    private InsetDrawable mRevealDrawable;
     protected final Drawable mBaseDrawable;
+    private final Rect mBgPaddingRect = new Rect();
 
     private View mRevealView;
     private View mContent;
 
+    private TransformingTouchDelegate mTouchDelegate;
+
+    private final PointF mLastTouchDownPosPx = new PointF(-1.0f, -1.0f);
+
     public BaseContainerView(Context context) {
         this(context, null);
     }
@@ -72,6 +83,12 @@
 
         DeviceProfile grid = Launcher.getLauncher(getContext()).getDeviceProfile();
         grid.addLauncherLayoutChangedListener(this);
+
+        View touchDelegateTargetView = getTouchDelegateTargetView();
+        if (touchDelegateTargetView != null) {
+            mTouchDelegate = new TransformingTouchDelegate(touchDelegateTargetView);
+            ((View) touchDelegateTargetView.getParent()).setTouchDelegate(mTouchDelegate);
+        }
     }
 
     @Override
@@ -93,10 +110,36 @@
     }
 
     @Override
+    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+        super.onLayout(changed, left, top, right, bottom);
+        getRevealView().getBackground().getPadding(mBgPaddingRect);
+
+        View touchDelegateTargetView = getTouchDelegateTargetView();
+        if (touchDelegateTargetView != null) {
+            mTouchDelegate.setBounds(
+                    touchDelegateTargetView.getLeft() - mBgPaddingRect.left,
+                    touchDelegateTargetView.getTop() - mBgPaddingRect.top,
+                    touchDelegateTargetView.getRight() + mBgPaddingRect.right,
+                    touchDelegateTargetView.getBottom() + mBgPaddingRect.bottom);
+        }
+    }
+
+    @Override
     public void onLauncherLayoutChanged() {
         updatePaddings();
     }
 
+    @Override
+    public boolean onInterceptTouchEvent(MotionEvent ev) {
+        return handleTouchEvent(ev);
+    }
+
+    @SuppressLint("ClickableViewAccessibility")
+    @Override
+    public boolean onTouchEvent(MotionEvent ev) {
+        return handleTouchEvent(ev);
+    }
+
     public void setRevealDrawableColor(int color) {
         ((ColorDrawable) mBaseDrawable).setColor(color);
     }
@@ -130,14 +173,47 @@
             }
         }
 
-        mRevealDrawable = new InsetDrawable(mBaseDrawable,
+        InsetDrawable revealDrawable = new InsetDrawable(mBaseDrawable,
                 mContainerPaddingLeft, mContainerPaddingTop, mContainerPaddingRight,
                 mContainerPaddingBottom);
-        mRevealView.setBackground(mRevealDrawable);
-        if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP && this instanceof AllAppsContainerView) {
-            // Skip updating the content background
-        } else {
-            mContent.setBackground(mRevealDrawable);
-        }
+        mRevealView.setBackground(revealDrawable);
+        mContent.setBackground(revealDrawable);
     }
+
+    /**
+     * Handles the touch events that shows the workspace when clicking outside the bounds of the
+     * touch delegate target view.
+     */
+    private boolean handleTouchEvent(MotionEvent ev) {
+        switch (ev.getAction()) {
+            case MotionEvent.ACTION_DOWN:
+                // Check if the touch is outside touch delegate target view
+                View touchDelegateTargetView = getTouchDelegateTargetView();
+                float leftBoundPx = touchDelegateTargetView.getLeft();
+                if (ev.getX() < leftBoundPx ||
+                        ev.getX() > (touchDelegateTargetView.getWidth() + leftBoundPx)) {
+                    mLastTouchDownPosPx.set((int) ev.getX(), (int) ev.getY());
+                }
+                break;
+            case MotionEvent.ACTION_UP:
+                if (mLastTouchDownPosPx.x > -1) {
+                    ViewConfiguration viewConfig = ViewConfiguration.get(getContext());
+                    float dx = ev.getX() - mLastTouchDownPosPx.x;
+                    float dy = ev.getY() - mLastTouchDownPosPx.y;
+                    float distance = PointF.length(dx, dy);
+                    if (distance < viewConfig.getScaledTouchSlop()) {
+                        // The background was clicked, so just go home
+                        Launcher.getLauncher(getContext()).showWorkspace(true);
+                        return true;
+                    }
+                }
+                // Fall through
+            case MotionEvent.ACTION_CANCEL:
+                mLastTouchDownPosPx.set(-1, -1);
+                break;
+        }
+        return false;
+    }
+
+    public abstract View getTouchDelegateTargetView();
 }
diff --git a/src/com/android/launcher3/BaseRecyclerViewFastScrollPopup.java b/src/com/android/launcher3/BaseRecyclerViewFastScrollPopup.java
index b9e6277..b9b044d 100644
--- a/src/com/android/launcher3/BaseRecyclerViewFastScrollPopup.java
+++ b/src/com/android/launcher3/BaseRecyclerViewFastScrollPopup.java
@@ -25,6 +25,8 @@
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 
+import com.android.launcher3.graphics.HolographicOutlineHelper;
+
 /**
  * The fast scroller popup that shows the section name the list will jump to.
  */
@@ -116,7 +118,7 @@
             mBgBounds.bottom = mBgBounds.top + bgHeight;
 
             // Generate a bitmap for a shadow matching these bounds
-            mShadow = HolographicOutlineHelper.obtain(
+            mShadow = HolographicOutlineHelper.getInstance(
                     mRv.getContext()).createMediumDropShadow(mBg, false /* shouldCache */);
         } else {
             mShadow = null;
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index a294fa5..7d693ec 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -42,6 +42,7 @@
 
 import com.android.launcher3.IconCache.IconLoadRequest;
 import com.android.launcher3.folder.FolderIcon;
+import com.android.launcher3.graphics.HolographicOutlineHelper;
 import com.android.launcher3.model.PackageItemInfo;
 
 import java.text.NumberFormat;
@@ -150,7 +151,7 @@
         mLongPressHelper = new CheckLongPressHelper(this);
         mStylusEventHelper = new StylusEventHelper(new SimpleOnStylusPressListener(this), this);
 
-        mOutlineHelper = HolographicOutlineHelper.obtain(getContext());
+        mOutlineHelper = HolographicOutlineHelper.getInstance(getContext());
         setAccessibilityDelegate(mLauncher.getAccessibilityDelegate());
     }
 
@@ -328,7 +329,7 @@
     void setStayPressed(boolean stayPressed) {
         mStayPressed = stayPressed;
         if (!stayPressed) {
-            HolographicOutlineHelper.obtain(getContext()).recycleShadowBitmap(mPressedBackground);
+            HolographicOutlineHelper.getInstance(getContext()).recycleShadowBitmap(mPressedBackground);
             mPressedBackground = null;
         } else {
             if (mPressedBackground == null) {
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index c4e6ed1..8e4567b 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -104,7 +104,7 @@
     public int hotseatCellWidthPx;
     public int hotseatCellHeightPx;
     public int hotseatIconSizePx;
-    private int hotseatBarHeightPx;
+    public int hotseatBarHeightPx;
     private int hotseatBarTopPaddingPx;
     private int hotseatLandGutterPx;
 
diff --git a/src/com/android/launcher3/FocusHelper.java b/src/com/android/launcher3/FocusHelper.java
index c73ceea..de079fe 100644
--- a/src/com/android/launcher3/FocusHelper.java
+++ b/src/com/android/launcher3/FocusHelper.java
@@ -250,14 +250,6 @@
         } else if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT &&
                 profile.isVerticalBarLayout()) {
             keyCode = KeyEvent.KEYCODE_PAGE_DOWN;
-        } else if (isUninstallKeyChord(e)) {
-            matrix = FocusLogic.createSparseMatrix(iconLayout);
-            if (UninstallDropTarget.supportsDrop(launcher, itemInfo)) {
-                UninstallDropTarget.startUninstallActivity(launcher, itemInfo);
-            }
-        } else if (isDeleteKeyChord(e)) {
-            matrix = FocusLogic.createSparseMatrix(iconLayout);
-            launcher.removeItem(v, itemInfo, true /* deleteFromDb */);
         } else {
             // For other KEYCODE_DPAD_LEFT and KEYCODE_DPAD_RIGHT navigation, do not use the
             // matrix extended with hotseat.
@@ -374,14 +366,6 @@
         } else if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT &&
                 profile.isVerticalBarLayout()) {
             matrix = FocusLogic.createSparseMatrixWithHotseat(iconLayout, hotseatLayout, profile);
-        } else if (isUninstallKeyChord(e)) {
-            matrix = FocusLogic.createSparseMatrix(iconLayout);
-            if (UninstallDropTarget.supportsDrop(launcher, itemInfo)) {
-                UninstallDropTarget.startUninstallActivity(launcher, itemInfo);
-            }
-        } else if (isDeleteKeyChord(e)) {
-            matrix = FocusLogic.createSparseMatrix(iconLayout);
-            launcher.removeItem(v, itemInfo, true /* deleteFromDb */);
         } else {
             matrix = FocusLogic.createSparseMatrix(iconLayout);
         }
@@ -532,24 +516,6 @@
         }
     }
 
-    /**
-     * Returns whether the key event represents a valid uninstall key chord.
-     */
-    private static boolean isUninstallKeyChord(KeyEvent event) {
-        int keyCode = event.getKeyCode();
-        return (keyCode == KeyEvent.KEYCODE_DEL || keyCode == KeyEvent.KEYCODE_FORWARD_DEL) &&
-                event.hasModifiers(KeyEvent.META_CTRL_ON | KeyEvent.META_SHIFT_ON);
-    }
-
-    /**
-     * Returns whether the key event represents a valid delete key chord.
-     */
-    private static boolean isDeleteKeyChord(KeyEvent event) {
-        int keyCode = event.getKeyCode();
-        return (keyCode == KeyEvent.KEYCODE_DEL || keyCode == KeyEvent.KEYCODE_FORWARD_DEL) &&
-                event.hasModifiers(KeyEvent.META_CTRL_ON);
-    }
-
     private static View handlePreviousPageLastItem(Workspace workspace, CellLayout hotseatLayout,
             int pageIndex, boolean isRtl) {
         if (pageIndex - 1 < 0) {
diff --git a/src/com/android/launcher3/IconCache.java b/src/com/android/launcher3/IconCache.java
index d3fb38e..8edfdf5 100644
--- a/src/com/android/launcher3/IconCache.java
+++ b/src/com/android/launcher3/IconCache.java
@@ -47,6 +47,7 @@
 import com.android.launcher3.compat.UserHandleCompat;
 import com.android.launcher3.compat.UserManagerCompat;
 import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.graphics.LauncherIcons;
 import com.android.launcher3.model.PackageItemInfo;
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.SQLiteCacheHelper;
@@ -187,7 +188,7 @@
 
     private Bitmap makeDefaultIcon(UserHandleCompat user) {
         Drawable unbadged = getFullResDefaultActivityIcon();
-        return Utilities.createBadgedIconBitmap(unbadged, user, mContext);
+        return LauncherIcons.createBadgedIconBitmap(unbadged, user, mContext);
     }
 
     /**
@@ -387,7 +388,7 @@
         }
         if (entry == null) {
             entry = new CacheEntry();
-            entry.icon = Utilities.createBadgedIconBitmap(
+            entry.icon = LauncherIcons.createBadgedIconBitmap(
                     mIconProvider.getIcon(app, mIconDpi), app.getUser(),
                     mContext);
         }
@@ -555,7 +556,7 @@
             // Check the DB first.
             if (!getEntryFromDB(cacheKey, entry, useLowResIcon) || DEBUG_IGNORE_CACHE) {
                 if (info != null) {
-                    entry.icon = Utilities.createBadgedIconBitmap(
+                    entry.icon = LauncherIcons.createBadgedIconBitmap(
                             mIconProvider.getIcon(info, mIconDpi), info.getUser(),
                             mContext);
                 } else {
@@ -606,7 +607,7 @@
             entry.title = title;
         }
         if (icon != null) {
-            entry.icon = Utilities.createIconBitmap(icon, mContext);
+            entry.icon = LauncherIcons.createIconBitmap(icon, mContext);
         }
     }
 
@@ -641,7 +642,7 @@
 
                     // Load the full res icon for the application, but if useLowResIcon is set, then
                     // only keep the low resolution icon instead of the larger full-sized icon
-                    Bitmap icon = Utilities.createBadgedIconBitmap(
+                    Bitmap icon = LauncherIcons.createBadgedIconBitmap(
                             appInfo.loadIcon(mPackageManager), user, mContext);
                     Bitmap lowResIcon =  generateLowResIcon(icon, mPackageBgColor);
                     entry.title = appInfo.loadLabel(mPackageManager);
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index c73a7a6..30d5b17 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -30,7 +30,6 @@
 import android.app.SearchManager;
 import android.appwidget.AppWidgetHostView;
 import android.appwidget.AppWidgetManager;
-import android.appwidget.AppWidgetProviderInfo;
 import android.content.ActivityNotFoundException;
 import android.content.BroadcastReceiver;
 import android.content.ComponentCallbacks2;
@@ -56,7 +55,6 @@
 import android.os.Build;
 import android.os.Bundle;
 import android.os.Handler;
-import android.os.Message;
 import android.os.StrictMode;
 import android.os.SystemClock;
 import android.os.Trace;
@@ -69,6 +67,8 @@
 import android.view.Display;
 import android.view.HapticFeedbackConstants;
 import android.view.KeyEvent;
+import android.view.KeyboardShortcutGroup;
+import android.view.KeyboardShortcutInfo;
 import android.view.Menu;
 import android.view.MotionEvent;
 import android.view.Surface;
@@ -79,9 +79,9 @@
 import android.view.ViewTreeObserver;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.AccessibilityNodeInfo;
 import android.view.animation.OvershootInterpolator;
 import android.view.inputmethod.InputMethodManager;
-import android.widget.Advanceable;
 import android.widget.ImageView;
 import android.widget.TextView;
 import android.widget.Toast;
@@ -106,6 +106,7 @@
 import com.android.launcher3.dynamicui.ExtractedColors;
 import com.android.launcher3.folder.Folder;
 import com.android.launcher3.folder.FolderIcon;
+import com.android.launcher3.keyboard.CustomActionsPopup;
 import com.android.launcher3.keyboard.ViewGroupFocusHelper;
 import com.android.launcher3.logging.FileLog;
 import com.android.launcher3.logging.UserEventDispatcher;
@@ -254,7 +255,6 @@
     @Thunk WidgetsContainerView mWidgetsView;
     @Thunk WidgetsModel mWidgetsModel;
 
-    private Bundle mSavedState;
     // We set the state in both onCreate and then onNewIntent in some cases, which causes both
     // scroll issues (because the workspace may not have been measured yet) and extra work.
     // Instead, just save the state that we need to restore Launcher to, and commit it in onResume.
@@ -275,27 +275,16 @@
     private IconCache mIconCache;
     private ExtractedColors mExtractedColors;
     private LauncherAccessibilityDelegate mAccessibilityDelegate;
+    private Handler mHandler = new Handler();
     private boolean mIsResumeFromActionScreenOff;
-    @Thunk boolean mUserPresent = true;
-    private boolean mVisible;
-    private boolean mHasFocus;
-    private boolean mAttached;
+    private boolean mHasFocus = false;
+    private boolean mAttached = false;
 
     /** Maps launcher activity components to their list of shortcut ids. */
     private MultiHashMap<ComponentKey, String> mDeepShortcutMap = new MultiHashMap<>();
 
     private View.OnTouchListener mHapticFeedbackTouchListener;
 
-    // Related to the auto-advancing of widgets
-    private final int ADVANCE_MSG = 1;
-    private static final int ADVANCE_INTERVAL = 20000;
-    private static final int ADVANCE_STAGGER = 250;
-
-    private boolean mAutoAdvanceRunning = false;
-    private long mAutoAdvanceSentTime;
-    private long mAutoAdvanceTimeLeft = -1;
-    @Thunk HashMap<View, AppWidgetProviderInfo> mWidgetsToAdvance = new HashMap<>();
-
     // Determines how long to wait after a rotation before restoring the screen orientation to
     // match the sensor state.
     private static final int RESTORE_SCREEN_ORIENTATION_DELAY = 500;
@@ -354,6 +343,9 @@
 
     private UserEventDispatcher mUserEventDispatcher;
 
+    private float mLastDispatchTouchEventX = 0.0f;
+    private float mEdgeOfScreenThresholdPx = 0.0f;
+
     public ViewGroupFocusHelper mFocusHandler;
     private boolean mRotationEnabled = false;
 
@@ -424,6 +416,9 @@
 
         setContentView(R.layout.launcher);
 
+        mEdgeOfScreenThresholdPx = getResources()
+                .getDimensionPixelSize(R.dimen.edge_of_screen_threshold);
+
         setupViews();
         mDeviceProfile.layout(this, false /* notifyListeners */);
         mExtractedColors = new ExtractedColors();
@@ -434,8 +429,7 @@
 
         lockAllApps();
 
-        mSavedState = savedInstanceState;
-        restoreState(mSavedState);
+        restoreState(savedInstanceState);
 
         if (LauncherAppState.PROFILE_STARTUP) {
             Trace.endSection();
@@ -443,11 +437,18 @@
 
         // We only load the page synchronously if the user rotates (or triggers a
         // configuration change) while launcher is in the foreground
-        if (!mModel.startLoader(mWorkspace.getRestorePage())) {
+        int currentScreen = PagedView.INVALID_RESTORE_PAGE;
+        if (savedInstanceState != null) {
+            currentScreen = savedInstanceState.getInt(RUNTIME_STATE_CURRENT_SCREEN, currentScreen);
+        }
+        if (!mModel.startLoader(currentScreen)) {
             // If we are not binding synchronously, show a fade in animation when
             // the first page bind completes.
             mDragLayer.setAlpha(0);
         } else {
+            // Pages bound synchronously.
+            mWorkspace.setCurrentPage(currentScreen);
+
             setWorkspaceLoading(true);
         }
 
@@ -611,7 +612,7 @@
     }
 
     /**
-     * Invoked by subclasses to signal a change to the {@link #addCustomContentToLeft} value to
+     * Invoked by subclasses to signal a change to the {@link #addToCustomContentPage} value to
      * ensure the custom content page is added or removed if necessary.
      */
     protected void invalidateHasCustomContentToLeft() {
@@ -1263,22 +1264,6 @@
     }
 
     /**
-     * Given the integer (ordinal) value of a State enum instance, convert it to a variable of type
-     * State
-     */
-    private static State intToState(int stateOrdinal) {
-        State state = State.WORKSPACE;
-        final State[] stateValues = State.values();
-        for (int i = 0; i < stateValues.length; i++) {
-            if (stateValues[i].ordinal() == stateOrdinal) {
-                state = stateValues[i];
-                break;
-            }
-        }
-        return state;
-    }
-
-    /**
      * Restores the previous state, if it exists.
      *
      * @param savedState The previous state.
@@ -1288,17 +1273,14 @@
             return;
         }
 
-        State state = intToState(savedState.getInt(RUNTIME_STATE, State.WORKSPACE.ordinal()));
+        int stateOrdinal = savedState.getInt(RUNTIME_STATE, State.WORKSPACE.ordinal());
+        State[] stateValues = State.values();
+        State state = (stateOrdinal >= 0 && stateOrdinal < stateValues.length)
+                ? stateValues[stateOrdinal] : State.WORKSPACE;
         if (state == State.APPS || state == State.WIDGETS) {
             mOnResumeState = state;
         }
 
-        int currentScreen = savedState.getInt(RUNTIME_STATE_CURRENT_SCREEN,
-                PagedView.INVALID_RESTORE_PAGE);
-        if (currentScreen != PagedView.INVALID_RESTORE_PAGE) {
-            mWorkspace.setRestorePage(currentScreen);
-        }
-
         PendingRequestArgs requestArgs = savedState.getParcelable(RUNTIME_STATE_PENDING_REQUEST_ARGS);
         if (requestArgs != null) {
             setWaitingForResult(requestArgs);
@@ -1576,10 +1558,6 @@
 
         mWorkspace.addInScreen(hostView, item.container, item.screenId,
                 item.cellX, item.cellY, item.spanX, item.spanY, insert);
-
-        if (!item.isCustomWidget()) {
-            addWidgetToAutoAdvanceIfNeeded(hostView, appWidgetInfo);
-        }
     }
 
     private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@@ -1587,9 +1565,7 @@
         public void onReceive(Context context, Intent intent) {
             final String action = intent.getAction();
             if (Intent.ACTION_SCREEN_OFF.equals(action)) {
-                mUserPresent = false;
-                mDragLayer.clearAllResizeFrames();
-                updateAutoAdvanceState();
+                mDragLayer.clearResizeFrame();
 
                 // Reset AllApps to its initial state only if we are not in the middle of
                 // processing a multi-step drop
@@ -1600,9 +1576,6 @@
                     }
                 }
                 mIsResumeFromActionScreenOff = true;
-            } else if (Intent.ACTION_USER_PRESENT.equals(action)) {
-                mUserPresent = true;
-                updateAutoAdvanceState();
             }
         }
     };
@@ -1614,11 +1587,9 @@
         // Listen for broadcasts related to user-presence
         final IntentFilter filter = new IntentFilter();
         filter.addAction(Intent.ACTION_SCREEN_OFF);
-        filter.addAction(Intent.ACTION_USER_PRESENT);
         registerReceiver(mReceiver, filter);
         FirstFrameAnimatorHelper.initializeDrawListener(getWindow().getDecorView());
         mAttached = true;
-        mVisible = true;
 
         if (mLauncherCallbacks != null) {
             mLauncherCallbacks.onAttachedToWindow();
@@ -1628,13 +1599,10 @@
     @Override
     public void onDetachedFromWindow() {
         super.onDetachedFromWindow();
-        mVisible = false;
-
         if (mAttached) {
             unregisterReceiver(mReceiver);
             mAttached = false;
         }
-        updateAutoAdvanceState();
 
         if (mLauncherCallbacks != null) {
             mLauncherCallbacks.onDetachedFromWindow();
@@ -1642,12 +1610,10 @@
     }
 
     public void onWindowVisibilityChanged(int visibility) {
-        mVisible = visibility == View.VISIBLE;
-        updateAutoAdvanceState();
         // The following code used to be in onResume, but it turns out onResume is called when
         // you're in All Apps and click home to go to the workspace. onWindowVisibilityChanged
         // is a more appropriate event to handle
-        if (mVisible) {
+        if (visibility == View.VISIBLE) {
             if (!mWorkspaceLoading) {
                 final ViewTreeObserver observer = mWorkspace.getViewTreeObserver();
                 // We want to let Launcher draw itself at least once before we force it to build
@@ -1682,72 +1648,6 @@
         }
     }
 
-    @Thunk void sendAdvanceMessage(long delay) {
-        mHandler.removeMessages(ADVANCE_MSG);
-        Message msg = mHandler.obtainMessage(ADVANCE_MSG);
-        mHandler.sendMessageDelayed(msg, delay);
-        mAutoAdvanceSentTime = System.currentTimeMillis();
-    }
-
-    @Thunk void updateAutoAdvanceState() {
-        boolean autoAdvanceRunning = mVisible && mUserPresent && !mWidgetsToAdvance.isEmpty();
-        if (autoAdvanceRunning != mAutoAdvanceRunning) {
-            mAutoAdvanceRunning = autoAdvanceRunning;
-            if (autoAdvanceRunning) {
-                long delay = mAutoAdvanceTimeLeft == -1 ? ADVANCE_INTERVAL : mAutoAdvanceTimeLeft;
-                sendAdvanceMessage(delay);
-            } else {
-                if (!mWidgetsToAdvance.isEmpty()) {
-                    mAutoAdvanceTimeLeft = Math.max(0, ADVANCE_INTERVAL -
-                            (System.currentTimeMillis() - mAutoAdvanceSentTime));
-                }
-                mHandler.removeMessages(ADVANCE_MSG);
-                mHandler.removeMessages(0); // Remove messages sent using postDelayed()
-            }
-        }
-    }
-
-    @Thunk final Handler mHandler = new Handler(new Handler.Callback() {
-
-        @Override
-        public boolean handleMessage(Message msg) {
-            if (msg.what == ADVANCE_MSG) {
-                int i = 0;
-                for (View key: mWidgetsToAdvance.keySet()) {
-                    final View v = key.findViewById(mWidgetsToAdvance.get(key).autoAdvanceViewId);
-                    final int delay = ADVANCE_STAGGER * i;
-                    if (v instanceof Advanceable) {
-                        mHandler.postDelayed(new Runnable() {
-                           public void run() {
-                               ((Advanceable) v).advance();
-                           }
-                       }, delay);
-                    }
-                    i++;
-                }
-                sendAdvanceMessage(ADVANCE_INTERVAL);
-            }
-            return true;
-        }
-    });
-
-    private void addWidgetToAutoAdvanceIfNeeded(View hostView, AppWidgetProviderInfo appWidgetInfo) {
-        if (appWidgetInfo == null || appWidgetInfo.autoAdvanceViewId == -1) return;
-        View v = hostView.findViewById(appWidgetInfo.autoAdvanceViewId);
-        if (v instanceof Advanceable) {
-            mWidgetsToAdvance.put(hostView, appWidgetInfo);
-            ((Advanceable) v).fyiWillBeAdvancedByHostKThx();
-            updateAutoAdvanceState();
-        }
-    }
-
-    private void removeWidgetToAutoAdvance(View hostView) {
-        if (mWidgetsToAdvance.containsKey(hostView)) {
-            mWidgetsToAdvance.remove(hostView);
-            updateAutoAdvanceState();
-        }
-    }
-
     public void showOutOfSpaceMessage(boolean isHotseatLayout) {
         int strId = (isHotseatLayout ? R.string.hotseat_out_of_space : R.string.out_of_space);
         Toast.makeText(this, getString(strId), Toast.LENGTH_SHORT).show();
@@ -1942,9 +1842,6 @@
     public void onDestroy() {
         super.onDestroy();
 
-        // Remove all pending runnables
-        mHandler.removeMessages(ADVANCE_MSG);
-        mHandler.removeMessages(0);
         mWorkspace.removeCallbacks(mBuildLayersRunnable);
         mWorkspace.removeFolderListeners();
 
@@ -1967,8 +1864,6 @@
         }
         mAppWidgetHost = null;
 
-        mWidgetsToAdvance.clear();
-
         TextKeyListener.getInstance().release();
 
         ((AccessibilityManager) getSystemService(ACCESSIBILITY_SERVICE))
@@ -2288,7 +2183,6 @@
         } else if (itemInfo instanceof LauncherAppWidgetInfo) {
             final LauncherAppWidgetInfo widgetInfo = (LauncherAppWidgetInfo) itemInfo;
             mWorkspace.removeWorkspaceItem(v);
-            removeWidgetToAutoAdvance(v);
             if (deleteFromDb) {
                 deleteWidgetInfo(widgetInfo);
             }
@@ -2781,6 +2675,7 @@
         }
     }
 
+    @TargetApi(Build.VERSION_CODES.M)
     private Bundle getActivityLaunchOptions(View v) {
         if (Utilities.ATLEAST_MARSHMALLOW) {
             int left = 0, top = 0;
@@ -3081,6 +2976,12 @@
     }
 
     @Override
+    public boolean dispatchTouchEvent(MotionEvent ev) {
+        mLastDispatchTouchEventX = ev.getX();
+        return super.dispatchTouchEvent(ev);
+    }
+
+    @Override
     public boolean onLongClick(View v) {
         if (!isDraggingEnabled()) return false;
         if (isWorkspaceLocked()) return false;
@@ -3092,9 +2993,12 @@
             return true;
         }
 
+        boolean fromEdgeOfScreen = mLastDispatchTouchEventX < mEdgeOfScreenThresholdPx
+                || mLastDispatchTouchEventX > (mDeviceProfile.widthPx - mEdgeOfScreenThresholdPx);
+
         if (v instanceof Workspace) {
             if (!mWorkspace.isInOverviewMode()) {
-                if (!mWorkspace.isTouchActive()) {
+                if (!mWorkspace.isTouchActive() && !fromEdgeOfScreen) {
                     showOverviewMode(true);
                     mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS,
                             HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
@@ -3121,13 +3025,16 @@
         if (!mDragController.isDragging()) {
             if (itemUnderLongClick == null) {
                 // User long pressed on empty space
-                mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS,
-                        HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
                 if (mWorkspace.isInOverviewMode()) {
                     mWorkspace.startReordering(v);
                 } else {
+                    if (fromEdgeOfScreen) {
+                        return false;
+                    }
                     showOverviewMode(true);
                 }
+                mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS,
+                        HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
             } else {
                 final boolean isAllAppsButton =
                         !FeatureFlags.NO_ALL_APPS_ICON && isHotseatLayout(v) &&
@@ -3135,17 +3042,7 @@
                                         longClickCellInfo.cellX, longClickCellInfo.cellY));
                 if (!(itemUnderLongClick instanceof Folder || isAllAppsButton)) {
                     // User long pressed on an item
-                    DragOptions dragOptions = new DragOptions();
-                    if (itemUnderLongClick instanceof BubbleTextView) {
-                        BubbleTextView icon = (BubbleTextView) itemUnderLongClick;
-                        if (icon.hasDeepShortcuts()) {
-                            DeepShortcutsContainer dsc = DeepShortcutsContainer.showForIcon(icon);
-                            if (dsc != null) {
-                                dragOptions.deferDragCondition = dsc.createDeferDragCondition(null);
-                            }
-                        }
-                    }
-                    mWorkspace.startDrag(longClickCellInfo, dragOptions);
+                    mWorkspace.startDrag(longClickCellInfo, new DragOptions());
                 }
             }
         }
@@ -3224,10 +3121,6 @@
         // Change the state *after* we've called all the transition code
         mState = State.WORKSPACE;
 
-        // Resume the auto-advance of widgets
-        mUserPresent = true;
-        updateAutoAdvanceState();
-
         if (changed) {
             // Send an accessibility event to announce the context change
             getWindow().getDecorView()
@@ -3333,9 +3226,6 @@
         // Change the state *after* we've called all the transition code
         mState = toState;
 
-        // Pause the auto-advance of widgets until we are out of AllApps
-        mUserPresent = false;
-        updateAutoAdvanceState();
         closeFolder();
         closeShortcutsContainer();
 
@@ -3561,7 +3451,6 @@
         mWorkspace.clearDropTargets();
         mWorkspace.removeAllWorkspaceScreens();
 
-        mWidgetsToAdvance.clear();
         if (mHotseat != null) {
             mHotseat.resetLayout();
         }
@@ -3984,14 +3873,6 @@
         if (LauncherAppState.PROFILE_STARTUP) {
             Trace.beginSection("Page bind completed");
         }
-        if (mSavedState != null) {
-            if (!mWorkspace.hasFocus()) {
-                mWorkspace.getChildAt(mWorkspace.getCurrentPage()).requestFocus();
-            }
-
-            mSavedState = null;
-        }
-
         mWorkspace.restoreInstanceStateForRemainingPages();
 
         setWorkspaceLoading(false);
@@ -4413,7 +4294,6 @@
      */
     public void dumpState() {
         Log.d(TAG, "BEGIN launcher3 dump state for launcher " + this);
-        Log.d(TAG, "mSavedState=" + mSavedState);
         Log.d(TAG, "mWorkspaceLoading=" + mWorkspaceLoading);
         Log.d(TAG, "mPendingRequestArgs=" + mPendingRequestArgs);
         Log.d(TAG, "mPendingActivityResult=" + mPendingActivityResult);
@@ -4460,6 +4340,65 @@
         }
     }
 
+    @Override
+    @TargetApi(Build.VERSION_CODES.N)
+    public void onProvideKeyboardShortcuts(
+            List<KeyboardShortcutGroup> data, Menu menu, int deviceId) {
+
+        ArrayList<KeyboardShortcutInfo> shortcutInfos = new ArrayList<>();
+        if (mState == State.WORKSPACE) {
+            shortcutInfos.add(new KeyboardShortcutInfo(getString(R.string.all_apps_button_label),
+                    KeyEvent.KEYCODE_A, KeyEvent.META_CTRL_ON));
+        }
+        View currentFocus = getCurrentFocus();
+        if (new CustomActionsPopup(this, currentFocus).canShow()) {
+            shortcutInfos.add(new KeyboardShortcutInfo(getString(R.string.custom_actions),
+                    KeyEvent.KEYCODE_O, KeyEvent.META_CTRL_ON));
+        }
+        if (currentFocus instanceof BubbleTextView &&
+                ((BubbleTextView) currentFocus).hasDeepShortcuts()) {
+            shortcutInfos.add(new KeyboardShortcutInfo(getString(R.string.action_deep_shortcut),
+                    KeyEvent.KEYCODE_S, KeyEvent.META_CTRL_ON));
+        }
+        if (!shortcutInfos.isEmpty()) {
+            data.add(new KeyboardShortcutGroup(getString(R.string.home_screen), shortcutInfos));
+        }
+
+        super.onProvideKeyboardShortcuts(data, menu, deviceId);
+    }
+
+    @Override
+    public boolean onKeyShortcut(int keyCode, KeyEvent event) {
+        if (event.hasModifiers(KeyEvent.META_CTRL_ON)) {
+            switch (keyCode) {
+                case KeyEvent.KEYCODE_A:
+                    if (mState == State.WORKSPACE) {
+                        showAppsView(true, true, false);
+                        return true;
+                    }
+                    break;
+                case KeyEvent.KEYCODE_S: {
+                    View focusedView = getCurrentFocus();
+                    if (focusedView instanceof BubbleTextView
+                            && focusedView.getTag() instanceof ItemInfo
+                            && mAccessibilityDelegate.performAction(focusedView,
+                                    (ItemInfo) focusedView.getTag(),
+                                    LauncherAccessibilityDelegate.DEEP_SHORTCUTS)) {
+                        getOpenShortcutsContainer().requestFocus();
+                        return true;
+                    }
+                    break;
+                }
+                case KeyEvent.KEYCODE_O:
+                    if (new CustomActionsPopup(this, getCurrentFocus()).show()) {
+                        return true;
+                    }
+                    break;
+            }
+        }
+        return super.onKeyShortcut(keyCode, event);
+    }
+
     public static CustomAppWidget getCustomAppWidget(String name) {
         return sCustomAppWidgets.get(name);
     }
@@ -4468,14 +4407,6 @@
         return sCustomAppWidgets;
     }
 
-    public static List<View> getFolderContents(View icon) {
-        if (icon instanceof FolderIcon) {
-            return ((FolderIcon) icon).getFolder().getItemsInReadingOrder();
-        } else {
-            return Collections.EMPTY_LIST;
-        }
-    }
-
     public static Launcher getLauncher(Context context) {
         if (context instanceof Launcher) {
             return (Launcher) context;
diff --git a/src/com/android/launcher3/LauncherAppWidgetHostView.java b/src/com/android/launcher3/LauncherAppWidgetHostView.java
index ed1079f..fa5e519 100644
--- a/src/com/android/launcher3/LauncherAppWidgetHostView.java
+++ b/src/com/android/launcher3/LauncherAppWidgetHostView.java
@@ -20,6 +20,9 @@
 import android.appwidget.AppWidgetProviderInfo;
 import android.content.Context;
 import android.graphics.Rect;
+import android.os.Handler;
+import android.os.SystemClock;
+import android.util.SparseBooleanArray;
 import android.view.KeyEvent;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
@@ -28,6 +31,7 @@
 import android.view.ViewDebug;
 import android.view.ViewGroup;
 import android.view.accessibility.AccessibilityNodeInfo;
+import android.widget.Advanceable;
 import android.widget.RemoteViews;
 
 import com.android.launcher3.dragndrop.DragLayer.TouchCompleteListener;
@@ -39,6 +43,13 @@
  */
 public class LauncherAppWidgetHostView extends AppWidgetHostView implements TouchCompleteListener {
 
+    // Related to the auto-advancing of widgets
+    private static final long ADVANCE_INTERVAL = 20000;
+    private static final long ADVANCE_STAGGER = 250;
+
+    // Maintains a list of widget ids which are supposed to be auto advanced.
+    private static final SparseBooleanArray sAutoAdvanceWidgetIds = new SparseBooleanArray();
+
     LayoutInflater mInflater;
 
     private CheckLongPressHelper mLongPressHelper;
@@ -54,6 +65,10 @@
 
     protected int mErrorViewId = R.layout.appwidget_error;
 
+    private boolean mIsAttachedToWindow;
+    private boolean mIsAutoAdvanceRegistered;
+    private Runnable mAutoAdvanceRunnable;
+
     public LauncherAppWidgetHostView(Context context) {
         super(context);
         mContext = context;
@@ -78,6 +93,9 @@
         // Store the orientation in which the widget was inflated
         updateLastInflationOrientation();
         super.updateAppWidget(remoteViews);
+
+        // The provider info or the views might have changed.
+        checkIfAutoAdvance();
     }
 
     public boolean isReinflateRequired() {
@@ -153,6 +171,19 @@
     protected void onAttachedToWindow() {
         super.onAttachedToWindow();
         mSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
+
+        mIsAttachedToWindow = true;
+        checkIfAutoAdvance();
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+
+        // We can't directly use isAttachedToWindow() here, as this is called before the internal
+        // state is updated. So isAttachedToWindow() will return true until next frame.
+        mIsAttachedToWindow = false;
+        checkIfAutoAdvance();
     }
 
     @Override
@@ -171,10 +202,6 @@
         return info;
     }
 
-    public LauncherAppWidgetProviderInfo getLauncherAppWidgetProviderInfo() {
-        return (LauncherAppWidgetProviderInfo) getAppWidgetInfo();
-    }
-
     @Override
     public void onTouchComplete() {
         if (!mLongPressHelper.hasPerformedLongPress()) {
@@ -296,4 +323,79 @@
         super.onInitializeAccessibilityNodeInfo(info);
         info.setClassName(getClass().getName());
     }
+
+    @Override
+    protected void onWindowVisibilityChanged(int visibility) {
+        super.onWindowVisibilityChanged(visibility);
+        maybeRegisterAutoAdvance();
+    }
+
+    private void checkIfAutoAdvance() {
+        boolean isAutoAdvance = false;
+        Advanceable target = getAdvanceable();
+        if (target != null) {
+            isAutoAdvance = true;
+            target.fyiWillBeAdvancedByHostKThx();
+        }
+
+        boolean wasAutoAdvance = sAutoAdvanceWidgetIds.indexOfKey(getAppWidgetId()) >= 0;
+        if (isAutoAdvance != wasAutoAdvance) {
+            if (isAutoAdvance) {
+                sAutoAdvanceWidgetIds.put(getAppWidgetId(), true);
+            } else {
+                sAutoAdvanceWidgetIds.delete(getAppWidgetId());
+            }
+            maybeRegisterAutoAdvance();
+        }
+    }
+
+    private Advanceable getAdvanceable() {
+        AppWidgetProviderInfo info = getAppWidgetInfo();
+        if (info == null || info.autoAdvanceViewId == NO_ID || !mIsAttachedToWindow) {
+            return null;
+        }
+        View v = findViewById(info.autoAdvanceViewId);
+        return (v instanceof Advanceable) ? (Advanceable) v : null;
+    }
+
+    private void maybeRegisterAutoAdvance() {
+        Handler handler = getHandler();
+        boolean shouldRegisterAutoAdvance = getWindowVisibility() == VISIBLE && handler != null
+                && (sAutoAdvanceWidgetIds.indexOfKey(getAppWidgetId()) >= 0);
+        if (shouldRegisterAutoAdvance != mIsAutoAdvanceRegistered) {
+            mIsAutoAdvanceRegistered = shouldRegisterAutoAdvance;
+            if (mAutoAdvanceRunnable == null) {
+                mAutoAdvanceRunnable = new Runnable() {
+                    @Override
+                    public void run() {
+                        runAutoAdvance();
+                    }
+                };
+            }
+
+            handler.removeCallbacks(mAutoAdvanceRunnable);
+            scheduleNextAdvance();
+        }
+    }
+
+    private void scheduleNextAdvance() {
+        if (!mIsAutoAdvanceRegistered) {
+            return;
+        }
+        long now = SystemClock.uptimeMillis();
+        long advanceTime = now + (ADVANCE_INTERVAL - (now % ADVANCE_INTERVAL)) +
+                ADVANCE_STAGGER * sAutoAdvanceWidgetIds.indexOfKey(getAppWidgetId());
+        Handler handler = getHandler();
+        if (handler != null) {
+            handler.postAtTime(mAutoAdvanceRunnable, advanceTime);
+        }
+    }
+
+    private void runAutoAdvance() {
+        Advanceable target = getAdvanceable();
+        if (target != null) {
+            target.advance();
+        }
+        scheduleNextAdvance();
+    }
 }
diff --git a/src/com/android/launcher3/LauncherClings.java b/src/com/android/launcher3/LauncherClings.java
deleted file mode 100644
index c1282b5..0000000
--- a/src/com/android/launcher3/LauncherClings.java
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright (C) 2008 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;
-
-@Deprecated
-public class LauncherClings {
-    private static final String WORKSPACE_CLING_DISMISSED_KEY = "cling_gel.workspace.dismissed";
-
-    public static void markFirstRunClingDismissed(Context ctx) {
-        Utilities.getPrefs(ctx).edit()
-                .putBoolean(WORKSPACE_CLING_DISMISSED_KEY, true)
-                .apply();
-    }
-}
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index d66ce6b..4afff18 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -57,8 +57,11 @@
 import com.android.launcher3.dynamicui.ExtractionUtils;
 import com.android.launcher3.folder.Folder;
 import com.android.launcher3.folder.FolderIcon;
+import com.android.launcher3.graphics.LauncherIcons;
 import com.android.launcher3.logging.FileLog;
+import com.android.launcher3.model.BgDataModel;
 import com.android.launcher3.model.GridSizeMigrationTask;
+import com.android.launcher3.model.SdCardAvailableReceiver;
 import com.android.launcher3.model.WidgetsModel;
 import com.android.launcher3.provider.ImportDataTask;
 import com.android.launcher3.provider.LauncherDbUtils;
@@ -69,12 +72,12 @@
 import com.android.launcher3.util.CursorIconInfo;
 import com.android.launcher3.util.FlagOp;
 import com.android.launcher3.util.GridOccupancy;
+import com.android.launcher3.util.ItemInfoMatcher;
 import com.android.launcher3.util.LongArrayMap;
 import com.android.launcher3.util.ManagedProfileHeuristic;
 import com.android.launcher3.util.MultiHashMap;
 import com.android.launcher3.util.PackageManagerHelper;
 import com.android.launcher3.util.Preconditions;
-import com.android.launcher3.util.StringFilter;
 import com.android.launcher3.util.Thunk;
 import com.android.launcher3.util.ViewOnDrawExecutor;
 
@@ -159,38 +162,11 @@
         }
     };
 
-    // The lock that must be acquired before referencing any static bg data structures.  Unlike
-    // other locks, this one can generally be held long-term because we never expect any of these
-    // static data structures to be referenced outside of the worker thread except on the first
-    // load after configuration change.
-    static final Object sBgLock = new Object();
-
-    // sBgItemsIdMap maps *all* the ItemInfos (shortcuts, folders, and widgets) created by
-    // LauncherModel to their ids
-    static final LongArrayMap<ItemInfo> sBgItemsIdMap = new LongArrayMap<>();
-
-    // sBgWorkspaceItems is passed to bindItems, which expects a list of all folders and shortcuts
-    //       created by LauncherModel that are directly on the home screen (however, no widgets or
-    //       shortcuts within folders).
-    static final ArrayList<ItemInfo> sBgWorkspaceItems = new ArrayList<ItemInfo>();
-
-    // sBgAppWidgets is all LauncherAppWidgetInfo created by LauncherModel. Passed to bindAppWidget()
-    static final ArrayList<LauncherAppWidgetInfo> sBgAppWidgets =
-        new ArrayList<LauncherAppWidgetInfo>();
-
-    // sBgFolders is all FolderInfos created by LauncherModel. Passed to bindFolders()
-    static final LongArrayMap<FolderInfo> sBgFolders = new LongArrayMap<>();
-
-    // sBgWorkspaceScreens is the ordered set of workspace screens.
-    static final ArrayList<Long> sBgWorkspaceScreens = new ArrayList<Long>();
-
-    // sBgPinnedShortcutCounts is the ComponentKey representing a pinned shortcut to the number of
-    // times it is pinned.
-    static final Map<ShortcutKey, MutableInt> sBgPinnedShortcutCounts = new HashMap<>();
-
-    // 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>>();
+    /**
+     * All the static data should be accessed on the background thread, A lock should be acquired
+     * on this object when accessing any data from this model.
+     */
+    static final BgDataModel sBgDataModel = new BgDataModel();
 
     // </ only access in worker thread >
 
@@ -232,10 +208,6 @@
         public void bindDeepShortcutMap(MultiHashMap<ComponentKey, String> deepShortcutMap);
     }
 
-    public interface ItemInfoFilter {
-        public boolean filterItem(ItemInfo parent, ItemInfo info, ComponentName cn);
-    }
-
     LauncherModel(LauncherAppState app, IconCache iconCache, AppFilter appFilter,
             DeepShortcutManager deepShortcutManager) {
         Context context = app.getContext();
@@ -276,7 +248,7 @@
 
             @Override
             public void run() {
-                synchronized (sBgLock) {
+                synchronized (sBgDataModel) {
                     final HashSet<ItemInfo> updates = new HashSet<>();
 
                     if (installInfo.state == PackageInstallerCompat.STATUS_INSTALLED) {
@@ -284,7 +256,7 @@
                         return;
                     }
 
-                    for (ItemInfo info : sBgItemsIdMap) {
+                    for (ItemInfo info : sBgDataModel.itemsIdMap) {
                         if (info instanceof ShortcutInfo) {
                             ShortcutInfo si = (ShortcutInfo) info;
                             ComponentName cn = si.getTargetComponent();
@@ -301,7 +273,7 @@
                         }
                     }
 
-                    for (LauncherAppWidgetInfo widget : sBgAppWidgets) {
+                    for (LauncherAppWidgetInfo widget : sBgDataModel.appWidgets) {
                         if (widget.providerName.getPackageName().equals(installInfo.packageName)) {
                             widget.installProgress = installInfo.progress;
                             updates.add(widget);
@@ -334,11 +306,11 @@
 
             @Override
             public void run() {
-                synchronized (sBgLock) {
+                synchronized (sBgDataModel) {
                     ArrayList<ShortcutInfo> updates = new ArrayList<>();
                     UserHandleCompat user = UserHandleCompat.myUserHandle();
 
-                    for (ItemInfo info : sBgItemsIdMap) {
+                    for (ItemInfo info : sBgDataModel.itemsIdMap) {
                         if (info instanceof ShortcutInfo) {
                             ShortcutInfo si = (ShortcutInfo) info;
                             ComponentName cn = si.getTargetComponent();
@@ -418,8 +390,8 @@
 
         // Use sBgItemsIdMap as all the items are already loaded.
         assertWorkspaceLoaded();
-        synchronized (sBgLock) {
-            for (ItemInfo info : sBgItemsIdMap) {
+        synchronized (sBgDataModel) {
+            for (ItemInfo info : sBgDataModel.itemsIdMap) {
                 if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
                     ArrayList<ItemInfo> items = screenItems.get(info.screenId);
                     if (items == null) {
@@ -496,7 +468,7 @@
                 // can not use sBgWorkspaceScreens because loadWorkspace() may not have been
                 // called.
                 ArrayList<Long> workspaceScreens = loadWorkspaceScreensDb(context);
-                synchronized(sBgLock) {
+                synchronized(sBgDataModel) {
                     for (ItemInfo item : workspaceApps) {
                         if (item instanceof ShortcutInfo) {
                             // Short-circuit this logic if the icon exists somewhere on the workspace
@@ -578,7 +550,7 @@
 
     static void checkItemInfoLocked(
             final long itemId, final ItemInfo item, StackTraceElement[] stackTrace) {
-        ItemInfo modelItem = sBgItemsIdMap.get(itemId);
+        ItemInfo modelItem = sBgDataModel.itemsIdMap.get(itemId);
         if (modelItem != null && item != modelItem) {
             // check all the data is consistent
             if (modelItem instanceof ShortcutInfo && item instanceof ShortcutInfo) {
@@ -619,7 +591,7 @@
         final long itemId = item.id;
         Runnable r = new Runnable() {
             public void run() {
-                synchronized (sBgLock) {
+                synchronized (sBgDataModel) {
                     checkItemInfoLocked(itemId, item, stackTrace);
                 }
             }
@@ -675,13 +647,13 @@
 
     static void updateItemArrays(ItemInfo item, long itemId, StackTraceElement[] stackTrace) {
         // Lock on mBgLock *after* the db operation
-        synchronized (sBgLock) {
+        synchronized (sBgDataModel) {
             checkItemInfoLocked(itemId, item, stackTrace);
 
             if (item.container != LauncherSettings.Favorites.CONTAINER_DESKTOP &&
                     item.container != LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
                 // Item is in a folder, make sure this folder exists
-                if (!sBgFolders.containsKey(item.container)) {
+                if (!sBgDataModel.folders.containsKey(item.container)) {
                     // An items container is being set to a that of an item which is not in
                     // the list of Folders.
                     String msg = "item: " + item + " container being set to: " +
@@ -693,7 +665,7 @@
             // Items are added/removed from the corresponding FolderInfo elsewhere, such
             // as in Workspace.onDrop. Here, we just add/remove them from the list of items
             // that are on the desktop, as appropriate
-            ItemInfo modelItem = sBgItemsIdMap.get(itemId);
+            ItemInfo modelItem = sBgDataModel.itemsIdMap.get(itemId);
             if (modelItem != null &&
                     (modelItem.container == LauncherSettings.Favorites.CONTAINER_DESKTOP ||
                      modelItem.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT)) {
@@ -702,15 +674,15 @@
                     case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
                     case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT:
                     case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
-                        if (!sBgWorkspaceItems.contains(modelItem)) {
-                            sBgWorkspaceItems.add(modelItem);
+                        if (!sBgDataModel.workspaceItems.contains(modelItem)) {
+                            sBgDataModel.workspaceItems.add(modelItem);
                         }
                         break;
                     default:
                         break;
                 }
             } else {
-                sBgWorkspaceItems.remove(modelItem);
+                sBgDataModel.workspaceItems.remove(modelItem);
             }
         }
     }
@@ -854,8 +826,8 @@
             intentWithoutPkg = intent.toUri(0);
         }
 
-        synchronized (sBgLock) {
-            for (ItemInfo item : sBgItemsIdMap) {
+        synchronized (sBgDataModel) {
+            for (ItemInfo item : sBgDataModel.itemsIdMap) {
                 if (item instanceof ShortcutInfo) {
                     ShortcutInfo info = (ShortcutInfo) item;
                     Intent targetIntent = info.promisedIntent == null
@@ -906,76 +878,35 @@
             public void run() {
                 cr.insert(LauncherSettings.Favorites.CONTENT_URI, values);
 
-                // Lock on mBgLock *after* the db operation
-                synchronized (sBgLock) {
+                synchronized (sBgDataModel) {
                     checkItemInfoLocked(item.id, item, stackTrace);
-                    sBgItemsIdMap.put(item.id, item);
-                    switch (item.itemType) {
-                        case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
-                            sBgFolders.put(item.id, (FolderInfo) item);
-                            // Fall through
-                        case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
-                        case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
-                        case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT:
-                            if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP ||
-                                    item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
-                                sBgWorkspaceItems.add(item);
-                            } else {
-                                if (!sBgFolders.containsKey(item.container)) {
-                                    // Adding an item to a folder that doesn't exist.
-                                    String msg = "adding item: " + item + " to a folder that " +
-                                            " doesn't exist";
-                                    Log.e(TAG, msg);
-                                }
-                            }
-                            if (item.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
-                                incrementPinnedShortcutCount(
-                                        ShortcutKey.fromShortcutInfo((ShortcutInfo) item),
-                                        true /* shouldPin */);
-                            }
-                            break;
-                        case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
-                            sBgAppWidgets.add((LauncherAppWidgetInfo) item);
-                            break;
-                    }
+                    sBgDataModel.addItem(item, true);
                 }
             }
         };
         runOnWorkerThread(r);
     }
 
-    private static ArrayList<ItemInfo> getItemsByPackageName(
-            final String pn, final UserHandleCompat user) {
-        ItemInfoFilter filter  = new ItemInfoFilter() {
-            @Override
-            public boolean filterItem(ItemInfo parent, ItemInfo info, ComponentName cn) {
-                return cn.getPackageName().equals(pn) && info.user.equals(user);
-            }
-        };
-        return filterItemInfos(sBgItemsIdMap, filter);
-    }
-
-    /**
-     * Removes all the items from the database corresponding to the specified package.
-     */
-    static void deletePackageFromDatabase(Context context, final String pn,
-            final UserHandleCompat user) {
-        deleteItemsFromDatabase(context, getItemsByPackageName(pn, user));
-    }
-
     /**
      * Removes the specified item from the database
      */
     public static void deleteItemFromDatabase(Context context, final ItemInfo item) {
-        ArrayList<ItemInfo> items = new ArrayList<ItemInfo>();
+        ArrayList<ItemInfo> items = new ArrayList<>();
         items.add(item);
         deleteItemsFromDatabase(context, items);
     }
 
     /**
+     * Removes all the items from the database matching {@param matcher}.
+     */
+    public static void deleteItemsFromDatabase(Context context, ItemInfoMatcher matcher) {
+        deleteItemsFromDatabase(context, matcher.filterItemInfos(sBgDataModel.itemsIdMap));
+    }
+
+    /**
      * Removes the specified items from the database
      */
-    static void deleteItemsFromDatabase(Context context, final ArrayList<? extends ItemInfo> items) {
+    static void deleteItemsFromDatabase(Context context, final Iterable<? extends ItemInfo> items) {
         final ContentResolver cr = context.getContentResolver();
         Runnable r = new Runnable() {
             public void run() {
@@ -983,36 +914,7 @@
                     final Uri uri = LauncherSettings.Favorites.getContentUri(item.id);
                     cr.delete(uri, null, null);
 
-                    // Lock on mBgLock *after* the db operation
-                    synchronized (sBgLock) {
-                        switch (item.itemType) {
-                            case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
-                                sBgFolders.remove(item.id);
-                                for (ItemInfo info: sBgItemsIdMap) {
-                                    if (info.container == item.id) {
-                                        // We are deleting a folder which still contains items that
-                                        // think they are contained by that folder.
-                                        String msg = "deleting a folder (" + item + ") which still " +
-                                                "contains items (" + info + ")";
-                                        Log.e(TAG, msg);
-                                    }
-                                }
-                                sBgWorkspaceItems.remove(item);
-                                break;
-                            case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT:
-                                decrementPinnedShortcutCount(ShortcutKey.fromShortcutInfo(
-                                        (ShortcutInfo) item));
-                                // Fall through.
-                            case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
-                            case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
-                                sBgWorkspaceItems.remove(item);
-                                break;
-                            case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
-                                sBgAppWidgets.remove((LauncherAppWidgetInfo) item);
-                                break;
-                        }
-                        sBgItemsIdMap.remove(item.id);
-                    }
+                    sBgDataModel.removeItem(item);
                 }
             }
         };
@@ -1020,39 +922,6 @@
     }
 
     /**
-     * Decrement the count for the given pinned shortcut, unpinning it if the count becomes 0.
-     */
-    private static void decrementPinnedShortcutCount(final ShortcutKey pinnedShortcut) {
-        synchronized (sBgLock) {
-            MutableInt count = sBgPinnedShortcutCounts.get(pinnedShortcut);
-            if (count == null || --count.value == 0) {
-                LauncherAppState.getInstance().getShortcutManager().unpinShortcut(pinnedShortcut);
-            }
-        }
-    }
-
-    /**
-     * Increment the count for the given shortcut, pinning it if the count becomes 1.
-     *
-     * As an optimization, the caller can pass shouldPin == false to avoid
-     * unnecessary RPC's if the shortcut is already pinned.
-     */
-    private static void incrementPinnedShortcutCount(ShortcutKey pinnedShortcut, boolean shouldPin) {
-        synchronized (sBgLock) {
-            MutableInt count = sBgPinnedShortcutCounts.get(pinnedShortcut);
-            if (count == null) {
-                count = new MutableInt(1);
-                sBgPinnedShortcutCounts.put(pinnedShortcut, count);
-            } else {
-                count.value++;
-            }
-            if (shouldPin && count.value == 1) {
-                LauncherAppState.getInstance().getShortcutManager().pinShortcut(pinnedShortcut);
-            }
-        }
-    }
-
-    /**
      * Update the order of the workspace screens in the database. The array list contains
      * a list of screen ids in the order that they should appear.
      */
@@ -1091,9 +960,9 @@
                     throw new RuntimeException(ex);
                 }
 
-                synchronized (sBgLock) {
-                    sBgWorkspaceScreens.clear();
-                    sBgWorkspaceScreens.addAll(screensCopy);
+                synchronized (sBgDataModel) {
+                    sBgDataModel.workspaceScreens.clear();
+                    sBgDataModel.workspaceScreens.addAll(screensCopy);
                 }
             }
         };
@@ -1108,22 +977,13 @@
 
         Runnable r = new Runnable() {
             public void run() {
-                cr.delete(LauncherSettings.Favorites.getContentUri(info.id), null, null);
-                // Lock on mBgLock *after* the db operation
-                synchronized (sBgLock) {
-                    sBgItemsIdMap.remove(info.id);
-                    sBgFolders.remove(info.id);
-                    sBgWorkspaceItems.remove(info);
-                }
-
                 cr.delete(LauncherSettings.Favorites.CONTENT_URI,
                         LauncherSettings.Favorites.CONTAINER + "=" + info.id, null);
-                // Lock on mBgLock *after* the db operation
-                synchronized (sBgLock) {
-                    for (ItemInfo childInfo : info.contents) {
-                        sBgItemsIdMap.remove(childInfo.id);
-                    }
-                }
+                sBgDataModel.removeItem(info.contents);
+                info.contents.clear();
+
+                cr.delete(LauncherSettings.Favorites.getContentUri(info.id), null, null);
+                sBgDataModel.removeItem(info);
             }
         };
         runOnWorkerThread(r);
@@ -1150,9 +1010,12 @@
 
     @Override
     public void onPackageRemoved(String packageName, UserHandleCompat user) {
+        onPackagesRemoved(user, packageName);
+    }
+
+    public void onPackagesRemoved(UserHandleCompat user, String... packages) {
         int op = PackageUpdatedTask.OP_REMOVE;
-        enqueueItemUpdatedTask(new PackageUpdatedTask(op, new String[] { packageName },
-                user));
+        enqueueItemUpdatedTask(new PackageUpdatedTask(op, packages, user));
     }
 
     @Override
@@ -1639,18 +1502,6 @@
             }
         }
 
-        /** Clears all the sBg data structures */
-        private void clearSBgDataStructures() {
-            synchronized (sBgLock) {
-                sBgWorkspaceItems.clear();
-                sBgAppWidgets.clear();
-                sBgFolders.clear();
-                sBgItemsIdMap.clear();
-                sBgWorkspaceScreens.clear();
-                sBgPinnedShortcutCounts.clear();
-            }
-        }
-
         private void loadWorkspace() {
             if (LauncherAppState.PROFILE_STARTUP) {
                 Trace.beginSection("Loading Workspace");
@@ -1663,6 +1514,7 @@
             final boolean isSafeMode = manager.isSafeMode();
             final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
             final boolean isSdCardReady = Utilities.isBootCompleted();
+            final MultiHashMap<UserHandleCompat, String> pendingPackages = new MultiHashMap<>();
 
             LauncherAppState app = LauncherAppState.getInstance();
             InvariantDeviceProfile profile = app.getInvariantDeviceProfile();
@@ -1693,11 +1545,12 @@
             LauncherSettings.Settings.call(contentResolver,
                     LauncherSettings.Settings.METHOD_LOAD_DEFAULT_FAVORITES);
 
-            synchronized (sBgLock) {
-                clearSBgDataStructures();
+            synchronized (sBgDataModel) {
+                sBgDataModel.clear();
+
                 final HashMap<String, Integer> installingPkgs = PackageInstallerCompat
                         .getInstance(mContext).updateAndGetActiveSessionCache();
-                sBgWorkspaceScreens.addAll(loadWorkspaceScreensDb(mContext));
+                sBgDataModel.workspaceScreens.addAll(loadWorkspaceScreensDb(mContext));
 
                 final ArrayList<Long> itemsToRemove = new ArrayList<>();
                 final ArrayList<Long> restoredRows = new ArrayList<>();
@@ -1901,12 +1754,7 @@
                                             // SdCard is not ready yet. Package might get available,
                                             // once it is ready.
                                             Log.d(TAG, "Invalid package: " + cn + " (check again later)");
-                                            HashSet<String> pkgs = sPendingPackages.get(user);
-                                            if (pkgs == null) {
-                                                pkgs = new HashSet<String>();
-                                                sPendingPackages.put(user, pkgs);
-                                            }
-                                            pkgs.add(cn.getPackageName());
+                                            pendingPackages.addToList(user, cn.getPackageName());
                                             allowMissingTarget = true;
                                             // Add the icon on the workspace anyway.
 
@@ -1977,7 +1825,6 @@
 
                                         info.isDisabled |= ShortcutInfo.FLAG_DISABLED_LOCKED_USER;
                                     }
-                                    incrementPinnedShortcutCount(key, false /* shouldPin */);
                                 } else { // item type == ITEM_TYPE_SHORTCUT
                                     info = getShortcutInfo(c, cursorIconInfo);
 
@@ -2019,7 +1866,7 @@
                                     }
 
                                     // check & update map of what's occupied
-                                    if (!checkItemPlacement(occupied, info, sBgWorkspaceScreens)) {
+                                    if (!checkItemPlacement(occupied, info, sBgDataModel.workspaceScreens)) {
                                         itemsToRemove.add(id);
                                         break;
                                     }
@@ -2036,19 +1883,7 @@
                                         }
                                     }
 
-                                    switch (container) {
-                                    case LauncherSettings.Favorites.CONTAINER_DESKTOP:
-                                    case LauncherSettings.Favorites.CONTAINER_HOTSEAT:
-                                        sBgWorkspaceItems.add(info);
-                                        break;
-                                    default:
-                                        // Item is in a user folder
-                                        FolderInfo folderInfo =
-                                                findOrMakeFolder(sBgFolders, container);
-                                        folderInfo.add(info, false);
-                                        break;
-                                    }
-                                    sBgItemsIdMap.put(info.id, info);
+                                    sBgDataModel.addItem(info, false);
                                 } else {
                                     throw new RuntimeException("Unexpected null ShortcutInfo");
                                 }
@@ -2056,7 +1891,7 @@
 
                             case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
                                 id = c.getLong(idIndex);
-                                FolderInfo folderInfo = findOrMakeFolder(sBgFolders, id);
+                                FolderInfo folderInfo = sBgDataModel.findOrMakeFolder(id);
 
                                 // Do not trim the folder label, as is was set by the user.
                                 folderInfo.title = c.getString(cursorIconInfo.titleIndex);
@@ -2070,25 +1905,16 @@
                                 folderInfo.options = c.getInt(optionsIndex);
 
                                 // check & update map of what's occupied
-                                if (!checkItemPlacement(occupied, folderInfo, sBgWorkspaceScreens)) {
+                                if (!checkItemPlacement(occupied, folderInfo, sBgDataModel.workspaceScreens)) {
                                     itemsToRemove.add(id);
                                     break;
                                 }
-
-                                switch (container) {
-                                    case LauncherSettings.Favorites.CONTAINER_DESKTOP:
-                                    case LauncherSettings.Favorites.CONTAINER_HOTSEAT:
-                                        sBgWorkspaceItems.add(folderInfo);
-                                        break;
-                                }
-
                                 if (restored) {
                                     // no special handling required for restored folders
                                     restoredRows.add(id);
                                 }
 
-                                sBgItemsIdMap.put(folderInfo.id, folderInfo);
-                                sBgFolders.put(folderInfo.id, folderInfo);
+                                sBgDataModel.addItem(folderInfo, false);
                                 break;
 
                             case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
@@ -2205,7 +2031,7 @@
 
                                     appWidgetInfo.container = container;
                                     // check & update map of what's occupied
-                                    if (!checkItemPlacement(occupied, appWidgetInfo, sBgWorkspaceScreens)) {
+                                    if (!checkItemPlacement(occupied, appWidgetInfo, sBgDataModel.workspaceScreens)) {
                                         itemsToRemove.add(id);
                                         break;
                                     }
@@ -2224,8 +2050,7 @@
                                             updateItem(id, values);
                                         }
                                     }
-                                    sBgItemsIdMap.put(appWidgetInfo.id, appWidgetInfo);
-                                    sBgAppWidgets.add(appWidgetInfo);
+                                    sBgDataModel.addItem(appWidgetInfo, false);
                                 }
                                 break;
                             }
@@ -2239,7 +2064,7 @@
 
                 // Break early if we've stopped loading
                 if (mStopped) {
-                    clearSBgDataStructures();
+                    sBgDataModel.clear();
                     return;
                 }
 
@@ -2259,15 +2084,15 @@
                                     LauncherSettings.Settings.METHOD_DELETE_EMPTY_FOLDERS)
                             .getSerializable(LauncherSettings.Settings.EXTRA_VALUE);
                     for (long folderId : deletedFolderIds) {
-                        sBgWorkspaceItems.remove(sBgFolders.get(folderId));
-                        sBgFolders.remove(folderId);
-                        sBgItemsIdMap.remove(folderId);
+                        sBgDataModel.workspaceItems.remove(sBgDataModel.folders.get(folderId));
+                        sBgDataModel.folders.remove(folderId);
+                        sBgDataModel.itemsIdMap.remove(folderId);
                     }
                 }
 
                 // Unpin shortcuts that don't exist on the workspace.
                 for (ShortcutKey key : shortcutKeyToPinnedShortcuts.keySet()) {
-                    MutableInt numTimesPinned = sBgPinnedShortcutCounts.get(key);
+                    MutableInt numTimesPinned = sBgDataModel.pinnedShortcutCounts.get(key);
                     if (numTimesPinned == null || numTimesPinned.value == 0) {
                         // Shortcut is pinned but doesn't exist on the workspace; unpin it.
                         mDeepShortcutManager.unpinShortcut(key);
@@ -2275,7 +2100,7 @@
                 }
 
                 // Sort all the folder items and make sure the first 3 items are high resolution.
-                for (FolderInfo folder : sBgFolders) {
+                for (FolderInfo folder : sBgDataModel.folders) {
                     Collections.sort(folder.contents, Folder.ITEM_POS_COMPARATOR);
                     int pos = 0;
                     for (ShortcutInfo info : folder.contents) {
@@ -2298,15 +2123,18 @@
                                     LauncherSettings.Favorites._ID, restoredRows), null);
                 }
 
-                if (!isSdCardReady && !sPendingPackages.isEmpty()) {
-                    context.registerReceiver(new AppsAvailabilityCheck(),
+                if (!isSdCardReady && !pendingPackages.isEmpty()) {
+                    context.registerReceiver(
+                            new SdCardAvailableReceiver(
+                                    LauncherModel.this, mContext, pendingPackages),
                             new IntentFilter(Intent.ACTION_BOOT_COMPLETED),
-                            null, sWorker);
+                            null,
+                            sWorker);
                 }
 
                 // Remove any empty screens
-                ArrayList<Long> unusedScreens = new ArrayList<Long>(sBgWorkspaceScreens);
-                for (ItemInfo item: sBgItemsIdMap) {
+                ArrayList<Long> unusedScreens = new ArrayList<Long>(sBgDataModel.workspaceScreens);
+                for (ItemInfo item: sBgDataModel.itemsIdMap) {
                     long screenId = item.screenId;
                     if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP &&
                             unusedScreens.contains(screenId)) {
@@ -2316,8 +2144,8 @@
 
                 // If there are any empty screens remove them, and update.
                 if (unusedScreens.size() != 0) {
-                    sBgWorkspaceScreens.removeAll(unusedScreens);
-                    updateWorkspaceScreenOrder(context, sBgWorkspaceScreens);
+                    sBgDataModel.workspaceScreens.removeAll(unusedScreens);
+                    updateWorkspaceScreenOrder(context, sBgDataModel.workspaceScreens);
                 }
 
                 if (DEBUG_LOADERS) {
@@ -2530,10 +2358,10 @@
             ArrayList<LauncherAppWidgetInfo> appWidgets = new ArrayList<>();
             ArrayList<Long> orderedScreenIds = new ArrayList<>();
 
-            synchronized (sBgLock) {
-                workspaceItems.addAll(sBgWorkspaceItems);
-                appWidgets.addAll(sBgAppWidgets);
-                orderedScreenIds.addAll(sBgWorkspaceScreens);
+            synchronized (sBgDataModel) {
+                workspaceItems.addAll(sBgDataModel.workspaceItems);
+                appWidgets.addAll(sBgDataModel.appWidgets);
+                orderedScreenIds.addAll(sBgDataModel.workspaceScreens);
             }
 
             final int currentScreen;
@@ -2677,8 +2505,8 @@
         private void updateIconCache() {
             // Ignore packages which have a promise icon.
             HashSet<String> packagesToIgnore = new HashSet<>();
-            synchronized (sBgLock) {
-                for (ItemInfo info : sBgItemsIdMap) {
+            synchronized (sBgDataModel) {
+                for (ItemInfo info : sBgDataModel.itemsIdMap) {
                     if (info instanceof ShortcutInfo) {
                         ShortcutInfo si = (ShortcutInfo) info;
                         if (si.isPromise() && si.getTargetComponent() != null) {
@@ -2841,11 +2669,11 @@
         }
 
         public void dumpState() {
-            synchronized (sBgLock) {
+            synchronized (sBgDataModel) {
                 Log.d(TAG, "mLoaderTask.mContext=" + mContext);
                 Log.d(TAG, "mLoaderTask.mStopped=" + mStopped);
                 Log.d(TAG, "mLoaderTask.mLoadAndBindStepFinished=" + mLoadAndBindStepFinished);
-                Log.d(TAG, "mItems size=" + sBgWorkspaceItems.size());
+                Log.d(TAG, "mItems size=" + sBgDataModel.workspaceItems.size());
             }
         }
     }
@@ -2914,8 +2742,8 @@
 
         // If any package icon has changed (app was updated while launcher was dead),
         // update the corresponding shortcuts.
-        synchronized (sBgLock) {
-            for (ItemInfo info : sBgItemsIdMap) {
+        synchronized (sBgDataModel) {
+            for (ItemInfo info : sBgDataModel.itemsIdMap) {
                 if (info instanceof ShortcutInfo && user.equals(info.user)
                         && info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
                     ShortcutInfo si = (ShortcutInfo) info;
@@ -2971,47 +2799,10 @@
         sWorker.post(task);
     }
 
-    @Thunk class AppsAvailabilityCheck extends BroadcastReceiver {
-
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            synchronized (sBgLock) {
-                final LauncherAppsCompat launcherApps = LauncherAppsCompat
-                        .getInstance(mApp.getContext());
-                final PackageManager manager = context.getPackageManager();
-                final ArrayList<String> packagesRemoved = new ArrayList<String>();
-                final ArrayList<String> packagesUnavailable = new ArrayList<String>();
-                for (Entry<UserHandleCompat, HashSet<String>> entry : sPendingPackages.entrySet()) {
-                    UserHandleCompat user = entry.getKey();
-                    packagesRemoved.clear();
-                    packagesUnavailable.clear();
-                    for (String pkg : entry.getValue()) {
-                        if (!launcherApps.isPackageEnabledForProfile(pkg, user)) {
-                            if (PackageManagerHelper.isAppOnSdcard(manager, pkg)) {
-                                packagesUnavailable.add(pkg);
-                            } else {
-                                packagesRemoved.add(pkg);
-                            }
-                        }
-                    }
-                    if (!packagesRemoved.isEmpty()) {
-                        enqueueItemUpdatedTask(new PackageUpdatedTask(PackageUpdatedTask.OP_REMOVE,
-                                packagesRemoved.toArray(new String[packagesRemoved.size()]), user));
-                    }
-                    if (!packagesUnavailable.isEmpty()) {
-                        enqueueItemUpdatedTask(new PackageUpdatedTask(PackageUpdatedTask.OP_UNAVAILABLE,
-                                packagesUnavailable.toArray(new String[packagesUnavailable.size()]), user));
-                    }
-                }
-                sPendingPackages.clear();
-            }
-        }
-    }
-
     private class PackageUpdatedTask implements Runnable {
-        int mOp;
-        String[] mPackages;
-        UserHandleCompat mUser;
+        final int mOp;
+        final String[] mPackages;
+        final UserHandleCompat mUser;
 
         public static final int OP_NONE = 0;
         public static final int OP_ADD = 1;
@@ -3038,7 +2829,7 @@
             final String[] packages = mPackages;
             final int N = packages.length;
             FlagOp flagOp = FlagOp.NO_OP;
-            StringFilter pkgFilter = StringFilter.of(new HashSet<>(Arrays.asList(packages)));
+            final HashSet<String> packageSet = new HashSet<>(Arrays.asList(packages));
             switch (mOp) {
                 case OP_ADD: {
                     for (int i=0; i<N; i++) {
@@ -3088,15 +2879,15 @@
                             FlagOp.addFlag(ShortcutInfo.FLAG_DISABLED_SUSPENDED) :
                                     FlagOp.removeFlag(ShortcutInfo.FLAG_DISABLED_SUSPENDED);
                     if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.(un)suspend " + N);
-                    mBgAllAppsList.updatePackageFlags(pkgFilter, mUser, flagOp);
+                    mBgAllAppsList.updateDisabledFlags(
+                            ItemInfoMatcher.ofPackages(packageSet, mUser), flagOp);
                     break;
                 case OP_USER_AVAILABILITY_CHANGE:
                     flagOp = UserManagerCompat.getInstance(context).isQuietModeEnabled(mUser)
                             ? FlagOp.addFlag(ShortcutInfo.FLAG_DISABLED_QUIET_USER)
                             : FlagOp.removeFlag(ShortcutInfo.FLAG_DISABLED_QUIET_USER);
                     // We want to update all packages for this user.
-                    pkgFilter = StringFilter.matchesAll();
-                    mBgAllAppsList.updatePackageFlags(pkgFilter, mUser, flagOp);
+                    mBgAllAppsList.updateDisabledFlags(ItemInfoMatcher.ofUser(mUser), flagOp);
                     break;
             }
 
@@ -3145,12 +2936,12 @@
 
             // Update shortcut infos
             if (mOp == OP_ADD || flagOp != FlagOp.NO_OP) {
-                final ArrayList<ShortcutInfo> updatedShortcuts = new ArrayList<ShortcutInfo>();
-                final ArrayList<ShortcutInfo> removedShortcuts = new ArrayList<ShortcutInfo>();
-                final ArrayList<LauncherAppWidgetInfo> widgets = new ArrayList<LauncherAppWidgetInfo>();
+                final ArrayList<ShortcutInfo> updatedShortcuts = new ArrayList<>();
+                final ArrayList<ShortcutInfo> removedShortcuts = new ArrayList<>();
+                final ArrayList<LauncherAppWidgetInfo> widgets = new ArrayList<>();
 
-                synchronized (sBgLock) {
-                    for (ItemInfo info : sBgItemsIdMap) {
+                synchronized (sBgDataModel) {
+                    for (ItemInfo info : sBgDataModel.itemsIdMap) {
                         if (info instanceof ShortcutInfo && mUser.equals(info.user)) {
                             ShortcutInfo si = (ShortcutInfo) info;
                             boolean infoUpdated = false;
@@ -3158,8 +2949,8 @@
 
                             // Update shortcuts which use iconResource.
                             if ((si.iconResource != null)
-                                    && pkgFilter.matches(si.iconResource.packageName)) {
-                                Bitmap icon = Utilities.createIconBitmap(
+                                    && packageSet.contains(si.iconResource.packageName)) {
+                                Bitmap icon = LauncherIcons.createIconBitmap(
                                         si.iconResource.packageName,
                                         si.iconResource.resourceName, context);
                                 if (icon != null) {
@@ -3170,7 +2961,7 @@
                             }
 
                             ComponentName cn = si.getTargetComponent();
-                            if (cn != null && pkgFilter.matches(cn.getPackageName())) {
+                            if (cn != null && packageSet.contains(cn.getPackageName())) {
                                 AppInfo appInfo = addedOrUpdatedApps.get(cn);
 
                                 if (si.isPromise()) {
@@ -3235,7 +3026,7 @@
                             LauncherAppWidgetInfo widgetInfo = (LauncherAppWidgetInfo) info;
                             if (mUser.equals(widgetInfo.user)
                                     && widgetInfo.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY)
-                                    && pkgFilter.matches(widgetInfo.providerName.getPackageName())) {
+                                    && packageSet.contains(widgetInfo.providerName.getPackageName())) {
                                 widgetInfo.restoreStatus &=
                                         ~LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY &
                                         ~LauncherAppWidgetInfo.FLAG_RESTORE_STARTED;
@@ -3293,12 +3084,10 @@
             }
 
             if (!removedPackages.isEmpty() || !removedComponents.isEmpty()) {
-                for (String pn : removedPackages) {
-                    deletePackageFromDatabase(context, pn, mUser);
-                }
-                for (ComponentName cn : removedComponents) {
-                    deleteItemsFromDatabase(context, getItemInfoForComponentName(cn, mUser));
-                }
+                deleteItemsFromDatabase(
+                        context, ItemInfoMatcher.ofPackages(removedPackages, mUser));
+                deleteItemsFromDatabase(
+                        context, ItemInfoMatcher.ofComponents(removedComponents, mUser));
 
                 // Remove any queued items from the install queue
                 InstallShortcutReceiver.removeFromInstallQueue(context, removedPackages, mUser);
@@ -3383,7 +3172,7 @@
             // Find ShortcutInfo's that have changed on the workspace.
             final ArrayList<ShortcutInfo> removedShortcutInfos = new ArrayList<>();
             MultiHashMap<String, ShortcutInfo> idsToWorkspaceShortcutInfos = new MultiHashMap<>();
-            for (ItemInfo itemInfo : sBgItemsIdMap) {
+            for (ItemInfo itemInfo : sBgDataModel.itemsIdMap) {
                 if (itemInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
                     ShortcutInfo si = (ShortcutInfo) itemInfo;
                     if (si.getPromisedIntent().getPackage().equals(mPackageName)
@@ -3473,7 +3262,7 @@
             // Update the workspace to reflect the changes to updated shortcuts residing on it.
             ArrayList<ShortcutInfo> updatedShortcutInfos = new ArrayList<>();
             ArrayList<ShortcutInfo> deletedShortcutInfos = new ArrayList<>();
-            for (ItemInfo itemInfo : sBgItemsIdMap) {
+            for (ItemInfo itemInfo : sBgDataModel.itemsIdMap) {
                 if (itemInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT
                         && mUser.equals(itemInfo.user)) {
                     ShortcutInfo si = (ShortcutInfo) itemInfo;
@@ -3549,18 +3338,6 @@
         return !launcherApps.isPackageEnabledForProfile(packageName, user);
     }
 
-    public static boolean isValidPackageActivity(Context context, ComponentName cn,
-            UserHandleCompat user) {
-        if (cn == null) {
-            return false;
-        }
-        final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
-        if (!launcherApps.isPackageEnabledForProfile(cn.getPackageName(), user)) {
-            return false;
-        }
-        return launcherApps.isActivityEnabledForProfile(cn, user);
-    }
-
     public static boolean isValidPackage(Context context, String packageName,
             UserHandleCompat user) {
         if (packageName == null) {
@@ -3683,50 +3460,6 @@
         return info;
     }
 
-    static ArrayList<ItemInfo> filterItemInfos(Iterable<ItemInfo> infos,
-            ItemInfoFilter f) {
-        HashSet<ItemInfo> filtered = new HashSet<ItemInfo>();
-        for (ItemInfo i : infos) {
-            if (i instanceof ShortcutInfo) {
-                ShortcutInfo info = (ShortcutInfo) i;
-                ComponentName cn = info.getTargetComponent();
-                if (cn != null && f.filterItem(null, info, cn)) {
-                    filtered.add(info);
-                }
-            } else if (i instanceof FolderInfo) {
-                FolderInfo info = (FolderInfo) i;
-                for (ShortcutInfo s : info.contents) {
-                    ComponentName cn = s.getTargetComponent();
-                    if (cn != null && f.filterItem(info, s, cn)) {
-                        filtered.add(s);
-                    }
-                }
-            } else if (i instanceof LauncherAppWidgetInfo) {
-                LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) i;
-                ComponentName cn = info.providerName;
-                if (cn != null && f.filterItem(null, info, cn)) {
-                    filtered.add(info);
-                }
-            }
-        }
-        return new ArrayList<ItemInfo>(filtered);
-    }
-
-    @Thunk ArrayList<ItemInfo> getItemInfoForComponentName(final ComponentName cname,
-            final UserHandleCompat user) {
-        ItemInfoFilter filter  = new ItemInfoFilter() {
-            @Override
-            public boolean filterItem(ItemInfo parent, ItemInfo info, ComponentName cn) {
-                if (info.user == null) {
-                    return cn.equals(cname);
-                } else {
-                    return cn.equals(cname) && info.user.equals(user);
-                }
-            }
-        };
-        return filterItemInfos(sBgItemsIdMap, filter);
-    }
-
     /**
      * Make an ShortcutInfo object for a shortcut that isn't an application.
      */
@@ -3768,17 +3501,15 @@
         }
 
         Bitmap icon = null;
-        boolean customIcon = false;
         ShortcutIconResource iconResource = null;
 
         if (bitmap instanceof Bitmap) {
-            icon = Utilities.createIconBitmap((Bitmap) bitmap, context);
-            customIcon = true;
+            icon = LauncherIcons.createIconBitmap((Bitmap) bitmap, context);
         } else {
             Parcelable extra = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE);
             if (extra instanceof ShortcutIconResource) {
                 iconResource = (ShortcutIconResource) extra;
-                icon = Utilities.createIconBitmap(iconResource.packageName,
+                icon = LauncherIcons.createIconBitmap(iconResource.packageName,
                         iconResource.resourceName, context);
             }
         }
@@ -3802,22 +3533,6 @@
         return info;
     }
 
-    /**
-     * Return an existing FolderInfo object if we have encountered this ID previously,
-     * or make a new one.
-     */
-    @Thunk static FolderInfo findOrMakeFolder(LongArrayMap<FolderInfo> folders, long id) {
-        // See if a placeholder was created for us already
-        FolderInfo folderInfo = folders.get(id);
-        if (folderInfo == null) {
-            // No placeholder -- create a new instance
-            folderInfo = new FolderInfo();
-            folders.put(id, folderInfo);
-        }
-        return folderInfo;
-    }
-
-
     static boolean isValidProvider(AppWidgetProviderInfo provider) {
         return (provider != null) && (provider.provider != null)
                 && (provider.provider.getPackageName() != null);
@@ -3844,8 +3559,8 @@
      * @return {@link FolderInfo} if its already loaded.
      */
     public FolderInfo findFolderById(Long folderId) {
-        synchronized (sBgLock) {
-            return sBgFolders.get(folderId);
+        synchronized (sBgDataModel) {
+            return sBgDataModel.folders.get(folderId);
         }
     }
 
diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java
index f3d9493..e1ff6db 100644
--- a/src/com/android/launcher3/LauncherProvider.java
+++ b/src/com/android/launcher3/LauncherProvider.java
@@ -764,12 +764,7 @@
                     }
                 }
                 case 16: {
-                    // We use the db version upgrade here to identify users who may not have seen
-                    // clings yet (because they weren't available), but for whom the clings are now
-                    // available (tablet users). Because one of the possible cling flows (migration)
-                    // is very destructive (wipes out workspaces), we want to prevent this from showing
-                    // until clear data. We do so by marking that the clings have been shown.
-                    LauncherClings.markFirstRunClingDismissed(mContext);
+                    // No-op
                 }
                 case 17: {
                     // No-op
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index bea55d2..0b9bf09 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -98,7 +98,6 @@
 
     @ViewDebug.ExportedProperty(category = "launcher")
     protected int mCurrentPage;
-    protected int mRestorePage = INVALID_RESTORE_PAGE;
     private int mChildCountOnLastLayout;
 
     @ViewDebug.ExportedProperty(category = "launcher")
@@ -418,17 +417,6 @@
     }
 
     /**
-     * The restore page will be set in place of the current page at the next (likely first)
-     * layout.
-     */
-    void setRestorePage(int restorePage) {
-        mRestorePage = restorePage;
-    }
-    int getRestorePage() {
-        return mRestorePage;
-    }
-
-    /**
      * Should be called whenever the page changes. In the case of a scroll, we wait until the page
      * has settled.
      */
@@ -879,12 +867,7 @@
         }
 
         if (mScroller.isFinished() && mChildCountOnLastLayout != childCount) {
-            if (mRestorePage != INVALID_RESTORE_PAGE) {
-                setCurrentPage(mRestorePage);
-                mRestorePage = INVALID_RESTORE_PAGE;
-            } else {
-                setCurrentPage(getNextPage());
-            }
+            setCurrentPage(getNextPage());
         }
         mChildCountOnLastLayout = childCount;
 
diff --git a/src/com/android/launcher3/PinchToOverviewListener.java b/src/com/android/launcher3/PinchToOverviewListener.java
index 48a75d1..bc5ac24 100644
--- a/src/com/android/launcher3/PinchToOverviewListener.java
+++ b/src/com/android/launcher3/PinchToOverviewListener.java
@@ -61,12 +61,12 @@
         mPinchDetector = new ScaleGestureDetector((Context) mLauncher, this);
     }
 
-    public boolean onInterceptTouchEvent(MotionEvent ev) {
+    public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
         mPinchDetector.onTouchEvent(ev);
         return mPinchStarted;
     }
 
-    public boolean onTouchEvent(MotionEvent ev) {
+    public boolean onControllerTouchEvent(MotionEvent ev) {
         if (mPinchStarted) {
             if (ev.getPointerCount() > 2) {
                 // Using more than two fingers causes weird behavior, so just cancel the pinch.
diff --git a/src/com/android/launcher3/QsbContainerView.java b/src/com/android/launcher3/QsbContainerView.java
index 7d939a0..65711e1 100644
--- a/src/com/android/launcher3/QsbContainerView.java
+++ b/src/com/android/launcher3/QsbContainerView.java
@@ -109,6 +109,8 @@
             if (mWidgetInfo == null) {
                 // There is no search provider, just show the default widget.
                 return getDefaultView(inflater, container, false);
+            } else {
+                mWidgetInfo = LauncherAppWidgetProviderInfo.fromProviderInfo(launcher, mWidgetInfo);
             }
 
             SharedPreferences prefs = Utilities.getPrefs(launcher);
diff --git a/src/com/android/launcher3/ShortcutInfo.java b/src/com/android/launcher3/ShortcutInfo.java
index fb93743..ce06291 100644
--- a/src/com/android/launcher3/ShortcutInfo.java
+++ b/src/com/android/launcher3/ShortcutInfo.java
@@ -31,6 +31,7 @@
 import com.android.launcher3.compat.UserHandleCompat;
 import com.android.launcher3.compat.UserManagerCompat;
 import com.android.launcher3.folder.FolderIcon;
+import com.android.launcher3.graphics.LauncherIcons;
 import com.android.launcher3.shortcuts.ShortcutInfoCompat;
 
 /**
@@ -323,13 +324,13 @@
         IconCache cache = launcherAppState.getIconCache();
         Bitmap unbadgedBitmap = unbadgedDrawable == null
                 ? cache.getDefaultIcon(UserHandleCompat.myUserHandle())
-                : Utilities.createScaledBitmapWithoutShadow(unbadgedDrawable, context);
+                : LauncherIcons.createScaledBitmapWithoutShadow(unbadgedDrawable, context);
         setIcon(getBadgedIcon(unbadgedBitmap, shortcutInfo, cache, context));
     }
 
     protected Bitmap getBadgedIcon(Bitmap unbadgedBitmap, ShortcutInfoCompat shortcutInfo,
             IconCache cache, Context context) {
-        unbadgedBitmap = Utilities.addShadowToIcon(unbadgedBitmap);
+        unbadgedBitmap = LauncherIcons.addShadowToIcon(unbadgedBitmap);
         // Get the app info for the source activity.
         AppInfo appInfo = new AppInfo();
         appInfo.user = user;
@@ -338,9 +339,9 @@
             cache.getTitleAndIcon(appInfo, shortcutInfo.getActivityInfo(context), false);
         } catch (NullPointerException e) {
             // This may happen when we fail to load the activity info. Worst case ignore badging.
-            return Utilities.badgeIconForUser(unbadgedBitmap, user, context);
+            return LauncherIcons.badgeIconForUser(unbadgedBitmap, user, context);
         }
-        return Utilities.badgeWithBitmap(unbadgedBitmap, appInfo.iconBitmap, context);
+        return LauncherIcons.badgeWithBitmap(unbadgedBitmap, appInfo.iconBitmap, context);
     }
 
     /** Returns the ShortcutInfo id associated with the deep shortcut. */
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index b0e096a..b4f0a49 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -31,19 +31,11 @@
 import android.content.pm.ResolveInfo;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
-import android.database.Cursor;
 import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.Matrix;
 import android.graphics.Paint;
-import android.graphics.PaintFlagsDrawFilter;
 import android.graphics.Rect;
-import android.graphics.RectF;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.PaintDrawable;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.PowerManager;
@@ -62,11 +54,7 @@
 import android.view.accessibility.AccessibilityManager;
 import android.widget.Toast;
 
-import com.android.launcher3.compat.UserHandleCompat;
-import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.config.ProviderConfig;
-import com.android.launcher3.graphics.ShadowGenerator;
-import com.android.launcher3.util.IconNormalizer;
 
 import java.io.ByteArrayOutputStream;
 import java.io.Closeable;
@@ -90,19 +78,9 @@
 
     private static final String TAG = "Launcher.Utilities";
 
-    private static final Rect sOldBounds = new Rect();
-    private static final Canvas sCanvas = new Canvas();
-
     private static final Pattern sTrimPattern =
             Pattern.compile("^[\\s|\\p{javaSpaceChar}]*(.*)[\\s|\\p{javaSpaceChar}]*$");
 
-    static {
-        sCanvas.setDrawFilter(new PaintFlagsDrawFilter(Paint.DITHER_FLAG,
-                Paint.FILTER_BITMAP_FLAG));
-    }
-    static int sColors[] = { 0xffff0000, 0xff00ff00, 0xff0000ff };
-    static int sColorIndex = 0;
-
     private static final int[] sLoc0 = new int[2];
     private static final int[] sLoc1 = new int[2];
 
@@ -170,198 +148,6 @@
         return false;
     }
 
-    public static Bitmap createIconBitmap(Cursor c, int iconIndex, Context context) {
-        byte[] data = c.getBlob(iconIndex);
-        try {
-            return createIconBitmap(BitmapFactory.decodeByteArray(data, 0, data.length), context);
-        } catch (Exception e) {
-            return null;
-        }
-    }
-
-    /**
-     * Returns a bitmap suitable for the all apps view. If the package or the resource do not
-     * exist, it returns null.
-     */
-    public static Bitmap createIconBitmap(String packageName, String resourceName,
-            Context context) {
-        PackageManager packageManager = context.getPackageManager();
-        // the resource
-        try {
-            Resources resources = packageManager.getResourcesForApplication(packageName);
-            if (resources != null) {
-                final int id = resources.getIdentifier(resourceName, null, null);
-                return createIconBitmap(
-                        resources.getDrawableForDensity(id, LauncherAppState.getInstance()
-                                .getInvariantDeviceProfile().fillResIconDpi), context);
-            }
-        } catch (Exception e) {
-            // Icon not found.
-        }
-        return null;
-    }
-
-    private static int getIconBitmapSize() {
-        return LauncherAppState.getInstance().getInvariantDeviceProfile().iconBitmapSize;
-    }
-
-    /**
-     * Returns a bitmap which is of the appropriate size to be displayed as an icon
-     */
-    public static Bitmap createIconBitmap(Bitmap icon, Context context) {
-        final int iconBitmapSize = getIconBitmapSize();
-        if (iconBitmapSize == icon.getWidth() && iconBitmapSize == icon.getHeight()) {
-            return icon;
-        }
-        return createIconBitmap(new BitmapDrawable(context.getResources(), icon), context);
-    }
-
-    /**
-     * Returns a bitmap suitable for the all apps view. The icon is badged for {@param user}.
-     * The bitmap is also visually normalized with other icons.
-     */
-    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
-    public static Bitmap createBadgedIconBitmap(
-            Drawable icon, UserHandleCompat user, Context context) {
-        float scale = FeatureFlags.LAUNCHER3_DISABLE_ICON_NORMALIZATION ?
-                1 : IconNormalizer.getInstance().getScale(icon, null);
-        Bitmap bitmap = createIconBitmap(icon, context, scale);
-        return badgeIconForUser(bitmap, user, context);
-    }
-
-    /**
-     * Badges the provided icon with the user badge if required.
-     */
-    public static Bitmap badgeIconForUser(Bitmap icon,  UserHandleCompat user, Context context) {
-        if (Utilities.ATLEAST_LOLLIPOP && user != null
-                && !UserHandleCompat.myUserHandle().equals(user)) {
-            BitmapDrawable drawable = new FixedSizeBitmapDrawable(icon);
-            Drawable badged = context.getPackageManager().getUserBadgedIcon(
-                    drawable, user.getUser());
-            if (badged instanceof BitmapDrawable) {
-                return ((BitmapDrawable) badged).getBitmap();
-            } else {
-                return createIconBitmap(badged, context);
-            }
-        } else {
-            return icon;
-        }
-    }
-
-    /**
-     * Creates a normalized bitmap suitable for the all apps view. The bitmap is also visually
-     * normalized with other icons and has enough spacing to add shadow.
-     */
-    public static Bitmap createScaledBitmapWithoutShadow(Drawable icon, Context context) {
-        RectF iconBounds = new RectF();
-        float scale = FeatureFlags.LAUNCHER3_DISABLE_ICON_NORMALIZATION ?
-                1 : IconNormalizer.getInstance().getScale(icon, iconBounds);
-        scale = Math.min(scale, ShadowGenerator.getScaleForBounds(iconBounds));
-        return createIconBitmap(icon, context, scale);
-    }
-
-    /**
-     * Adds a shadow to the provided icon. It assumes that the icon has already been scaled using
-     * {@link #createScaledBitmapWithoutShadow(Drawable, Context)}
-     */
-    public static Bitmap addShadowToIcon(Bitmap icon) {
-        return ShadowGenerator.getInstance().recreateIcon(icon);
-    }
-
-    /**
-     * Adds the {@param badge} on top of {@param srcTgt} using the badge dimensions.
-     */
-    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
-    public static Bitmap badgeWithBitmap(Bitmap srcTgt, Bitmap badge, Context context) {
-        int badgeSize = context.getResources().getDimensionPixelSize(R.dimen.profile_badge_size);
-        synchronized (sCanvas) {
-            sCanvas.setBitmap(srcTgt);
-            sCanvas.drawBitmap(badge, new Rect(0, 0, badge.getWidth(), badge.getHeight()),
-                    new Rect(srcTgt.getWidth() - badgeSize,
-                            srcTgt.getHeight() - badgeSize, srcTgt.getWidth(), srcTgt.getHeight()),
-                    new Paint(Paint.FILTER_BITMAP_FLAG));
-            sCanvas.setBitmap(null);
-        }
-        return srcTgt;
-    }
-
-    /**
-     * Returns a bitmap suitable for the all apps view.
-     */
-    public static Bitmap createIconBitmap(Drawable icon, Context context) {
-        return createIconBitmap(icon, context, 1.0f /* scale */);
-    }
-
-    /**
-     * @param scale the scale to apply before drawing {@param icon} on the canvas
-     */
-    public static Bitmap createIconBitmap(Drawable icon, Context context, float scale) {
-        synchronized (sCanvas) {
-            final int iconBitmapSize = getIconBitmapSize();
-
-            int width = iconBitmapSize;
-            int height = iconBitmapSize;
-
-            if (icon instanceof PaintDrawable) {
-                PaintDrawable painter = (PaintDrawable) icon;
-                painter.setIntrinsicWidth(width);
-                painter.setIntrinsicHeight(height);
-            } else if (icon instanceof BitmapDrawable) {
-                // Ensure the bitmap has a density.
-                BitmapDrawable bitmapDrawable = (BitmapDrawable) icon;
-                Bitmap bitmap = bitmapDrawable.getBitmap();
-                if (bitmap != null && bitmap.getDensity() == Bitmap.DENSITY_NONE) {
-                    bitmapDrawable.setTargetDensity(context.getResources().getDisplayMetrics());
-                }
-            }
-            int sourceWidth = icon.getIntrinsicWidth();
-            int sourceHeight = icon.getIntrinsicHeight();
-            if (sourceWidth > 0 && sourceHeight > 0) {
-                // Scale the icon proportionally to the icon dimensions
-                final float ratio = (float) sourceWidth / sourceHeight;
-                if (sourceWidth > sourceHeight) {
-                    height = (int) (width / ratio);
-                } else if (sourceHeight > sourceWidth) {
-                    width = (int) (height * ratio);
-                }
-            }
-
-            // no intrinsic size --> use default size
-            int textureWidth = iconBitmapSize;
-            int textureHeight = iconBitmapSize;
-
-            final Bitmap bitmap = Bitmap.createBitmap(textureWidth, textureHeight,
-                    Bitmap.Config.ARGB_8888);
-            final Canvas canvas = sCanvas;
-            canvas.setBitmap(bitmap);
-
-            final int left = (textureWidth-width) / 2;
-            final int top = (textureHeight-height) / 2;
-
-            @SuppressWarnings("all") // suppress dead code warning
-            final boolean debug = false;
-            if (debug) {
-                // draw a big box for the icon for debugging
-                canvas.drawColor(sColors[sColorIndex]);
-                if (++sColorIndex >= sColors.length) sColorIndex = 0;
-                Paint debugPaint = new Paint();
-                debugPaint.setColor(0xffcccc00);
-                canvas.drawRect(left, top, left+width, top+height, debugPaint);
-            }
-
-            sOldBounds.set(icon.getBounds());
-            icon.setBounds(left, top, left+width, top+height);
-            canvas.save(Canvas.MATRIX_SAVE_FLAG);
-            canvas.scale(scale, scale, textureWidth / 2, textureHeight / 2);
-            icon.draw(canvas);
-            canvas.restore();
-            icon.setBounds(sOldBounds);
-            canvas.setBitmap(null);
-
-            return bitmap;
-        }
-    }
-
     /**
      * Given a coordinate relative to the descendant, find the coordinate in a parent view's
      * coordinates.
@@ -880,28 +666,6 @@
         return c == null || c.isEmpty();
     }
 
-    /**
-     * An extension of {@link BitmapDrawable} which returns the bitmap pixel size as intrinsic size.
-     * This allows the badging to be done based on the action bitmap size rather than
-     * the scaled bitmap size.
-     */
-    private static class FixedSizeBitmapDrawable extends BitmapDrawable {
-
-        public FixedSizeBitmapDrawable(Bitmap bitmap) {
-            super(null, bitmap);
-        }
-
-        @Override
-        public int getIntrinsicHeight() {
-            return getBitmap().getWidth();
-        }
-
-        @Override
-        public int getIntrinsicWidth() {
-            return getBitmap().getWidth();
-        }
-    }
-
     public static int getColorAccent(Context context) {
         TypedArray ta = context.obtainStyledAttributes(new int[]{android.R.attr.colorAccent});
         int colorAccent = ta.getColor(0, 0);
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index fc35064..315e23a 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -28,7 +28,6 @@
 import android.app.WallpaperManager;
 import android.appwidget.AppWidgetHostView;
 import android.appwidget.AppWidgetProviderInfo;
-import android.content.ComponentName;
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
@@ -74,6 +73,7 @@
 import com.android.launcher3.folder.Folder;
 import com.android.launcher3.folder.FolderIcon;
 import com.android.launcher3.graphics.DragPreviewProvider;
+import com.android.launcher3.shortcuts.DeepShortcutsContainer;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
 import com.android.launcher3.util.ItemInfoMatcher;
@@ -737,11 +737,7 @@
         addFullScreenPage(customScreen);
 
         // Update the custom content hint
-        if (mRestorePage != INVALID_RESTORE_PAGE) {
-            mRestorePage = mRestorePage + 1;
-        } else {
-            setCurrentPage(getCurrentPage() + 1);
-        }
+        setCurrentPage(getCurrentPage() + 1);
     }
 
     public void removeCustomContentPage() {
@@ -762,11 +758,7 @@
         mCustomContentCallbacks = null;
 
         // Update the custom content hint
-        if (mRestorePage != INVALID_RESTORE_PAGE) {
-            mRestorePage = mRestorePage - 1;
-        } else {
-            setCurrentPage(getCurrentPage() - 1);
-        }
+        setCurrentPage(getCurrentPage() - 1);
     }
 
     public void addToCustomContentPage(View customContent, CustomContentCallbacks callbacks,
@@ -1756,7 +1748,7 @@
     }
 
     public boolean isOnOrMovingToCustomContent() {
-        return hasCustomContent() && getNextPage() == 0 && mRestorePage == INVALID_RESTORE_PAGE;
+        return hasCustomContent() && getNextPage() == 0;
     }
 
     private void updateStateForCustomContent(int screenCenter) {
@@ -2009,7 +2001,7 @@
 
     public void exitWidgetResizeMode() {
         DragLayer dragLayer = mLauncher.getDragLayer();
-        dragLayer.clearAllResizeFrames();
+        dragLayer.clearResizeFrame();
     }
 
     @Override
@@ -2348,6 +2340,13 @@
             mDragSourceInternal = (ShortcutAndWidgetContainer) child.getParent();
         }
 
+        if (child instanceof BubbleTextView) {
+            DeepShortcutsContainer dsc = DeepShortcutsContainer.showForIcon((BubbleTextView) child);
+            if (dsc != null) {
+                dragOptions.preDragCondition = dsc.createPreDragCondition();
+            }
+        }
+
         DragView dv = mDragController.startDrag(b, dragLayerX, dragLayerY, source,
                 dragObject, dragVisualizeOffset, dragRect, scale, dragOptions);
         dv.setIntrinsicIconScaleFactor(source.getIntrinsicIconScaleFactor());
@@ -2710,7 +2709,7 @@
                                 public void run() {
                                     if (!isPageMoving() && !mIsSwitchingState) {
                                         DragLayer dragLayer = mLauncher.getDragLayer();
-                                        dragLayer.addResizeFrame(info, hostView, cellLayout);
+                                        dragLayer.addResizeFrame(hostView, cellLayout);
                                     }
                                 }
                             };
@@ -4000,63 +3999,34 @@
         for (final CellLayout layoutParent: cellLayouts) {
             final ViewGroup layout = layoutParent.getShortcutsAndWidgets();
 
-            final HashMap<ItemInfo, View> children = new HashMap<>();
+            LongArrayMap<View> idToViewMap = new LongArrayMap<>();
+            ArrayList<ItemInfo> items = new ArrayList<>();
             for (int j = 0; j < layout.getChildCount(); j++) {
                 final View view = layout.getChildAt(j);
-                children.put((ItemInfo) view.getTag(), view);
+                if (view.getTag() instanceof ItemInfo) {
+                    ItemInfo item = (ItemInfo) view.getTag();
+                    items.add(item);
+                    idToViewMap.put(item.id, view);
+                }
             }
 
-            final ArrayList<View> childrenToRemove = new ArrayList<>();
-            final HashMap<FolderInfo, ArrayList<ShortcutInfo>> folderAppsToRemove = new HashMap<>();
-            LauncherModel.ItemInfoFilter filter = new LauncherModel.ItemInfoFilter() {
-                @Override
-                public boolean filterItem(ItemInfo parent, ItemInfo info,
-                        ComponentName cn) {
-                    if (parent instanceof FolderInfo) {
-                        if (matcher.matches(info, cn)) {
-                            FolderInfo folder = (FolderInfo) parent;
-                            ArrayList<ShortcutInfo> appsToRemove;
-                            if (folderAppsToRemove.containsKey(folder)) {
-                                appsToRemove = folderAppsToRemove.get(folder);
-                            } else {
-                                appsToRemove = new ArrayList<ShortcutInfo>();
-                                folderAppsToRemove.put(folder, appsToRemove);
-                            }
-                            appsToRemove.add((ShortcutInfo) info);
-                            return true;
-                        }
-                    } else {
-                        if (matcher.matches(info, cn)) {
-                            childrenToRemove.add(children.get(info));
-                            return true;
-                        }
+            for (ItemInfo itemToRemove : matcher.filterItemInfos(items)) {
+                View child = idToViewMap.get(itemToRemove.id);
+
+                if (child != null) {
+                    // Note: We can not remove the view directly from CellLayoutChildren as this
+                    // does not re-mark the spaces as unoccupied.
+                    layoutParent.removeViewInLayout(child);
+                    if (child instanceof DropTarget) {
+                        mDragController.removeDropTarget((DropTarget) child);
                     }
-                    return false;
+                } else if (itemToRemove.container >= 0) {
+                    // The item may belong to a folder.
+                    View parent = idToViewMap.get(itemToRemove.container);
+                    if (parent != null) {
+                        ((FolderInfo) parent.getTag()).remove((ShortcutInfo) itemToRemove, false);
+                    }
                 }
-            };
-            LauncherModel.filterItemInfos(children.keySet(), filter);
-
-            // Remove all the apps from their folders
-            for (FolderInfo folder : folderAppsToRemove.keySet()) {
-                ArrayList<ShortcutInfo> appsToRemove = folderAppsToRemove.get(folder);
-                for (ShortcutInfo info : appsToRemove) {
-                    folder.remove(info, false);
-                }
-            }
-
-            // Remove all the other children
-            for (View child : childrenToRemove) {
-                // Note: We can not remove the view directly from CellLayoutChildren as this
-                // does not re-mark the spaces as unoccupied.
-                layoutParent.removeViewInLayout(child);
-                if (child instanceof DropTarget) {
-                    mDragController.removeDropTarget((DropTarget) child);
-                }
-            }
-
-            if (childrenToRemove.size() > 0) {
-                layout.requestLayout();
-                layout.invalidate();
             }
         }
 
@@ -4157,8 +4127,9 @@
     public void removeAbandonedPromise(String packageName, UserHandleCompat user) {
         HashSet<String> packages = new HashSet<>(1);
         packages.add(packageName);
-        LauncherModel.deletePackageFromDatabase(mLauncher, packageName, user);
-        removeItemsByMatcher(ItemInfoMatcher.ofPackages(packages, user));
+        ItemInfoMatcher matcher = ItemInfoMatcher.ofPackages(packages, user);
+        LauncherModel.deleteItemsFromDatabase(mLauncher, matcher);
+        removeItemsByMatcher(matcher);
     }
 
     public void updateRestoreItems(final HashSet<ItemInfo> updates) {
@@ -4335,7 +4306,6 @@
                 @Override
                 public boolean evaluate(ItemInfo info, View view) {
                     if (view instanceof PendingAppWidgetHostView && mInfos.contains(info)) {
-                        PendingAppWidgetHostView hostView = (PendingAppWidgetHostView) view;
                         mLauncher.removeItem(view, info, false /* deleteFromDb */);
                         mLauncher.bindAppWidget((LauncherAppWidgetInfo) info);
                     }
diff --git a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
index 173aad0..439e314 100644
--- a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
+++ b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
@@ -57,7 +57,7 @@
     protected static final int MOVE = R.id.action_move;
     protected static final int MOVE_TO_WORKSPACE = R.id.action_move_to_workspace;
     protected static final int RESIZE = R.id.action_resize;
-    protected static final int DEEP_SHORTCUTS = R.id.action_deep_shortcuts;
+    public static final int DEEP_SHORTCUTS = R.id.action_deep_shortcuts;
 
     public enum DragType {
         ICON,
@@ -100,14 +100,17 @@
     @Override
     public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
         super.onInitializeAccessibilityNodeInfo(host, info);
-        addActions(host, info);
+        addSupportedActions(host, info, false);
     }
 
-    protected void addActions(View host, AccessibilityNodeInfo info) {
+    public void addSupportedActions(View host, AccessibilityNodeInfo info, boolean fromKeyboard) {
         if (!(host.getTag() instanceof ItemInfo)) return;
         ItemInfo item = (ItemInfo) host.getTag();
 
-        if (host instanceof BubbleTextView && ((BubbleTextView) host).hasDeepShortcuts()) {
+        // If the request came from keyboard, do not add custom shortcuts as that is already
+        // exposed as a direct shortcut
+        if (!fromKeyboard && host instanceof BubbleTextView
+                && ((BubbleTextView) host).hasDeepShortcuts()) {
             info.addAction(mActions.get(DEEP_SHORTCUTS));
         }
 
@@ -121,9 +124,10 @@
             info.addAction(mActions.get(INFO));
         }
 
-        if ((item instanceof ShortcutInfo)
+        // Do not add move actions for keyboard request as this uses virtual nodes.
+        if (!fromKeyboard && ((item instanceof ShortcutInfo)
                 || (item instanceof LauncherAppWidgetInfo)
-                || (item instanceof FolderInfo)) {
+                || (item instanceof FolderInfo))) {
             info.addAction(mActions.get(MOVE));
 
             if (item.container >= 0) {
diff --git a/src/com/android/launcher3/accessibility/ShortcutMenuAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/ShortcutMenuAccessibilityDelegate.java
index 0baa8f3..5baa7b5 100644
--- a/src/com/android/launcher3/accessibility/ShortcutMenuAccessibilityDelegate.java
+++ b/src/com/android/launcher3/accessibility/ShortcutMenuAccessibilityDelegate.java
@@ -40,8 +40,10 @@
     }
 
     @Override
-    protected void addActions(View host, AccessibilityNodeInfo info) {
-        info.addAction(mActions.get(ADD_TO_WORKSPACE));
+    public void addSupportedActions(View host, AccessibilityNodeInfo info, boolean fromKeyboard) {
+        if ((host.getParent() instanceof DeepShortcutView)) {
+            info.addAction(mActions.get(ADD_TO_WORKSPACE));
+        }
     }
 
     @Override
diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java
index 5892787..77e8ad1 100644
--- a/src/com/android/launcher3/allapps/AllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java
@@ -15,10 +15,8 @@
  */
 package com.android.launcher3.allapps;
 
-import android.annotation.SuppressLint;
 import android.content.Context;
 import android.content.res.Resources;
-import android.graphics.Point;
 import android.graphics.Rect;
 import android.support.v7.widget.RecyclerView;
 import android.text.Selection;
@@ -31,18 +29,17 @@
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.view.View;
-import android.view.ViewConfiguration;
 import android.view.ViewGroup;
 
 import com.android.launcher3.AppInfo;
 import com.android.launcher3.BaseContainerView;
-import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.CellLayout;
 import com.android.launcher3.DeleteDropTarget;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.DragSource;
 import com.android.launcher3.DropTarget;
 import com.android.launcher3.ExtendedEditText;
+import com.android.launcher3.Insettable;
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherTransitionable;
@@ -50,11 +47,11 @@
 import com.android.launcher3.Utilities;
 import com.android.launcher3.Workspace;
 import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.dragndrop.DragController;
 import com.android.launcher3.dragndrop.DragOptions;
 import com.android.launcher3.folder.Folder;
 import com.android.launcher3.graphics.TintedDrawableSpan;
 import com.android.launcher3.keyboard.FocusedItemDecorator;
-import com.android.launcher3.shortcuts.DeepShortcutsContainer;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
 import com.android.launcher3.util.ComponentKey;
 
@@ -134,7 +131,8 @@
  * The all apps view container.
  */
 public class AllAppsContainerView extends BaseContainerView implements DragSource,
-        LauncherTransitionable, View.OnLongClickListener, AllAppsSearchBarController.Callbacks {
+        LauncherTransitionable, View.OnLongClickListener, AllAppsSearchBarController.Callbacks,
+        Insettable {
 
     private static final int MIN_ROWS_IN_MERGED_SECTION_PHONE = 3;
     private static final int MAX_NUM_MERGES_PHONE = 2;
@@ -145,25 +143,18 @@
     private final RecyclerView.LayoutManager mLayoutManager;
     private final RecyclerView.ItemDecoration mItemDecoration;
 
-    // The computed bounds of the container
-    private final Rect mContentBounds = new Rect();
-
     private AllAppsRecyclerView mAppsRecyclerView;
     private AllAppsSearchBarController mSearchBarController;
 
     private View mSearchContainer;
     private ExtendedEditText mSearchInput;
     private HeaderElevationController mElevationController;
-    private int mSearchContainerOffsetTop;
 
     private SpannableStringBuilder mSearchQueryBuilder = null;
 
     private int mSectionNamesMargin;
     private int mNumAppsPerRow;
     private int mNumPredictedAppsPerRow;
-    private int mRecyclerViewBottomPadding;
-    // This coordinate is relative to this container view
-    private final Point mBoundsCheckLastTouchDownPos = new Point(-1, -1);
 
     public AllAppsContainerView(Context context) {
         this(context, null);
@@ -184,14 +175,6 @@
         mApps.setAdapter(mAdapter);
         mLayoutManager = mAdapter.getLayoutManager();
         mItemDecoration = mAdapter.getItemDecoration();
-        DeviceProfile grid = mLauncher.getDeviceProfile();
-        if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP && !grid.isVerticalBarLayout()) {
-            mRecyclerViewBottomPadding = 0;
-            setPadding(0, 0, 0, 0);
-        } else {
-            mRecyclerViewBottomPadding =
-                    res.getDimensionPixelSize(R.dimen.all_apps_list_bottom_padding);
-        }
         mSearchQueryBuilder = new SpannableStringBuilder();
         Selection.setSelection(mSearchQueryBuilder, 0);
     }
@@ -340,9 +323,6 @@
                 0, 1, Spannable.SPAN_EXCLUSIVE_INCLUSIVE);
         mSearchInput.setHint(spanned);
 
-        mSearchContainerOffsetTop = getResources().getDimensionPixelSize(
-                R.dimen.all_apps_search_bar_margin_top);
-
         mElevationController = Utilities.ATLEAST_LOLLIPOP
                 ? new HeaderElevationController.ControllerVL(mSearchContainer)
                 : new HeaderElevationController.ControllerV16(mSearchContainer);
@@ -370,6 +350,19 @@
             getContentView().setVisibility(View.VISIBLE);
             getContentView().setBackground(null);
         }
+
+        int maxScrollBarWidth = mAppsRecyclerView.getMaxScrollbarWidth();
+        int startInset = Math.max(mSectionNamesMargin, maxScrollBarWidth);
+        if (Utilities.isRtl(getResources())) {
+            mAppsRecyclerView.setPadding(maxScrollBarWidth, 0, startInset, 0);
+        } else {
+            mAppsRecyclerView.setPadding(startInset, 0, maxScrollBarWidth, 0);
+        }
+    }
+
+    @Override
+    public View getTouchDelegateTargetView() {
+        return mAppsRecyclerView;
     }
 
     @Override
@@ -377,11 +370,6 @@
 
     @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-        int widthPx = MeasureSpec.getSize(widthMeasureSpec);
-        int heightPx = MeasureSpec.getSize(heightMeasureSpec);
-        updatePaddingsAndMargins(widthPx, heightPx);
-        mContentBounds.set(mContainerPaddingLeft, 0, widthPx - mContainerPaddingRight, heightPx);
-
         DeviceProfile grid = mLauncher.getDeviceProfile();
         grid.updateAppsViewNumCols();
         if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP) {
@@ -393,17 +381,12 @@
                 mAppsRecyclerView.setNumAppsPerRow(grid, mNumAppsPerRow);
                 mAdapter.setNumAppsPerRow(mNumAppsPerRow);
                 mApps.setNumAppsPerRow(mNumAppsPerRow, mNumPredictedAppsPerRow, new FullMergeAlgorithm());
-                if (mNumAppsPerRow > 0) {
-                    int rvPadding = mAppsRecyclerView.getPaddingStart(); // Assumes symmetry
-                    final int thumbMaxWidth =
-                            getResources().getDimensionPixelSize(
-                                    R.dimen.container_fastscroll_thumb_max_width);
-                    mSearchContainer.setPadding(
-                            rvPadding - mContainerPaddingLeft + thumbMaxWidth,
-                            mSearchContainer.getPaddingTop(),
-                            rvPadding - mContainerPaddingRight + thumbMaxWidth,
-                            mSearchContainer.getPaddingBottom());
-                }
+            }
+            if (!grid.isVerticalBarLayout()) {
+                MarginLayoutParams searchContainerLp =
+                        (MarginLayoutParams) mSearchContainer.getLayoutParams();
+                searchContainerLp.height = grid.hotseatBarHeightPx;
+                mSearchContainer.setLayoutParams(searchContainerLp);
             }
             super.onMeasure(widthMeasureSpec, heightMeasureSpec);
             return;
@@ -437,73 +420,6 @@
         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
     }
 
-    /**
-     * Update the background and padding of the Apps view and children.  Instead of insetting the
-     * container view, we inset the background and padding of the recycler view to allow for the
-     * recycler view to handle touch events (for fast scrolling) all the way to the edge.
-     */
-    private void updatePaddingsAndMargins(int widthPx, int heightPx) {
-        Rect bgPadding = new Rect();
-        getRevealView().getBackground().getPadding(bgPadding);
-
-        mAppsRecyclerView.updateBackgroundPadding(bgPadding);
-        mAdapter.updateBackgroundPadding(bgPadding);
-        mElevationController.updateBackgroundPadding(bgPadding);
-
-        // Pad the recycler view by the background padding plus the start margin (for the section
-        // names)
-        int maxScrollBarWidth = mAppsRecyclerView.getMaxScrollbarWidth();
-        int startInset = Math.max(mSectionNamesMargin, maxScrollBarWidth);
-        if (Utilities.isRtl(getResources())) {
-            mAppsRecyclerView.setPadding(bgPadding.left + maxScrollBarWidth, 0, bgPadding.right
-                    + startInset, mRecyclerViewBottomPadding);
-        } else {
-            mAppsRecyclerView.setPadding(bgPadding.left + startInset, 0, bgPadding.right +
-                    maxScrollBarWidth, mRecyclerViewBottomPadding);
-        }
-
-        MarginLayoutParams lp = (MarginLayoutParams) mSearchContainer.getLayoutParams();
-        lp.leftMargin = bgPadding.left;
-        lp.rightMargin = bgPadding.right;
-
-        // Clip the view to the left and right edge of the background to
-        // to prevent shadows from rendering beyond the edges
-        final Rect newClipBounds = new Rect(
-                bgPadding.left, 0, widthPx - bgPadding.right, heightPx);
-        setClipBounds(newClipBounds);
-
-        // Allow the overscroll effect to reach the edges of the view
-        mAppsRecyclerView.setClipToPadding(false);
-
-        DeviceProfile grid = mLauncher.getDeviceProfile();
-        if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP) {
-            if (!grid.isVerticalBarLayout()) {
-                MarginLayoutParams mlp = (MarginLayoutParams) mAppsRecyclerView.getLayoutParams();
-
-                Rect insets = mLauncher.getDragLayer().getInsets();
-                getContentView().setPadding(0, 0, 0, 0);
-                int height = insets.top + grid.hotseatCellHeightPx;
-
-                mlp.topMargin = height;
-                mAppsRecyclerView.setLayoutParams(mlp);
-
-                mSearchContainer.setPadding(
-                        mSearchContainer.getPaddingLeft(),
-                        insets.top + mSearchContainerOffsetTop,
-                        mSearchContainer.getPaddingRight(),
-                        mSearchContainer.getPaddingBottom());
-                lp.height = height;
-
-                View navBarBg = findViewById(R.id.nav_bar_bg);
-                ViewGroup.LayoutParams params = navBarBg.getLayoutParams();
-                params.height = insets.bottom;
-                navBarBg.setLayoutParams(params);
-                navBarBg.setVisibility(View.VISIBLE);
-            }
-        }
-        mSearchContainer.setLayoutParams(lp);
-    }
-
     @Override
     public boolean dispatchKeyEvent(KeyEvent event) {
         // Determine if the key event was actual text, if so, focus the search bar and then dispatch
@@ -526,18 +442,7 @@
     }
 
     @Override
-    public boolean onInterceptTouchEvent(MotionEvent ev) {
-        return handleTouchEvent(ev);
-    }
-
-    @SuppressLint("ClickableViewAccessibility")
-    @Override
-    public boolean onTouchEvent(MotionEvent ev) {
-        return handleTouchEvent(ev);
-    }
-
-    @Override
-    public boolean onLongClick(View v) {
+    public boolean onLongClick(final View v) {
         // Return early if this is not initiated from a touch
         if (!v.isInTouchMode()) return false;
         // When we have exited all apps or are in transition, disregard long clicks
@@ -549,22 +454,20 @@
         if (mLauncher.getDragController().isDragging()) return false;
 
         // Start the drag
-        DragOptions dragOptions = new DragOptions();
-        if (v instanceof BubbleTextView) {
-            final BubbleTextView icon = (BubbleTextView) v;
-            if (icon.hasDeepShortcuts()) {
-                DeepShortcutsContainer dsc = DeepShortcutsContainer.showForIcon(icon);
-                if (dsc != null) {
-                    dragOptions.deferDragCondition = dsc.createDeferDragCondition(new Runnable() {
-                        @Override
-                        public void run() {
-                            icon.setVisibility(VISIBLE);
-                        }
-                    });
-                }
+        final DragController dragController = mLauncher.getDragController();
+        dragController.addDragListener(new DragController.DragListener() {
+            @Override
+            public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) {
+                v.setVisibility(INVISIBLE);
             }
-        }
-        mLauncher.getWorkspace().beginDragShared(v, this, dragOptions);
+
+            @Override
+            public void onDragEnd() {
+                v.setVisibility(VISIBLE);
+                dragController.removeDragListener(this);
+            }
+        });
+        mLauncher.getWorkspace().beginDragShared(v, this, new DragOptions());
         if (FeatureFlags.LAUNCHER3_LEGACY_WORKSPACE_DND) {
             // Enter spring loaded mode (the new workspace does this in
             // onDragStart(), so we don't want to do it here)
@@ -619,7 +522,7 @@
         // target layout we were dropping on.
         if (!success) {
             boolean showOutOfSpaceMessage = false;
-            if (target instanceof Workspace && !mLauncher.getDragController().isDeferringDrag()) {
+            if (target instanceof Workspace) {
                 int currentScreen = mLauncher.getCurrentWorkspaceScreen();
                 Workspace workspace = (Workspace) target;
                 CellLayout layout = (CellLayout) workspace.getChildAt(currentScreen);
@@ -660,55 +563,6 @@
         }
     }
 
-    /**
-     * Handles the touch events to dismiss all apps when clicking outside the bounds of the
-     * recycler view.
-     */
-    private boolean handleTouchEvent(MotionEvent ev) {
-        DeviceProfile grid = mLauncher.getDeviceProfile();
-        int x = (int) ev.getX();
-        int y = (int) ev.getY();
-
-        switch (ev.getAction()) {
-            case MotionEvent.ACTION_DOWN:
-                if (!mContentBounds.isEmpty()) {
-                    // Outset the fixed bounds and check if the touch is outside all apps
-                    Rect tmpRect = new Rect(mContentBounds);
-                    tmpRect.inset(-grid.allAppsIconSizePx / 2, 0);
-                    if (ev.getX() < tmpRect.left || ev.getX() > tmpRect.right) {
-                        mBoundsCheckLastTouchDownPos.set(x, y);
-                        return true;
-                    }
-                } else {
-                    // Check if the touch is outside all apps
-                    if (ev.getX() < getPaddingLeft() ||
-                            ev.getX() > (getWidth() - getPaddingRight())) {
-                        mBoundsCheckLastTouchDownPos.set(x, y);
-                        return true;
-                    }
-                }
-                break;
-            case MotionEvent.ACTION_UP:
-                if (mBoundsCheckLastTouchDownPos.x > -1) {
-                    ViewConfiguration viewConfig = ViewConfiguration.get(getContext());
-                    float dx = ev.getX() - mBoundsCheckLastTouchDownPos.x;
-                    float dy = ev.getY() - mBoundsCheckLastTouchDownPos.y;
-                    float distance = (float) Math.hypot(dx, dy);
-                    if (distance < viewConfig.getScaledTouchSlop()) {
-                        // The background was clicked, so just go home
-                        Launcher launcher = (Launcher) getContext();
-                        launcher.showWorkspace(true);
-                        return true;
-                    }
-                }
-                // Fall through
-            case MotionEvent.ACTION_CANCEL:
-                mBoundsCheckLastTouchDownPos.set(-1, -1);
-                break;
-        }
-        return false;
-    }
-
     @Override
     public void onSearchResult(String query, ArrayList<ComponentKey> apps) {
         if (apps != null) {
@@ -739,4 +593,22 @@
     public boolean shouldRestoreImeState() {
         return !TextUtils.isEmpty(mSearchInput.getText());
     }
+
+    @Override
+    public void setInsets(Rect insets) {
+        DeviceProfile grid = mLauncher.getDeviceProfile();
+        if (grid.isVerticalBarLayout()) {
+            ViewGroup.MarginLayoutParams mlp = (MarginLayoutParams) getLayoutParams();
+            mlp.leftMargin = insets.left;
+            mlp.topMargin = insets.top;
+            mlp.rightMargin = insets.right;
+            setLayoutParams(mlp);
+        } else {
+            View navBarBg = findViewById(R.id.nav_bar_bg);
+            ViewGroup.LayoutParams navBarBgLp = navBarBg.getLayoutParams();
+            navBarBgLp.height = insets.bottom;
+            navBarBg.setLayoutParams(navBarBgLp);
+            navBarBg.setVisibility(View.VISIBLE);
+        }
+    }
 }
diff --git a/src/com/android/launcher3/allapps/AllAppsRecyclerViewContainerView.java b/src/com/android/launcher3/allapps/AllAppsRecyclerViewContainerView.java
index 1d5b209..cac388c 100644
--- a/src/com/android/launcher3/allapps/AllAppsRecyclerViewContainerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsRecyclerViewContainerView.java
@@ -21,6 +21,7 @@
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.FrameLayout;
+import android.widget.RelativeLayout;
 
 import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.BubbleTextView.BubbleTextShadowHandler;
@@ -33,7 +34,7 @@
  * A container for RecyclerView to allow for the click shadow view to be shown behind an icon that
  * is launching.
  */
-public class AllAppsRecyclerViewContainerView extends FrameLayout
+public class AllAppsRecyclerViewContainerView extends RelativeLayout
         implements BubbleTextShadowHandler {
 
     private final ClickShadowView mTouchFeedbackView;
diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
index b129bb0..adfad08 100644
--- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java
+++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
@@ -108,7 +108,7 @@
     }
 
     @Override
-    public boolean onInterceptTouchEvent(MotionEvent ev) {
+    public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
         if (ev.getAction() == MotionEvent.ACTION_DOWN) {
             mNoIntercept = false;
             if (!mLauncher.isAllAppsVisible() && mLauncher.getWorkspace().workspaceInModalState()) {
@@ -174,7 +174,7 @@
     }
 
     @Override
-    public boolean onTouchEvent(MotionEvent ev) {
+    public boolean onControllerTouchEvent(MotionEvent ev) {
         return mDetector.onTouchEvent(ev);
     }
 
diff --git a/src/com/android/launcher3/dragndrop/DragController.java b/src/com/android/launcher3/dragndrop/DragController.java
index 6eb7dcc..19a47e8 100644
--- a/src/com/android/launcher3/dragndrop/DragController.java
+++ b/src/com/android/launcher3/dragndrop/DragController.java
@@ -131,7 +131,7 @@
     protected final int mFlingToDeleteThresholdVelocity;
     private VelocityTracker mVelocityTracker;
 
-    private boolean mIsDragDeferred;
+    private boolean mIsInPreDrag;
 
     /**
      * Interface to receive notifications when a drag starts or stops
@@ -230,13 +230,14 @@
 
         mDragObject = new DropTarget.DragObject();
 
-        mIsDragDeferred = !mOptions.deferDragCondition.shouldStartDeferredDrag(0);
+        mIsInPreDrag = mOptions.preDragCondition != null
+                && !mOptions.preDragCondition.shouldStartDrag(0);
 
         final Resources res = mLauncher.getResources();
         final float scaleDps = FeatureFlags.LAUNCHER3_LEGACY_WORKSPACE_DND
                 ? res.getDimensionPixelSize(R.dimen.dragViewScale)
-                : mIsDragDeferred
-                    ? res.getDimensionPixelSize(R.dimen.deferred_drag_view_scale)
+                : mIsInPreDrag
+                    ? res.getDimensionPixelSize(R.dimen.pre_drag_view_scale)
                     : 0f;
         final DragView dragView = mDragObject.dragView = new DragView(mLauncher, b, registrationX,
                 registrationY, initialDragViewScale, scaleDps);
@@ -271,10 +272,10 @@
         dragView.show(mMotionDownX, mMotionDownY);
         mDistanceSinceScroll = 0;
 
-        if (!mIsDragDeferred) {
-            startDeferredDrag();
-        } else {
-            mOptions.deferDragCondition.onDeferredDragStart();
+        if (!mIsInPreDrag) {
+            callOnDragStart();
+        } else if (mOptions.preDragCondition != null) {
+            mOptions.preDragCondition.onPreDragStart();
         }
 
         mLastTouch[0] = mMotionDownX;
@@ -284,16 +285,18 @@
         return dragView;
     }
 
-    public boolean isDeferringDrag() {
-        return mIsDragDeferred;
-    }
-
-    public void startDeferredDrag() {
+    private void callOnDragStart() {
         for (DragListener listener : new ArrayList<>(mListeners)) {
             listener.onDragStart(mDragObject, mOptions);
         }
-        mOptions.deferDragCondition.onDragStart();
-        mIsDragDeferred = false;
+        if (mOptions.preDragCondition != null) {
+            mOptions.preDragCondition.onPreDragEnd(true /* dragStarted*/);
+        }
+        mIsInPreDrag = false;
+    }
+
+    public boolean isInPreDrag() {
+        return mIsInPreDrag;
     }
 
     /**
@@ -329,7 +332,9 @@
             mDragObject.deferDragViewCleanupPostAnimation = false;
             mDragObject.cancelled = true;
             mDragObject.dragComplete = true;
-            mDragObject.dragSource.onDropCompleted(null, mDragObject, false, false);
+            if (!mIsInPreDrag) {
+                mDragObject.dragSource.onDropCompleted(null, mDragObject, false, false);
+            }
         }
         endDrag();
     }
@@ -350,7 +355,6 @@
     private void endDrag() {
         if (isDragging()) {
             mDragDriver = null;
-            mOptions = null;
             clearScrollRunnable();
             boolean isDeferred = false;
             if (mDragObject.dragView != null) {
@@ -363,15 +367,24 @@
 
             // Only end the drag if we are not deferred
             if (!isDeferred) {
-                for (DragListener listener : new ArrayList<>(mListeners)) {
-                    listener.onDragEnd();
-                }
+                callOnDragEnd();
             }
         }
 
         releaseVelocityTracker();
     }
 
+    private void callOnDragEnd() {
+        if (mIsInPreDrag && mOptions.preDragCondition != null) {
+            mOptions.preDragCondition.onPreDragEnd(false /* dragStarted*/);
+        }
+        mIsInPreDrag = false;
+        mOptions = null;
+        for (DragListener listener : new ArrayList<>(mListeners)) {
+            listener.onDragEnd();
+        }
+    }
+
     /**
      * This only gets called as a result of drag view cleanup being deferred in endDrag();
      */
@@ -380,9 +393,7 @@
 
         if (mDragObject.deferDragViewCleanupPostAnimation) {
             // If we skipped calling onDragEnd() before, do it now
-            for (DragListener listener : new ArrayList<>(mListeners)) {
-                listener.onDragEnd();
-            }
+            callOnDragEnd();
         }
     }
 
@@ -456,7 +467,7 @@
     /**
      * Call this from a drag source view.
      */
-    public boolean onInterceptTouchEvent(MotionEvent ev) {
+    public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
         if (mOptions != null && mOptions.isAccessibleDrag) {
             return false;
         }
@@ -536,9 +547,9 @@
         mLastTouch[1] = y;
         checkScrollState(x, y);
 
-        if (mIsDragDeferred && mOptions.deferDragCondition.shouldStartDeferredDrag(
-                Math.hypot(x - mMotionDownX, y - mMotionDownY))) {
-            startDeferredDrag();
+        if (mIsInPreDrag && mOptions.preDragCondition != null
+                && mOptions.preDragCondition.shouldStartDrag(mDistanceSinceScroll)) {
+            callOnDragStart();
         }
     }
 
@@ -604,7 +615,7 @@
     /**
      * Call this from a drag source view.
      */
-    public boolean onTouchEvent(MotionEvent ev) {
+    public boolean onControllerTouchEvent(MotionEvent ev) {
         if (mDragDriver == null || mOptions == null || mOptions.isAccessibleDrag) {
             return false;
         }
@@ -701,7 +712,7 @@
                 (vec1.length() * vec2.length()));
     }
 
-    void drop(DropTarget dropTarget, float x, float y, PointF flingVel) {
+    private void drop(DropTarget dropTarget, float x, float y, PointF flingVel) {
         final int[] coordinates = mCoordinatesTemp;
 
         mDragObject.x = coordinates[0];
@@ -734,11 +745,15 @@
             }
         }
         final View dropTargetAsView = dropTarget instanceof View ? (View) dropTarget : null;
-        mDragObject.dragSource.onDropCompleted(
-                dropTargetAsView, mDragObject, flingVel != null, accepted);
         mLauncher.getUserEventDispatcher().logDragNDrop(mDragObject, dropTargetAsView);
-        if (mIsDragDeferred) {
-            mOptions.deferDragCondition.onDropBeforeDeferredDrag();
+        if (!mIsInPreDrag) {
+            mDragObject.dragSource.onDropCompleted(
+                    dropTargetAsView, mDragObject, flingVel != null, accepted);
+        } else {
+            // Only defer the drag view cleanup if the drag source handles the drop.
+            if (!(mDragObject.dragSource instanceof DropTarget)) {
+                mDragObject.deferDragViewCleanupPostAnimation = false;
+            }
         }
     }
 
diff --git a/src/com/android/launcher3/dragndrop/DragLayer.java b/src/com/android/launcher3/dragndrop/DragLayer.java
index 016347b..15ee411 100644
--- a/src/com/android/launcher3/dragndrop/DragLayer.java
+++ b/src/com/android/launcher3/dragndrop/DragLayer.java
@@ -36,7 +36,6 @@
 import android.graphics.drawable.Drawable;
 import android.os.Build;
 import android.util.AttributeSet;
-import android.util.Log;
 import android.view.DragEvent;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
@@ -53,8 +52,6 @@
 import com.android.launcher3.CellLayout;
 import com.android.launcher3.DropTargetBar;
 import com.android.launcher3.InsettableFrameLayout;
-import com.android.launcher3.InstallShortcutReceiver;
-import com.android.launcher3.ItemInfo;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAppWidgetHostView;
 import com.android.launcher3.PinchToOverviewListener;
@@ -72,7 +69,6 @@
 import com.android.launcher3.util.Thunk;
 import com.android.launcher3.util.TouchController;
 
-import java.net.URISyntaxException;
 import java.util.ArrayList;
 
 /**
@@ -90,11 +86,9 @@
 
     @Thunk DragController mDragController;
 
-    private int mXDown, mYDown;
     private Launcher mLauncher;
 
     // Variables relating to resizing widgets
-    private final ArrayList<AppWidgetResizeFrame> mResizeFrames = new ArrayList<>();
     private final boolean mIsRtl;
     private AppWidgetResizeFrame mCurrentResizeFrame;
 
@@ -209,23 +203,6 @@
     }
 
     private boolean handleTouchDown(MotionEvent ev, boolean intercept) {
-        Rect hitRect = new Rect();
-        int x = (int) ev.getX();
-        int y = (int) ev.getY();
-
-        for (AppWidgetResizeFrame child: mResizeFrames) {
-            child.getHitRect(hitRect);
-            if (hitRect.contains(x, y)) {
-                if (child.beginResizeIfPointInRegion(x - child.getLeft(), y - child.getTop())) {
-                    mCurrentResizeFrame = child;
-                    mXDown = x;
-                    mYDown = y;
-                    requestDisallowInterceptTouchEvent(true);
-                    return true;
-                }
-            }
-        }
-
         // Remove the shortcuts container when touching outside of it.
         DeepShortcutsContainer deepShortcutsContainer = mLauncher.getOpenShortcutsContainer();
         if (deepShortcutsContainer != null) {
@@ -242,7 +219,7 @@
                     mLauncher.closeShortcutsContainer();
                     // We let touches on the original icon go through so that users can launch
                     // the app with one tap if they don't find a shortcut they want.
-                    return !isEventOverView(deepShortcutsContainer.getDeferredDragIcon(), ev);
+                    return !isEventOverView(deepShortcutsContainer.getOriginalIcon(), ev);
                 }
             }
         }
@@ -289,21 +266,27 @@
             }
             mTouchCompleteListener = null;
         }
-        clearAllResizeFrames();
-
         mActiveController = null;
 
-        if (mDragController.onInterceptTouchEvent(ev)) {
+        if (mCurrentResizeFrame != null
+                && mCurrentResizeFrame.onControllerInterceptTouchEvent(ev)) {
+            mActiveController = mCurrentResizeFrame;
+            return true;
+        } else {
+            clearResizeFrame();
+        }
+
+        if (mDragController.onControllerInterceptTouchEvent(ev)) {
             mActiveController = mDragController;
             return true;
         }
 
-        if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP && mAllAppsController.onInterceptTouchEvent(ev)) {
+        if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP && mAllAppsController.onControllerInterceptTouchEvent(ev)) {
             mActiveController = mAllAppsController;
             return true;
         }
 
-        if (mPinchListener != null && mPinchListener.onInterceptTouchEvent(ev)) {
+        if (mPinchListener != null && mPinchListener.onControllerInterceptTouchEvent(ev)) {
             // Stop listening for scrolling etc. (onTouchEvent() handles the rest of the pinch.)
             mActiveController = mPinchListener;
             return true;
@@ -405,12 +388,8 @@
 
     @Override
     public boolean onTouchEvent(MotionEvent ev) {
-        boolean handled = false;
         int action = ev.getAction();
 
-        int x = (int) ev.getX();
-        int y = (int) ev.getY();
-
         if (action == MotionEvent.ACTION_DOWN) {
             if (handleTouchDown(ev, false)) {
                 return true;
@@ -422,22 +401,8 @@
             mTouchCompleteListener = null;
         }
 
-        if (mCurrentResizeFrame != null) {
-            handled = true;
-            switch (action) {
-                case MotionEvent.ACTION_MOVE:
-                    mCurrentResizeFrame.visualizeResizeForDelta(x - mXDown, y - mYDown);
-                    break;
-                case MotionEvent.ACTION_CANCEL:
-                case MotionEvent.ACTION_UP:
-                    mCurrentResizeFrame.visualizeResizeForDelta(x - mXDown, y - mYDown);
-                    mCurrentResizeFrame.onTouchUp();
-                    mCurrentResizeFrame = null;
-            }
-        }
-        if (handled) return true;
         if (mActiveController != null) {
-            return mActiveController.onTouchEvent(ev);
+            return mActiveController.onControllerTouchEvent(ev);
         }
         return false;
     }
@@ -645,36 +610,24 @@
         }
     }
 
-    public void clearAllResizeFrames() {
-        if (mResizeFrames.size() > 0) {
-            for (AppWidgetResizeFrame frame: mResizeFrames) {
-                frame.commitResize();
-                removeView(frame);
-            }
-            mResizeFrames.clear();
+    public void clearResizeFrame() {
+        if (mCurrentResizeFrame != null) {
+            mCurrentResizeFrame.commitResize();
+            removeView(mCurrentResizeFrame);
+            mCurrentResizeFrame = null;
         }
     }
 
-    public boolean hasResizeFrames() {
-        return mResizeFrames.size() > 0;
-    }
+    public void addResizeFrame(LauncherAppWidgetHostView widget, CellLayout cellLayout) {
+        clearResizeFrame();
 
-    public boolean isWidgetBeingResized() {
-        return mCurrentResizeFrame != null;
-    }
-
-    public void addResizeFrame(ItemInfo itemInfo, LauncherAppWidgetHostView widget,
-            CellLayout cellLayout) {
-        AppWidgetResizeFrame resizeFrame = new AppWidgetResizeFrame(getContext(),
-                widget, cellLayout, this);
+        mCurrentResizeFrame = new AppWidgetResizeFrame(getContext(), widget, cellLayout, this);
 
         LayoutParams lp = new LayoutParams(-1, -1);
         lp.customPosition = true;
 
-        addView(resizeFrame, lp);
-        mResizeFrames.add(resizeFrame);
-
-        resizeFrame.snapToWidget(false);
+        addView(mCurrentResizeFrame, lp);
+        mCurrentResizeFrame.snapToWidget(false);
     }
 
     public void animateViewIntoPosition(DragView dragView, final int[] pos, float alpha,
diff --git a/src/com/android/launcher3/dragndrop/DragOptions.java b/src/com/android/launcher3/dragndrop/DragOptions.java
index dbf46f3..906855a 100644
--- a/src/com/android/launcher3/dragndrop/DragOptions.java
+++ b/src/com/android/launcher3/dragndrop/DragOptions.java
@@ -17,6 +17,8 @@
 package com.android.launcher3.dragndrop;
 
 import android.graphics.Point;
+import android.support.annotation.CallSuper;
+import android.view.View;
 
 /**
  * Set of options to control the drag and drop behavior.
@@ -29,8 +31,8 @@
     /** Specifies the start location for the system DnD, null when using internal DnD */
     public Point systemDndStartPoint = null;
 
-    /** Determines when a deferred drag should start. By default, drags aren't deferred at all. */
-    public DeferDragCondition deferDragCondition = new DeferDragCondition();
+    /** Determines when a pre-drag should transition to a drag. By default, this is immediate. */
+    public PreDragCondition preDragCondition = null;
 
     /**
      * Specifies a condition that must be met before DragListener#onDragStart() is called.
@@ -38,34 +40,26 @@
      * DragController#startDrag().
      *
      * This condition can be overridden, and callbacks are provided for the following cases:
-     * - The drag starts, but onDragStart() is deferred (onDeferredDragStart()).
-     * - The drag ends before the condition is met (onDropBeforeDeferredDrag()).
-     * - The condition is met (onDragStart()).
+     * - The pre-drag starts, but onDragStart() is deferred (onPreDragStart()).
+     * - The pre-drag ends before the condition is met (onPreDragEnd(false)).
+     * - The actual drag starts when the condition is met (onPreDragEnd(true)).
      */
-    public static class DeferDragCondition {
-        public boolean shouldStartDeferredDrag(double distanceDragged) {
-            return true;
-        }
+    public interface PreDragCondition {
+
+        public boolean shouldStartDrag(double distanceDragged);
 
         /**
-         * The drag has started, but onDragStart() is deferred.
-         * This happens when shouldStartDeferredDrag() returns true.
+         * The pre-drag has started, but onDragStart() is
+         * deferred until shouldStartDrag() returns true.
          */
-        public void onDeferredDragStart() {
-            // Do nothing.
-        }
+        void onPreDragStart();
 
         /**
-         * User dropped before the deferred condition was met,
-         * i.e. before shouldStartDeferredDrag() returned true.
+         * The pre-drag has ended. This gets called at the same time as onDragStart()
+         * if the condition is met, otherwise at the same time as onDragEnd().
+         * @param dragStarted Whether the pre-drag ended because the actual drag started.
+         *                    This will be true if the condition was met, otherwise false.
          */
-        public void onDropBeforeDeferredDrag() {
-            // Do nothing
-        }
-
-        /** onDragStart() has been called, now we are in a normal drag. */
-        public void onDragStart() {
-            // Do nothing
-        }
+        void onPreDragEnd(boolean dragStarted);
     }
 }
diff --git a/src/com/android/launcher3/dragndrop/ExternalDragPreviewProvider.java b/src/com/android/launcher3/dragndrop/ExternalDragPreviewProvider.java
index 6b14be7..ac50332 100644
--- a/src/com/android/launcher3/dragndrop/ExternalDragPreviewProvider.java
+++ b/src/com/android/launcher3/dragndrop/ExternalDragPreviewProvider.java
@@ -23,10 +23,10 @@
 import android.graphics.Rect;
 
 import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.HolographicOutlineHelper;
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.graphics.DragPreviewProvider;
+import com.android.launcher3.graphics.HolographicOutlineHelper;
 
 /**
  * Extension of {@link DragPreviewProvider} which provides a dummy outline when drag starts from
@@ -72,7 +72,7 @@
         canvas.drawCircle(DRAG_BITMAP_PADDING / 2 + radius,
                 DRAG_BITMAP_PADDING / 2 + radius, radius * 0.9f, paint);
 
-        HolographicOutlineHelper.obtain(mLauncher).applyExpensiveOutlineWithBlur(b, canvas);
+        HolographicOutlineHelper.getInstance(mLauncher).applyExpensiveOutlineWithBlur(b, canvas);
         canvas.setBitmap(null);
         return b;
     }
diff --git a/src/com/android/launcher3/dynamicui/ExtractionUtils.java b/src/com/android/launcher3/dynamicui/ExtractionUtils.java
index 6dc0035..1e663a9 100644
--- a/src/com/android/launcher3/dynamicui/ExtractionUtils.java
+++ b/src/com/android/launcher3/dynamicui/ExtractionUtils.java
@@ -16,18 +16,18 @@
 
 package com.android.launcher3.dynamicui;
 
+import android.annotation.TargetApi;
 import android.app.WallpaperManager;
 import android.content.Context;
 import android.content.Intent;
 import android.content.SharedPreferences;
 import android.graphics.Color;
+import android.os.Build;
 import android.support.v4.graphics.ColorUtils;
 import android.support.v7.graphics.Palette;
 
 import com.android.launcher3.Utilities;
 
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
 import java.util.List;
 
 /**
@@ -37,7 +37,6 @@
     public static final String EXTRACTED_COLORS_PREFERENCE_KEY = "pref_extractedColors";
     public static final String WALLPAPER_ID_PREFERENCE_KEY = "pref_wallpaperId";
 
-    private static final int FLAG_SET_SYSTEM = 1 << 0; // TODO: use WallpaperManager.FLAG_SET_SYSTEM
     private static final float MIN_CONTRAST_RATIO = 2f;
 
     /**
@@ -73,14 +72,10 @@
         return wallpaperId != savedWallpaperId;
     }
 
+    @TargetApi(Build.VERSION_CODES.N)
     public static int getWallpaperId(WallpaperManager wallpaperManager) {
-        // TODO: use WallpaperManager#getWallpaperId(WallpaperManager.FLAG_SET_SYSTEM) directly.
-        try {
-            Method getWallpaperId = WallpaperManager.class.getMethod("getWallpaperId", int.class);
-            return (int) getWallpaperId.invoke(wallpaperManager, FLAG_SET_SYSTEM);
-        } catch (InvocationTargetException | NoSuchMethodException | IllegalAccessException e) {
-            return -1;
-        }
+        return Utilities.isNycOrAbove() ?
+                wallpaperManager.getWallpaperId(WallpaperManager.FLAG_SYSTEM) : -1;
     }
 
     public static boolean isSuperLight(Palette p) {
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index ebbe641..4fe3513 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -281,17 +281,7 @@
     public boolean onLongClick(View v) {
         // Return if global dragging is not enabled
         if (!mLauncher.isDraggingEnabled()) return true;
-        DragOptions dragOptions = new DragOptions();
-        if (v instanceof BubbleTextView) {
-            BubbleTextView icon = (BubbleTextView) v;
-            if (icon.hasDeepShortcuts()) {
-                DeepShortcutsContainer dsc = DeepShortcutsContainer.showForIcon(icon);
-                if (dsc != null) {
-                    dragOptions.deferDragCondition = dsc.createDeferDragCondition(null);
-                }
-            }
-        }
-        return startDrag(v, dragOptions);
+        return startDrag(v, new DragOptions());
     }
 
     public boolean startDrag(View v, DragOptions options) {
@@ -916,7 +906,7 @@
             if (mDeleteFolderOnDropCompleted && !mItemAddedBackToSelfViaIcon && target != this) {
                 replaceFolderWithFinalItem();
             }
-        } else if (!mDragController.isDeferringDrag()) {
+        } else {
             // The drag failed, we need to return the item to the folder
             ShortcutInfo info = (ShortcutInfo) d.dragInfo;
             View icon = (mCurrentDragView != null && mCurrentDragView.getTag() == info)
@@ -1309,7 +1299,8 @@
             mIsExternalDrag = false;
         } else {
             currentDragView = mCurrentDragView;
-            if (!mDragController.isDeferringDrag()) {
+            // The view was never removed from this folder if we are still in the pre-drag.
+            if (!mDragController.isInPreDrag()) {
                 mContent.addViewForRank(currentDragView, si, mEmptyCellRank);
             }
         }
@@ -1332,7 +1323,8 @@
         mItemsInvalidated = true;
         rearrangeChildren();
 
-        if (!mDragController.isDeferringDrag()) {
+        // The ShortcutInfo was never removed if we are still in the pre-drag.
+        if (!mDragController.isInPreDrag()) {
             // Temporarily suppress the listener, as we did all the work already here.
             try (SuppressInfoChanges s = new SuppressInfoChanges()) {
                 mInfo.add(si, false);
diff --git a/src/com/android/launcher3/graphics/DragPreviewProvider.java b/src/com/android/launcher3/graphics/DragPreviewProvider.java
index bc91c15..7ad1e3a 100644
--- a/src/com/android/launcher3/graphics/DragPreviewProvider.java
+++ b/src/com/android/launcher3/graphics/DragPreviewProvider.java
@@ -24,7 +24,6 @@
 import android.view.View;
 import android.widget.TextView;
 
-import com.android.launcher3.HolographicOutlineHelper;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.PreloadIconDrawable;
 import com.android.launcher3.Workspace;
@@ -137,7 +136,7 @@
                 mView.getHeight() + DRAG_BITMAP_PADDING, Bitmap.Config.ALPHA_8);
         canvas.setBitmap(b);
         drawDragView(canvas);
-        HolographicOutlineHelper.obtain(mView.getContext())
+        HolographicOutlineHelper.getInstance(mView.getContext())
                 .applyExpensiveOutlineWithBlur(b, canvas);
         canvas.setBitmap(null);
         return b;
diff --git a/src/com/android/launcher3/HolographicOutlineHelper.java b/src/com/android/launcher3/graphics/HolographicOutlineHelper.java
similarity index 95%
rename from src/com/android/launcher3/HolographicOutlineHelper.java
rename to src/com/android/launcher3/graphics/HolographicOutlineHelper.java
index 9dec7d9..0d70bde 100644
--- a/src/com/android/launcher3/HolographicOutlineHelper.java
+++ b/src/com/android/launcher3/graphics/HolographicOutlineHelper.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.launcher3;
+package com.android.launcher3.graphics;
 
 import android.content.Context;
 import android.content.res.Resources;
@@ -29,6 +29,8 @@
 import android.graphics.drawable.Drawable;
 import android.util.SparseArray;
 
+import com.android.launcher3.BubbleTextView;
+import com.android.launcher3.R;
 import com.android.launcher3.config.ProviderConfig;
 
 import java.nio.ByteBuffer;
@@ -72,9 +74,9 @@
         mErasePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
     }
 
-    public static HolographicOutlineHelper obtain(Context context) {
+    public static HolographicOutlineHelper getInstance(Context context) {
         if (sInstance == null) {
-            sInstance = new HolographicOutlineHelper(context);
+            sInstance = new HolographicOutlineHelper(context.getApplicationContext());
         }
         return sInstance;
     }
@@ -155,11 +157,11 @@
         thickInnerBlur.recycle();
     }
 
-    Bitmap createMediumDropShadow(BubbleTextView view) {
+    public Bitmap createMediumDropShadow(BubbleTextView view) {
         return createMediumDropShadow(view.getIcon(), view.getScaleX(), view.getScaleY(), true);
     }
 
-    Bitmap createMediumDropShadow(Drawable drawable, boolean shouldCache) {
+    public Bitmap createMediumDropShadow(Drawable drawable, boolean shouldCache) {
         return createMediumDropShadow(drawable, 1f, 1f, shouldCache);
     }
 
diff --git a/src/com/android/launcher3/util/IconNormalizer.java b/src/com/android/launcher3/graphics/IconNormalizer.java
similarity index 99%
rename from src/com/android/launcher3/util/IconNormalizer.java
rename to src/com/android/launcher3/graphics/IconNormalizer.java
index 040a1b5..f90b2d7 100644
--- a/src/com/android/launcher3/util/IconNormalizer.java
+++ b/src/com/android/launcher3/graphics/IconNormalizer.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.launcher3.util;
+package com.android.launcher3.graphics;
 
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
diff --git a/src/com/android/launcher3/graphics/LauncherIcons.java b/src/com/android/launcher3/graphics/LauncherIcons.java
new file mode 100644
index 0000000..9f3f357
--- /dev/null
+++ b/src/com/android/launcher3/graphics/LauncherIcons.java
@@ -0,0 +1,258 @@
+/*
+ * Copyright (C) 2016 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.graphics;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.PaintFlagsDrawFilter;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.PaintDrawable;
+import android.os.Build;
+
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.compat.UserHandleCompat;
+import com.android.launcher3.config.FeatureFlags;
+
+/**
+ * Helper methods for generating various launcher icons
+ */
+public class LauncherIcons {
+
+    private static final Rect sOldBounds = new Rect();
+    private static final Canvas sCanvas = new Canvas();
+
+    static {
+        sCanvas.setDrawFilter(new PaintFlagsDrawFilter(Paint.DITHER_FLAG,
+                Paint.FILTER_BITMAP_FLAG));
+    }
+
+
+    public static Bitmap createIconBitmap(Cursor c, int iconIndex, Context context) {
+        byte[] data = c.getBlob(iconIndex);
+        try {
+            return createIconBitmap(BitmapFactory.decodeByteArray(data, 0, data.length), context);
+        } catch (Exception e) {
+            return null;
+        }
+    }
+
+    /**
+     * Returns a bitmap suitable for the all apps view. If the package or the resource do not
+     * exist, it returns null.
+     */
+    public static Bitmap createIconBitmap(String packageName, String resourceName,
+            Context context) {
+        PackageManager packageManager = context.getPackageManager();
+        // the resource
+        try {
+            Resources resources = packageManager.getResourcesForApplication(packageName);
+            if (resources != null) {
+                final int id = resources.getIdentifier(resourceName, null, null);
+                return createIconBitmap(
+                        resources.getDrawableForDensity(id, LauncherAppState.getInstance()
+                                .getInvariantDeviceProfile().fillResIconDpi), context);
+            }
+        } catch (Exception e) {
+            // Icon not found.
+        }
+        return null;
+    }
+
+    private static int getIconBitmapSize() {
+        return LauncherAppState.getInstance().getInvariantDeviceProfile().iconBitmapSize;
+    }
+
+    /**
+     * Returns a bitmap which is of the appropriate size to be displayed as an icon
+     */
+    public static Bitmap createIconBitmap(Bitmap icon, Context context) {
+        final int iconBitmapSize = getIconBitmapSize();
+        if (iconBitmapSize == icon.getWidth() && iconBitmapSize == icon.getHeight()) {
+            return icon;
+        }
+        return createIconBitmap(new BitmapDrawable(context.getResources(), icon), context);
+    }
+
+    /**
+     * Returns a bitmap suitable for the all apps view. The icon is badged for {@param user}.
+     * The bitmap is also visually normalized with other icons.
+     */
+    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+    public static Bitmap createBadgedIconBitmap(
+            Drawable icon, UserHandleCompat user, Context context) {
+        float scale = FeatureFlags.LAUNCHER3_DISABLE_ICON_NORMALIZATION ?
+                1 : IconNormalizer.getInstance().getScale(icon, null);
+        Bitmap bitmap = createIconBitmap(icon, context, scale);
+        return badgeIconForUser(bitmap, user, context);
+    }
+
+    /**
+     * Badges the provided icon with the user badge if required.
+     */
+    public static Bitmap badgeIconForUser(Bitmap icon,  UserHandleCompat user, Context context) {
+        if (Utilities.ATLEAST_LOLLIPOP && user != null
+                && !UserHandleCompat.myUserHandle().equals(user)) {
+            BitmapDrawable drawable = new FixedSizeBitmapDrawable(icon);
+            Drawable badged = context.getPackageManager().getUserBadgedIcon(
+                    drawable, user.getUser());
+            if (badged instanceof BitmapDrawable) {
+                return ((BitmapDrawable) badged).getBitmap();
+            } else {
+                return createIconBitmap(badged, context);
+            }
+        } else {
+            return icon;
+        }
+    }
+
+    /**
+     * Creates a normalized bitmap suitable for the all apps view. The bitmap is also visually
+     * normalized with other icons and has enough spacing to add shadow.
+     */
+    public static Bitmap createScaledBitmapWithoutShadow(Drawable icon, Context context) {
+        RectF iconBounds = new RectF();
+        float scale = FeatureFlags.LAUNCHER3_DISABLE_ICON_NORMALIZATION ?
+                1 : IconNormalizer.getInstance().getScale(icon, iconBounds);
+        scale = Math.min(scale, ShadowGenerator.getScaleForBounds(iconBounds));
+        return createIconBitmap(icon, context, scale);
+    }
+
+    /**
+     * Adds a shadow to the provided icon. It assumes that the icon has already been scaled using
+     * {@link #createScaledBitmapWithoutShadow(Drawable, Context)}
+     */
+    public static Bitmap addShadowToIcon(Bitmap icon) {
+        return ShadowGenerator.getInstance().recreateIcon(icon);
+    }
+
+    /**
+     * Adds the {@param badge} on top of {@param srcTgt} using the badge dimensions.
+     */
+    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+    public static Bitmap badgeWithBitmap(Bitmap srcTgt, Bitmap badge, Context context) {
+        int badgeSize = context.getResources().getDimensionPixelSize(R.dimen.profile_badge_size);
+        synchronized (sCanvas) {
+            sCanvas.setBitmap(srcTgt);
+            sCanvas.drawBitmap(badge, new Rect(0, 0, badge.getWidth(), badge.getHeight()),
+                    new Rect(srcTgt.getWidth() - badgeSize,
+                            srcTgt.getHeight() - badgeSize, srcTgt.getWidth(), srcTgt.getHeight()),
+                    new Paint(Paint.FILTER_BITMAP_FLAG));
+            sCanvas.setBitmap(null);
+        }
+        return srcTgt;
+    }
+
+    /**
+     * Returns a bitmap suitable for the all apps view.
+     */
+    public static Bitmap createIconBitmap(Drawable icon, Context context) {
+        return createIconBitmap(icon, context, 1.0f /* scale */);
+    }
+
+    /**
+     * @param scale the scale to apply before drawing {@param icon} on the canvas
+     */
+    public static Bitmap createIconBitmap(Drawable icon, Context context, float scale) {
+        synchronized (sCanvas) {
+            final int iconBitmapSize = getIconBitmapSize();
+
+            int width = iconBitmapSize;
+            int height = iconBitmapSize;
+
+            if (icon instanceof PaintDrawable) {
+                PaintDrawable painter = (PaintDrawable) icon;
+                painter.setIntrinsicWidth(width);
+                painter.setIntrinsicHeight(height);
+            } else if (icon instanceof BitmapDrawable) {
+                // Ensure the bitmap has a density.
+                BitmapDrawable bitmapDrawable = (BitmapDrawable) icon;
+                Bitmap bitmap = bitmapDrawable.getBitmap();
+                if (bitmap != null && bitmap.getDensity() == Bitmap.DENSITY_NONE) {
+                    bitmapDrawable.setTargetDensity(context.getResources().getDisplayMetrics());
+                }
+            }
+            int sourceWidth = icon.getIntrinsicWidth();
+            int sourceHeight = icon.getIntrinsicHeight();
+            if (sourceWidth > 0 && sourceHeight > 0) {
+                // Scale the icon proportionally to the icon dimensions
+                final float ratio = (float) sourceWidth / sourceHeight;
+                if (sourceWidth > sourceHeight) {
+                    height = (int) (width / ratio);
+                } else if (sourceHeight > sourceWidth) {
+                    width = (int) (height * ratio);
+                }
+            }
+
+            // no intrinsic size --> use default size
+            int textureWidth = iconBitmapSize;
+            int textureHeight = iconBitmapSize;
+
+            final Bitmap bitmap = Bitmap.createBitmap(textureWidth, textureHeight,
+                    Bitmap.Config.ARGB_8888);
+            final Canvas canvas = sCanvas;
+            canvas.setBitmap(bitmap);
+
+            final int left = (textureWidth-width) / 2;
+            final int top = (textureHeight-height) / 2;
+
+            sOldBounds.set(icon.getBounds());
+            icon.setBounds(left, top, left+width, top+height);
+            canvas.save(Canvas.MATRIX_SAVE_FLAG);
+            canvas.scale(scale, scale, textureWidth / 2, textureHeight / 2);
+            icon.draw(canvas);
+            canvas.restore();
+            icon.setBounds(sOldBounds);
+            canvas.setBitmap(null);
+
+            return bitmap;
+        }
+    }
+
+    /**
+     * An extension of {@link BitmapDrawable} which returns the bitmap pixel size as intrinsic size.
+     * This allows the badging to be done based on the action bitmap size rather than
+     * the scaled bitmap size.
+     */
+    private static class FixedSizeBitmapDrawable extends BitmapDrawable {
+
+        public FixedSizeBitmapDrawable(Bitmap bitmap) {
+            super(null, bitmap);
+        }
+
+        @Override
+        public int getIntrinsicHeight() {
+            return getBitmap().getWidth();
+        }
+
+        @Override
+        public int getIntrinsicWidth() {
+            return getBitmap().getWidth();
+        }
+    }
+}
diff --git a/src/com/android/launcher3/keyboard/CustomActionsPopup.java b/src/com/android/launcher3/keyboard/CustomActionsPopup.java
new file mode 100644
index 0000000..6056f4c
--- /dev/null
+++ b/src/com/android/launcher3/keyboard/CustomActionsPopup.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2016 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.keyboard;
+
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
+import android.widget.PopupMenu;
+import android.widget.PopupMenu.OnMenuItemClickListener;
+
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
+import com.android.launcher3.shortcuts.DeepShortcutsContainer;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Handles showing a popup menu with available custom actions for a launcher icon.
+ * This allows exposing various custom actions using keyboard shortcuts.
+ */
+public class CustomActionsPopup implements OnMenuItemClickListener {
+
+    private final Launcher mLauncher;
+    private final LauncherAccessibilityDelegate mDelegate;
+    private final View mIcon;
+
+    public CustomActionsPopup(Launcher launcher, View icon) {
+        mLauncher = launcher;
+        mIcon = icon;
+        DeepShortcutsContainer container = launcher.getOpenShortcutsContainer();
+        if (container != null) {
+            mDelegate = container.getAccessibilityDelegate();
+        } else {
+            mDelegate = launcher.getAccessibilityDelegate();
+        }
+    }
+
+    private List<AccessibilityAction> getActionList() {
+        if (mIcon == null || !(mIcon.getTag() instanceof ItemInfo)) {
+            return Collections.EMPTY_LIST;
+        }
+
+        AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain();
+        mDelegate.addSupportedActions(mIcon, info, true);
+        List<AccessibilityAction> result = new ArrayList<>(info.getActionList());
+        info.recycle();
+        return result;
+    }
+
+    public boolean canShow() {
+        return !getActionList().isEmpty();
+    }
+
+    public boolean show() {
+        List<AccessibilityAction> actions = getActionList();
+        if (actions.isEmpty()) {
+            return false;
+        }
+
+        PopupMenu popup = new PopupMenu(mLauncher, mIcon);
+        popup.setOnMenuItemClickListener(this);
+        Menu menu = popup.getMenu();
+        for (AccessibilityAction action : actions) {
+            menu.add(Menu.NONE, action.getId(), Menu.NONE, action.getLabel());
+        }
+        popup.show();
+        return true;
+    }
+
+    @Override
+    public boolean onMenuItemClick(MenuItem menuItem) {
+        return mDelegate.performAction(mIcon, (ItemInfo) mIcon.getTag(), menuItem.getItemId());
+    }
+}
diff --git a/src/com/android/launcher3/keyboard/FocusIndicatorHelper.java b/src/com/android/launcher3/keyboard/FocusIndicatorHelper.java
index 7672f5a..b0d6b2d 100644
--- a/src/com/android/launcher3/keyboard/FocusIndicatorHelper.java
+++ b/src/com/android/launcher3/keyboard/FocusIndicatorHelper.java
@@ -143,7 +143,7 @@
     }
 
     private Rect getDrawRect() {
-        if (mCurrentView != null) {
+        if (mCurrentView != null && mCurrentView.isAttachedToWindow()) {
             viewToRect(mCurrentView, sTempRect1);
 
             if (mShift > 0 && mTargetView != null) {
diff --git a/src/com/android/launcher3/model/BgDataModel.java b/src/com/android/launcher3/model/BgDataModel.java
new file mode 100644
index 0000000..2b70399
--- /dev/null
+++ b/src/com/android/launcher3/model/BgDataModel.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright (C) 2016 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.model;
+
+import android.util.Log;
+import android.util.MutableInt;
+
+import com.android.launcher3.FolderInfo;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherAppWidgetInfo;
+import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.ShortcutInfo;
+import com.android.launcher3.config.ProviderConfig;
+import com.android.launcher3.shortcuts.ShortcutKey;
+import com.android.launcher3.util.LongArrayMap;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * All the data stored in-memory and managed by the LauncherModel
+ */
+public class BgDataModel {
+
+    private static final String TAG = "BgDataModel";
+
+    /**
+     * Map of all the ItemInfos (shortcuts, folders, and widgets) created by
+     * LauncherModel to their ids
+     */
+    public final LongArrayMap<ItemInfo> itemsIdMap = new LongArrayMap<>();
+
+    /**
+     * List of all the folders and shortcuts directly on the home screen (no widgets
+     * or shortcuts within folders).
+     */
+    public final ArrayList<ItemInfo> workspaceItems = new ArrayList<>();
+
+    /**
+     * All LauncherAppWidgetInfo created by LauncherModel.
+     */
+    public final ArrayList<LauncherAppWidgetInfo> appWidgets = new ArrayList<>();
+
+    /**
+     * Map of id to FolderInfos of all the folders created by LauncherModel
+     */
+    public final LongArrayMap<FolderInfo> folders = new LongArrayMap<>();
+
+    /**
+     * Ordered list of workspace screens ids.
+     */
+    public final ArrayList<Long> workspaceScreens = new ArrayList<>();
+
+    /**
+     * Map of ShortcutKey to the number of times it is pinned.
+     */
+    public final Map<ShortcutKey, MutableInt> pinnedShortcutCounts = new HashMap<>();
+
+    /**
+     * Clears all the data
+     */
+    public synchronized void clear() {
+        workspaceItems.clear();
+        appWidgets.clear();
+        folders.clear();
+        itemsIdMap.clear();
+        workspaceScreens.clear();
+        pinnedShortcutCounts.clear();
+    }
+
+    public synchronized void removeItem(ItemInfo... items) {
+        removeItem(Arrays.asList(items));
+    }
+
+    public synchronized void removeItem(Iterable<? extends ItemInfo> items) {
+        for (ItemInfo item : items) {
+            switch (item.itemType) {
+                case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
+                    folders.remove(item.id);
+                    if (ProviderConfig.IS_DOGFOOD_BUILD) {
+                        for (ItemInfo info : itemsIdMap) {
+                            if (info.container == item.id) {
+                                // We are deleting a folder which still contains items that
+                                // think they are contained by that folder.
+                                String msg = "deleting a folder (" + item + ") which still " +
+                                        "contains items (" + info + ")";
+                                Log.e(TAG, msg);
+                            }
+                        }
+                    }
+                    workspaceItems.remove(item);
+                    break;
+                case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT: {
+                    // Decrement pinned shortcut count
+                    ShortcutKey pinnedShortcut = ShortcutKey.fromShortcutInfo((ShortcutInfo) item);
+                    MutableInt count = pinnedShortcutCounts.get(pinnedShortcut);
+                    if (count == null || --count.value == 0) {
+                        LauncherAppState.getInstance()
+                                .getShortcutManager().unpinShortcut(pinnedShortcut);
+                    }
+                    // Fall through.
+                }
+                case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
+                case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
+                    workspaceItems.remove(item);
+                    break;
+                case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
+                case LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET:
+                    appWidgets.remove(item);
+                    break;
+            }
+            itemsIdMap.remove(item.id);
+        }
+    }
+
+    public synchronized void addItem(ItemInfo item, boolean newItem) {
+        itemsIdMap.put(item.id, item);
+        switch (item.itemType) {
+            case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
+                folders.put(item.id, (FolderInfo) item);
+                workspaceItems.add(item);
+                break;
+            case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT: {
+                // Increment the count for the given shortcut
+                ShortcutKey pinnedShortcut = ShortcutKey.fromShortcutInfo((ShortcutInfo) item);
+                MutableInt count = pinnedShortcutCounts.get(pinnedShortcut);
+                if (count == null) {
+                    count = new MutableInt(1);
+                    pinnedShortcutCounts.put(pinnedShortcut, count);
+                } else {
+                    count.value++;
+                }
+
+                // Since this is a new item, pin the shortcut in the system server.
+                if (newItem && count.value == 1) {
+                    LauncherAppState.getInstance().getShortcutManager()
+                            .pinShortcut(pinnedShortcut);
+                }
+                // Fall through
+            }
+            case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
+            case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
+                if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP ||
+                        item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
+                    workspaceItems.add(item);
+                } else {
+                    if (newItem) {
+                        if (!folders.containsKey(item.container)) {
+                            // Adding an item to a folder that doesn't exist.
+                            String msg = "adding item: " + item + " to a folder that " +
+                                    " doesn't exist";
+                            Log.e(TAG, msg);
+                        }
+                    } else {
+                        findOrMakeFolder(item.container).add((ShortcutInfo) item, false);
+                    }
+
+                }
+                break;
+            case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
+            case LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET:
+                appWidgets.add((LauncherAppWidgetInfo) item);
+                break;
+        }
+    }
+
+    /**
+     * Return an existing FolderInfo object if we have encountered this ID previously,
+     * or make a new one.
+     */
+    public synchronized FolderInfo findOrMakeFolder(long id) {
+        // See if a placeholder was created for us already
+        FolderInfo folderInfo = folders.get(id);
+        if (folderInfo == null) {
+            // No placeholder -- create a new instance
+            folderInfo = new FolderInfo();
+            folders.put(id, folderInfo);
+        }
+        return folderInfo;
+    }
+}
diff --git a/src/com/android/launcher3/model/SdCardAvailableReceiver.java b/src/com/android/launcher3/model/SdCardAvailableReceiver.java
new file mode 100644
index 0000000..54260c9
--- /dev/null
+++ b/src/com/android/launcher3/model/SdCardAvailableReceiver.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2016 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.model;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherModel;
+import com.android.launcher3.compat.LauncherAppsCompat;
+import com.android.launcher3.compat.UserHandleCompat;
+import com.android.launcher3.util.MultiHashMap;
+import com.android.launcher3.util.PackageManagerHelper;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Map.Entry;
+
+/**
+ * Helper class to re-query app status when SD-card becomes available.
+ *
+ * During first load, just after reboot, some apps on sdcard might not be available immediately due
+ * to some race conditions in the system. We wait for ACTION_BOOT_COMPLETED and process such
+ * apps again.
+ */
+public class SdCardAvailableReceiver extends BroadcastReceiver {
+
+    private final LauncherModel mModel;
+    private final Context mContext;
+    private final MultiHashMap<UserHandleCompat, String> mPackages;
+
+    public SdCardAvailableReceiver(LauncherModel model, Context context,
+            MultiHashMap<UserHandleCompat, String> packages) {
+        mModel = model;
+        mContext = context;
+        mPackages = packages;
+    }
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
+        final PackageManager manager = context.getPackageManager();
+        for (Entry<UserHandleCompat, ArrayList<String>> entry : mPackages.entrySet()) {
+            UserHandleCompat user = entry.getKey();
+
+            final ArrayList<String> packagesRemoved = new ArrayList<>();
+            final ArrayList<String> packagesUnavailable = new ArrayList<>();
+
+            for (String pkg : new HashSet<>(entry.getValue())) {
+                if (!launcherApps.isPackageEnabledForProfile(pkg, user)) {
+                    if (PackageManagerHelper.isAppOnSdcard(manager, pkg)) {
+                        packagesUnavailable.add(pkg);
+                    } else {
+                        packagesRemoved.add(pkg);
+                    }
+                }
+            }
+            if (!packagesRemoved.isEmpty()) {
+                mModel.onPackagesRemoved(user,
+                        packagesRemoved.toArray(new String[packagesRemoved.size()]));
+            }
+            if (!packagesUnavailable.isEmpty()) {
+                mModel.onPackagesUnavailable(
+                        packagesUnavailable.toArray(new String[packagesUnavailable.size()]),
+                        user, false);
+            }
+        }
+
+        // Unregister the broadcast receiver, just in case
+        mContext.unregisterReceiver(this);
+    }
+}
diff --git a/src/com/android/launcher3/shortcuts/DeepShortcutManager.java b/src/com/android/launcher3/shortcuts/DeepShortcutManager.java
index 49d6fa9..9037af4 100644
--- a/src/com/android/launcher3/shortcuts/DeepShortcutManager.java
+++ b/src/com/android/launcher3/shortcuts/DeepShortcutManager.java
@@ -42,13 +42,8 @@
 public class DeepShortcutManager {
     private static final String TAG = "DeepShortcutManager";
 
-    // TODO: Replace this with platform constants when the new sdk is available.
-    public static final int FLAG_MATCH_DYNAMIC = 1 << 0;
-    public static final int FLAG_MATCH_MANIFEST = 1 << 3;
-    public static final int FLAG_MATCH_PINNED = 1 << 1;
-
-    private static final int FLAG_GET_ALL =
-            FLAG_MATCH_DYNAMIC | FLAG_MATCH_PINNED | FLAG_MATCH_MANIFEST;
+    private static final int FLAG_GET_ALL = ShortcutQuery.FLAG_MATCH_DYNAMIC
+            | ShortcutQuery.FLAG_MATCH_MANIFEST | ShortcutQuery.FLAG_MATCH_PINNED;
 
     private final LauncherApps mLauncherApps;
     private boolean mWasLastCallSuccess;
@@ -86,7 +81,7 @@
      */
     public List<ShortcutInfoCompat> queryForShortcutsContainer(ComponentName activity,
             List<String> ids, UserHandleCompat user) {
-        return query(FLAG_MATCH_MANIFEST | FLAG_MATCH_DYNAMIC,
+        return query(ShortcutQuery.FLAG_MATCH_MANIFEST | ShortcutQuery.FLAG_MATCH_DYNAMIC,
                 activity.getPackageName(), activity, ids, user);
     }
 
@@ -172,7 +167,7 @@
      */
     public List<ShortcutInfoCompat> queryForPinnedShortcuts(String packageName,
             UserHandleCompat user) {
-        return query(FLAG_MATCH_PINNED, packageName, null, null, user);
+        return query(ShortcutQuery.FLAG_MATCH_PINNED, packageName, null, null, user);
     }
 
     public List<ShortcutInfoCompat> queryForAllShortcuts(UserHandleCompat user) {
diff --git a/src/com/android/launcher3/shortcuts/DeepShortcutsContainer.java b/src/com/android/launcher3/shortcuts/DeepShortcutsContainer.java
index 2702d4e..31f3823 100644
--- a/src/com/android/launcher3/shortcuts/DeepShortcutsContainer.java
+++ b/src/com/android/launcher3/shortcuts/DeepShortcutsContainer.java
@@ -85,7 +85,7 @@
     private final ShortcutMenuAccessibilityDelegate mAccessibilityDelegate;
     private final boolean mIsRtl;
 
-    private BubbleTextView mDeferredDragIcon;
+    private BubbleTextView mOriginalIcon;
     private final Rect mTempRect = new Rect();
     private Point mIconLastTouchPos = new Point();
     private boolean mIsLeftAligned;
@@ -150,7 +150,8 @@
 
         animateOpen();
 
-        deferDrag(originalIcon);
+        mOriginalIcon = originalIcon;
+        mLauncher.getDragController().addDragListener(this);
 
         // Load the shortcuts on a background thread and update the container as it animates.
         final Looper workerLooper = LauncherModel.getWorkerLooper();
@@ -375,13 +376,8 @@
         return arrowView;
     }
 
-    private void deferDrag(BubbleTextView originalIcon) {
-        mDeferredDragIcon = originalIcon;
-        mLauncher.getDragController().addDragListener(this);
-    }
-
-    public BubbleTextView getDeferredDragIcon() {
-        return mDeferredDragIcon;
+    public BubbleTextView getOriginalIcon() {
+        return mOriginalIcon;
     }
 
     /**
@@ -390,30 +386,26 @@
      * Current behavior:
      * - Start the drag if the touch passes a certain distance from the original touch down.
      */
-    public DragOptions.DeferDragCondition createDeferDragCondition(final Runnable onDragStart) {
-        return new DragOptions.DeferDragCondition() {
+    public DragOptions.PreDragCondition createPreDragCondition() {
+        return new DragOptions.PreDragCondition() {
             @Override
-            public boolean shouldStartDeferredDrag(double distanceDragged) {
+            public boolean shouldStartDrag(double distanceDragged) {
                 return distanceDragged > mStartDragThreshold;
             }
 
             @Override
-            public void onDeferredDragStart() {
-                mDeferredDragIcon.setVisibility(INVISIBLE);
+            public void onPreDragStart() {
+                mOriginalIcon.setVisibility(INVISIBLE);
             }
 
             @Override
-            public void onDropBeforeDeferredDrag() {
-                mLauncher.getUserEventDispatcher().logDeepShortcutsOpen(mDeferredDragIcon);
-                if (!mIsAboveIcon) {
-                    mDeferredDragIcon.setTextVisibility(false);
-                }
-            }
-
-            @Override
-            public void onDragStart() {
-                if (onDragStart != null) {
-                    onDragStart.run();
+            public void onPreDragEnd(boolean dragStarted) {
+                if (!dragStarted) {
+                    mOriginalIcon.setVisibility(VISIBLE);
+                    mLauncher.getUserEventDispatcher().logDeepShortcutsOpen(mOriginalIcon);
+                    if (!mIsAboveIcon) {
+                        mOriginalIcon.setTextVisibility(false);
+                    }
                 }
             }
         };
@@ -511,7 +503,6 @@
                 }
             }
         }
-        mDeferredDragIcon.setVisibility(VISIBLE);
     }
 
     @Override
@@ -609,6 +600,10 @@
         shortcutAnims.start();
     }
 
+    public ShortcutMenuAccessibilityDelegate getAccessibilityDelegate() {
+        return mAccessibilityDelegate;
+    }
+
     /**
      * Closes the folder without animation.
      */
@@ -619,9 +614,9 @@
         }
         mIsOpen = false;
         mDeferContainerRemoval = false;
-        boolean isInHotseat = ((ItemInfo) mDeferredDragIcon.getTag()).container
+        boolean isInHotseat = ((ItemInfo) mOriginalIcon.getTag()).container
                 == LauncherSettings.Favorites.CONTAINER_HOTSEAT;
-        mDeferredDragIcon.setTextVisibility(!isInHotseat);
+        mOriginalIcon.setTextVisibility(!isInHotseat);
         mLauncher.getDragController().removeDragListener(this);
         mLauncher.getDragLayer().removeView(this);
     }
@@ -643,7 +638,6 @@
         }
         List<String> ids = launcher.getShortcutIdsForItem((ItemInfo) icon.getTag());
         if (!ids.isEmpty()) {
-            // There are shortcuts associated with the app, so defer its drag.
             final DeepShortcutsContainer container =
                     (DeepShortcutsContainer) launcher.getLayoutInflater().inflate(
                             R.layout.deep_shortcuts_container, launcher.getDragLayer(), false);
diff --git a/src/com/android/launcher3/shortcuts/ShortcutDragPreviewProvider.java b/src/com/android/launcher3/shortcuts/ShortcutDragPreviewProvider.java
index 2adb82e..fc474f5 100644
--- a/src/com/android/launcher3/shortcuts/ShortcutDragPreviewProvider.java
+++ b/src/com/android/launcher3/shortcuts/ShortcutDragPreviewProvider.java
@@ -23,7 +23,7 @@
 import android.graphics.drawable.Drawable;
 import android.view.View;
 
-import com.android.launcher3.HolographicOutlineHelper;
+import com.android.launcher3.graphics.HolographicOutlineHelper;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.graphics.DragPreviewProvider;
@@ -44,7 +44,7 @@
     public Bitmap createDragOutline(Canvas canvas) {
         Bitmap b = drawScaledPreview(canvas, Bitmap.Config.ALPHA_8);
 
-        HolographicOutlineHelper.obtain(mView.getContext())
+        HolographicOutlineHelper.getInstance(mView.getContext())
                 .applyExpensiveOutlineWithBlur(b, canvas);
         canvas.setBitmap(null);
         return b;
diff --git a/src/com/android/launcher3/util/CursorIconInfo.java b/src/com/android/launcher3/util/CursorIconInfo.java
index 4fefa98..6603ee7 100644
--- a/src/com/android/launcher3/util/CursorIconInfo.java
+++ b/src/com/android/launcher3/util/CursorIconInfo.java
@@ -25,6 +25,7 @@
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.ShortcutInfo;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.graphics.LauncherIcons;
 
 /**
  * Utility class to load icon from a cursor.
@@ -59,7 +60,7 @@
             info.iconResource = new ShortcutIconResource();
             info.iconResource.packageName = packageName;
             info.iconResource.resourceName = resourceName;
-            icon = Utilities.createIconBitmap(packageName, resourceName, mContext);
+            icon = LauncherIcons.createIconBitmap(packageName, resourceName, mContext);
         }
         if (icon == null) {
             // Failed to load from resource, try loading from DB.
@@ -72,7 +73,7 @@
      * Loads the fixed bitmap from the icon if available.
      */
     public Bitmap loadIcon(Cursor c) {
-        return Utilities.createIconBitmap(c, iconIndex, mContext);
+        return LauncherIcons.createIconBitmap(c, iconIndex, mContext);
     }
 
     /**
diff --git a/src/com/android/launcher3/util/FocusLogic.java b/src/com/android/launcher3/util/FocusLogic.java
index 163c953..afc45fe 100644
--- a/src/com/android/launcher3/util/FocusLogic.java
+++ b/src/com/android/launcher3/util/FocusLogic.java
@@ -79,8 +79,7 @@
         return (keyCode == KeyEvent.KEYCODE_DPAD_LEFT || keyCode == KeyEvent.KEYCODE_DPAD_RIGHT ||
                 keyCode == KeyEvent.KEYCODE_DPAD_UP || keyCode == KeyEvent.KEYCODE_DPAD_DOWN ||
                 keyCode == KeyEvent.KEYCODE_MOVE_HOME || keyCode == KeyEvent.KEYCODE_MOVE_END ||
-                keyCode == KeyEvent.KEYCODE_PAGE_UP || keyCode == KeyEvent.KEYCODE_PAGE_DOWN ||
-                keyCode == KeyEvent.KEYCODE_DEL || keyCode == KeyEvent.KEYCODE_FORWARD_DEL);
+                keyCode == KeyEvent.KEYCODE_PAGE_UP || keyCode == KeyEvent.KEYCODE_PAGE_DOWN);
     }
 
     public static int handleKeyEvent(int keyCode, int [][] map, int iconIdx, int pageIndex,
diff --git a/src/com/android/launcher3/util/ItemInfoMatcher.java b/src/com/android/launcher3/util/ItemInfoMatcher.java
index 46e9184..8f985c3 100644
--- a/src/com/android/launcher3/util/ItemInfoMatcher.java
+++ b/src/com/android/launcher3/util/ItemInfoMatcher.java
@@ -18,7 +18,9 @@
 
 import android.content.ComponentName;
 
+import com.android.launcher3.FolderInfo;
 import com.android.launcher3.ItemInfo;
+import com.android.launcher3.LauncherAppWidgetInfo;
 import com.android.launcher3.LauncherSettings.Favorites;
 import com.android.launcher3.ShortcutInfo;
 import com.android.launcher3.compat.UserHandleCompat;
@@ -33,6 +35,46 @@
 
     public abstract boolean matches(ItemInfo info, ComponentName cn);
 
+    /**
+     * Filters {@param infos} to those satisfying the {@link #matches(ItemInfo, ComponentName)}.
+     */
+    public final HashSet<ItemInfo> filterItemInfos(Iterable<ItemInfo> infos) {
+        HashSet<ItemInfo> filtered = new HashSet<>();
+        for (ItemInfo i : infos) {
+            if (i instanceof ShortcutInfo) {
+                ShortcutInfo info = (ShortcutInfo) i;
+                ComponentName cn = info.getTargetComponent();
+                if (cn != null && matches(info, cn)) {
+                    filtered.add(info);
+                }
+            } else if (i instanceof FolderInfo) {
+                FolderInfo info = (FolderInfo) i;
+                for (ShortcutInfo s : info.contents) {
+                    ComponentName cn = s.getTargetComponent();
+                    if (cn != null && matches(s, cn)) {
+                        filtered.add(s);
+                    }
+                }
+            } else if (i instanceof LauncherAppWidgetInfo) {
+                LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) i;
+                ComponentName cn = info.providerName;
+                if (cn != null && matches(info, cn)) {
+                    filtered.add(info);
+                }
+            }
+        }
+        return filtered;
+    }
+
+    public static ItemInfoMatcher ofUser(final UserHandleCompat user) {
+        return new ItemInfoMatcher() {
+            @Override
+            public boolean matches(ItemInfo info, ComponentName cn) {
+                return info.user.equals(user);
+            }
+        };
+    }
+
     public static ItemInfoMatcher ofComponents(
             final HashSet<ComponentName> components, final UserHandleCompat user) {
         return new ItemInfoMatcher() {
diff --git a/src/com/android/launcher3/util/NoLocaleSqliteContext.java b/src/com/android/launcher3/util/NoLocaleSqliteContext.java
index 3b258e4..c8a5ffb 100644
--- a/src/com/android/launcher3/util/NoLocaleSqliteContext.java
+++ b/src/com/android/launcher3/util/NoLocaleSqliteContext.java
@@ -11,9 +11,6 @@
  */
 public class NoLocaleSqliteContext extends ContextWrapper {
 
-    // TODO: Use the flag defined in Context when the new SDK is available
-    private static final int MODE_NO_LOCALIZED_COLLATORS = 0x0010;
-
     public NoLocaleSqliteContext(Context context) {
         super(context);
     }
@@ -22,6 +19,6 @@
     public SQLiteDatabase openOrCreateDatabase(
             String name, int mode, CursorFactory factory, DatabaseErrorHandler errorHandler) {
         return super.openOrCreateDatabase(
-                name, mode | MODE_NO_LOCALIZED_COLLATORS, factory, errorHandler);
+                name, mode | Context.MODE_NO_LOCALIZED_COLLATORS, factory, errorHandler);
     }
 }
diff --git a/src/com/android/launcher3/util/StringFilter.java b/src/com/android/launcher3/util/StringFilter.java
deleted file mode 100644
index f539ad1..0000000
--- a/src/com/android/launcher3/util/StringFilter.java
+++ /dev/null
@@ -1,31 +0,0 @@
-package com.android.launcher3.util;
-
-import java.util.Set;
-
-/**
- * Abstract class to filter a set of strings.
- */
-public abstract class StringFilter {
-
-    private StringFilter() { }
-
-    public abstract boolean matches(String str);
-
-    public static StringFilter matchesAll() {
-        return new StringFilter() {
-            @Override
-            public boolean matches(String str) {
-                return true;
-            }
-        };
-    }
-
-    public static StringFilter of(final Set<String> validEntries) {
-        return new StringFilter() {
-            @Override
-            public boolean matches(String str) {
-                return validEntries.contains(str);
-            }
-        };
-    }
-}
diff --git a/src/com/android/launcher3/util/TouchController.java b/src/com/android/launcher3/util/TouchController.java
index d1409c8..3cca215 100644
--- a/src/com/android/launcher3/util/TouchController.java
+++ b/src/com/android/launcher3/util/TouchController.java
@@ -1,8 +1,32 @@
+/*
+ * Copyright (C) 2016 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.util;
 
 import android.view.MotionEvent;
 
 public interface TouchController {
-    boolean onTouchEvent(MotionEvent ev);
-    boolean onInterceptTouchEvent(MotionEvent ev);
+
+    /**
+     * Called when the draglayer receives touch event.
+     */
+    boolean onControllerTouchEvent(MotionEvent ev);
+
+    /**
+     * Called when the draglayer receives a intercept touch event.
+     */
+    boolean onControllerInterceptTouchEvent(MotionEvent ev);
 }
diff --git a/src/com/android/launcher3/widget/PendingItemPreviewProvider.java b/src/com/android/launcher3/widget/PendingItemPreviewProvider.java
index eaa0bb3..7c06701 100644
--- a/src/com/android/launcher3/widget/PendingItemPreviewProvider.java
+++ b/src/com/android/launcher3/widget/PendingItemPreviewProvider.java
@@ -21,7 +21,7 @@
 import android.graphics.Rect;
 import android.view.View;
 
-import com.android.launcher3.HolographicOutlineHelper;
+import com.android.launcher3.graphics.HolographicOutlineHelper;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.PendingAddItemInfo;
 import com.android.launcher3.Workspace;
@@ -67,7 +67,7 @@
         // Don't clip alpha values for the drag outline if we're using the default widget preview
         boolean clipAlpha = !(mAddInfo instanceof PendingAddWidgetInfo &&
                 (((PendingAddWidgetInfo) mAddInfo).previewImage == 0));
-        HolographicOutlineHelper.obtain(mView.getContext())
+        HolographicOutlineHelper.getInstance(mView.getContext())
                 .applyExpensiveOutlineWithBlur(b, canvas, clipAlpha);
         canvas.setBitmap(null);
 
diff --git a/src/com/android/launcher3/widget/WidgetsContainerView.java b/src/com/android/launcher3/widget/WidgetsContainerView.java
index 89c44c8..8b72258 100644
--- a/src/com/android/launcher3/widget/WidgetsContainerView.java
+++ b/src/com/android/launcher3/widget/WidgetsContainerView.java
@@ -21,7 +21,6 @@
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.support.v7.widget.LinearLayoutManager;
-import android.support.v7.widget.RecyclerView.State;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.View;
@@ -45,11 +44,11 @@
 import com.android.launcher3.WidgetPreviewLoader;
 import com.android.launcher3.Workspace;
 import com.android.launcher3.dragndrop.DragController;
+import com.android.launcher3.graphics.LauncherIcons;
 import com.android.launcher3.model.WidgetsModel;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
 import com.android.launcher3.util.Thunk;
-import com.android.launcher3.util.TransformingTouchDelegate;
 
 /**
  * The widgets list view container.
@@ -64,12 +63,9 @@
     private DragController mDragController;
     private IconCache mIconCache;
 
-    private final Rect mTmpBgPaddingRect = new Rect();
-
     /* Recycler view related member variables */
     private WidgetsRecyclerView mRecyclerView;
     private WidgetsListAdapter mAdapter;
-    private TransformingTouchDelegate mRecyclerViewTouchDelegate;
 
     /* Touch handling related member variables. */
     private Toast mWidgetInstructionToast;
@@ -97,14 +93,8 @@
     }
 
     @Override
-    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
-        super.onLayout(changed, left, top, right, bottom);
-        getRevealView().getBackground().getPadding(mTmpBgPaddingRect);
-        mRecyclerViewTouchDelegate.setBounds(
-                mRecyclerView.getLeft() - mTmpBgPaddingRect.left,
-                mRecyclerView.getTop() - mTmpBgPaddingRect.top,
-                mRecyclerView.getRight() + mTmpBgPaddingRect.right,
-                mRecyclerView.getBottom() + mTmpBgPaddingRect.bottom);
+    public View getTouchDelegateTargetView() {
+        return mRecyclerView;
     }
 
     @Override
@@ -113,13 +103,6 @@
         mRecyclerView = (WidgetsRecyclerView) getContentView().findViewById(R.id.widgets_list_view);
         mRecyclerView.setAdapter(mAdapter);
         mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
-        mRecyclerViewTouchDelegate = new TransformingTouchDelegate(mRecyclerView);
-    }
-
-    @Override
-    protected void onAttachedToWindow() {
-        super.onAttachedToWindow();
-        ((View) mRecyclerView.getParent()).setTouchDelegate(mRecyclerViewTouchDelegate);
     }
 
     //
@@ -241,7 +224,7 @@
         } else {
             PendingAddShortcutInfo createShortcutInfo = (PendingAddShortcutInfo) v.getTag();
             Drawable icon = mIconCache.getFullResIcon(createShortcutInfo.activityInfo);
-            preview = Utilities.createIconBitmap(icon, mLauncher);
+            preview = LauncherIcons.createIconBitmap(icon, mLauncher);
             createItemInfo.spanX = createItemInfo.spanY = 1;
             scale = ((float) mLauncher.getDeviceProfile().iconSizePx) / preview.getWidth();
         }
diff --git a/tests/src/com/android/launcher3/BindWidgetTest.java b/tests/src/com/android/launcher3/BindWidgetTest.java
index 5c5069f..c133bf6 100644
--- a/tests/src/com/android/launcher3/BindWidgetTest.java
+++ b/tests/src/com/android/launcher3/BindWidgetTest.java
@@ -232,7 +232,6 @@
             runTestOnUiThread(new Runnable() {
                 @Override
                 public void run() {
-                    LauncherClings.markFirstRunClingDismissed(mTargetContext);
                     ManagedProfileHeuristic.markExistingUsersForNoFolderCreation(mTargetContext);
                     LauncherAppState.getInstance().getModel().resetLoadedState(true, true);
                 }
diff --git a/tests/src/com/android/launcher3/ui/LauncherInstrumentationTestCase.java b/tests/src/com/android/launcher3/ui/LauncherInstrumentationTestCase.java
index e858d17..e94fca6 100644
--- a/tests/src/com/android/launcher3/ui/LauncherInstrumentationTestCase.java
+++ b/tests/src/com/android/launcher3/ui/LauncherInstrumentationTestCase.java
@@ -23,7 +23,6 @@
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherAppWidgetProviderInfo;
-import com.android.launcher3.LauncherClings;
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
@@ -189,7 +188,6 @@
                 LauncherSettings.Settings.METHOD_CREATE_EMPTY_DB);
         LauncherSettings.Settings.call(mTargetContext.getContentResolver(),
                 LauncherSettings.Settings.METHOD_CLEAR_EMPTY_DB_FLAG);
-        LauncherClings.markFirstRunClingDismissed(mTargetContext);
         ManagedProfileHeuristic.markExistingUsersForNoFolderCreation(mTargetContext);
 
         runTestOnUiThread(new Runnable() {
diff --git a/tests/src/com/android/launcher3/util/FocusLogicTest.java b/tests/src/com/android/launcher3/util/FocusLogicTest.java
index eee567f..79aed80 100644
--- a/tests/src/com/android/launcher3/util/FocusLogicTest.java
+++ b/tests/src/com/android/launcher3/util/FocusLogicTest.java
@@ -49,8 +49,6 @@
          assertTrue(FocusLogic.shouldConsume(KeyEvent.KEYCODE_MOVE_END));
          assertTrue(FocusLogic.shouldConsume(KeyEvent.KEYCODE_PAGE_UP));
          assertTrue(FocusLogic.shouldConsume(KeyEvent.KEYCODE_PAGE_DOWN));
-         assertTrue(FocusLogic.shouldConsume(KeyEvent.KEYCODE_DEL));
-         assertTrue(FocusLogic.shouldConsume(KeyEvent.KEYCODE_FORWARD_DEL));
     }
 
     public void testCreateSparseMatrix() {