Merge "Make common base class to update container bounds and to handle scroll logic." into ub-launcher3-burnaby
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 4fbe87e..46830d6 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -50,8 +50,6 @@
     <dimen name="apps_container_width">0dp</dimen>
     <dimen name="apps_container_height">0dp</dimen>
     <dimen name="apps_container_inset">8dp</dimen>
-    <!-- Note: This needs to match the fixed insets for the search box -->
-    <dimen name="apps_container_fixed_bounds_inset">8dp</dimen>
     <dimen name="apps_grid_view_start_margin">52dp</dimen>
     <dimen name="apps_grid_section_y_offset">8dp</dimen>
     <dimen name="apps_view_row_height">64dp</dimen>
@@ -62,6 +60,9 @@
     <dimen name="apps_view_fast_scroll_popup_size">64dp</dimen>
     <dimen name="apps_view_fast_scroll_text_size">40dp</dimen>
 
+    <!-- Note: This needs to match the fixed insets for the search box. -->
+    <dimen name="container_fixed_bounds_inset">8dp</dimen>
+
 <!-- AllApps/Customize/AppsCustomize -->
     <dimen name="app_icon_size">48dp</dimen>
     <dimen name="apps_customize_horizontal_padding">0dp</dimen>
diff --git a/src/com/android/launcher3/AppsContainerRecyclerView.java b/src/com/android/launcher3/AppsContainerRecyclerView.java
index 7f64be2..fb5f6d4 100644
--- a/src/com/android/launcher3/AppsContainerRecyclerView.java
+++ b/src/com/android/launcher3/AppsContainerRecyclerView.java
@@ -30,23 +30,15 @@
 import android.view.View;
 import android.view.ViewConfiguration;
 
-import com.android.launcher3.util.Thunk;
-
 import java.util.List;
 
 /**
  * A RecyclerView with custom fastscroll support.  This is the main container for the all apps
  * icons.
  */
-public class AppsContainerRecyclerView extends RecyclerView
-        implements RecyclerView.OnItemTouchListener {
+public class AppsContainerRecyclerView extends BaseContainerRecyclerView {
 
     private static final float FAST_SCROLL_OVERLAY_Y_OFFSET_FACTOR = 1.5f;
-    private static final int SCROLL_DELTA_THRESHOLD = 4;
-
-    /** Keeps the last known scrolling delta/velocity along y-axis. */
-    @Thunk int mDy = 0;
-    private float mDeltaThreshold;
 
     private AlphabeticalAppsList mApps;
     private int mNumAppsPerRow;
@@ -66,7 +58,6 @@
     private int mScrollbarWidth;
     private int mScrollbarMinHeight;
     private int mScrollbarInset;
-    private RecyclerView.OnScrollListener mScrollListenerProxy;
 
     public AppsContainerRecyclerView(Context context) {
         this(context, null);
@@ -100,21 +91,6 @@
         mScrollbarInset =
                 res.getDimensionPixelSize(R.dimen.apps_view_fast_scroll_scrubber_touch_inset);
         setFastScrollerAlpha(getFastScrollerAlpha());
-        mDeltaThreshold = getResources().getDisplayMetrics().density * SCROLL_DELTA_THRESHOLD;
-
-        ScrollListener listener = new ScrollListener();
-        setOnScrollListener(listener);
-    }
-
-    private class ScrollListener extends RecyclerView.OnScrollListener {
-        public ScrollListener() {
-        }
-
-        @Override
-        public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
-            mDy = dy;
-            mScrollListenerProxy.onScrolled(recyclerView, dx, dy);
-        }
     }
 
     /**
@@ -132,13 +108,6 @@
     }
 
     /**
-     * Sets an additional scroll listener, not necessary in master support lib.
-     */
-    public void setOnScrollListenerProxy(RecyclerView.OnScrollListener listener) {
-        mScrollListenerProxy = listener;
-    }
-
-    /**
      * Sets the fast scroller alpha.
      */
     public void setFastScrollerAlpha(float alpha) {
@@ -187,10 +156,6 @@
         handleTouchEvent(ev);
     }
 
-    public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
-        // DO NOT REMOVE, NEEDED IMPLEMENTATION FOR M BUILDS
-    }
-
     /**
      * Handles the touch event and determines whether to show the fast scroller (or updates it if
      * it is already showing).
@@ -206,8 +171,7 @@
                 // Keep track of the down positions
                 mDownX = mLastX = x;
                 mDownY = mLastY = y;
-                if ((Math.abs(mDy) < mDeltaThreshold &&
-                        getScrollState() != RecyclerView.SCROLL_STATE_IDLE)) {
+                if (shouldStopScroll(ev)) {
                     stopScroll();
                 }
                 break;
diff --git a/src/com/android/launcher3/AppsContainerView.java b/src/com/android/launcher3/AppsContainerView.java
index 9122427..024e539 100644
--- a/src/com/android/launcher3/AppsContainerView.java
+++ b/src/com/android/launcher3/AppsContainerView.java
@@ -31,7 +31,6 @@
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputMethodManager;
 import android.widget.EditText;
-import android.widget.FrameLayout;
 import android.widget.LinearLayout;
 import android.widget.TextView;
 import com.android.launcher3.util.Thunk;
@@ -40,10 +39,10 @@
 
 
 /**
- * The all apps list view container.
+ * The all apps view container.
  */
