Converting widget panel into a floating view

> The widget panel is only inflated when needed
> Using the swipe up/down interaction for widgets tray
> Removing additional view wrappers from all-apps
> Widget tray is preserved across activity recreation
> Launcher no longer has WIDGET state, the actual code around
  the states will be removed in a follow-up cl

Bug: 67678570
Bug: 67585158
Change-Id: Ia29a7c33ec81e6c53cc24e2906b7022b6f41755b
diff --git a/src/com/android/launcher3/AbstractFloatingView.java b/src/com/android/launcher3/AbstractFloatingView.java
index 0fbad52..62e0fb1 100644
--- a/src/com/android/launcher3/AbstractFloatingView.java
+++ b/src/com/android/launcher3/AbstractFloatingView.java
@@ -20,6 +20,7 @@
 import android.content.Context;
 import android.support.annotation.IntDef;
 import android.util.AttributeSet;
+import android.util.Log;
 import android.view.MotionEvent;
 import android.view.View;
 import android.widget.LinearLayout;
@@ -40,7 +41,8 @@
             TYPE_FOLDER,
             TYPE_ACTION_POPUP,
             TYPE_WIDGETS_BOTTOM_SHEET,
-            TYPE_WIDGET_RESIZE_FRAME
+            TYPE_WIDGET_RESIZE_FRAME,
+            TYPE_WIDGETS_FULL_SHEET
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface FloatingViewType {}
@@ -48,6 +50,10 @@
     public static final int TYPE_ACTION_POPUP = 1 << 1;
     public static final int TYPE_WIDGETS_BOTTOM_SHEET = 1 << 2;
     public static final int TYPE_WIDGET_RESIZE_FRAME = 1 << 3;
+    public static final int TYPE_WIDGETS_FULL_SHEET = 1 << 4;
+
+    public static final int TYPE_ALL = TYPE_FOLDER | TYPE_ACTION_POPUP
+            | TYPE_WIDGETS_BOTTOM_SHEET | TYPE_WIDGET_RESIZE_FRAME | TYPE_WIDGETS_FULL_SHEET;
 
     protected boolean mIsOpen;
 
@@ -121,24 +127,31 @@
         }
     }
 
-    public static void closeAllOpenViews(Launcher launcher, boolean animate) {
+    public static void closeOpenViews(Launcher launcher, boolean animate,
+            @FloatingViewType int type) {
         DragLayer dragLayer = launcher.getDragLayer();
         // Iterate in reverse order. AbstractFloatingView is added later to the dragLayer,
         // and will be one of the last views.
         for (int i = dragLayer.getChildCount() - 1; i >= 0; i--) {
             View child = dragLayer.getChildAt(i);
             if (child instanceof AbstractFloatingView) {
-                ((AbstractFloatingView) child).close(animate);
+                AbstractFloatingView abs = (AbstractFloatingView) child;
+                if (abs.isOfType(type)) {
+                    abs.close(animate);
+                }
             }
         }
     }
 
+    public static void closeAllOpenViews(Launcher launcher, boolean animate) {
+        closeOpenViews(launcher, animate, TYPE_ALL);
+    }
+
     public static void closeAllOpenViews(Launcher launcher) {
         closeAllOpenViews(launcher, true);
     }
 
     public static AbstractFloatingView getTopOpenView(Launcher launcher) {
-        return getOpenView(launcher, TYPE_FOLDER | TYPE_ACTION_POPUP
-                | TYPE_WIDGETS_BOTTOM_SHEET | TYPE_WIDGET_RESIZE_FRAME);
+        return getOpenView(launcher, TYPE_ALL);
     }
 }
diff --git a/src/com/android/launcher3/BaseContainerView.java b/src/com/android/launcher3/BaseContainerView.java
deleted file mode 100644
index 82175b7..0000000
--- a/src/com/android/launcher3/BaseContainerView.java
+++ /dev/null
@@ -1,218 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3;
-
-import android.annotation.SuppressLint;
-import android.content.Context;
-import android.content.res.TypedArray;
-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.
- */
-public abstract class BaseContainerView extends FrameLayout
-        implements DeviceProfile.LauncherLayoutChangeListener {
-
-    private static final Rect sBgPaddingRect = new Rect();
-
-    protected final Drawable mBaseDrawable;
-
-    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);
-    }
-
-    public BaseContainerView(Context context, AttributeSet attrs) {
-        this(context, attrs, 0);
-    }
-
-    public BaseContainerView(Context context, AttributeSet attrs, int defStyleAttr) {
-        super(context, attrs, defStyleAttr);
-
-        if (this instanceof AllAppsContainerView) {
-            mBaseDrawable = new ColorDrawable();
-        } else {
-            TypedArray a = context.obtainStyledAttributes(attrs,
-                    R.styleable.BaseContainerView, defStyleAttr, 0);
-            mBaseDrawable = a.getDrawable(R.styleable.BaseContainerView_revealBackground);
-            a.recycle();
-        }
-    }
-
-    @Override
-    protected void onAttachedToWindow() {
-        super.onAttachedToWindow();
-
-        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
-    protected void onDetachedFromWindow() {
-        super.onDetachedFromWindow();
-
-        DeviceProfile grid = Launcher.getLauncher(getContext()).getDeviceProfile();
-        grid.removeLauncherLayoutChangedListener(this);
-    }
-
-    @Override
-    protected void onFinishInflate() {
-        super.onFinishInflate();
-
-        mContent = findViewById(R.id.main_content);
-        mRevealView = findViewById(R.id.reveal_view);
-
-        updatePaddings();
-    }
-
-    @Override
-    public void onLauncherLayoutChanged() {
-        updatePaddings();
-    }
-
-    /**
-     * Calculate the background padding as it can change due to insets/content padding change.
-     */
-    private void updatePaddings() {
-        DeviceProfile grid = Launcher.getLauncher(getContext()).getDeviceProfile();
-        int[] padding = grid.getContainerPadding();
-
-        int paddingLeft = padding[0];
-        int paddingRight = padding[1];
-        int paddingTop = 0;
-        int paddingBottom = 0;
-
-        if (!grid.isVerticalBarLayout()) {
-            paddingLeft += grid.edgeMarginPx;
-            paddingRight += grid.edgeMarginPx;
-            paddingTop = paddingBottom = grid.edgeMarginPx;
-        }
-        updateBackground(paddingLeft, paddingTop, paddingRight, paddingBottom);
-    }
-
-    /**
-     * Update the background for the reveal view and content view based on the background padding.
-     */
-    protected void updateBackground(int paddingLeft, int paddingTop,
-            int paddingRight, int paddingBottom) {
-        mRevealView.setBackground(new InsetDrawable(mBaseDrawable,
-                paddingLeft, paddingTop, paddingRight, paddingBottom));
-        mContent.setBackground(new InsetDrawable(mBaseDrawable,
-                paddingLeft, paddingTop, paddingRight, paddingBottom));
-    }
-
-    @Override
-    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
-        super.onLayout(changed, left, top, right, bottom);
-
-        View touchDelegateTargetView = getTouchDelegateTargetView();
-        if (touchDelegateTargetView != null) {
-            getRevealView().getBackground().getPadding(sBgPaddingRect);
-            mTouchDelegate.setBounds(
-                    touchDelegateTargetView.getLeft() - sBgPaddingRect.left,
-                    touchDelegateTargetView.getTop() - sBgPaddingRect.top,
-                    touchDelegateTargetView.getRight() + sBgPaddingRect.right,
-                    touchDelegateTargetView.getBottom() + sBgPaddingRect.bottom);
-        }
-    }
-
-    @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);
-    }
-
-    public final View getContentView() {
-        return mContent;
-    }
-
-    public final View getRevealView() {
-        return mRevealView;
-    }
-
-
-    /**
-     * 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/BaseRecyclerView.java b/src/com/android/launcher3/BaseRecyclerView.java
index 3ee6e51..afb83be 100644
--- a/src/com/android/launcher3/BaseRecyclerView.java
+++ b/src/com/android/launcher3/BaseRecyclerView.java
@@ -21,6 +21,7 @@
 import android.support.v7.widget.RecyclerView;
 import android.util.AttributeSet;
 import android.view.MotionEvent;
+import android.view.View;
 import android.view.ViewGroup;
 import android.widget.TextView;
 
@@ -99,11 +100,15 @@
         // DO NOT REMOVE, NEEDED IMPLEMENTATION FOR M BUILDS
     }
 
+    public int getScrollBarTop() {
+        return getPaddingTop();
+    }
+
     /**
      * Returns the height of the fast scroll bar
      */
     public int getScrollbarTrackHeight() {
-        return getHeight() - getPaddingTop() - getPaddingBottom();
+        return getHeight() - getScrollBarTop() - getPaddingBottom();
     }
 
     /**
@@ -121,13 +126,6 @@
         return availableScrollBarHeight;
     }
 
-    /**
-     * Returns the scrollbar for this recycler view.
-     */
-    public RecyclerViewFastScroller getScrollBar() {
-        return mScrollbar;
-    }
-
     @Override
     protected void dispatchDraw(Canvas canvas) {
         onUpdateScrollbar(0);
@@ -160,6 +158,28 @@
     }
 
     /**
+     * Returns whether the view itself will handle the touch event or not.
+     * @param ev MotionEvent in {@param eventSource}
+     */
+    public boolean shouldContainerScroll(MotionEvent ev, View eventSource) {
+        int[] point = new int[2];
+        point[0] = (int) ev.getX();
+        point[1] = (int) ev.getY();
+        Utilities.mapCoordInSelfToDescendant(mScrollbar, eventSource, point);
+        // IF the MotionEvent is inside the thumb, container should not be pulled down.
+        if (mScrollbar.shouldBlockIntercept(point[0], point[1])) {
+            return false;
+        }
+
+        // IF scroller is at the very top OR there is no scroll bar because there is probably not
+        // enough items to scroll, THEN it's okay for the container to be pulled down.
+        if (getCurrentScrollY() == 0) {
+            return true;
+        }
+        return false;
+    }
+
+    /**
      * @return whether fast scrolling is supported in the current state.
      */
     public boolean supportsFastScrolling() {
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 1bb4807..5c635ca 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -54,6 +54,7 @@
 import android.os.Build;
 import android.os.Bundle;
 import android.os.Handler;
+import android.os.Parcelable;
 import android.os.Process;
 import android.os.StrictMode;
 import android.os.UserHandle;
@@ -63,6 +64,7 @@
 import android.text.TextUtils;
 import android.text.method.TextKeyListener;
 import android.util.Log;
+import android.util.SparseArray;
 import android.view.Display;
 import android.view.HapticFeedbackConstants;
 import android.view.KeyEvent;
@@ -105,8 +107,6 @@
 import com.android.launcher3.logging.FileLog;
 import com.android.launcher3.logging.UserEventDispatcher;
 import com.android.launcher3.model.ModelWriter;
-import com.android.launcher3.model.PackageItemInfo;
-import com.android.launcher3.model.WidgetItem;
 import com.android.launcher3.notification.NotificationListener;
 import com.android.launcher3.pageindicators.PageIndicator;
 import com.android.launcher3.popup.BaseActionPopup;
@@ -136,7 +136,8 @@
 import com.android.launcher3.widget.PendingAddWidgetInfo;
 import com.android.launcher3.widget.WidgetAddFlowHandler;
 import com.android.launcher3.widget.WidgetHostViewLoader;
-import com.android.launcher3.widget.WidgetsContainerView;
+import com.android.launcher3.widget.WidgetListRowEntry;
+import com.android.launcher3.widget.WidgetsFullSheet;
 import com.android.launcher3.widget.custom.CustomWidgetParser;
 
 import java.io.FileDescriptor;
@@ -198,6 +199,8 @@
     private static final String RUNTIME_STATE_PENDING_REQUEST_ARGS = "launcher.request_args";
     // Type: ActivityResultInfo
     private static final String RUNTIME_STATE_PENDING_ACTIVITY_RESULT = "launcher.activity_result";
+    // Type: SparseArray<Parcelable>
+    private static final String RUNTIME_STATE_WIDGET_PANEL = "launcher.widget_panel";
 
     static final String APPS_VIEW_SHOWN = "launcher.apps_view_shown";
 
@@ -234,7 +237,6 @@
     private ViewGroup mOverviewPanel;
 
     private View mAllAppsButton;
-    private View mWidgetsButton;
 
     private DropTargetBar mDropTargetBar;
 
@@ -242,9 +244,6 @@
     @Thunk AllAppsContainerView mAppsView;
     AllAppsTransitionController mAllAppsController;
 
-    // Main container view and the model for the widget tray screen.
-    @Thunk WidgetsContainerView mWidgetsView;
-
     // We need to store the orientation Launcher was created with, due to a bug (b/64916689)
     // that results in widgets being inflated in the wrong orientation.
     private int mOrientation;
@@ -1043,8 +1042,6 @@
                 ? stateValues[stateOrdinal] : State.WORKSPACE;
         if (state == State.APPS) {
             showAppsView(false /* animated */);
-        } else if (state == State.WIDGETS) {
-            showWidgetsView(false, false);
         }
 
         PendingRequestArgs requestArgs = savedState.getParcelable(RUNTIME_STATE_PENDING_REQUEST_ARGS);
@@ -1053,6 +1050,12 @@
         }
 
         mPendingActivityResult = savedState.getParcelable(RUNTIME_STATE_PENDING_ACTIVITY_RESULT);