-public class AppsContainerView extends FrameLayout implements DragSource, Insettable, TextWatcher,
-        TextView.OnEditorActionListener, LauncherTransitionable, View.OnTouchListener,
+public class AppsContainerView extends BaseContainerView implements DragSource, Insettable,
+        TextWatcher, TextView.OnEditorActionListener, LauncherTransitionable, View.OnTouchListener,
         View.OnClickListener, View.OnLongClickListener {
 
     public static final boolean GRID_MERGE_SECTIONS = true;
@@ -73,13 +72,9 @@
     private int mNumAppsPerRow;
     private Point mLastTouchDownPos = new Point(-1, -1);
     private Point mLastTouchPos = new Point();
-    private Rect mInsets = new Rect();
-    private Rect mFixedBounds = new Rect();
     private int mContentMarginStart;
     // Normal container insets
     private int mContainerInset;
-    // Fixed bounds container insets
-    private int mFixedBoundsContainerInset;
     // RecyclerView scroll position
     @Thunk int mRecyclerViewScrollY;
 
@@ -99,8 +94,6 @@
 
         mContainerInset = context.getResources().getDimensionPixelSize(
                 R.dimen.apps_container_inset);
-        mFixedBoundsContainerInset = context.getResources().getDimensionPixelSize(
-                R.dimen.apps_container_fixed_bounds_inset);
         mLauncher = (Launcher) context;
         mNumAppsPerRow = grid.appsViewNumCols;
         mApps = new AlphabeticalAppsList(context, mNumAppsPerRow);
@@ -146,8 +139,8 @@
      */
     public void hideHeaderBar() {
         mHeaderView.setVisibility(View.GONE);
-        updateBackgrounds();
-        updatePaddings();
+        onUpdateBackgrounds();
+        onUpdatePaddings();
     }
 
     /**
@@ -225,46 +218,81 @@
         if (mItemDecoration != null) {
             mAppsRecyclerView.addItemDecoration(mItemDecoration);
         }
-        updateBackgrounds();
-        updatePaddings();
+        onUpdateBackgrounds();
+        onUpdatePaddings();
     }
 
     @Override
-    public void setInsets(Rect insets) {
-        mInsets.set(insets);
-        updatePaddings();
+    protected void onFixedBoundsUpdated() {
+        // Update the number of items in the grid
+        LauncherAppState app = LauncherAppState.getInstance();
+        DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
+        if (grid.updateAppsViewNumCols(getContext().getResources(), mFixedBounds.width())) {
+            mNumAppsPerRow = grid.appsViewNumCols;
+            mAppsRecyclerView.setNumAppsPerRow(mNumAppsPerRow);
+            mAdapter.setNumAppsPerRow(mNumAppsPerRow);
+            mApps.setNumAppsPerRow(mNumAppsPerRow);
+        }
     }
 
     /**
-     * Sets the fixed bounds for this Apps view.
+     * Update the padding of the Apps view and children.  To ensure that the RecyclerView has the
+     * full width to handle touches right to the edge of the screen, we only apply the top and
+     * bottom padding to the AppsContainerView and then the left/right padding on the RecyclerView
+     * itself.  In particular, the left/right padding is applied to the background of the view,
+     * and then additionally inset by the start margin.
      */
-    public void setFixedBounds(Context context, Rect fixedBounds) {
-        if (!fixedBounds.isEmpty() && !fixedBounds.equals(mFixedBounds)) {
-            // Update the number of items in the grid
-            LauncherAppState app = LauncherAppState.getInstance();
-            DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
-            if (grid.updateAppsViewNumCols(context.getResources(), fixedBounds.width())) {
-                mNumAppsPerRow = grid.appsViewNumCols;
-                mAppsRecyclerView.setNumAppsPerRow(mNumAppsPerRow);
-                mAdapter.setNumAppsPerRow(mNumAppsPerRow);
-                mApps.setNumAppsPerRow(mNumAppsPerRow);
-            }
+    @Override
+    protected void onUpdatePaddings() {
+        boolean isRtl = (getResources().getConfiguration().getLayoutDirection() ==
+                LAYOUT_DIRECTION_RTL);
+        boolean hasSearchBar = (mSearchBarEditView != null) &&
+                (mSearchBarEditView.getVisibility() == View.VISIBLE);
 
-            mFixedBounds.set(fixedBounds);
-            if (Launcher.DISABLE_ALL_APPS_SEARCH_INTEGRATION) {
-                mFixedBounds.top = mInsets.top;
-                mFixedBounds.bottom = getMeasuredHeight();
-            }
+        if (mFixedBounds.isEmpty()) {
+            // If there are no fixed bounds, then use the default padding and insets
+            setPadding(mInsets.left, mContainerInset + mInsets.top, mInsets.right,
+                    mContainerInset + mInsets.bottom);
+        } else {
+            // If there are fixed bounds, then we update the padding to reflect the fixed bounds.
+            setPadding(mFixedBounds.left, mFixedBounds.top, getMeasuredWidth() - mFixedBounds.right,
+                    mInsets.bottom);
         }
-        // Post the updates since they can trigger a relayout, and this call can be triggered from
-        // a layout pass itself.
-        post(new Runnable() {
-            @Override
-            public void run() {
-                updateBackgrounds();
-                updatePaddings();
-            }
-        });
+
+        // Update the apps recycler view, inset it by the container inset as well
+        int inset = mFixedBounds.isEmpty() ? mContainerInset : mFixedBoundsContainerInset;
+        if (isRtl) {
+            mAppsRecyclerView.setPadding(inset, inset, inset + mContentMarginStart, inset);
+        } else {
+            mAppsRecyclerView.setPadding(inset + mContentMarginStart, inset, inset, inset);
+        }
+
+        // Update the header bar
+        if (hasSearchBar) {
+            LinearLayout.LayoutParams lp =
+                    (LinearLayout.LayoutParams) mHeaderView.getLayoutParams();
+            lp.leftMargin = lp.rightMargin = inset;
+        }
+    }
+
+    /**
+     * Update the background of the Apps view and children.
+     */
+    @Override
+    protected void onUpdateBackgrounds() {
+        int inset = mFixedBounds.isEmpty() ? mContainerInset : mFixedBoundsContainerInset;
+        boolean hasSearchBar = (mSearchBarEditView != null) &&
+                (mSearchBarEditView.getVisibility() == View.VISIBLE);
+
+        // Update the background of the reveal view and list to be inset with the fixed bound
+        // insets instead of the default insets
+        mAppsRecyclerView.setBackground(new InsetDrawable(
+                getContext().getResources().getDrawable(
+                        hasSearchBar ? R.drawable.apps_list_search_bg : R.drawable.apps_list_bg),
+                inset, 0, inset, 0));
+        getRevealView().setBackground(new InsetDrawable(
+                getContext().getResources().getDrawable(R.drawable.apps_reveal_bg),
+                inset, 0, inset, 0));
     }
 
     @Override
@@ -531,65 +559,6 @@
     }
 
     /**
-     * Update the padding of the Apps view and children.  To ensure that the RecyclerView has the
-     * full width to handle touches right to the edge of the screen, we only apply the top and
-     * bottom padding to the AppsContainerView and then the left/right padding on the RecyclerView
-     * itself.  In particular, the left/right padding is applied to the background of the view,
-     * and then additionally inset by the start margin.
-     */
-    private void updatePaddings() {
-        boolean isRtl = (getResources().getConfiguration().getLayoutDirection() ==
-                LAYOUT_DIRECTION_RTL);
-        boolean hasSearchBar = (mSearchBarEditView != null) &&
-                (mSearchBarEditView.getVisibility() == View.VISIBLE);
-
-        if (mFixedBounds.isEmpty()) {
-            // If there are no fixed bounds, then use the default padding and insets
-            setPadding(mInsets.left, mContainerInset + mInsets.top, mInsets.right,
-                    mContainerInset + mInsets.bottom);
-        } else {
-            // If there are fixed bounds, then we update the padding to reflect the fixed bounds.
-            setPadding(mFixedBounds.left, mFixedBounds.top + mFixedBoundsContainerInset,
-                    getMeasuredWidth() - mFixedBounds.right,
-                    mInsets.bottom + mFixedBoundsContainerInset);
-        }
-
-        // Update the apps recycler view
-        int inset = mFixedBounds.isEmpty() ? mContainerInset : mFixedBoundsContainerInset;
-        if (isRtl) {
-            mAppsRecyclerView.setPadding(inset, inset, inset + mContentMarginStart, inset);
-        } else {
-            mAppsRecyclerView.setPadding(inset + mContentMarginStart, inset, inset, inset);
-        }
-
-        // Update the header
-        if (hasSearchBar) {
-            LinearLayout.LayoutParams lp =
-                    (LinearLayout.LayoutParams) mHeaderView.getLayoutParams();
-            lp.leftMargin = lp.rightMargin = inset;
-        }
-    }
-
-    /**
-     * Update the background of the Apps view and children.
-     */
-    private void updateBackgrounds() {
-        int inset = mFixedBounds.isEmpty() ? mContainerInset : mFixedBoundsContainerInset;
-        boolean hasSearchBar = (mSearchBarEditView != null) &&
-                (mSearchBarEditView.getVisibility() == View.VISIBLE);
-
-        // Update the background of the reveal view and list to be inset with the fixed bound
-        // insets instead of the default insets
-        mAppsRecyclerView.setBackground(new InsetDrawable(
-                getContext().getResources().getDrawable(
-                        hasSearchBar ? R.drawable.apps_list_search_bg : R.drawable.apps_list_bg),
-                inset, 0, inset, 0));
-        getRevealView().setBackground(new InsetDrawable(
-                getContext().getResources().getDrawable(R.drawable.apps_reveal_bg),
-                inset, 0, inset, 0));
-    }
-
-    /**
      * Shows the search field.
      */
     private void showSearchField() {
diff --git a/src/com/android/launcher3/BaseContainerRecyclerView.java b/src/com/android/launcher3/BaseContainerRecyclerView.java
new file mode 100644
index 0000000..5b30e3d
--- /dev/null
+++ b/src/com/android/launcher3/BaseContainerRecyclerView.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3;
+
+import android.content.Context;
+import android.support.v7.widget.RecyclerView;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import com.android.launcher3.util.Thunk;
+
+/**
+ * A base {@link RecyclerView}, which will NOT intercept a touch sequence unless the scrolling
+ * velocity is below a predefined threshold.
+ */
+public class BaseContainerRecyclerView extends RecyclerView
+        implements RecyclerView.OnItemTouchListener {
+
+    private static final int SCROLL_DELTA_THRESHOLD_DP = 4;
+
+    /** Keeps the last known scrolling delta/velocity along y-axis. */
+    @Thunk int mDy = 0;
+    private float mDeltaThreshold;
+    private RecyclerView.OnScrollListener mScrollListenerProxy;
+
+    public BaseContainerRecyclerView(Context context) {
+        this(context, null);
+    }
+
+    public BaseContainerRecyclerView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public BaseContainerRecyclerView(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        mDeltaThreshold = getResources().getDisplayMetrics().density * SCROLL_DELTA_THRESHOLD_DP;
+
+        ScrollListener listener = new ScrollListener();
+        setOnScrollListener(listener);
+    }
+
+    private class ScrollListener extends OnScrollListener {
+        public ScrollListener() {
+            // Do nothing
+        }
+
+        @Override
+        public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
+            mDy = dy;
+            if (mScrollListenerProxy != null) {
+                mScrollListenerProxy.onScrolled(recyclerView, dx, dy);
+            }
+        }
+    }
+
+    /**
+     * Sets an additional scroll listener, only needed for LMR1 version of the support lib.
+     */
+    public void setOnScrollListenerProxy(RecyclerView.OnScrollListener listener) {
+        mScrollListenerProxy = listener;
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        addOnItemTouchListener(this);
+    }
+
+    @Override
+    public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent ev) {
+        if (shouldStopScroll(ev)) {
+            stopScroll();
+        }
+        return false;
+    }
+
+    @Override
+    public void onTouchEvent(RecyclerView rv, MotionEvent ev) {
+        // Do nothing.
+    }
+
+    public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
+        // DO NOT REMOVE, NEEDED IMPLEMENTATION FOR M BUILDS
+    }
+
+    /**
+     * Returns whether this {@link MotionEvent} should trigger the scroll to be stopped.
+     */
+    protected boolean shouldStopScroll(MotionEvent ev) {
+        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+            if ((Math.abs(mDy) < mDeltaThreshold &&
+                    getScrollState() != RecyclerView.SCROLL_STATE_IDLE)) {
+                // now the touch events are being passed to the {@link WidgetCell} until the
+                // touch sequence goes over the touch slop.
+                return true;
+            }
+        }
+        return false;
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/launcher3/BaseContainerView.java b/src/com/android/launcher3/BaseContainerView.java
new file mode 100644
index 0000000..2a84432
--- /dev/null
+++ b/src/com/android/launcher3/BaseContainerView.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.widget.FrameLayout;
+
+/**
+ * A base container view, which supports resizing.
+ */
+public class BaseContainerView extends FrameLayout implements Insettable {
+
+    protected Rect mInsets = new Rect();
+    protected Rect mFixedBounds = new Rect();
+    protected int mFixedBoundsContainerInset;
+
+    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);
+        mFixedBoundsContainerInset = context.getResources().getDimensionPixelSize(
+                R.dimen.container_fixed_bounds_inset);
+    }
+
+    @Override
+    final public void setInsets(Rect insets) {
+        mInsets.set(insets);
+        onUpdateBackgrounds();
+        onUpdatePaddings();
+    }
+
+    /**
+     * Sets the fixed bounds for this container view.
+     */
+    final public void setFixedBounds(Rect fixedBounds) {
+        if (!fixedBounds.isEmpty() && !fixedBounds.equals(mFixedBounds)) {
+            mFixedBounds.set(fixedBounds);
+            if (Launcher.DISABLE_ALL_APPS_SEARCH_INTEGRATION) {
+                mFixedBounds.top = mInsets.top;
+                mFixedBounds.bottom = getMeasuredHeight();
+            }
+            // To ensure that the child RecyclerView has the full width to handle touches right to
+            // the edge of the screen, we only apply the top and bottom padding to the bounds
+            mFixedBounds.inset(0, mFixedBoundsContainerInset);
+            onFixedBoundsUpdated();
+        }
+        // Post the updates since they can trigger a relayout, and this call can be triggered from
+        // a layout pass itself.
+        post(new Runnable() {
+            @Override
+            public void run() {
+                onUpdateBackgrounds();
+                onUpdatePaddings();
+            }
+        });
+    }
+
+    /**
+     * Update the UI in response to a change in the fixed bounds.
+     */
+    protected void onFixedBoundsUpdated() {
+        // Do nothing
+    }
+
+    /**
+     * Update the paddings in response to a change in the bounds or insets.
+     */
+    protected void onUpdatePaddings() {
+        // Do nothing
+    }
+
+    /**
+     * Update the backgrounds in response to a change in the bounds or insets.
+     */
+    protected void onUpdateBackgrounds() {
+        // Do nothing
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 1de383c..5645759 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -528,7 +528,8 @@
                 if (LOGD) {
                     Log.d(TAG, "onAllAppsBoundsChanged(Rect): " + bounds);
                 }
-                mAppsView.setFixedBounds(Launcher.this, bounds);
+                mAppsView.setFixedBounds(bounds);
+                mWidgetsView.setFixedBounds(bounds);
             }
 
             @Override
diff --git a/src/com/android/launcher3/widget/WidgetsContainerRecyclerView.java b/src/com/android/launcher3/widget/WidgetsContainerRecyclerView.java
index 65694bf..6f15324 100644
--- a/src/com/android/launcher3/widget/WidgetsContainerRecyclerView.java
+++ b/src/com/android/launcher3/widget/WidgetsContainerRecyclerView.java
@@ -17,26 +17,13 @@
 package com.android.launcher3.widget;
 
 import android.content.Context;
-import android.support.v7.widget.RecyclerView;
 import android.util.AttributeSet;
-import android.view.MotionEvent;
-
-import com.android.launcher3.util.Thunk;
+import com.android.launcher3.BaseContainerRecyclerView;
 
 /**
  * The widgets recycler view container.
- * <p>
- * Overwritten to NOT intercept a touch sequence that started when the {@link RecycleView}
- * scrolling slowing down below the internally defined threshold.
  */
-public class WidgetsContainerRecyclerView extends RecyclerView
-        implements RecyclerView.OnItemTouchListener {
-
-    private static final int SCROLL_DELTA_THRESHOLD = 4;
-
-    /** Keeps the last known scrolling delta/velocity along y-axis. */
-    @Thunk int mDy = 0;
-    private float mDeltaThreshold;
+public class WidgetsContainerRecyclerView extends BaseContainerRecyclerView {
 
     public WidgetsContainerRecyclerView(Context context) {
         this(context, null);
@@ -48,47 +35,6 @@
 
     public WidgetsContainerRecyclerView(Context context, AttributeSet attrs, int defStyleAttr) {
         super(context, attrs, defStyleAttr);
-        mDeltaThreshold = getResources().getDisplayMetrics().density * SCROLL_DELTA_THRESHOLD;
-
-        ScrollListener listener = new ScrollListener();
-        setOnScrollListener(listener);
     }
 
-    private class ScrollListener extends RecyclerView.OnScrollListener {
-        public ScrollListener() {
-        }
-
-        @Override
-        public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
-            mDy = dy;
-        }
-    }
-
-    @Override
-    protected void onFinishInflate() {
-        super.onFinishInflate();
-        addOnItemTouchListener(this);
-    }
-
-    @Override
-    public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent ev) {
-        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
-            if ((Math.abs(mDy) < mDeltaThreshold &&
-                    getScrollState() != RecyclerView.SCROLL_STATE_IDLE)) {
-                // now the touch events are being passed to the {@link WidgetCell} until the
-                // touch sequence goes over the touch slop.
-                stopScroll();
-            }
-        }
-        return false;
-    }
-
-    @Override
-    public void onTouchEvent(RecyclerView rv, MotionEvent ev) {
-        // Do nothing.
-    }
-
-    public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
-        // DO NOT REMOVE, NEEDED IMPLEMENTATION FOR M BUILDS
-    }
 }