+
+        SparseArray<Parcelable> widgetsState =
+                savedState.getSparseParcelableArray(RUNTIME_STATE_WIDGET_PANEL);
+        if (widgetsState != null) {
+            WidgetsFullSheet.show(this, false).restoreHierarchyState(widgetsState);
+        }
     }
 
     /**
@@ -1093,9 +1096,8 @@
         // Get the search/delete/uninstall bar
         mDropTargetBar = mDragLayer.findViewById(R.id.drop_target_bar);
 
-        // Setup Apps and Widgets
-        mAppsView = (AllAppsContainerView) findViewById(R.id.apps_view);
-        mWidgetsView = (WidgetsContainerView) findViewById(R.id.widgets_view);
+        // Setup Apps
+        mAppsView = findViewById(R.id.apps_view);
 
         // Setup the drag controller (drop targets have to be added in reverse order in priority)
         mDragController.setMoveTarget(mWorkspace);
@@ -1110,7 +1112,7 @@
     }
 
     private void setupOverviewPanel() {
-        mOverviewPanel = (ViewGroup) findViewById(R.id.overview_panel);
+        mOverviewPanel = findViewById(R.id.overview_panel);
 
         // Bind wallpaper button actions
         View wallpaperButton = findViewById(R.id.wallpaper_button);
@@ -1122,13 +1124,12 @@
         }.attachTo(wallpaperButton);
 
         // Bind widget button actions
-        mWidgetsButton = findViewById(R.id.widget_button);
         new OverviewButtonClickListener(ControlType.WIDGETS_BUTTON) {
             @Override
             public void handleViewClick(View view) {
                 onClickAddWidgetButton(view);
             }
-        }.attachTo(mWidgetsButton);
+        }.attachTo(findViewById(R.id.widget_button));
 
         // Bind settings actions
         View settingsButton = findViewById(R.id.settings_button);
@@ -1155,14 +1156,6 @@
         mAllAppsButton = allAppsButton;
     }
 
-    public View getStartViewForAllAppsRevealAnimation() {
-        return FeatureFlags.NO_ALL_APPS_ICON ? mWorkspace.getPageIndicator() : mAllAppsButton;
-    }
-
-    public View getWidgetsButton() {
-        return mWidgetsButton;
-    }
-
     /**
      * Creates a view representing a shortcut.
      *
@@ -1325,7 +1318,7 @@
             if (Intent.ACTION_SCREEN_OFF.equals(action)) {
                 // Reset AllApps to its initial state only if we are not in the middle of
                 // processing a multi-step drop
-                if (mAppsView != null && mWidgetsView != null && mPendingRequestArgs == null) {
+                if (mAppsView != null && mPendingRequestArgs == null) {
                     if (!showWorkspace(false)) {
                         // If we are already on the workspace, then manually reset all apps
                         mAppsView.reset();
@@ -1385,10 +1378,6 @@
         return mAppsView;
     }
 
-    public WidgetsContainerView getWidgetsView() {
-        return mWidgetsView;
-    }
-
     public Workspace getWorkspace() {
         return mWorkspace;
     }
@@ -1468,11 +1457,6 @@
                 mAppsView.reset();
             }
 
-            // Reset the widgets view
-            if (!alreadyOnHome && mWidgetsView != null) {
-                mWidgetsView.scrollToTop();
-            }
-
             if (mLauncherCallbacks != null) {
                 mLauncherCallbacks.onHomeIntent();
             }
@@ -1517,9 +1501,19 @@
             outState.putInt(RUNTIME_STATE_CURRENT_SCREEN, mWorkspace.getNextPage());
 
         }
-        super.onSaveInstanceState(outState);
-
         outState.putInt(RUNTIME_STATE, mState.ordinal());
+
+
+        AbstractFloatingView widgets = AbstractFloatingView
+                .getOpenView(this, AbstractFloatingView.TYPE_WIDGETS_FULL_SHEET);
+        if (widgets != null) {
+            SparseArray<Parcelable> widgetsState = new SparseArray<>();
+            widgets.saveHierarchyState(widgetsState);
+            outState.putSparseParcelableArray(RUNTIME_STATE_WIDGET_PANEL, widgetsState);
+        } else {
+            outState.remove(RUNTIME_STATE_WIDGET_PANEL);
+        }
+
         // We close any open folders and shortcut containers since they will not be re-opened,
         // and we need to make sure this state is reflected.
         AbstractFloatingView.closeAllOpenViews(this, false);
@@ -1531,6 +1525,8 @@
             outState.putParcelable(RUNTIME_STATE_PENDING_ACTIVITY_RESULT, mPendingActivityResult);
         }
 
+        super.onSaveInstanceState(outState);
+
         if (mLauncherCallbacks != null) {
             mLauncherCallbacks.onSaveInstanceState(outState);
         }
@@ -2159,7 +2155,7 @@
         if (mIsSafeModeEnabled) {
             Toast.makeText(this, R.string.safemode_widget_error, Toast.LENGTH_SHORT).show();
         } else {
-            showWidgetsView(true /* animated */, true /* resetPageToZero */);
+            WidgetsFullSheet.show(this, true /* animated */);
         }
     }
 
@@ -2557,24 +2553,6 @@
     }
 
     /**
-     * Shows the widgets view.
-     */
-    void showWidgetsView(boolean animated, boolean resetPageToZero) {
-        if (LOGD) Log.d(TAG, "showWidgetsView:" + animated + " resetPageToZero:" + resetPageToZero);
-        if (resetPageToZero) {
-            mWidgetsView.scrollToTop();
-        }
-        showAppsOrWidgets(State.WIDGETS, animated);
-
-        mWidgetsView.post(new Runnable() {
-            @Override
-            public void run() {
-                mWidgetsView.requestFocus();
-            }
-        });
-    }
-
-    /**
      * Sets up the transition to show the apps/widgets view.
      *
      * @return whether the current from and to state allowed this operation
@@ -2642,7 +2620,6 @@
                     // Before we show workspace, hide all apps again because
                     // exitSpringLoadedDragMode made it visible. This is a bit hacky; we should
                     // clean up our state transition functions
-                    mWidgetsView.setVisibility(View.GONE);
                     showWorkspace(true, onCompleteRunnable);
                 } else {
                     exitSpringLoadedDragMode();
@@ -2661,8 +2638,6 @@
     public void exitSpringLoadedDragMode() {
         if (mState == State.APPS_SPRING_LOADED) {
             showAppsView(true /* animated */);
-        } else if (mState == State.WIDGETS_SPRING_LOADED) {
-            showWidgetsView(true, false);
         } else if (mState == State.WORKSPACE_SPRING_LOADED) {
             showWorkspace(true);
         }