\ No newline at end of file
diff --git a/src/com/android/launcher3/widget/WidgetsContainerView.java b/src/com/android/launcher3/widget/WidgetsContainerView.java
index 22e29f3..439227f 100644
--- a/src/com/android/launcher3/widget/WidgetsContainerView.java
+++ b/src/com/android/launcher3/widget/WidgetsContainerView.java
@@ -28,10 +28,9 @@
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.View;
-import android.widget.FrameLayout;
 import android.widget.ImageView;
 import android.widget.Toast;
-
+import com.android.launcher3.BaseContainerView;
 import com.android.launcher3.CellLayout;
 import com.android.launcher3.DeleteDropTarget;
 import com.android.launcher3.DragController;
@@ -55,8 +54,8 @@
 /**
  * The widgets list view container.
  */
-public class WidgetsContainerView extends FrameLayout implements Insettable,
-        View.OnLongClickListener, View.OnClickListener, DragSource{
+public class WidgetsContainerView extends BaseContainerView
+        implements View.OnLongClickListener, View.OnClickListener, DragSource{
 
     private static final String TAG = "WidgetsContainerView";
     private static final boolean DEBUG = false;
@@ -129,6 +128,7 @@
         });
         mPadding.set(getPaddingLeft(), getPaddingTop(), getPaddingRight(),
                 getPaddingBottom());
+        onUpdatePaddings();
     }
 
     //
@@ -364,13 +364,17 @@
     // Container rendering related.
     //
 
-    /*
-     * @see Insettable#setInsets(Rect)
-     */
     @Override
-    public void setInsets(Rect insets) {
-        setPadding(mPadding.left + insets.left, mPadding.top + insets.top,
-                mPadding.right + insets.right, mPadding.bottom + insets.bottom);
+    protected void onUpdatePaddings() {
+        if (mFixedBounds.isEmpty()) {
+            // If there are no fixed bounds, then use the default padding and insets
+            setPadding(mPadding.left + mInsets.left, mPadding.top + mInsets.top,
+                    mPadding.right + mInsets.right, mPadding.bottom + mInsets.bottom);
+        } else {
+            // If there are fixed bounds, then we update the padding to reflect the fixed bounds.
+            setPadding(mFixedBounds.left, mFixedBounds.top, getMeasuredWidth() - mFixedBounds.right,
+                    mInsets.bottom);
+        }
     }
 
     /**