@@ -2676,8 +2651,6 @@
         // Populate event with a fake title based on the current state.
         if (mState == State.APPS) {
             text.add(getString(R.string.all_apps_button_label));
-        } else if (mState == State.WIDGETS) {
-            text.add(getString(R.string.widget_button_text));
         } else if (mWorkspace != null) {
             text.add(mWorkspace.getCurrentPageDescription());
         } else {
@@ -2777,7 +2750,11 @@
      */
     public void startBinding() {
         TraceHelper.beginSection("startBinding");
-        AbstractFloatingView.closeAllOpenViews(this);
+        // Floating panels (except the full widget sheet) are associated with individual icons. If
+        // we are starting a fresh bind, close all such panels as all the icons are about
+        // to go away.
+        AbstractFloatingView.closeOpenViews(this, true,
+                AbstractFloatingView.TYPE_ALL & ~AbstractFloatingView.TYPE_WIDGETS_FULL_SHEET);
 
         setWorkspaceLoading(true);
 
@@ -3388,7 +3365,8 @@
     }
 
     @Override
-    public void bindAllWidgets(final MultiHashMap<PackageItemInfo, WidgetItem> allWidgets) {
+    public void bindAllWidgets(final ArrayList<WidgetListRowEntry> allWidgets) {
+        mPopupDataProvider.setAllWidgets(allWidgets);
         Runnable r = new RunnableWithId(RUNNABLE_ID_BIND_WIDGETS) {
             @Override
             public void run() {
@@ -3399,25 +3377,12 @@
             return;
         }
 
-        if (mWidgetsView != null && allWidgets != null) {
-            Executor pendingExecutor = getPendingExecutor();
-            if (pendingExecutor != null && mState != State.WIDGETS) {
-                pendingExecutor.execute(r);
-                return;
-            }
-            mWidgetsView.setWidgets(allWidgets);
-        }
-
         AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(this);
         if (topView != null) {
             topView.onWidgetsBound();
         }
     }
 
-    public List<WidgetItem> getWidgetsForPackageUser(PackageUserKey packageUserKey) {
-        return mWidgetsView.getWidgetsForPackageUser(packageUserKey);
-    }
-
     @Override
     public void notifyWidgetProvidersChanged() {
         if (mWorkspace.getState().shouldUpdateWidget) {
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index 3e22366..618bd0f 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -62,6 +62,7 @@
 import com.android.launcher3.util.Provider;
 import com.android.launcher3.util.Thunk;
 import com.android.launcher3.util.ViewOnDrawExecutor;
+import com.android.launcher3.widget.WidgetListRowEntry;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -153,7 +154,7 @@
         public void bindRestoreItemsChange(HashSet<ItemInfo> updates);
         public void bindWorkspaceComponentsRemoved(ItemInfoMatcher matcher);
         public void bindAppInfosRemoved(ArrayList<AppInfo> appInfos);
-        public void bindAllWidgets(MultiHashMap<PackageItemInfo, WidgetItem> widgets);
+        public void bindAllWidgets(ArrayList<WidgetListRowEntry> widgets);
         public void onPageBoundSynchronously(int page);
         public void executeOnNextDraw(ViewOnDrawExecutor executor);
         public void bindDeepShortcutMap(MultiHashMap<ComponentKey, String> deepShortcutMap);
diff --git a/src/com/android/launcher3/LauncherStateTransitionAnimation.java b/src/com/android/launcher3/LauncherStateTransitionAnimation.java
index be0ed0c..5823734 100644
--- a/src/com/android/launcher3/LauncherStateTransitionAnimation.java
+++ b/src/com/android/launcher3/LauncherStateTransitionAnimation.java
@@ -19,22 +19,15 @@
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
-import android.animation.ObjectAnimator;
-import android.animation.PropertyValuesHolder;
-import android.animation.TimeInterpolator;
-import android.animation.ValueAnimator;
 import android.content.res.Resources;
 import android.util.Log;
 import android.view.View;
-import android.view.animation.AccelerateInterpolator;
 
 import com.android.launcher3.allapps.AllAppsContainerView;
 import com.android.launcher3.allapps.AllAppsTransitionController;
 import com.android.launcher3.anim.AnimationLayerSet;
-import com.android.launcher3.anim.CircleRevealOutlineProvider;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.util.Thunk;
-import com.android.launcher3.widget.WidgetsContainerView;
 
 /**
  * TODO: figure out what kind of tests we can write for this
@@ -79,41 +72,8 @@
  */
 public class LauncherStateTransitionAnimation {
 
-    /**
-     * animation used for the widget tray
-     */
-    public static final int CIRCULAR_REVEAL = 0;
-    /**
-     * animation used for all apps tray
-     */
-    public static final int PULLUP = 1;
-
-    private static final float FINAL_REVEAL_ALPHA_FOR_WIDGETS = 0.3f;
-
-    /**
-     * Private callbacks made during transition setup.
-     */
-    private static class PrivateTransitionCallbacks {
-        private final float materialRevealViewFinalAlpha;
-
-        PrivateTransitionCallbacks(float revealAlpha) {
-            materialRevealViewFinalAlpha = revealAlpha;
-        }
-
-        float getMaterialRevealViewStartFinalRadius() {
-            return 0;
-        }
-        AnimatorListenerAdapter getMaterialRevealViewAnimatorListener(View revealView,
-                View buttonView) {
-            return null;
-        }
-        void onTransitionComplete() {}
-    }
-
     public static final String TAG = "LSTAnimation";
 
-    public static final int SINGLE_FRAME_DELAY = 16;
-
     @Thunk Launcher mLauncher;
     @Thunk AnimatorSet mCurrentAnimation;
     AllAppsTransitionController mAllAppsController;
@@ -128,49 +88,63 @@
      */
     public void startAnimationToAllApps(final boolean animated) {
         final AllAppsContainerView toView = mLauncher.getAppsView();
-        final View buttonView = mLauncher.getStartViewForAllAppsRevealAnimation();
-        PrivateTransitionCallbacks cb = new PrivateTransitionCallbacks(1f) {
+
+        final AnimatorSet animation = LauncherAnimUtils.createAnimatorSet();
+        final Resources res = mLauncher.getResources();
+        final int revealDurationSlide = res.getInteger(R.integer.config_overlaySlideRevealTime);
+
+        final AnimationLayerSet layerViews = new AnimationLayerSet();
+
+        // If for some reason our views aren't initialized, don't animate
+        boolean initialized = toView != null;
+
+        // Cancel the current animation
+        cancelAnimation();
+
+        playCommonTransitionAnimations(Workspace.State.NORMAL_HIDDEN,
+                animated, initialized, animation, layerViews);
+        if (!animated || !initialized) {
+            mAllAppsController.finishPullUp();
+            toView.setTranslationX(0.0f);
+            toView.setTranslationY(0.0f);
+            toView.setScaleX(1.0f);
+            toView.setScaleY(1.0f);
+            toView.setAlpha(1.0f);
+            toView.setVisibility(View.VISIBLE);
+
+            mLauncher.getUserEventDispatcher().resetElapsedContainerMillis();
+            return;
+        }
+        if (!FeatureFlags.LAUNCHER3_PHYSICS) {
+            // We are animating the content view alpha, so ensure we have a layer for it.
+            layerViews.addView(toView);
+        }
+
+        animation.addListener(new AnimatorListenerAdapter() {
             @Override
-            public float getMaterialRevealViewStartFinalRadius() {
-                int allAppsButtonSize = mLauncher.getDeviceProfile().allAppsButtonVisualSize;
-                return allAppsButtonSize / 2;
-            }
-            @Override
-            public AnimatorListenerAdapter getMaterialRevealViewAnimatorListener(
-                    final View revealView, final View allAppsButtonView) {
-                return new AnimatorListenerAdapter() {
-                    public void onAnimationStart(Animator animation) {
-                        allAppsButtonView.setVisibility(View.INVISIBLE);
-                    }
-                    public void onAnimationEnd(Animator animation) {
-                        allAppsButtonView.setVisibility(View.VISIBLE);
-                    }
-                };
-            }
-            @Override
-            void onTransitionComplete() {
+            public void onAnimationEnd(Animator animation) {
+                cleanupAnimation();
                 mLauncher.getUserEventDispatcher().resetElapsedContainerMillis();
             }
-        };
-        // Only animate the search bar if animating from spring loaded mode back to all apps
-        startAnimationToOverlay(
-                Workspace.State.NORMAL_HIDDEN, buttonView, toView, animated, PULLUP, cb);
+        });
+        boolean shouldPost = mAllAppsController.animateToAllApps(animation, revealDurationSlide);
+
+        Runnable startAnimRunnable = new StartAnimRunnable(animation, toView);
+        mCurrentAnimation = animation;
+        mCurrentAnimation.addListener(layerViews);
+        if (shouldPost) {
+            toView.post(startAnimRunnable);
+        } else {
+            startAnimRunnable.run();
+        }
     }
 
     /**
      * Starts an animation to the widgets view.
      */
     public void startAnimationToWidgets(final boolean animated) {
-        final WidgetsContainerView toView = mLauncher.getWidgetsView();
-        final View buttonView = mLauncher.getWidgetsButton();
-        startAnimationToOverlay(
-                Workspace.State.OVERVIEW_HIDDEN, buttonView, toView, animated, CIRCULAR_REVEAL,
-                new PrivateTransitionCallbacks(FINAL_REVEAL_ALPHA_FOR_WIDGETS){
-                    @Override
-                    void onTransitionComplete() {
-                        mLauncher.getUserEventDispatcher().resetElapsedContainerMillis();
-                    }
-                });
+        // TODO: Remove this
+        throw new RuntimeException("This cannot happen");
     }
 
     /**
@@ -188,10 +162,6 @@
         if (fromState == Launcher.State.APPS || fromState == Launcher.State.APPS_SPRING_LOADED
                 || mAllAppsController.isTransitioning()) {
             startAnimationToWorkspaceFromAllApps(fromWorkspaceState, toWorkspaceState,
-                    animated, PULLUP, onCompleteRunnable);
-        } else if (fromState == Launcher.State.WIDGETS ||
-                fromState == Launcher.State.WIDGETS_SPRING_LOADED) {
-            startAnimationToWorkspaceFromWidgets(fromWorkspaceState, toWorkspaceState,
                     animated, onCompleteRunnable);
         } else {
             startAnimationToNewWorkspaceState(fromWorkspaceState, toWorkspaceState,
@@ -200,159 +170,6 @@
     }
 
     /**
-     * Creates and starts a new animation to a particular overlay view.
-     */
-    private void startAnimationToOverlay(
-            final Workspace.State toWorkspaceState,
-            final View buttonView, final BaseContainerView toView,
-            final boolean animated, int animType, final PrivateTransitionCallbacks pCb) {
-        final AnimatorSet animation = LauncherAnimUtils.createAnimatorSet();
-        final Resources res = mLauncher.getResources();
-        final int revealDuration = res.getInteger(R.integer.config_overlayRevealTime);
-        final int revealDurationSlide = res.getInteger(R.integer.config_overlaySlideRevealTime);
-
-        final int itemsAlphaStagger = res.getInteger(R.integer.config_overlayItemsAlphaStagger);
-
-        final AnimationLayerSet layerViews = new AnimationLayerSet();
-
-        // If for some reason our views aren't initialized, don't animate
-        boolean initialized = buttonView != null;
-
-        // Cancel the current animation
-        cancelAnimation();
-
-        final View contentView = toView.getContentView();
-        playCommonTransitionAnimations(toWorkspaceState,
-                animated, initialized, animation, layerViews);
-        if (!animated || !initialized) {
-            if (toWorkspaceState == Workspace.State.NORMAL_HIDDEN) {
-                mAllAppsController.finishPullUp();
-            }
-            toView.setTranslationX(0.0f);
-            toView.setTranslationY(0.0f);
-            toView.setScaleX(1.0f);
-            toView.setScaleY(1.0f);
-            toView.setAlpha(1.0f);
-            toView.setVisibility(View.VISIBLE);
-
-            // Show the content view
-            contentView.setVisibility(View.VISIBLE);
-            pCb.onTransitionComplete();
-            return;
-        }
-        if (animType == CIRCULAR_REVEAL) {
-            // Setup the reveal view animation
-            final View revealView = toView.getRevealView();
-
-            int width = revealView.getMeasuredWidth();
-            int height = revealView.getMeasuredHeight();
-            float revealRadius = (float) Math.hypot(width / 2, height / 2);
-            revealView.setVisibility(View.VISIBLE);
-            revealView.setAlpha(0f);
-            revealView.setTranslationY(0f);
-            revealView.setTranslationX(0f);
-
-            // Calculate the final animation values
-            int[] buttonViewToPanelDelta =
-                    Utilities.getCenterDeltaInScreenSpace(revealView, buttonView);
-            final float revealViewToAlpha = pCb.materialRevealViewFinalAlpha;
-            final float revealViewToXDrift = buttonViewToPanelDelta[0];
-            final float revealViewToYDrift = buttonViewToPanelDelta[1];
-
-            // Create the animators
-            PropertyValuesHolder panelAlpha =
-                    PropertyValuesHolder.ofFloat(View.ALPHA, revealViewToAlpha, 1f);
-            PropertyValuesHolder panelDriftY =
-                    PropertyValuesHolder.ofFloat(View.TRANSLATION_Y, revealViewToYDrift, 0);
-            PropertyValuesHolder panelDriftX =
-                    PropertyValuesHolder.ofFloat(View.TRANSLATION_X, revealViewToXDrift, 0);
-            ObjectAnimator panelAlphaAndDrift = ObjectAnimator.ofPropertyValuesHolder(revealView,
-                    panelAlpha, panelDriftY, panelDriftX);
-            panelAlphaAndDrift.setDuration(revealDuration);
-            panelAlphaAndDrift.setInterpolator(new LogDecelerateInterpolator(100, 0));
-
-            // Play the animation
-            layerViews.addView(revealView);
-            animation.play(panelAlphaAndDrift);
-
-            // Setup the animation for the content view
-            contentView.setVisibility(View.VISIBLE);
-            contentView.setAlpha(0f);
-            contentView.setTranslationY(revealViewToYDrift);
-            layerViews.addView(contentView);
-
-            // Create the individual animators
-            ObjectAnimator pageDrift = ObjectAnimator.ofFloat(contentView, "translationY",
-                    revealViewToYDrift, 0);
-            pageDrift.setDuration(revealDuration);
-            pageDrift.setInterpolator(new LogDecelerateInterpolator(100, 0));
-            pageDrift.setStartDelay(itemsAlphaStagger);
-            animation.play(pageDrift);
-
-            ObjectAnimator itemsAlpha = ObjectAnimator.ofFloat(contentView, "alpha", 0f, 1f);
-            itemsAlpha.setDuration(revealDuration);
-            itemsAlpha.setInterpolator(new AccelerateInterpolator(1.5f));
-            itemsAlpha.setStartDelay(itemsAlphaStagger);
-            animation.play(itemsAlpha);
-
-            float startRadius = pCb.getMaterialRevealViewStartFinalRadius();
-            AnimatorListenerAdapter listener = pCb.getMaterialRevealViewAnimatorListener(
-                    revealView, buttonView);
-            Animator reveal = new CircleRevealOutlineProvider(width / 2, height / 2,
-                    startRadius, revealRadius).createRevealAnimator(revealView);
-            reveal.setDuration(revealDuration);
-            reveal.setInterpolator(new LogDecelerateInterpolator(100, 0));
-            if (listener != null) {
-                reveal.addListener(listener);
-            }
-            animation.play(reveal);
-
-            animation.addListener(new AnimatorListenerAdapter() {
-                @Override
-                public void onAnimationEnd(Animator animation) {
-                    // Hide the reveal view
-                    revealView.setVisibility(View.INVISIBLE);
-
-                    // This can hold unnecessary references to views.
-                    cleanupAnimation();
-                    pCb.onTransitionComplete();
-                }
-
-            });
-
-            toView.bringToFront();
-            toView.setVisibility(View.VISIBLE);
-
-            animation.addListener(layerViews);
-            toView.post(new StartAnimRunnable(animation, toView));
-            mCurrentAnimation = animation;
-        } else if (animType == PULLUP) {
-            if (!FeatureFlags.LAUNCHER3_PHYSICS) {
-                // We are animating the content view alpha, so ensure we have a layer for it.
-                layerViews.addView(contentView);
-            }
-
-            animation.addListener(new AnimatorListenerAdapter() {
-                @Override
-                public void onAnimationEnd(Animator animation) {
-                    cleanupAnimation();
-                    pCb.onTransitionComplete();
-                }
-            });
-            boolean shouldPost = mAllAppsController.animateToAllApps(animation, revealDurationSlide);
-
-            Runnable startAnimRunnable = new StartAnimRunnable(animation, toView);
-            mCurrentAnimation = animation;
-            mCurrentAnimation.addListener(layerViews);
-            if (shouldPost) {
-                toView.post(startAnimRunnable);
-            } else {
-                startAnimRunnable.run();
-            }
-        }
-    }
-
-    /**
      * Plays animations used by various transitions.
      */
     private void playCommonTransitionAnimations(
@@ -376,73 +193,73 @@
      * Starts an animation to the workspace from the apps view.
      */
     private void startAnimationToWorkspaceFromAllApps(final Workspace.State fromWorkspaceState,
-            final Workspace.State toWorkspaceState, final boolean animated, int type,
-            final Runnable onCompleteRunnable) {
-        // No alpha anim from all apps
-        PrivateTransitionCallbacks cb = new PrivateTransitionCallbacks(1f) {
-            @Override
-            float getMaterialRevealViewStartFinalRadius() {
-                int allAppsButtonSize = mLauncher.getDeviceProfile().allAppsButtonVisualSize;
-                return allAppsButtonSize / 2;
-            }
-            @Override
-            public AnimatorListenerAdapter getMaterialRevealViewAnimatorListener(
-                    final View revealView, final View allAppsButtonView) {
-                return new AnimatorListenerAdapter() {
-                    public void onAnimationStart(Animator animation) {
-                        // We set the alpha instead of visibility to ensure that the focus does not
-                        // get taken from the all apps view
-                        allAppsButtonView.setVisibility(View.VISIBLE);
-                        allAppsButtonView.setAlpha(0f);
-                    }
-                    public void onAnimationEnd(Animator animation) {
-                        // Hide the reveal view
-                        revealView.setVisibility(View.INVISIBLE);
-
-                        // Show the all apps button, and focus it
-                        allAppsButtonView.setAlpha(1f);
-                    }
-                };
-            }
-            @Override
-            void onTransitionComplete() {
-                mLauncher.getUserEventDispatcher().resetElapsedContainerMillis();
-            }
-        };
-        // Only animate the search bar if animating to spring loaded mode from all apps
-        startAnimationToWorkspaceFromOverlay(fromWorkspaceState, toWorkspaceState,
-                mLauncher.getStartViewForAllAppsRevealAnimation(), mLauncher.getAppsView(),
-                animated, type, onCompleteRunnable, cb);
-    }
-
-    /**
-     * Starts an animation to the workspace from the widgets view.
-     */
-    private void startAnimationToWorkspaceFromWidgets(final Workspace.State fromWorkspaceState,
             final Workspace.State toWorkspaceState, final boolean animated,
             final Runnable onCompleteRunnable) {
-        final WidgetsContainerView widgetsView = mLauncher.getWidgetsView();
-        PrivateTransitionCallbacks cb =
-                new PrivateTransitionCallbacks(FINAL_REVEAL_ALPHA_FOR_WIDGETS) {
-            @Override
-            public AnimatorListenerAdapter getMaterialRevealViewAnimatorListener(
-                    final View revealView, final View widgetsButtonView) {
-                return new AnimatorListenerAdapter() {
-                    public void onAnimationEnd(Animator animation) {
-                        // Hide the reveal view
-                        revealView.setVisibility(View.INVISIBLE);
-                    }
-                };
+
+        final AllAppsContainerView fromView = mLauncher.getAppsView();
+        final AnimatorSet animation = LauncherAnimUtils.createAnimatorSet();
+        final Resources res = mLauncher.getResources();
+        final int revealDurationSlide = res.getInteger(R.integer.config_overlaySlideRevealTime);
+
+        final View toView = mLauncher.getWorkspace();
+
+        final AnimationLayerSet layerViews = new AnimationLayerSet();
+
+        // If for some reason our views aren't initialized, don't animate
+        boolean initialized = fromView != null;
+
+        // Cancel the current animation
+        cancelAnimation();
+
+        playCommonTransitionAnimations(toWorkspaceState,
+                animated, initialized, animation, layerViews);
+        if (!animated || !initialized) {
+            if (fromWorkspaceState == Workspace.State.NORMAL_HIDDEN) {
+                mAllAppsController.finishPullDown();
             }
+            fromView.setVisibility(View.GONE);
+            mLauncher.getUserEventDispatcher().resetElapsedContainerMillis();
+
+            // Run any queued runnables
+            if (onCompleteRunnable != null) {
+                onCompleteRunnable.run();
+            }
+            return;
+        }
+
+        // We are animating the content view alpha, so ensure we have a layer for it
+        layerViews.addView(toView);
+
+        animation.addListener(new AnimatorListenerAdapter() {
+            boolean canceled = false;
             @Override
-            void onTransitionComplete() {
+            public void onAnimationCancel(Animator animation) {
+                canceled = true;
+            }
+
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                if (canceled) return;
+                // Run any queued runnables
+                if (onCompleteRunnable != null) {
+                    onCompleteRunnable.run();
+                }
+
+                cleanupAnimation();
                 mLauncher.getUserEventDispatcher().resetElapsedContainerMillis();
             }
-        };
-        startAnimationToWorkspaceFromOverlay(
-                fromWorkspaceState, toWorkspaceState,
-                mLauncher.getWidgetsButton(), widgetsView,
-                animated, CIRCULAR_REVEAL, onCompleteRunnable, cb);
+
+        });
+        boolean shouldPost = mAllAppsController.animateToWorkspace(animation, revealDurationSlide);
+
+        Runnable startAnimRunnable = new StartAnimRunnable(animation, toView);
+        mCurrentAnimation = animation;
+        mCurrentAnimation.addListener(layerViews);
+        if (shouldPost) {
+            fromView.post(startAnimRunnable);
+        } else {
+            startAnimRunnable.run();
+        }
     }
 
     /**
@@ -488,199 +305,6 @@
     }
 
     /**
-     * Creates and starts a new animation to the workspace.
-     */
-    private void startAnimationToWorkspaceFromOverlay(
-            final Workspace.State fromWorkspaceState, final Workspace.State toWorkspaceState,
-            final View buttonView, final BaseContainerView fromView,
-            final boolean animated, int animType, final Runnable onCompleteRunnable,
-            final PrivateTransitionCallbacks pCb) {
-        final AnimatorSet animation = LauncherAnimUtils.createAnimatorSet();
-        final Resources res = mLauncher.getResources();
-        final int revealDuration = res.getInteger(R.integer.config_overlayRevealTime);
-        final int revealDurationSlide = res.getInteger(R.integer.config_overlaySlideRevealTime);
-        final int itemsAlphaStagger = res.getInteger(R.integer.config_overlayItemsAlphaStagger);
-
-        final View toView = mLauncher.getWorkspace();
-        final View revealView = fromView.getRevealView();
-        final View contentView = fromView.getContentView();
-
-        final AnimationLayerSet layerViews = new AnimationLayerSet();
-
-        // If for some reason our views aren't initialized, don't animate
-        boolean initialized = buttonView != null;
-
-        // Cancel the current animation
-        cancelAnimation();
-
-        playCommonTransitionAnimations(toWorkspaceState,
-                animated, initialized, animation, layerViews);
-        if (!animated || !initialized) {
-            if (fromWorkspaceState == Workspace.State.NORMAL_HIDDEN) {
-                mAllAppsController.finishPullDown();
-            }
-            fromView.setVisibility(View.GONE);
-            pCb.onTransitionComplete();
-
-            // Run any queued runnables
-            if (onCompleteRunnable != null) {
-                onCompleteRunnable.run();
-            }
-            return;
-        }
-        if (animType == CIRCULAR_REVEAL) {
-            // hideAppsCustomizeHelper is called in some cases when it is already hidden
-            // don't perform all these no-op animations. In particularly, this was causing
-            // the all-apps button to pop in and out.
-            if (fromView.getVisibility() == View.VISIBLE) {
-                int width = revealView.getMeasuredWidth();
-                int height = revealView.getMeasuredHeight();
-                float revealRadius = (float) Math.hypot(width / 2, height / 2);
-                revealView.setVisibility(View.VISIBLE);
-                revealView.setAlpha(1f);
-                revealView.setTranslationY(0);
-                layerViews.addView(revealView);
-
-                // Calculate the final animation values
-                int[] buttonViewToPanelDelta = Utilities.getCenterDeltaInScreenSpace(revealView, buttonView);
-                final float revealViewToXDrift = buttonViewToPanelDelta[0];
-                final float revealViewToYDrift = buttonViewToPanelDelta[1];
-
-                // The vertical motion of the apps panel should be delayed by one frame
-                // from the conceal animation in order to give the right feel. We correspondingly
-                // shorten the duration so that the slide and conceal end at the same time.
-                TimeInterpolator decelerateInterpolator = new LogDecelerateInterpolator(100, 0);
-                ObjectAnimator panelDriftY = ObjectAnimator.ofFloat(revealView, "translationY",
-                        0, revealViewToYDrift);
-                panelDriftY.setDuration(revealDuration - SINGLE_FRAME_DELAY);
-                panelDriftY.setStartDelay(itemsAlphaStagger + SINGLE_FRAME_DELAY);
-                panelDriftY.setInterpolator(decelerateInterpolator);
-                animation.play(panelDriftY);
-
-                ObjectAnimator panelDriftX = ObjectAnimator.ofFloat(revealView, "translationX",
-                        0, revealViewToXDrift);
-                panelDriftX.setDuration(revealDuration - SINGLE_FRAME_DELAY);
-                panelDriftX.setStartDelay(itemsAlphaStagger + SINGLE_FRAME_DELAY);
-                panelDriftX.setInterpolator(decelerateInterpolator);
-                animation.play(panelDriftX);
-
-                // Setup animation for the reveal panel alpha
-                if (pCb.materialRevealViewFinalAlpha != 1f) {
-                    ObjectAnimator panelAlpha = ObjectAnimator.ofFloat(revealView, "alpha",
-                            1f, pCb.materialRevealViewFinalAlpha);
-                    panelAlpha.setDuration(revealDuration);
-                    panelAlpha.setInterpolator(decelerateInterpolator);
-                    animation.play(panelAlpha);
-                }
-
-                // Setup the animation for the content view
-                layerViews.addView(contentView);
-
-                // Create the individual animators
-                ObjectAnimator pageDrift = ObjectAnimator.ofFloat(contentView, "translationY",
-                        0, revealViewToYDrift);
-                contentView.setTranslationY(0);
-                pageDrift.setDuration(revealDuration - SINGLE_FRAME_DELAY);
-                pageDrift.setInterpolator(decelerateInterpolator);
-                pageDrift.setStartDelay(itemsAlphaStagger + SINGLE_FRAME_DELAY);
-                animation.play(pageDrift);
-
-                contentView.setAlpha(1f);
-                ObjectAnimator itemsAlpha = ObjectAnimator.ofFloat(contentView, "alpha", 1f, 0f);
-                itemsAlpha.setDuration(100);
-                itemsAlpha.setInterpolator(decelerateInterpolator);
-                animation.play(itemsAlpha);
-
-                // Invalidate the scrim throughout the animation to ensure the highlight
-                // cutout is correct throughout.
-                ValueAnimator invalidateScrim = ValueAnimator.ofFloat(0f, 1f);
-                invalidateScrim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
-                    @Override
-                    public void onAnimationUpdate(ValueAnimator animation) {
-                        mLauncher.getDragLayer().invalidateScrim();
-                    }
-                });
-                animation.play(invalidateScrim);
-
-                // Animate the all apps button
-                float finalRadius = pCb.getMaterialRevealViewStartFinalRadius();
-                AnimatorListenerAdapter listener =
-                        pCb.getMaterialRevealViewAnimatorListener(revealView, buttonView);
-                Animator reveal = new CircleRevealOutlineProvider(width / 2, height / 2,
-                        revealRadius, finalRadius).createRevealAnimator(revealView);
-                reveal.setInterpolator(new LogDecelerateInterpolator(100, 0));
-                reveal.setDuration(revealDuration);
-                reveal.setStartDelay(itemsAlphaStagger);
-                if (listener != null) {
-                    reveal.addListener(listener);
-                }
-                animation.play(reveal);
-            }
-
-            animation.addListener(new AnimatorListenerAdapter() {
-                @Override
-                public void onAnimationEnd(Animator animation) {
-                    fromView.setVisibility(View.GONE);
-                    // Run any queued runnables
-                    if (onCompleteRunnable != null) {
-                        onCompleteRunnable.run();
-                    }
-
-                    // Reset page transforms
-                    if (contentView != null) {
-                        contentView.setTranslationX(0);
-                        contentView.setTranslationY(0);
-                        contentView.setAlpha(1);
-                    }
-
-                    // This can hold unnecessary references to views.
-                    cleanupAnimation();
-                    pCb.onTransitionComplete();
-                }
-            });
-
-            mCurrentAnimation = animation;
-            mCurrentAnimation.addListener(layerViews);
-            fromView.post(new StartAnimRunnable(animation, null));
-        } else if (animType == PULLUP) {
-            // We are animating the content view alpha, so ensure we have a layer for it
-            layerViews.addView(contentView);
-
-            animation.addListener(new AnimatorListenerAdapter() {
-                boolean canceled = false;
-                @Override
-                public void onAnimationCancel(Animator animation) {
-                    canceled = true;
-                }
-
-                @Override
-                public void onAnimationEnd(Animator animation) {
-                    if (canceled) return;
-                    // Run any queued runnables
-                    if (onCompleteRunnable != null) {
-                        onCompleteRunnable.run();
-                    }
-
-                    cleanupAnimation();
-                    pCb.onTransitionComplete();
-                }
-
-            });
-            boolean shouldPost = mAllAppsController.animateToWorkspace(animation, revealDurationSlide);
-
-            Runnable startAnimRunnable = new StartAnimRunnable(animation, toView);
-            mCurrentAnimation = animation;
-            mCurrentAnimation.addListener(layerViews);
-            if (shouldPost) {
-                fromView.post(startAnimRunnable);
-            } else {
-                startAnimRunnable.run();
-            }
-        }
-        return;
-    }
-
-    /**
      * Cancels the current animation.
      */
     private void cancelAnimation() {
diff --git a/src/com/android/launcher3/WidgetPreviewLoader.java b/src/com/android/launcher3/WidgetPreviewLoader.java
index f150c89..bdfeae1 100644
--- a/src/com/android/launcher3/WidgetPreviewLoader.java
+++ b/src/com/android/launcher3/WidgetPreviewLoader.java
@@ -92,12 +92,11 @@
      * @return a request id which can be used to cancel the request.
      */
     public CancellationSignal getPreview(WidgetItem item, int previewWidth,
-            int previewHeight, WidgetCell caller, boolean animate) {
+            int previewHeight, WidgetCell caller) {
         String size = previewWidth + "x" + previewHeight;
         WidgetCacheKey key = new WidgetCacheKey(item.componentName, item.user, size);
 
-        PreviewLoadTask task = new PreviewLoadTask(key, item, previewWidth, previewHeight, caller,
-                animate);
+        PreviewLoadTask task = new PreviewLoadTask(key, item, previewWidth, previewHeight, caller);
         task.executeOnExecutor(Utilities.THREAD_POOL_EXECUTOR);
 
         CancellationSignal signal = new CancellationSignal();
@@ -527,19 +526,17 @@
         private final int mPreviewHeight;
         private final int mPreviewWidth;
         private final WidgetCell mCaller;
-        private final boolean mAnimatePreviewIn;
         private final BaseActivity mActivity;
         @Thunk long[] mVersions;
         @Thunk Bitmap mBitmapToRecycle;
 
         PreviewLoadTask(WidgetCacheKey key, WidgetItem info, int previewWidth,
-                int previewHeight, WidgetCell caller, boolean animate) {
+                int previewHeight, WidgetCell caller) {
             mKey = key;
             mInfo = info;
             mPreviewHeight = previewHeight;
             mPreviewWidth = previewWidth;
             mCaller = caller;
-            mAnimatePreviewIn = animate;
             mActivity = BaseActivity.fromContext(mCaller.getContext());
             if (DEBUG) {
                 Log.d(TAG, String.format("%s, %s, %d, %d",
@@ -595,7 +592,7 @@
 
         @Override
         protected void onPostExecute(final Bitmap preview) {
-            mCaller.applyPreview(preview, mAnimatePreviewIn);
+            mCaller.applyPreview(preview);
 
             // Write the generated preview to the DB in the worker thread
             if (mVersions != null) {
diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java
index d63ae41..91b196e 100644
--- a/src/com/android/launcher3/allapps/AllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java
@@ -16,10 +16,8 @@
 package com.android.launcher3.allapps;
 
 import android.content.Context;
-import android.graphics.Color;
+import android.graphics.Bitmap;
 import android.graphics.Rect;
-import android.graphics.drawable.ColorDrawable;
-import android.graphics.drawable.InsetDrawable;
 import android.support.v7.widget.LinearLayoutManager;
 import android.text.Selection;
 import android.text.SpannableStringBuilder;
@@ -28,10 +26,11 @@
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup;
+import android.widget.RelativeLayout;
 
 import com.android.launcher3.AppInfo;
-import com.android.launcher3.BaseContainerView;
 import com.android.launcher3.BubbleTextView;
+import com.android.launcher3.ClickShadowView;
 import com.android.launcher3.DeleteDropTarget;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.DragSource;
@@ -41,7 +40,6 @@
 import com.android.launcher3.Launcher;
 import com.android.launcher3.PromiseAppInfo;
 import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.SpringAnimationHandler;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.dragndrop.DragController;
@@ -49,9 +47,8 @@
 import com.android.launcher3.folder.Folder;
 import com.android.launcher3.keyboard.FocusedItemDecorator;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
-import com.android.launcher3.util.ComponentKey;
-import com.android.launcher3.util.ComponentKeyMapper;
 import com.android.launcher3.util.PackageUserKey;
+import com.android.launcher3.util.TransformingTouchDelegate;
 
 import java.util.List;
 import java.util.Set;
@@ -59,13 +56,17 @@
 /**
  * The all apps view container.
  */
-public class AllAppsContainerView extends BaseContainerView implements DragSource,
-        View.OnLongClickListener, Insettable {
+public class AllAppsContainerView extends RelativeLayout implements DragSource,
+        View.OnLongClickListener, Insettable, DeviceProfile.LauncherLayoutChangeListener,
+        BubbleTextView.BubbleTextShadowHandler {
+
+    protected final Rect mBasePadding = new Rect();
 
     private final Launcher mLauncher;
     private final AlphabeticalAppsList mApps;
     private final AllAppsGridAdapter mAdapter;
     private final LinearLayoutManager mLayoutManager;
+    private final ClickShadowView mTouchFeedbackView;
 
     private AllAppsRecyclerView mAppsRecyclerView;
     private SearchUiManager mSearchUiManager;
@@ -78,6 +79,8 @@
 
     private SpringAnimationHandler mSpringAnimationHandler;
 
+    private TransformingTouchDelegate mTouchDelegate;
+
     public AllAppsContainerView(Context context) {
         this(context, null);
     }
@@ -98,19 +101,69 @@
         mSearchQueryBuilder = new SpannableStringBuilder();
 
         Selection.setSelection(mSearchQueryBuilder, 0);
+
+        mTouchFeedbackView = new ClickShadowView(context);
+        // Make the feedback view large enough to hold the blur bitmap.
+        int size = mLauncher.getDeviceProfile().allAppsIconSizePx
+                + mTouchFeedbackView.getExtraSize();
+        addView(mTouchFeedbackView, size, size);
     }
 
     @Override
-    protected void updateBackground(
-            int paddingLeft, int paddingTop, int paddingRight, int paddingBottom) {
-        if (mLauncher.getDeviceProfile().isVerticalBarLayout()) {
-            getRevealView().setBackground(new InsetDrawable(mBaseDrawable,
-                    paddingLeft, paddingTop, paddingRight, paddingBottom));
-            getContentView().setBackground(
-                    new InsetDrawable(new ColorDrawable(Color.TRANSPARENT),
-                            paddingLeft, paddingTop, paddingRight, paddingBottom));
-        } else {
-            getRevealView().setBackground(mBaseDrawable);
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+
+        DeviceProfile grid = Launcher.getLauncher(getContext()).getDeviceProfile();
+        grid.addLauncherLayoutChangedListener(this);
+
+        mTouchDelegate = new TransformingTouchDelegate(mAppsRecyclerView);
+        setTouchDelegate(mTouchDelegate);
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+
+        DeviceProfile grid = Launcher.getLauncher(getContext()).getDeviceProfile();
+        grid.removeLauncherLayoutChangedListener(this);
+    }
+
+    /**
+     * Calculate the background padding as it can change due to insets/content padding change.
+     */
+    @Override
+    public void onLauncherLayoutChanged() {
+        DeviceProfile grid = mLauncher.getDeviceProfile();
+        if (!grid.isVerticalBarLayout()) {
+            return;
+        }
+
+        int[] padding = grid.getContainerPadding();
+        int paddingLeft = padding[0];
+        int paddingRight = padding[1];
+        mBasePadding.set(paddingLeft, 0, paddingRight, 0);
+        setPadding(paddingLeft, 0, paddingRight, 0);
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+        super.onLayout(changed, left, top, right, bottom);
+        mTouchDelegate.setBounds(
+                mAppsRecyclerView.getLeft() - mBasePadding.left,
+                mAppsRecyclerView.getTop() - mBasePadding.top,
+                mAppsRecyclerView.getRight() + mBasePadding.right,
+                mAppsRecyclerView.getBottom() + mBasePadding.bottom);
+    }
+
+    @Override
+    public void setPressedIcon(BubbleTextView icon, Bitmap background) {
+        if (icon == null || background == null) {
+            mTouchFeedbackView.setBitmap(null);
+            mTouchFeedbackView.animate().cancel();
+        } else if (mTouchFeedbackView.setBitmap(background)) {
+            View rv = findViewById(R.id.apps_list_view);
+            mTouchFeedbackView.alignWithIconView(icon, (ViewGroup) icon.getParent(), rv);
+            mTouchFeedbackView.animateShadow();
         }
     }
 
@@ -157,23 +210,7 @@
         if (mLauncher.getDragLayer().isEventOverView(mSearchContainer, ev)) {
             return true;
         }
-
-        int[] point = new int[2];
-        point[0] = (int) ev.getX();
-        point[1] = (int) ev.getY();
-        Utilities.mapCoordInSelfToDescendant(
-                mAppsRecyclerView.getScrollBar(), mLauncher.getDragLayer(), point);
-        // IF the MotionEvent is inside the thumb, container should not be pulled down.
-        if (mAppsRecyclerView.getScrollBar().shouldBlockIntercept(point[0], point[1])) {
-            return false;
-        }
-
-        // IF scroller is at the very top OR there is no scroll bar because there is probably not
-        // enough items to scroll, THEN it's okay for the container to be pulled down.
-        if (mAppsRecyclerView.getCurrentScrollY() == 0) {
-            return true;
-        }
-        return false;
+        return mAppsRecyclerView.shouldContainerScroll(ev, mLauncher.getDragLayer());
     }
 
     /**
@@ -191,7 +228,7 @@
 
         // This is a focus listener that proxies focus from a view into the list view.  This is to
         // work around the search box from getting first focus and showing the cursor.
-        getContentView().setOnFocusChangeListener(new View.OnFocusChangeListener() {
+        setOnFocusChangeListener(new View.OnFocusChangeListener() {
             @Override
             public void onFocusChange(View v, boolean hasFocus) {
                 if (hasFocus) {
@@ -222,9 +259,7 @@
         mAppsRecyclerView.preMeasureViews(mAdapter);
         mAdapter.setIconFocusListener(focusedItemDecorator.getFocusListener());
 
-        getRevealView().setVisibility(View.VISIBLE);
-        getContentView().setVisibility(View.VISIBLE);
-        getContentView().setBackground(null);
+        onLauncherLayoutChanged();
     }
 
     public SearchUiManager getSearchUiManager() {
@@ -232,11 +267,6 @@
     }
 
     @Override
-    public View getTouchDelegateTargetView() {
-        return mAppsRecyclerView;
-    }
-
-    @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         DeviceProfile grid = mLauncher.getDeviceProfile();
         // Update the number of items in the grid before we measure the view
diff --git a/src/com/android/launcher3/allapps/AllAppsRecyclerViewContainerView.java b/src/com/android/launcher3/allapps/AllAppsRecyclerViewContainerView.java
deleted file mode 100644
index 517dc94..0000000
--- a/src/com/android/launcher3/allapps/AllAppsRecyclerViewContainerView.java
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3.allapps;
-
-import android.content.Context;
-import android.graphics.Bitmap;
-import android.util.AttributeSet;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.RelativeLayout;
-
-import com.android.launcher3.BubbleTextView;
-import com.android.launcher3.BubbleTextView.BubbleTextShadowHandler;
-import com.android.launcher3.ClickShadowView;
-import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.Launcher;
-import com.android.launcher3.R;
-
-/**
- * A container for RecyclerView to allow for the click shadow view to be shown behind an icon that
- * is launching.
- */
-public class AllAppsRecyclerViewContainerView extends RelativeLayout
-        implements BubbleTextShadowHandler {
-
-    private final ClickShadowView mTouchFeedbackView;
-
-    public AllAppsRecyclerViewContainerView(Context context) {
-        this(context, null);
-    }
-
-    public AllAppsRecyclerViewContainerView(Context context, AttributeSet attrs) {
-        this(context, attrs, 0);
-    }
-
-    public AllAppsRecyclerViewContainerView(Context context, AttributeSet attrs, int defStyleAttr) {
-        super(context, attrs, defStyleAttr);
-
-        Launcher launcher = Launcher.getLauncher(context);
-        DeviceProfile grid = launcher.getDeviceProfile();
-
-        mTouchFeedbackView = new ClickShadowView(context);
-
-        // Make the feedback view large enough to hold the blur bitmap.
-        int size = grid.allAppsIconSizePx + mTouchFeedbackView.getExtraSize();
-        addView(mTouchFeedbackView, size, size);
-    }
-
-    @Override
-    public void setPressedIcon(BubbleTextView icon, Bitmap background) {
-        if (icon == null || background == null) {
-            mTouchFeedbackView.setBitmap(null);
-            mTouchFeedbackView.animate().cancel();
-        } else if (mTouchFeedbackView.setBitmap(background)) {
-            View rv = findViewById(R.id.apps_list_view);
-            mTouchFeedbackView.alignWithIconView(icon, (ViewGroup) icon.getParent(), rv);
-            mTouchFeedbackView.animateShadow();
-        }
-    }
-}
diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
index b844ba3..bb0822f 100644
--- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java
+++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
@@ -294,7 +294,7 @@
         float hotseatAlpha = mHotseatAccelInterpolator.getInterpolation(workspaceHotseatAlpha);
 
         updateAllAppsBg(alpha);
-        mAppsView.getContentView().setAlpha(alpha);
+        mAppsView.setAlpha(alpha);
         mAppsView.setTranslationY(shiftCurrent);
 
         if (!mLauncher.getDeviceProfile().isVerticalBarLayout()) {
diff --git a/src/com/android/launcher3/anim/CircleRevealOutlineProvider.java b/src/com/android/launcher3/anim/CircleRevealOutlineProvider.java
deleted file mode 100644
index 9fb6b49..0000000
--- a/src/com/android/launcher3/anim/CircleRevealOutlineProvider.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * 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.anim;
-
-public class CircleRevealOutlineProvider extends RevealOutlineAnimation {
-
-    private int mCenterX;
-    private int mCenterY;
-    private float mRadius0;
-    private float mRadius1;
-
-    /**
-     * @param x reveal center x
-     * @param y reveal center y
-     * @param r0 initial radius
-     * @param r1 final radius
-     */
-    public CircleRevealOutlineProvider(int x, int y, float r0, float r1) {
-        mCenterX = x;
-        mCenterY = y;
-        mRadius0 = r0;
-        mRadius1 = r1;
-    }
-
-    @Override
-    public boolean shouldRemoveElevationDuringAnimation() {
-        return true;
-    }
-
-    @Override
-    public void setProgress(float progress) {
-        mOutlineRadius = (1 - progress) * mRadius0 + progress * mRadius1;
-
-        mOutline.left = (int) (mCenterX - mOutlineRadius);
-        mOutline.top = (int) (mCenterY - mOutlineRadius);
-        mOutline.right = (int) (mCenterX + mOutlineRadius);
-        mOutline.bottom = (int) (mCenterY + mOutlineRadius);
-    }
-}
diff --git a/src/com/android/launcher3/model/BaseModelUpdateTask.java b/src/com/android/launcher3/model/BaseModelUpdateTask.java
index d5b5aa7..9aa30e7 100644
--- a/src/com/android/launcher3/model/BaseModelUpdateTask.java
+++ b/src/com/android/launcher3/model/BaseModelUpdateTask.java
@@ -28,6 +28,8 @@
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.ItemInfoMatcher;
 import com.android.launcher3.util.MultiHashMap;
+import com.android.launcher3.widget.WidgetListRowEntry;
+import com.android.launcher3.widget.WidgetsListAdapter;
 
 import java.util.ArrayList;
 import java.util.concurrent.Executor;
@@ -117,8 +119,8 @@
     }
 
     public void bindUpdatedWidgets(BgDataModel dataModel) {
-        final MultiHashMap<PackageItemInfo, WidgetItem> widgets
-                = dataModel.widgetsModel.getWidgetsMap();
+        final ArrayList<WidgetListRowEntry> widgets =
+                dataModel.widgetsModel.getWidgetsList(mApp.getContext());
         scheduleCallbackTask(new CallbackTask() {
             @Override
             public void execute(Callbacks callbacks) {
diff --git a/src/com/android/launcher3/model/LoaderResults.java b/src/com/android/launcher3/model/LoaderResults.java
index b7a6b68..24e5b9c 100644
--- a/src/com/android/launcher3/model/LoaderResults.java
+++ b/src/com/android/launcher3/model/LoaderResults.java
@@ -36,6 +36,8 @@
 import com.android.launcher3.util.LooperIdleLock;
 import com.android.launcher3.util.MultiHashMap;
 import com.android.launcher3.util.ViewOnDrawExecutor;
+import com.android.launcher3.widget.WidgetListRowEntry;
+import com.android.launcher3.widget.WidgetsListAdapter;
 
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
@@ -362,8 +364,8 @@
     }
 
     public void bindWidgets() {
-        final MultiHashMap<PackageItemInfo, WidgetItem> widgets
-                = mBgDataModel.widgetsModel.getWidgetsMap();
+        final ArrayList<WidgetListRowEntry> widgets =
+                mBgDataModel.widgetsModel.getWidgetsList(mApp.getContext());
         Runnable r = new Runnable() {
             public void run() {
                 Callbacks callbacks = mCallbacks.get();
diff --git a/src/com/android/launcher3/model/WidgetsModel.java b/src/com/android/launcher3/model/WidgetsModel.java
index ed900bf..1ff0dac 100644
--- a/src/com/android/launcher3/model/WidgetsModel.java
+++ b/src/com/android/launcher3/model/WidgetsModel.java
@@ -15,6 +15,7 @@
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherAppWidgetProviderInfo;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.compat.AlphabeticIndexCompat;
 import com.android.launcher3.compat.AppWidgetManagerCompat;
 import com.android.launcher3.compat.LauncherAppsCompat;
 import com.android.launcher3.compat.ShortcutConfigActivityInfo;
@@ -22,10 +23,14 @@
 import com.android.launcher3.util.MultiHashMap;
 import com.android.launcher3.util.PackageUserKey;
 import com.android.launcher3.util.Preconditions;
+import com.android.launcher3.widget.WidgetItemComparator;
+import com.android.launcher3.widget.WidgetListRowEntry;
 
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.Iterator;
+import java.util.Map;
 
 /**
  * Widgets data model that is used by the adapters of the widget views and controllers.
@@ -42,8 +47,26 @@
 
     private AppFilter mAppFilter;
 
-    public synchronized MultiHashMap<PackageItemInfo, WidgetItem> getWidgetsMap() {
-        return mWidgetsList.clone();
+    /**
+     * Returns a list of {@link WidgetListRowEntry}. All {@link WidgetItem} in a single row
+     * are sorted (based on label and user), but the overall list of {@link WidgetListRowEntry}s
+     * is not sorted. This list is sorted at the UI when using
+     * {@link com.android.launcher3.widget.WidgetsDiffReporter}
+     *
+     * @see com.android.launcher3.widget.WidgetsListAdapter#setWidgets(ArrayList)
+     */
+    public synchronized ArrayList<WidgetListRowEntry> getWidgetsList(Context context) {
+        ArrayList<WidgetListRowEntry> result = new ArrayList<>();
+        AlphabeticIndexCompat indexer = new AlphabeticIndexCompat(context);
+
+        WidgetItemComparator widgetComparator = new WidgetItemComparator();
+        for (Map.Entry<PackageItemInfo, ArrayList<WidgetItem>> entry : mWidgetsList.entrySet()) {
+            WidgetListRowEntry row = new WidgetListRowEntry(entry.getKey(), entry.getValue());
+            row.titleSectionName = indexer.computeSectionName(row.pkgItem.title);
+            Collections.sort(row.widgets, widgetComparator);
+            result.add(row);
+        }
+        return result;
     }
 
     /**
diff --git a/src/com/android/launcher3/popup/PopupDataProvider.java b/src/com/android/launcher3/popup/PopupDataProvider.java
index aeb7134..070ac39 100644
--- a/src/com/android/launcher3/popup/PopupDataProvider.java
+++ b/src/com/android/launcher3/popup/PopupDataProvider.java
@@ -25,6 +25,8 @@
 import com.android.launcher3.Launcher;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.badge.BadgeInfo;
+import com.android.launcher3.model.PackageItemInfo;
+import com.android.launcher3.model.WidgetItem;
 import com.android.launcher3.notification.NotificationInfo;
 import com.android.launcher3.notification.NotificationKeyData;
 import com.android.launcher3.notification.NotificationListener;
@@ -32,6 +34,7 @@
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.MultiHashMap;
 import com.android.launcher3.util.PackageUserKey;
+import com.android.launcher3.widget.WidgetListRowEntry;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -62,6 +65,8 @@
     private MultiHashMap<ComponentKey, String> mDeepShortcutMap = new MultiHashMap<>();
     /** Maps packages to their BadgeInfo's . */
     private Map<PackageUserKey, BadgeInfo> mPackageUserToBadgeInfos = new HashMap<>();
+    /** Maps packages to their Widgets */
+    private ArrayList<WidgetListRowEntry> mAllWidgets = new ArrayList<>();
 
     public PopupDataProvider(Launcher launcher) {
         mLauncher = launcher;
@@ -265,4 +270,29 @@
         }
         notificationListener.cancelNotification(notificationKey);
     }
+
+    public void setAllWidgets(ArrayList<WidgetListRowEntry> allWidgets) {
+        mAllWidgets = allWidgets;
+    }
+
+    public ArrayList<WidgetListRowEntry> getAllWidgets() {
+        return mAllWidgets;
+    }
+
+    public List<WidgetItem> getWidgetsForPackageUser(PackageUserKey packageUserKey) {
+        for (WidgetListRowEntry entry : mAllWidgets) {
+            if (entry.pkgItem.packageName.equals(packageUserKey.mPackageName)) {
+                ArrayList<WidgetItem> widgets = new ArrayList<>(entry.widgets);
+                // Remove widgets not associated with the correct user.
+                Iterator<WidgetItem> iterator = widgets.iterator();
+                while (iterator.hasNext()) {
+                    if (!iterator.next().user.equals(packageUserKey.mUser)) {
+                        iterator.remove();
+                    }
+                }
+                return widgets.isEmpty() ? null : widgets;
+            }
+        }
+        return null;
+    }
 }
diff --git a/src/com/android/launcher3/popup/SystemShortcut.java b/src/com/android/launcher3/popup/SystemShortcut.java
index 3f7bf42..e709b93 100644
--- a/src/com/android/launcher3/popup/SystemShortcut.java
+++ b/src/com/android/launcher3/popup/SystemShortcut.java
@@ -59,8 +59,9 @@
         @Override
         public View.OnClickListener getOnClickListener(final Launcher launcher,
                 final ItemInfo itemInfo) {
-            final List<WidgetItem> widgets = launcher.getWidgetsForPackageUser(new PackageUserKey(
-                    itemInfo.getTargetComponent().getPackageName(), itemInfo.user));
+            final List<WidgetItem> widgets =
+                    launcher.getPopupDataProvider().getWidgetsForPackageUser(new PackageUserKey(
+                            itemInfo.getTargetComponent().getPackageName(), itemInfo.user));
             if (widgets == null) {
                 return null;
             }
diff --git a/src/com/android/launcher3/views/RecyclerViewFastScroller.java b/src/com/android/launcher3/views/RecyclerViewFastScroller.java
index 7b5bcdb..8c9a441 100644
--- a/src/com/android/launcher3/views/RecyclerViewFastScroller.java
+++ b/src/com/android/launcher3/views/RecyclerViewFastScroller.java
@@ -285,7 +285,7 @@
             return;
         }
         int saveCount = canvas.save(Canvas.MATRIX_SAVE_FLAG);
-        canvas.translate(getWidth() / 2, mRv.getPaddingTop());
+        canvas.translate(getWidth() / 2, mRv.getScrollBarTop());
         // Draw the track
         float halfW = mWidth / 2;
         canvas.drawRoundRect(-halfW, 0, halfW, mRv.getScrollbarTrackHeight(),
@@ -317,7 +317,7 @@
      * Returns whether the specified point is inside the thumb bounds.
      */
     private boolean isNearThumb(int x, int y) {
-        int offset = y - mRv.getPaddingTop() - mThumbOffsetY;
+        int offset = y - mRv.getScrollBarTop() - mThumbOffsetY;
 
         return x >= 0 && x < getWidth() && offset >= 0 && offset <= mThumbHeight;
     }
@@ -348,7 +348,7 @@
     private void updatePopupY(int lastTouchY) {
         int height = mPopupView.getHeight();
         float top = lastTouchY - (FAST_SCROLL_OVERLAY_Y_OFFSET_FACTOR * height)
-                + mRv.getPaddingTop();
+                + mRv.getScrollBarTop();
         top = Utilities.boundToRange(top,
                 mMaxWidth, mRv.getScrollbarTrackHeight() - mMaxWidth - height);
         mPopupView.setTranslationY(top);
diff --git a/src/com/android/launcher3/widget/BaseWidgetSheet.java b/src/com/android/launcher3/widget/BaseWidgetSheet.java
new file mode 100644
index 0000000..ee5dd66b
--- /dev/null
+++ b/src/com/android/launcher3/widget/BaseWidgetSheet.java
@@ -0,0 +1,288 @@
+/*
+ * Copyright (C) 2017 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.widget;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.animation.PropertyValuesHolder;
+import android.content.Context;
+import android.graphics.Point;
+import android.util.AttributeSet;
+import android.util.Property;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.View.OnLongClickListener;
+import android.view.animation.AccelerateInterpolator;
+import android.view.animation.DecelerateInterpolator;
+import android.widget.Toast;
+
+import com.android.launcher3.AbstractFloatingView;
+import com.android.launcher3.DeleteDropTarget;
+import com.android.launcher3.DragSource;
+import com.android.launcher3.DropTarget.DragObject;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherAnimUtils;
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.dragndrop.DragOptions;
+import com.android.launcher3.folder.Folder;
+import com.android.launcher3.graphics.GradientView;
+import com.android.launcher3.touch.SwipeDetector;
+import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
+import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
+import com.android.launcher3.util.SystemUiController;
+import com.android.launcher3.util.Themes;
+
+/**
+ * Base class for various widgets popup
+ */
+abstract class BaseWidgetSheet extends AbstractFloatingView
+        implements OnClickListener, OnLongClickListener, DragSource, SwipeDetector.Listener {
+
+
+    protected static Property<BaseWidgetSheet, Float> TRANSLATION_SHIFT =
+            new Property<BaseWidgetSheet, Float>(Float.class, "translationShift") {
+
+                @Override
+                public Float get(BaseWidgetSheet view) {
+                    return view.mTranslationShift;
+                }
+
+                @Override
+                public void set(BaseWidgetSheet view, Float value) {
+                    view.setTranslationShift(value);
+                }
+            };
+    protected static final float TRANSLATION_SHIFT_CLOSED = 1f;
+    protected static final float TRANSLATION_SHIFT_OPENED = 0f;
+
+    /* Touch handling related member variables. */
+    private Toast mWidgetInstructionToast;
+
+    protected final Launcher mLauncher;
+    protected final SwipeDetector.ScrollInterpolator mScrollInterpolator;
+    protected final SwipeDetector mSwipeDetector;
+    protected final ObjectAnimator mOpenCloseAnimator;
+
+    protected View mContent;
+    protected GradientView mGradientView;
+
+    // range [0, 1], 0=> completely open, 1=> completely closed
+    protected float mTranslationShift = TRANSLATION_SHIFT_CLOSED;
+
+    protected boolean mNoIntercept;
+
+    public BaseWidgetSheet(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        mLauncher = Launcher.getLauncher(context);
+
+        mScrollInterpolator = new SwipeDetector.ScrollInterpolator();
+        mSwipeDetector = new SwipeDetector(context, this, SwipeDetector.VERTICAL);
+
+        mOpenCloseAnimator = LauncherAnimUtils.ofPropertyValuesHolder(this);
+        mOpenCloseAnimator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                mSwipeDetector.finishedScrolling();
+            }
+        });
+    }
+
+    @Override
+    public final void onClick(View v) {
+        // Let the user know that they have to long press to add a widget
+        if (mWidgetInstructionToast != null) {
+            mWidgetInstructionToast.cancel();
+        }
+
+        CharSequence msg = Utilities.wrapForTts(
+                getContext().getText(R.string.long_press_widget_to_add),
+                getContext().getString(R.string.long_accessible_way_to_add));
+        mWidgetInstructionToast = Toast.makeText(getContext(), msg, Toast.LENGTH_SHORT);
+        mWidgetInstructionToast.show();
+    }
+
+    @Override
+    public final boolean onLongClick(View v) {
+        if (!mLauncher.isDraggingEnabled()) return false;
+
+        if (v instanceof WidgetCell) {
+            return beginDraggingWidget((WidgetCell) v);
+        }
+        return true;
+    }
+
+    protected void setTranslationShift(float translationShift) {
+        mTranslationShift = translationShift;
+        mGradientView.setAlpha(1 - mTranslationShift);
+        mContent.setTranslationY(mTranslationShift * mContent.getHeight());
+    }
+
+    private boolean beginDraggingWidget(WidgetCell v) {
+        // Get the widget preview as the drag representation
+        WidgetImageView image = v.getWidgetView();
+
+        // If the ImageView doesn't have a drawable yet, the widget preview hasn't been loaded and
+        // we abort the drag.
+        if (image.getBitmap() == null) {
+            return false;
+        }
+
+        int[] loc = new int[2];
+        mLauncher.getDragLayer().getLocationInDragLayer(image, loc);
+
+        new PendingItemDragHelper(v).startDrag(
+                image.getBitmapBounds(), image.getBitmap().getWidth(), image.getWidth(),
+                new Point(loc[0], loc[1]), this, new DragOptions());
+        close(true);
+        return true;
+    }
+
+    //
+    // Drag related handling methods that implement {@link DragSource} interface.
+    //
+
+    @Override
+    public void onDropCompleted(View target, DragObject d, boolean isFlingToDelete,
+            boolean success) {
+        if (isFlingToDelete || !success || (target != mLauncher.getWorkspace() &&
+                !(target instanceof DeleteDropTarget) && !(target instanceof Folder))) {
+            // Exit spring loaded mode if we have not successfully dropped or have not handled the
+            // drop in Workspace
+            mLauncher.exitSpringLoadedDragModeDelayed(true,
+                    Launcher.EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, null);
+        }
+        mLauncher.unlockScreenOrientation(false);
+
+        if (!success) {
+            d.deferDragViewCleanupPostAnimation = false;
+        }
+    }
+
+
+    @Override
+    public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
+        if (ev.getAction() == MotionEvent.ACTION_UP && !mNoIntercept) {
+            // If we got ACTION_UP without ever returning true on intercept,
+            // the user never started dragging the bottom sheet.
+            if (!mLauncher.getDragLayer().isEventOverView(mContent, ev)) {
+                close(true);
+                return false;
+            }
+        }
+
+        if (mNoIntercept) {
+            return false;
+        }
+
+        int directionsToDetectScroll = mSwipeDetector.isIdleState() ?
+                SwipeDetector.DIRECTION_NEGATIVE : 0;
+        mSwipeDetector.setDetectableScrollConditions(
+                directionsToDetectScroll, false);
+        mSwipeDetector.onTouchEvent(ev);
+        return mSwipeDetector.isDraggingOrSettling();
+    }
+
+    @Override
+    public boolean onControllerTouchEvent(MotionEvent ev) {
+        return mSwipeDetector.onTouchEvent(ev);
+    }
+
+    /* SwipeDetector.Listener */
+
+    @Override
+    public void onDragStart(boolean start) { }
+
+    @Override
+    public boolean onDrag(float displacement, float velocity) {
+        float range = mContent.getHeight();
+        displacement = Utilities.boundToRange(displacement, 0, range);
+        setTranslationShift(displacement / range);
+        return true;
+    }
+
+    @Override
+    public void onDragEnd(float velocity, boolean fling) {
+        if ((fling && velocity > 0) || mTranslationShift > 0.5f) {
+            mScrollInterpolator.setVelocityAtZero(velocity);
+            mOpenCloseAnimator.setDuration(SwipeDetector.calculateDuration(
+                    velocity, TRANSLATION_SHIFT_CLOSED - mTranslationShift));
+            close(true);
+        } else {
+            mOpenCloseAnimator.setValues(PropertyValuesHolder.ofFloat(
+                    TRANSLATION_SHIFT, TRANSLATION_SHIFT_OPENED));
+            mOpenCloseAnimator.setDuration(
+                    SwipeDetector.calculateDuration(velocity, mTranslationShift))
+                    .setInterpolator(new DecelerateInterpolator());
+            mOpenCloseAnimator.start();
+        }
+    }
+
+    protected void handleClose(boolean animate, long defaultDuration) {
+        if (!mIsOpen || mOpenCloseAnimator.isRunning()) {
+            return;
+        }
+        if (animate) {
+            mOpenCloseAnimator.setValues(
+                    PropertyValuesHolder.ofFloat(TRANSLATION_SHIFT, TRANSLATION_SHIFT_CLOSED));
+            mOpenCloseAnimator.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    onCloseComplete();
+                }
+            });
+            if (mSwipeDetector.isIdleState()) {
+                mOpenCloseAnimator
+                        .setDuration(defaultDuration)
+                        .setInterpolator(new AccelerateInterpolator());
+            } else {
+                mOpenCloseAnimator.setInterpolator(mScrollInterpolator);
+            }
+            mOpenCloseAnimator.start();
+        } else {
+            setTranslationShift(TRANSLATION_SHIFT_CLOSED);
+            onCloseComplete();
+        }
+    }
+
+    protected void onCloseComplete() {
+        mIsOpen = false;
+        mLauncher.getDragLayer().removeView(this);
+        mLauncher.getSystemUiController().updateUiState(
+                SystemUiController.UI_STATE_WIDGET_BOTTOM_SHEET, 0);
+    }
+
+    protected void setupNavBarColor() {
+        boolean isSheetDark = Themes.getAttrBoolean(mLauncher, R.attr.isMainColorDark);
+        mLauncher.getSystemUiController().updateUiState(
+                SystemUiController.UI_STATE_WIDGET_BOTTOM_SHEET,
+                isSheetDark ? SystemUiController.FLAG_DARK_NAV : SystemUiController.FLAG_LIGHT_NAV);
+    }
+
+    @Override
+    public void fillInLogContainerData(View v, ItemInfo info, Target target, Target targetParent) {
+        targetParent.containerType = ContainerType.WIDGETS;
+    }
+
+    @Override
+    public final void logActionCommand(int command) {
+        // TODO: be more specific
+        mLauncher.getUserEventDispatcher().logActionCommand(command, ContainerType.WIDGETS);
+    }
+}
diff --git a/src/com/android/launcher3/widget/WidgetCell.java b/src/com/android/launcher3/widget/WidgetCell.java
index 40dbd52..2ba55ab 100644
--- a/src/com/android/launcher3/widget/WidgetCell.java
+++ b/src/com/android/launcher3/widget/WidgetCell.java
@@ -75,6 +75,9 @@
     protected CancellationSignal mActiveRequest;
     private boolean mAnimatePreview = true;
 
+    private boolean mApplyBitmapDeferred = false;
+    private Bitmap mDeferredBitmap;
+
     protected final BaseActivity mActivity;
 
     public WidgetCell(Context context) {
@@ -150,15 +153,31 @@
         return mWidgetImage;
     }
 
+    /**
+     * Sets if applying bitmap preview should be deferred. The UI will still load the bitmap, but
+     * will not cause invalidate, so that when deferring is disabled later, all the bitmaps are
+     * ready.
+     * This prevents invalidates while the animation is running.
+     */
+    public void setApplyBitmapDeferred(boolean isDeferred) {
+        if (mApplyBitmapDeferred != isDeferred) {
+            mApplyBitmapDeferred = isDeferred;
+            if (!mApplyBitmapDeferred && mDeferredBitmap != null) {
+                applyPreview(mDeferredBitmap);
+                mDeferredBitmap = null;
+            }
+        }
+    }
+
     public void setAnimatePreview(boolean shouldAnimate) {
         mAnimatePreview = shouldAnimate;
     }
 
     public void applyPreview(Bitmap bitmap) {
-        applyPreview(bitmap, true);
-    }
-
-    public void applyPreview(Bitmap bitmap, boolean animate) {
+        if (mApplyBitmapDeferred) {
+            mDeferredBitmap = bitmap;
+            return;
+        }
         if (bitmap != null) {
             mWidgetImage.setBitmap(bitmap,
                     DrawableFactory.get(getContext()).getBadgeForUser(mItem.user, getContext()));
@@ -173,15 +192,11 @@
     }
 
     public void ensurePreview() {
-        ensurePreview(true);
-    }
-
-    public void ensurePreview(boolean animate) {
         if (mActiveRequest != null) {
             return;
         }
         mActiveRequest = mWidgetPreviewLoader.getPreview(
-                mItem, mPresetPreviewSize, mPresetPreviewSize, this, animate);
+                mItem, mPresetPreviewSize, mPresetPreviewSize, this);
     }
 
     @Override
diff --git a/src/com/android/launcher3/widget/WidgetsBottomSheet.java b/src/com/android/launcher3/widget/WidgetsBottomSheet.java
index 7aa50a4..201bd1c 100644
--- a/src/com/android/launcher3/widget/WidgetsBottomSheet.java
+++ b/src/com/android/launcher3/widget/WidgetsBottomSheet.java
@@ -16,62 +16,38 @@
 
 package com.android.launcher3.widget;
 
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ObjectAnimator;
+import android.animation.PropertyValuesHolder;
 import android.content.Context;
 import android.graphics.Rect;
 import android.util.AttributeSet;
 import android.view.Gravity;
 import android.view.LayoutInflater;
-import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.animation.AnimationUtils;
 import android.view.animation.Interpolator;
 import android.widget.TextView;
 
-import com.android.launcher3.AbstractFloatingView;
-import com.android.launcher3.DropTarget;
 import com.android.launcher3.Insettable;
 import com.android.launcher3.ItemInfo;
-import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherAnimUtils;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
-import com.android.launcher3.anim.PropertyListBuilder;
-import com.android.launcher3.dragndrop.DragController;
-import com.android.launcher3.dragndrop.DragOptions;
 import com.android.launcher3.graphics.GradientView;
 import com.android.launcher3.model.WidgetItem;
-import com.android.launcher3.touch.SwipeDetector;
-import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 import com.android.launcher3.util.PackageUserKey;
-import com.android.launcher3.util.SystemUiController;
-import com.android.launcher3.util.Themes;
 
 import java.util.List;
 
 /**
  * Bottom sheet for the "Widgets" system shortcut in the long-press popup.
  */
-public class WidgetsBottomSheet extends AbstractFloatingView implements Insettable,
-        SwipeDetector.Listener, View.OnClickListener, View.OnLongClickListener,
-        DragController.DragListener {
+public class WidgetsBottomSheet extends BaseWidgetSheet implements Insettable {
 
-    private int mTranslationYOpen;
-    private int mTranslationYClosed;
-    private float mTranslationYRange;
-
-    private Launcher mLauncher;
+    private static final int DEFAULT_CLOSE_DURATION = 200;
     private ItemInfo mOriginalItemInfo;
-    private ObjectAnimator mOpenCloseAnimator;
     private Interpolator mFastOutSlowInInterpolator;
-    private SwipeDetector.ScrollInterpolator mScrollInterpolator;
     private Rect mInsets;
-    private SwipeDetector mSwipeDetector;
-    private GradientView mGradientBackground;
 
     public WidgetsBottomSheet(Context context, AttributeSet attrs) {
         this(context, attrs, 0);
@@ -80,23 +56,20 @@
     public WidgetsBottomSheet(Context context, AttributeSet attrs, int defStyleAttr) {
         super(context, attrs, defStyleAttr);
         setWillNotDraw(false);
-        mLauncher = Launcher.getLauncher(context);
-        mOpenCloseAnimator = LauncherAnimUtils.ofPropertyValuesHolder(this);
         mFastOutSlowInInterpolator =
                 AnimationUtils.loadInterpolator(context, android.R.interpolator.fast_out_slow_in);
-        mScrollInterpolator = new SwipeDetector.ScrollInterpolator();
         mInsets = new Rect();
-        mSwipeDetector = new SwipeDetector(context, this, SwipeDetector.VERTICAL);
-        mGradientBackground = (GradientView) mLauncher.getLayoutInflater().inflate(
+
+        mGradientView = (GradientView) mLauncher.getLayoutInflater().inflate(
                 R.layout.gradient_bg, mLauncher.getDragLayer(), false);
+        mGradientView.setProgress(1, false);
+        mContent = this;
     }
 
     @Override
-    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
-        mTranslationYOpen = 0;
-        mTranslationYClosed = getMeasuredHeight();
-        mTranslationYRange = mTranslationYClosed - mTranslationYOpen;
+    protected void onLayout(boolean changed, int l, int t, int r, int b) {
+        super.onLayout(changed, l, t, r, b);
+        setTranslationShift(mTranslationShift);
     }
 
     public void populateAndShow(ItemInfo itemInfo) {
@@ -106,21 +79,21 @@
 
         onWidgetsBound();
 
-        mLauncher.getDragLayer().addView(mGradientBackground);
+        mLauncher.getDragLayer().addView(mGradientView);
         mLauncher.getDragLayer().addView(this);
-        measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
-        setTranslationY(mTranslationYClosed);
         mIsOpen = false;
         open(true);
     }
 
     @Override
     protected void onWidgetsBound() {
-        List<WidgetItem> widgets = mLauncher.getWidgetsForPackageUser(new PackageUserKey(
-                mOriginalItemInfo.getTargetComponent().getPackageName(), mOriginalItemInfo.user));
+        List<WidgetItem> widgets = mLauncher.getPopupDataProvider().getWidgetsForPackageUser(
+                new PackageUserKey(
+                        mOriginalItemInfo.getTargetComponent().getPackageName(),
+                        mOriginalItemInfo.user));
 
-        ViewGroup widgetRow = (ViewGroup) findViewById(R.id.widgets);
-        ViewGroup widgetCells = (ViewGroup) widgetRow.findViewById(R.id.widgets_cell_list);
+        ViewGroup widgetRow = findViewById(R.id.widgets);
+        ViewGroup widgetCells = widgetRow.findViewById(R.id.widgets_cell_list);
 
         widgetCells.removeAllViews();
 
@@ -166,72 +139,31 @@
         return widget;
     }
 
-    @Override
-    public void onClick(View view) {
-        mLauncher.getWidgetsView().handleClick();
-    }
-
-    @Override
-    public boolean onLongClick(View view) {
-        mLauncher.getDragController().addDragListener(this);
-        return mLauncher.getWidgetsView().handleLongClick(view);
-    }
-
     private void open(boolean animate) {
         if (mIsOpen || mOpenCloseAnimator.isRunning()) {
             return;
         }
         mIsOpen = true;
-        boolean isSheetDark = Themes.getAttrBoolean(mLauncher, R.attr.isMainColorDark);
-        mLauncher.getSystemUiController().updateUiState(
-                SystemUiController.UI_STATE_WIDGET_BOTTOM_SHEET,
-                isSheetDark ? SystemUiController.FLAG_DARK_NAV : SystemUiController.FLAG_LIGHT_NAV);
+        setupNavBarColor();
         if (animate) {
-            mOpenCloseAnimator.setValues(new PropertyListBuilder()
-                    .translationY(mTranslationYOpen).build());
-            mOpenCloseAnimator.addListener(new AnimatorListenerAdapter() {
-                @Override
-                public void onAnimationEnd(Animator animation) {
-                    mSwipeDetector.finishedScrolling();
-                }
-            });
+            mOpenCloseAnimator.setValues(
+                    PropertyValuesHolder.ofFloat(TRANSLATION_SHIFT, TRANSLATION_SHIFT_OPENED));
             mOpenCloseAnimator.setInterpolator(mFastOutSlowInInterpolator);
             mOpenCloseAnimator.start();
         } else {
-            setTranslationY(mTranslationYOpen);
+            setTranslationShift(TRANSLATION_SHIFT_OPENED);
         }
     }
 
     @Override
     protected void handleClose(boolean animate) {
-        if (!mIsOpen || mOpenCloseAnimator.isRunning()) {
-            return;
-        }
-        if (animate) {
-            mOpenCloseAnimator.setValues(new PropertyListBuilder()
-                    .translationY(mTranslationYClosed).build());
-            mOpenCloseAnimator.addListener(new AnimatorListenerAdapter() {
-                @Override
-                public void onAnimationEnd(Animator animation) {
-                    mSwipeDetector.finishedScrolling();
-                    onCloseComplete();
-                }
-            });
-            mOpenCloseAnimator.setInterpolator(mSwipeDetector.isIdleState()
-                    ? mFastOutSlowInInterpolator : mScrollInterpolator);
-            mOpenCloseAnimator.start();
-        } else {
-            setTranslationY(mTranslationYClosed);
-            onCloseComplete();
-        }
+        handleClose(animate, DEFAULT_CLOSE_DURATION);
     }
 
-    private void onCloseComplete() {
-        mIsOpen = false;
-        mLauncher.getDragLayer().removeView(mGradientBackground);
-        mLauncher.getDragLayer().removeView(WidgetsBottomSheet.this);
-        mLauncher.getSystemUiController().updateUiState(
-                SystemUiController.UI_STATE_WIDGET_BOTTOM_SHEET, 0);
+    @Override
+    protected void onCloseComplete() {
+        super.onCloseComplete();
+        mLauncher.getDragLayer().removeView(mGradientView);
     }
 
     @Override
@@ -249,83 +181,4 @@
         setPadding(getPaddingLeft() + leftInset, getPaddingTop(),
                 getPaddingRight() + rightInset, getPaddingBottom() + bottomInset);
     }
-
-    /* SwipeDetector.Listener */
-
-    @Override
-    public void onDragStart(boolean start) {
-    }
-
-    @Override
-    public boolean onDrag(float displacement, float velocity) {
-        setTranslationY(Utilities.boundToRange(displacement, mTranslationYOpen,
-                mTranslationYClosed));
-        return true;
-    }
-
-    @Override
-    public void setTranslationY(float translationY) {
-        super.setTranslationY(translationY);
-        if (mGradientBackground == null) return;
-        float p = (mTranslationYClosed - translationY) / mTranslationYRange;
-        boolean showScrim = p <= 0;
-        mGradientBackground.setProgress(p, showScrim);
-    }
-
-    @Override
-    public void onDragEnd(float velocity, boolean fling) {
-        if ((fling && velocity > 0) || getTranslationY() > (mTranslationYRange) / 2) {
-            mScrollInterpolator.setVelocityAtZero(velocity);
-            mOpenCloseAnimator.setDuration(SwipeDetector.calculateDuration(velocity,
-                    (mTranslationYClosed - getTranslationY()) / mTranslationYRange));
-            close(true);
-        } else {
-            mIsOpen = false;
-            mOpenCloseAnimator.setDuration(SwipeDetector.calculateDuration(velocity,
-                    (getTranslationY() - mTranslationYOpen) / mTranslationYRange));
-            open(true);
-        }
-    }
-
-    @Override
-    public void logActionCommand(int command) {
-        // TODO: be more specific
-        mLauncher.getUserEventDispatcher().logActionCommand(command, ContainerType.WIDGETS);
-    }
-
-    @Override
-    public boolean onControllerTouchEvent(MotionEvent ev) {
-        return mSwipeDetector.onTouchEvent(ev);
-    }
-
-    @Override
-    public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
-        if (ev.getAction() == MotionEvent.ACTION_UP) {
-            // If we got ACTION_UP without ever returning true on intercept,
-            // the user never started dragging the bottom sheet.
-            if (!mLauncher.getDragLayer().isEventOverView(this, ev)) {
-                close(true);
-                return false;
-            }
-        }
-
-        int directionsToDetectScroll = mSwipeDetector.isIdleState() ?
-                SwipeDetector.DIRECTION_NEGATIVE : 0;
-        mSwipeDetector.setDetectableScrollConditions(
-                directionsToDetectScroll, false);
-        mSwipeDetector.onTouchEvent(ev);
-        return mSwipeDetector.isDraggingOrSettling();
-    }
-
-    /* DragListener */
-
-    @Override
-    public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) {
-        // A widget or custom shortcut was dragged.
-        close(true);
-    }
-
-    @Override
-    public void onDragEnd() {
-    }
 }
diff --git a/src/com/android/launcher3/widget/WidgetsContainerView.java b/src/com/android/launcher3/widget/WidgetsContainerView.java
deleted file mode 100644
index 39dd0d4..0000000
--- a/src/com/android/launcher3/widget/WidgetsContainerView.java
+++ /dev/null
@@ -1,243 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3.widget;
-
-import android.content.Context;
-import android.content.pm.LauncherApps;
-import android.graphics.Point;
-import android.support.v7.widget.LinearLayoutManager;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.Toast;
-
-import com.android.launcher3.BaseContainerView;
-import com.android.launcher3.DeleteDropTarget;
-import com.android.launcher3.DragSource;
-import com.android.launcher3.DropTarget.DragObject;
-import com.android.launcher3.ItemInfo;
-import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
-import com.android.launcher3.compat.AlphabeticIndexCompat;
-import com.android.launcher3.dragndrop.DragOptions;
-import com.android.launcher3.folder.Folder;
-import com.android.launcher3.model.PackageItemInfo;
-import com.android.launcher3.model.WidgetItem;
-import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
-import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
-import com.android.launcher3.util.MultiHashMap;
-import com.android.launcher3.util.PackageUserKey;
-import com.android.launcher3.util.Thunk;
-
-import java.util.List;
-
-/**
- * The widgets list view container.
- */
-public class WidgetsContainerView extends BaseContainerView
-        implements View.OnLongClickListener, View.OnClickListener, DragSource {
-    private static final String TAG = "WidgetsContainerView";
-    private static final boolean LOGD = false;
-
-    /* Global instances that are used inside this container. */
-    @Thunk Launcher mLauncher;
-
-    /* Recycler view related member variables */
-    private WidgetsRecyclerView mRecyclerView;
-    private WidgetsListAdapter mAdapter;
-
-    /* Touch handling related member variables. */
-    private Toast mWidgetInstructionToast;
-
-    public WidgetsContainerView(Context context) {
-        this(context, null);
-    }
-
-    public WidgetsContainerView(Context context, AttributeSet attrs) {
-        this(context, attrs, 0);
-    }
-
-    public WidgetsContainerView(Context context, AttributeSet attrs, int defStyleAttr) {
-        super(context, attrs, defStyleAttr);
-        mLauncher = Launcher.getLauncher(context);
-        LauncherAppState apps = LauncherAppState.getInstance(context);
-        mAdapter = new WidgetsListAdapter(context, LayoutInflater.from(context),
-                apps.getWidgetCache(), new AlphabeticIndexCompat(context), this, this,
-                new WidgetsDiffReporter(apps.getIconCache()));
-        mAdapter.setNotifyListener();
-        if (LOGD) {
-            Log.d(TAG, "WidgetsContainerView constructor");
-        }
-    }
-
-    @Override
-    public View getTouchDelegateTargetView() {
-        return mRecyclerView;
-    }
-
-    @Override
-    protected void onFinishInflate() {
-        super.onFinishInflate();
-        mRecyclerView = (WidgetsRecyclerView) getContentView().findViewById(R.id.widgets_list_view);
-        mRecyclerView.setAdapter(mAdapter);
-        mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
-    }
-
-    //
-    // Returns views used for launcher transitions.
-    //
-
-    public void scrollToTop() {
-        mRecyclerView.scrollToPosition(0);
-    }
-
-    //
-    // Touch related handling.
-    //
-
-    @Override
-    public void onClick(View v) {
-        // When we have exited widget tray or are in transition, disregard clicks
-        if (!mLauncher.isWidgetsViewVisible()
-                || mLauncher.getWorkspace().isSwitchingState()
-                || !(v instanceof WidgetCell)) return;
-
-        handleClick();
-    }
-
-    public void handleClick() {
-        // Let the user know that they have to long press to add a widget
-        if (mWidgetInstructionToast != null) {
-            mWidgetInstructionToast.cancel();
-        }
-
-        CharSequence msg = Utilities.wrapForTts(
-                getContext().getText(R.string.long_press_widget_to_add),
-                getContext().getString(R.string.long_accessible_way_to_add));
-        mWidgetInstructionToast = Toast.makeText(getContext(), msg, Toast.LENGTH_SHORT);
-        mWidgetInstructionToast.show();
-    }
-
-    @Override
-    public boolean onLongClick(View v) {
-        // When we have exited the widget tray, disregard long clicks
-        if (!mLauncher.isWidgetsViewVisible()) return false;
-        return handleLongClick(v);
-    }
-
-    public boolean handleLongClick(View v) {
-        if (LOGD) {
-            Log.d(TAG, String.format("onLongClick [v=%s]", v));
-        }
-        // When we  are in transition, disregard long clicks
-        if (mLauncher.getWorkspace().isSwitchingState()) return false;
-        // Return if global dragging is not enabled
-        if (!mLauncher.isDraggingEnabled()) return false;
-
-        return beginDragging(v);
-    }
-
-    private boolean beginDragging(View v) {
-        if (v instanceof WidgetCell) {
-            if (!beginDraggingWidget((WidgetCell) v)) {
-                return false;
-            }
-        } else {
-            Log.e(TAG, "Unexpected dragging view: " + v);
-        }
-
-        // We don't enter spring-loaded mode if the drag has been cancelled
-        if (mLauncher.getDragController().isDragging()) {
-            // Go into spring loaded mode (must happen before we startDrag())
-            mLauncher.enterSpringLoadedDragMode();
-        }
-
-        return true;
-    }
-
-    private boolean beginDraggingWidget(WidgetCell v) {
-        // Get the widget preview as the drag representation
-        WidgetImageView image = (WidgetImageView) v.findViewById(R.id.widget_preview);
-
-        // If the ImageView doesn't have a drawable yet, the widget preview hasn't been loaded and
-        // we abort the drag.
-        if (image.getBitmap() == null) {
-            return false;
-        }
-
-        int[] loc = new int[2];
-        mLauncher.getDragLayer().getLocationInDragLayer(image, loc);
-
-        new PendingItemDragHelper(v).startDrag(
-                image.getBitmapBounds(), image.getBitmap().getWidth(), image.getWidth(),
-                new Point(loc[0], loc[1]), this, new DragOptions());
-        return true;
-    }
-
-    //
-    // Drag related handling methods that implement {@link DragSource} interface.
-    //
-
-    @Override
-    public void onDropCompleted(View target, DragObject d, boolean isFlingToDelete,
-            boolean success) {
-        if (LOGD) {
-            Log.d(TAG, "onDropCompleted");
-        }
-        if (isFlingToDelete || !success || (target != mLauncher.getWorkspace() &&
-                !(target instanceof DeleteDropTarget) && !(target instanceof Folder))) {
-            // Exit spring loaded mode if we have not successfully dropped or have not handled the
-            // drop in Workspace
-            mLauncher.exitSpringLoadedDragModeDelayed(true,
-                    Launcher.EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, null);
-        }
-        mLauncher.unlockScreenOrientation(false);
-
-        if (!success) {
-            d.deferDragViewCleanupPostAnimation = false;
-        }
-    }
-
-    /**
-     * Initialize the widget data model.
-     */
-    public void setWidgets(MultiHashMap<PackageItemInfo, WidgetItem> model) {
-        mAdapter.setWidgets(model);
-
-        View loader = getContentView().findViewById(R.id.loader);
-        if (loader != null) {
-            ((ViewGroup) getContentView()).removeView(loader);
-        }
-    }
-
-    public boolean isEmpty() {
-        return mAdapter.getItemCount() == 0;
-    }
-
-    public List<WidgetItem> getWidgetsForPackageUser(PackageUserKey packageUserKey) {
-        return mAdapter.copyWidgetsForPackageUser(packageUserKey);
-    }
-
-    @Override
-    public void fillInLogContainerData(View v, ItemInfo info, Target target, Target targetParent) {
-        targetParent.containerType = ContainerType.WIDGETS;
-    }
-}
\ No newline at end of file
diff --git a/src/com/android/launcher3/widget/WidgetsDiffReporter.java b/src/com/android/launcher3/widget/WidgetsDiffReporter.java
index 52deec3..d67f403 100644
--- a/src/com/android/launcher3/widget/WidgetsDiffReporter.java
+++ b/src/com/android/launcher3/widget/WidgetsDiffReporter.java
@@ -16,6 +16,7 @@
 
 package com.android.launcher3.widget;
 
+import android.support.v7.widget.RecyclerView;
 import android.util.Log;
 
 import com.android.launcher3.IconCache;
@@ -26,26 +27,18 @@
 import java.util.Iterator;
 
 /**
- * Do diff on widget's tray list items and call the {@link NotifyListener} methods accordingly.
+ * Do diff on widget's tray list items and call the {@link RecyclerView.Adapter}
+ * methods accordingly.
  */
 public class WidgetsDiffReporter {
-    private final boolean DEBUG = false;
-    private final String TAG = "WidgetsDiffReporter";
+    private static final boolean DEBUG = false;
+    private static final String TAG = "WidgetsDiffReporter";
+
     private final IconCache mIconCache;
-    private NotifyListener mListener;
+    private final RecyclerView.Adapter mListener;
 
-    public interface NotifyListener {
-        void notifyDataSetChanged();
-        void notifyItemChanged(int index);
-        void notifyItemInserted(int index);
-        void notifyItemRemoved(int index);
-    }
-
-    public WidgetsDiffReporter(IconCache iconCache) {
+    public WidgetsDiffReporter(IconCache iconCache, RecyclerView.Adapter listener) {
         mIconCache = iconCache;
-    }
-
-    public void setListener(NotifyListener listener) {
         mListener = listener;
     }
 
@@ -55,9 +48,17 @@
             Log.d(TAG, "process oldEntries#=" + currentEntries.size()
                     + " newEntries#=" + newEntries.size());
         }
-        if (currentEntries.size() == 0 && newEntries.size() > 0) {
-            currentEntries.addAll(newEntries);
-            mListener.notifyDataSetChanged();
+        // Early exit if either of the list is empty
+        if (currentEntries.isEmpty() || newEntries.isEmpty()) {
+            // Skip if both list are empty.
+            // On rotation, we open the widget tray with empty. Then try to fetch the list again
+            // when the animation completes (which still gives empty). And we get the final result
+            // when the bind actually completes.
+            if (currentEntries.size() != newEntries.size()) {
+                currentEntries.clear();
+                currentEntries.addAll(newEntries);
+                mListener.notifyDataSetChanged();
+            }
             return;
         }
         ArrayList<WidgetListRowEntry> orgEntries =
diff --git a/src/com/android/launcher3/widget/WidgetsFullSheet.java b/src/com/android/launcher3/widget/WidgetsFullSheet.java
new file mode 100644
index 0000000..72277a2
--- /dev/null
+++ b/src/com/android/launcher3/widget/WidgetsFullSheet.java
@@ -0,0 +1,222 @@
+/*
+ * Copyright (C) 2017 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.widget;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.PropertyValuesHolder;
+import android.content.Context;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.animation.AnimationUtils;
+
+import com.android.launcher3.Insettable;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherAppWidgetHost.ProviderChangedListener;
+import com.android.launcher3.R;
+
+/**
+ * Popup for showing the full list of available widgets
+ */
+public class WidgetsFullSheet extends BaseWidgetSheet
+        implements Insettable, ProviderChangedListener {
+
+    private static final long DEFAULT_OPEN_DURATION = 267;
+    private static final long FADE_IN_DURATION = 150;
+    private static final float VERTICAL_START_POSITION = 0.3f;
+
+    private static final Rect sTempRect = new Rect();
+
+    private final Rect mInsets = new Rect();
+
+    private final WidgetsListAdapter mAdapter;
+
+    private View mNavBarScrim;
+    private WidgetsRecyclerView mRecyclerView;
+
+    public WidgetsFullSheet(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        LauncherAppState apps = LauncherAppState.getInstance(context);
+        mAdapter = new WidgetsListAdapter(context,
+                LayoutInflater.from(context), apps.getWidgetCache(), apps.getIconCache(),
+                this, this);
+    }
+
+    public WidgetsFullSheet(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        mContent = findViewById(R.id.container);
+        mNavBarScrim = findViewById(R.id.nav_bar_bg);
+
+        mRecyclerView = findViewById(R.id.widgets_list_view);
+        mRecyclerView.setAdapter(mAdapter);
+        mAdapter.setApplyBitmapDeferred(true, mRecyclerView);
+
+        mGradientView = findViewById(R.id.gradient_bg);
+        mGradientView.setProgress(1, false);
+
+        onWidgetsBound();
+    }
+
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        mLauncher.getAppWidgetHost().addProviderChangeListener(this);
+        notifyWidgetProvidersChanged();
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        mLauncher.getAppWidgetHost().removeProviderChangeListener(this);
+    }
+
+    @Override
+    public void setInsets(Rect insets) {
+        mInsets.set(insets);
+
+        mNavBarScrim.getLayoutParams().height = insets.bottom;
+        mRecyclerView.setPadding(
+                mRecyclerView.getPaddingLeft(), mRecyclerView.getPaddingTop(),
+                mRecyclerView.getPaddingRight(), insets.bottom);
+        if (insets.bottom > 0) {
+            setupNavBarColor();
+        }
+        requestLayout();
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        int widthUsed;
+        if (mInsets.bottom > 0) {
+            // If we have bottom insets, we do not show the scrim as it would overlap
+            // with the navbar scrim
+            mGradientView.setVisibility(View.INVISIBLE);
+            widthUsed = 0;
+        } else {
+            mLauncher.getDeviceProfile().getWorkspacePadding(sTempRect);
+            widthUsed = Math.max(sTempRect.left + sTempRect.right,
+                    2 * (mInsets.left + mInsets.right));
+        }
+
+        int heightUsed = mInsets.top + mLauncher.getDeviceProfile().edgeMarginPx;
+        measureChildWithMargins(mContent, widthMeasureSpec,
+                widthUsed, heightMeasureSpec, heightUsed);
+        measureChild(mGradientView, widthMeasureSpec, heightMeasureSpec);
+        setMeasuredDimension(mGradientView.getMeasuredWidth(), mGradientView.getMeasuredHeight());
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int l, int t, int r, int b) {
+        int width = r - l;
+        int height = b - t;
+        mGradientView.layout(0, 0, width, height);
+
+        // Content is laid out as center bottom aligned
+        int contentWidth = mContent.getMeasuredWidth();
+        int contentLeft = (width - contentWidth) / 2;
+        mContent.layout(contentLeft, height - mContent.getMeasuredHeight(),
+                contentLeft + contentWidth, height);
+
+        setTranslationShift(mTranslationShift);
+    }
+
+    @Override
+    public void notifyWidgetProvidersChanged() {
+        mLauncher.refreshAndBindWidgetsForPackageUser(null);
+    }
+
+    @Override
+    protected void onWidgetsBound() {
+        mAdapter.setWidgets(mLauncher.getPopupDataProvider().getAllWidgets());
+    }
+
+    private void open(boolean animate) {
+        if (mIsOpen) {
+            return;
+        }
+        mIsOpen = true;
+        if (animate) {
+            if (mLauncher.getDragLayer().getInsets().bottom > 0) {
+                mContent.setAlpha(0);
+                setTranslationShift(VERTICAL_START_POSITION);
+            }
+            mOpenCloseAnimator.setValues(
+                    PropertyValuesHolder.ofFloat(TRANSLATION_SHIFT, TRANSLATION_SHIFT_OPENED));
+            mOpenCloseAnimator
+                    .setDuration(DEFAULT_OPEN_DURATION)
+                    .setInterpolator(AnimationUtils.loadInterpolator(
+                            getContext(), android.R.interpolator.linear_out_slow_in));
+            mOpenCloseAnimator.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    mRecyclerView.setLayoutFrozen(false);
+                    mAdapter.setApplyBitmapDeferred(false, mRecyclerView);
+                    mOpenCloseAnimator.removeListener(this);
+                }
+            });
+            post(new Runnable() {
+                @Override
+                public void run() {
+                    mRecyclerView.setLayoutFrozen(true);
+                    mOpenCloseAnimator.start();
+                    mContent.animate().alpha(1).setDuration(FADE_IN_DURATION);
+                }
+            });
+        } else {
+            setTranslationShift(TRANSLATION_SHIFT_OPENED);
+            mAdapter.setApplyBitmapDeferred(false, mRecyclerView);
+        }
+    }
+
+    @Override
+    protected void handleClose(boolean animate) {
+        handleClose(animate, DEFAULT_OPEN_DURATION);
+    }
+
+    @Override
+    protected boolean isOfType(int type) {
+        return (type & TYPE_WIDGETS_FULL_SHEET) != 0;
+    }
+
+    @Override
+    public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
+        // Disable swipe down when recycler view is scrolling
+        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+            mNoIntercept = false;
+            if (mLauncher.getDragLayer().isEventOverView(mContent, ev)) {
+                mNoIntercept = !mRecyclerView.shouldContainerScroll(ev, mLauncher.getDragLayer());
+            }
+        }
+        return super.onControllerInterceptTouchEvent(ev);
+    }
+
+    public static WidgetsFullSheet show(Launcher launcher, boolean animate) {
+        WidgetsFullSheet sheet = (WidgetsFullSheet) launcher.getLayoutInflater()
+                .inflate(R.layout.widgets_full_sheet, launcher.getDragLayer(), false);
+        launcher.getDragLayer().addView(sheet);
+        sheet.open(animate);
+        return sheet;
+    }
+}
diff --git a/src/com/android/launcher3/widget/WidgetsListAdapter.java b/src/com/android/launcher3/widget/WidgetsListAdapter.java
index 6b1800c..0147ea4 100644
--- a/src/com/android/launcher3/widget/WidgetsListAdapter.java
+++ b/src/com/android/launcher3/widget/WidgetsListAdapter.java
@@ -25,13 +25,11 @@
 import android.view.View.OnLongClickListener;
 import android.view.ViewGroup;
 
+import com.android.launcher3.IconCache;
 import com.android.launcher3.R;
 import com.android.launcher3.WidgetPreviewLoader;
-import com.android.launcher3.compat.AlphabeticIndexCompat;
-import com.android.launcher3.model.PackageItemInfo;
 import com.android.launcher3.model.WidgetItem;
 import com.android.launcher3.util.LabelComparator;
-import com.android.launcher3.util.MultiHashMap;
 import com.android.launcher3.util.PackageUserKey;
 
 import java.util.ArrayList;
@@ -39,7 +37,6 @@
 import java.util.Comparator;
 import java.util.Iterator;
 import java.util.List;
-import java.util.Map;
 
 /**
  * List view adapter for the widget tray.
@@ -56,7 +53,6 @@
 
     private final WidgetPreviewLoader mWidgetPreviewLoader;
     private final LayoutInflater mLayoutInflater;
-    private final AlphabeticIndexCompat mIndexer;
 
     private final OnClickListener mIconClickListener;
     private final OnLongClickListener mIconLongClickListener;
@@ -64,56 +60,43 @@
     private ArrayList<WidgetListRowEntry> mEntries = new ArrayList<>();
     private final WidgetsDiffReporter mDiffReporter;
 
+    private boolean mApplyBitmapDeferred;
+
     public WidgetsListAdapter(Context context, LayoutInflater layoutInflater,
-            WidgetPreviewLoader widgetPreviewLoader, AlphabeticIndexCompat indexCompat,
-            OnClickListener iconClickListener, OnLongClickListener iconLongClickListener,
-            WidgetsDiffReporter diffReporter) {
+            WidgetPreviewLoader widgetPreviewLoader, IconCache iconCache,
+            OnClickListener iconClickListener, OnLongClickListener iconLongClickListener) {
         mLayoutInflater = layoutInflater;
         mWidgetPreviewLoader = widgetPreviewLoader;
-        mIndexer = indexCompat;
         mIconClickListener = iconClickListener;
         mIconLongClickListener = iconLongClickListener;
         mIndent = context.getResources().getDimensionPixelSize(R.dimen.widget_section_indent);
-        mDiffReporter = diffReporter;
+        mDiffReporter = new WidgetsDiffReporter(iconCache, this);
     }
 
-    public void setNotifyListener() {
-        mDiffReporter.setListener(new WidgetsDiffReporter.NotifyListener() {
-            @Override
-            public void notifyDataSetChanged() {
-                WidgetsListAdapter.this.notifyDataSetChanged();
-            }
+    /**
+     * Defers applying bitmap on all the {@link WidgetCell} in the {@param rv}
+     *
+     * @see WidgetCell#setApplyBitmapDeferred(boolean)
+     */
+    public void setApplyBitmapDeferred(boolean isDeferred, RecyclerView rv) {
+        mApplyBitmapDeferred = isDeferred;
 
-            @Override
-            public void notifyItemChanged(int index) {
-                WidgetsListAdapter.this.notifyItemChanged(index);
+        for (int i = rv.getChildCount() - 1; i >= 0; i--) {
+            WidgetsRowViewHolder holder = (WidgetsRowViewHolder)
+                    rv.getChildViewHolder(rv.getChildAt(i));
+            for (int j = holder.cellContainer.getChildCount() - 1; j >= 0; j--) {
+                View v = holder.cellContainer.getChildAt(j);
+                if (v instanceof WidgetCell) {
+                    ((WidgetCell) v).setApplyBitmapDeferred(mApplyBitmapDeferred);
+                }
             }
-
-            @Override
-            public void notifyItemInserted(int index) {
-                WidgetsListAdapter.this.notifyItemInserted(index);
-            }
-
-            @Override
-            public void notifyItemRemoved(int index) {
-                WidgetsListAdapter.this.notifyItemRemoved(index);
-            }
-        });
+        }
     }
 
     /**
      * Update the widget list.
      */
-    public void setWidgets(MultiHashMap<PackageItemInfo, WidgetItem> widgets) {
-        ArrayList<WidgetListRowEntry> tempEntries = new ArrayList<>();
-
-        WidgetItemComparator widgetComparator = new WidgetItemComparator();
-        for (Map.Entry<PackageItemInfo, ArrayList<WidgetItem>> entry : widgets.entrySet()) {
-            WidgetListRowEntry row = new WidgetListRowEntry(entry.getKey(), entry.getValue());
-            row.titleSectionName = mIndexer.computeSectionName(row.pkgItem.title);
-            Collections.sort(row.widgets, widgetComparator);
-            tempEntries.add(row);
-        }
+    public void setWidgets(ArrayList<WidgetListRowEntry> tempEntries) {
         WidgetListRowEntryComparator rowComparator = new WidgetListRowEntryComparator();
         Collections.sort(tempEntries, rowComparator);
         mDiffReporter.process(mEntries, tempEntries, rowComparator);
@@ -128,26 +111,6 @@
         return mEntries.get(pos).titleSectionName;
     }
 
-    /**
-     * Copies and returns the widgets associated with the package and user of the ComponentKey.
-     */
-    public List<WidgetItem> copyWidgetsForPackageUser(PackageUserKey packageUserKey) {
-        for (WidgetListRowEntry entry : mEntries) {
-            if (entry.pkgItem.packageName.equals(packageUserKey.mPackageName)) {
-                ArrayList<WidgetItem> widgets = new ArrayList<>(entry.widgets);
-                // Remove widgets not associated with the correct user.
-                Iterator<WidgetItem> iterator = widgets.iterator();
-                while (iterator.hasNext()) {
-                    if (!iterator.next().user.equals(packageUserKey.mUser)) {
-                        iterator.remove();
-                    }
-                }
-                return widgets.isEmpty() ? null : widgets;
-            }
-        }
-        return null;
-    }
-
     @Override
     public void onBindViewHolder(WidgetsRowViewHolder holder, int pos) {
         WidgetListRowEntry entry = mEntries.get(pos);
@@ -194,6 +157,7 @@
         for (int i=0; i < infoList.size(); i++) {
             WidgetCell widget = (WidgetCell) row.getChildAt(2*i);
             widget.applyFromCellItem(infoList.get(i), mWidgetPreviewLoader);
+            widget.setApplyBitmapDeferred(mApplyBitmapDeferred);
             widget.ensurePreview();
             widget.setVisibility(View.VISIBLE);
 
@@ -253,5 +217,4 @@
             return mComparator.compare(a.pkgItem.title.toString(), b.pkgItem.title.toString());
         }
     }
-
 }
diff --git a/src/com/android/launcher3/widget/WidgetsRecyclerView.java b/src/com/android/launcher3/widget/WidgetsRecyclerView.java
index 9730a82..89c88a4 100644
--- a/src/com/android/launcher3/widget/WidgetsRecyclerView.java
+++ b/src/com/android/launcher3/widget/WidgetsRecyclerView.java
@@ -17,12 +17,12 @@
 package com.android.launcher3.widget;
 
 import android.content.Context;
-import android.graphics.Color;
 import android.support.v7.widget.LinearLayoutManager;
 import android.util.AttributeSet;
 import android.view.View;
 
 import com.android.launcher3.BaseRecyclerView;
+import com.android.launcher3.R;
 
 /**
  * The widgets recycler view.
@@ -32,6 +32,8 @@
     private static final String TAG = "WidgetsRecyclerView";
     private WidgetsListAdapter mAdapter;
 
+    private final int mScrollbarTop;
+
     public WidgetsRecyclerView(Context context) {
         this(context, null);
     }
@@ -43,6 +45,7 @@
     public WidgetsRecyclerView(Context context, AttributeSet attrs, int defStyleAttr) {
         // API 21 and below only support 3 parameter ctor.
         super(context, attrs, defStyleAttr);
+        mScrollbarTop = getResources().getDimensionPixelSize(R.dimen.dynamic_grid_edge_margin);
     }
 
     public WidgetsRecyclerView(Context context, AttributeSet attrs, int defStyleAttr,
@@ -130,13 +133,16 @@
     @Override
     protected int getAvailableScrollHeight() {
         View child = getChildAt(0);
-        int height = child.getMeasuredHeight() * mAdapter.getItemCount();
-        int totalHeight = getPaddingTop() + height + getPaddingBottom();
-        int availableScrollHeight = totalHeight - getScrollbarTrackHeight();
-        return availableScrollHeight;
+        return child.getMeasuredHeight() * mAdapter.getItemCount() - getScrollbarTrackHeight()
+                - mScrollbarTop;
     }
 
     private boolean isModelNotReady() {
         return mAdapter.getItemCount() == 0;
     }
+
+    @Override
+    public int getScrollBarTop() {
+        return mScrollbarTop;
+    }
 }
\ No newline at end of file