Merge "Using xml-drawable and elevation as folder background" into ub-launcher3-burnaby
diff --git a/res/layout/apps_list_view.xml b/res/layout/apps_list_view.xml
index e29cac5..a726cd8 100644
--- a/res/layout/apps_list_view.xml
+++ b/res/layout/apps_list_view.xml
@@ -43,7 +43,7 @@
                 android:paddingBottom="12dp"
                 android:contentDescription="@string/all_apps_button_label"
                 android:src="@drawable/ic_arrow_back_grey" />
-            <EditText
+            <com.android.launcher3.AppsContainerSearchEditTextView
                 android:id="@+id/app_search_box"
                 android:layout_width="match_parent"
                 android:layout_height="match_parent"
@@ -59,6 +59,7 @@
                 android:textColor="#4c4c4c"
                 android:textColorHint="#9c9c9c"
                 android:imeOptions="actionDone|flagNoExtractUi"
+                android:focusableInTouchMode="true"
                 android:background="@android:color/transparent" />
         </LinearLayout>
         <ImageView
diff --git a/res/values/config.xml b/res/values/config.xml
index 6ef8635..84ccef1 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -102,4 +102,5 @@
     <item type="id" name="action_move_to_workspace" />
     <item type="id" name="action_move_screen_backwards" />
     <item type="id" name="action_move_screen_forwards" />
+    <item type="id" name="action_resize" />
 </resources>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 5b5b5fc..4d33b88 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/res/values/strings.xml b/res/values/strings.xml
index 1681fc6..5962584 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -252,4 +252,23 @@
 
     <!-- Accessibility confirmation when a screen was moved [DO NOT TRANSLATE] -->
     <string name="screen_moved">Screen moved</string>
+
+    <!-- Accessibility action to resize a widget [DO NOT TRANSLATE] -->
+    <string name="action_resize">Resize</string>
+
+    <!-- Accessibility action to increase width of a widget [DO NOT TRANSLATE] -->
+    <string name="action_increase_width">Increase width</string>
+
+    <!-- Accessibility action to increase height of a widget [DO NOT TRANSLATE] -->
+    <string name="action_increase_height">Increase height</string>
+
+    <!-- Accessibility action to decrease width of a widget [DO NOT TRANSLATE] -->
+    <string name="action_decrease_width">Decrease width</string>
+
+    <!-- Accessibility action to decrease height of a widget [DO NOT TRANSLATE] -->
+    <string name="action_decrease_height">Decrease height</string>
+
+    <!-- Accessibility confirmation for widget resize [DO NOT TRANSLATE]-->
+    <string name="widget_resized">Widget resized to width <xliff:g id="number" example="2">%1$s</xliff:g> height <xliff:g id="number" example="1">%2$s</xliff:g></string>
+
 </resources>
diff --git a/src/com/android/launcher3/AlphabeticalAppsList.java b/src/com/android/launcher3/AlphabeticalAppsList.java
index 477c00f..70e36a7 100644
--- a/src/com/android/launcher3/AlphabeticalAppsList.java
+++ b/src/com/android/launcher3/AlphabeticalAppsList.java
@@ -82,17 +82,30 @@
      * Info about a section in the alphabetic list
      */
     public static class SectionInfo {
-        // The name of this section
-        public String sectionName;
         // The number of applications in this section
-        public int numAppsInSection;
-        // The section AdapterItem for this section
-        public AdapterItem sectionItem;
+        public int numApps;
+        // The section break AdapterItem for this section
+        public AdapterItem sectionBreakItem;
         // The first app AdapterItem for this section
         public AdapterItem firstAppItem;
+    }
 
-        public SectionInfo(String name) {
-            sectionName = name;
+    /**
+     * Info about a fast scroller section, depending if sections are merged, the fast scroller
+     * sections will not be the same set as the section headers.
+     */
+    public static class FastScrollSectionInfo {
+        // The section name
+        public String sectionName;
+        // To map the touch (from 0..1) to the index in the app list to jump to in the fast
+        // scroller, we use the fraction in range (0..1) of the app index / total app count.
+        public float appRangeFraction;
+        // The AdapterItem to scroll to for this section
+        public AdapterItem appItem;
+
+        public FastScrollSectionInfo(String sectionName, float appRangeFraction) {
+            this.sectionName = sectionName;
+            this.appRangeFraction = appRangeFraction;
         }
     }
 
@@ -100,32 +113,41 @@
      * Info about a particular adapter item (can be either section or app)
      */
     public static class AdapterItem {
+        /** Section & App properties */
         // The index of this adapter item in the list
         public int position;
         // Whether or not the item at this adapter position is a section or not
         public boolean isSectionHeader;
-        // The name of this section, or the section that this app is contained in
-        public String sectionName;
-        // The associated AppInfo, or null if this adapter item is a section
-        public AppInfo appInfo;
-        // The index of this app (not including sections), or -1 if this adapter item is a section
-        public int appIndex;
+        // The section for this item
+        public SectionInfo sectionInfo;
 
-        public static AdapterItem asSection(int pos, String name) {
+        /** App-only properties */
+        // The section name of this app.  Note that there can be multiple items with different
+        // sectionNames in the same section
+        public String sectionName = null;
+        // The index of this app in the section
+        public int sectionAppIndex = -1;
+        // The associated AppInfo for the app
+        public AppInfo appInfo = null;
+        // The index of this app not including sections
+        public int appIndex = -1;
+
+        public static AdapterItem asSectionBreak(int pos, SectionInfo section) {
             AdapterItem item = new AdapterItem();
             item.position = pos;
             item.isSectionHeader = true;
-            item.sectionName = name;
-            item.appInfo = null;
-            item.appIndex = -1;
+            item.sectionInfo = section;
             return item;
         }
 
-        public static AdapterItem asApp(int pos, String sectionName, AppInfo appInfo, int appIndex) {
+        public static AdapterItem asApp(int pos, SectionInfo section, String sectionName,
+                                        int sectionAppIndex, AppInfo appInfo, int appIndex) {
             AdapterItem item = new AdapterItem();
             item.position = pos;
             item.isSectionHeader = false;
+            item.sectionInfo = section;
             item.sectionName = sectionName;
+            item.sectionAppIndex = sectionAppIndex;
             item.appInfo = appInfo;
             item.appIndex = appIndex;
             return item;
@@ -140,12 +162,13 @@
     }
 
     // The maximum number of rows allowed in a merged section before we stop merging
-    private static final int MAX_ROWS_IN_MERGED_SECTION = Integer.MAX_VALUE;
+    private static final int MAX_ROWS_IN_MERGED_SECTION = 3;
 
     private List<AppInfo> mApps = new ArrayList<>();
     private List<AppInfo> mFilteredApps = new ArrayList<>();
     private List<AdapterItem> mSectionedFilteredApps = new ArrayList<>();
     private List<SectionInfo> mSections = new ArrayList<>();
+    private List<FastScrollSectionInfo> mFastScrollerSections = new ArrayList<>();
     private RecyclerView.Adapter mAdapter;
     private Filter mFilter;
     private AlphabeticIndexCompat mIndexer;
@@ -184,6 +207,13 @@
     }
 
     /**
+     * Returns fast scroller sections of all the current filtered applications.
+     */
+    public List<FastScrollSectionInfo> getFastScrollerSections() {
+        return mFastScrollerSections;
+    }
+
+    /**
      * Returns the current filtered list of applications broken down into their sections.
      */
     public List<AdapterItem> getAdapterItems() {
@@ -226,7 +256,6 @@
      * Sets the current set of apps.
      */
     public void setApps(List<AppInfo> apps) {
-        Collections.sort(apps, mAppNameComparator.getComparator());
         mApps.clear();
         mApps.addAll(apps);
         onAppsUpdated();
@@ -241,6 +270,8 @@
         for (AppInfo info : apps) {
             addApp(info);
         }
+        onAppsUpdated();
+        mAdapter.notifyDataSetChanged();
     }
 
     /**
@@ -251,12 +282,12 @@
             int index = mApps.indexOf(info);
             if (index != -1) {
                 mApps.set(index, info);
-                onAppsUpdated();
-                mAdapter.notifyItemChanged(index);
             } else {
                 addApp(info);
             }
         }
+        onAppsUpdated();
+        mAdapter.notifyDataSetChanged();
     }
 
     /**
@@ -267,10 +298,10 @@
             int removeIndex = findAppByComponent(mApps, info);
             if (removeIndex != -1) {
                 mApps.remove(removeIndex);
-                onAppsUpdated();
-                mAdapter.notifyDataSetChanged();
             }
         }
+        onAppsUpdated();
+        mAdapter.notifyDataSetChanged();
     }
 
     /**
@@ -290,14 +321,12 @@
     }
 
     /**
-     * Implementation to actually add an app to the alphabetic list
+     * Implementation to actually add an app to the alphabetic list, but does not notify.
      */
     private void addApp(AppInfo info) {
         int index = Collections.binarySearch(mApps, info, mAppNameComparator.getComparator());
         if (index < 0) {
             mApps.add(-(index + 1), info);
-            onAppsUpdated();
-            mAdapter.notifyDataSetChanged();
         }
     }
 
@@ -305,13 +334,20 @@
      * Updates internals when the set of apps are updated.
      */
     private void onAppsUpdated() {
+        // Sort the list of apps
+        Collections.sort(mApps, mAppNameComparator.getComparator());
+
         // Recreate the filtered and sectioned apps (for convenience for the grid layout)
         mFilteredApps.clear();
         mSections.clear();
         mSectionedFilteredApps.clear();
+        mFastScrollerSections.clear();
         SectionInfo lastSectionInfo = null;
+        String lastSectionName = null;
+        FastScrollSectionInfo lastFastScrollerSectionInfo = null;
         int position = 0;
         int appIndex = 0;
+        int numApps = mApps.size();
         for (AppInfo info : mApps) {
             String sectionName = mIndexer.computeSectionName(info.title.toString().trim());
 
@@ -321,24 +357,29 @@
             }
 
             // Create a new section if necessary
-            if (lastSectionInfo == null || !lastSectionInfo.sectionName.equals(sectionName)) {
-                lastSectionInfo = new SectionInfo(sectionName);
+            if (lastSectionInfo == null || !sectionName.equals(lastSectionName)) {
+                lastSectionName = sectionName;
+                lastSectionInfo = new SectionInfo();
                 mSections.add(lastSectionInfo);
+                lastFastScrollerSectionInfo = new FastScrollSectionInfo(sectionName,
+                        (float) appIndex / numApps);
+                mFastScrollerSections.add(lastFastScrollerSectionInfo);
 
                 // Create a new section item, this item is used to break the flow of items in the
                 // list
-                AdapterItem sectionItem = AdapterItem.asSection(position++, sectionName);
+                AdapterItem sectionItem = AdapterItem.asSectionBreak(position++, lastSectionInfo);
                 if (!AppsContainerView.GRID_HIDE_SECTION_HEADERS && !hasFilter()) {
-                    lastSectionInfo.sectionItem = sectionItem;
+                    lastSectionInfo.sectionBreakItem = sectionItem;
                     mSectionedFilteredApps.add(sectionItem);
                 }
             }
 
             // Create an app item
-            AdapterItem appItem = AdapterItem.asApp(position++, sectionName, info, appIndex++);
-            lastSectionInfo.numAppsInSection++;
+            AdapterItem appItem = AdapterItem.asApp(position++, lastSectionInfo, sectionName,
+                    lastSectionInfo.numApps++, info, appIndex++);
             if (lastSectionInfo.firstAppItem == null) {
                 lastSectionInfo.firstAppItem = appItem;
+                lastFastScrollerSectionInfo.appItem = appItem;
             }
             mSectionedFilteredApps.add(appItem);
             mFilteredApps.add(info);
@@ -350,40 +391,38 @@
             int sectionAppCount = 0;
             for (int i = 0; i < mSections.size(); i++) {
                 SectionInfo section = mSections.get(i);
-                String mergedSectionName = section.sectionName;
-                sectionAppCount = section.numAppsInSection;
+                sectionAppCount = section.numApps;
                 int mergeCount = 1;
+
                 // Merge rows if the last app in this section is in a column that is greater than
                 // 0, but less than the min number of apps per row.  In addition, apply the
                 // constraint to stop merging if the number of rows in the section is greater than
                 // some limit, and also if there are no lessons to merge.
                 while (0 < (sectionAppCount % mNumAppsPerRow) &&
                         (sectionAppCount % mNumAppsPerRow) < minNumAppsPerRow &&
-                        (int) Math.ceil(sectionAppCount / mNumAppsPerRow) < MAX_ROWS_IN_MERGED_SECTION &&
+                        (sectionAppCount / mNumAppsPerRow) < MAX_ROWS_IN_MERGED_SECTION &&
                         (i + 1) < mSections.size()) {
                     SectionInfo nextSection = mSections.remove(i + 1);
-                    // Merge the section names
-                    if (AppsContainerView.GRID_MERGE_SECTION_HEADERS) {
-                        mergedSectionName += nextSection.sectionName;
-                    }
+
                     // Remove the next section break
-                    mSectionedFilteredApps.remove(nextSection.sectionItem);
-                    if (AppsContainerView.GRID_MERGE_SECTION_HEADERS) {
-                        // Update the section names for the two sections
-                        int pos = mSectionedFilteredApps.indexOf(section.firstAppItem);
-                        for (int j = pos; j < (pos + section.numAppsInSection + nextSection.numAppsInSection); j++) {
-                            AdapterItem item = mSectionedFilteredApps.get(j);
-                            item.sectionName = mergedSectionName;
-                        }
+                    mSectionedFilteredApps.remove(nextSection.sectionBreakItem);
+                    int pos = mSectionedFilteredApps.indexOf(section.firstAppItem);
+                    // Point the section for these new apps to the merged section
+                    int nextPos = pos + section.numApps;
+                    for (int j = nextPos; j < (nextPos + nextSection.numApps); j++) {
+                        AdapterItem item = mSectionedFilteredApps.get(j);
+                        item.sectionInfo = section;
+                        item.sectionAppIndex += section.numApps;
                     }
-                    // Update the following adapter items of the removed section
-                    int pos = mSectionedFilteredApps.indexOf(nextSection.firstAppItem);
+
+                    // Update the following adapter items of the removed section item
+                    pos = mSectionedFilteredApps.indexOf(nextSection.firstAppItem);
                     for (int j = pos; j < mSectionedFilteredApps.size(); j++) {
                         AdapterItem item = mSectionedFilteredApps.get(j);
                         item.position--;
                     }
-                    section.numAppsInSection += nextSection.numAppsInSection;
-                    sectionAppCount += nextSection.numAppsInSection;
+                    section.numApps += nextSection.numApps;
+                    sectionAppCount += nextSection.numApps;
                     mergeCount++;
                     if (mergeCount >= mMaxAllowableMerges) {
                         break;
diff --git a/src/com/android/launcher3/AppsContainerRecyclerView.java b/src/com/android/launcher3/AppsContainerRecyclerView.java
index 7f64be2..6556cf9 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;
@@ -290,8 +254,9 @@
             mFastScrollTextPaint.setAlpha((int) (mFastScrollAlpha * 255));
             mFastScrollTextPaint.getTextBounds(mFastScrollSectionName, 0,
                     mFastScrollSectionName.length(), mFastScrollTextBounds);
+            float textWidth = mFastScrollTextPaint.measureText(mFastScrollSectionName);
             canvas.drawText(mFastScrollSectionName,
-                    (bgBounds.width() - mFastScrollTextBounds.width()) / 2,
+                    (bgBounds.width() - textWidth) / 2,
                     bgBounds.height() - (bgBounds.height() - mFastScrollTextBounds.height()) / 2,
                     mFastScrollTextPaint);
             canvas.restoreToCount(restoreCount);
@@ -321,38 +286,33 @@
     }
 
     /**
-     * Maps the progress (from 0..1) to the position that should be visible
+     * Maps the touch (from 0..1) to the adapter position that should be visible.
      */
-    private String scrollToPositionAtProgress(float progress) {
-        List<AlphabeticalAppsList.SectionInfo> sections = mApps.getSections();
-        if (sections.isEmpty()) {
+    private String scrollToPositionAtProgress(float touchFraction) {
+        // Ensure that we have any sections
+        List<AlphabeticalAppsList.FastScrollSectionInfo> fastScrollSections =
+                mApps.getFastScrollerSections();
+        if (fastScrollSections.isEmpty()) {
             return "";
         }
 
-        // Find the position of the first application in the section that contains the row at the
-        // current progress
-        List<AlphabeticalAppsList.AdapterItem> items = mApps.getAdapterItems();
-        int rowAtProgress = (int) (progress * getNumRows());
-        int rowCount = 0;
-        AlphabeticalAppsList.SectionInfo lastSectionInfo = null;
-        for (AlphabeticalAppsList.SectionInfo section : sections) {
-            int numRowsInSection = (int) Math.ceil((float) section.numAppsInSection / mNumAppsPerRow);
-            if (rowCount + numRowsInSection >= rowAtProgress) {
-                lastSectionInfo = section;
+        AlphabeticalAppsList.FastScrollSectionInfo lastScrollSection = fastScrollSections.get(0);
+        for (int i = 1; i < fastScrollSections.size(); i++) {
+            AlphabeticalAppsList.FastScrollSectionInfo scrollSection = fastScrollSections.get(i);
+            if (lastScrollSection.appRangeFraction <= touchFraction &&
+                    touchFraction < scrollSection.appRangeFraction) {
                 break;
             }
-            rowCount += numRowsInSection;
+            lastScrollSection = scrollSection;
         }
-        int position = items.indexOf(lastSectionInfo.firstAppItem);
 
         // Scroll the position into view, anchored at the top of the screen if possible. We call the
         // scroll method on the LayoutManager directly since it is not exposed by RecyclerView.
         LinearLayoutManager layoutManager = (LinearLayoutManager) getLayoutManager();
         stopScroll();
-        layoutManager.scrollToPositionWithOffset(position, 0);
+        layoutManager.scrollToPositionWithOffset(lastScrollSection.appItem.position, 0);
 
-        // Return the section name of the row
-        return lastSectionInfo.sectionName;
+        return lastScrollSection.sectionName;
     }
 
     /**
@@ -428,11 +388,11 @@
         int appIndex = 0;
         int rowCount = 0;
         for (AlphabeticalAppsList.SectionInfo info : sections) {
-            int numRowsInSection = (int) Math.ceil((float) info.numAppsInSection / mNumAppsPerRow);
-            if (appIndex + info.numAppsInSection > position) {
+            int numRowsInSection = (int) Math.ceil((float) info.numApps / mNumAppsPerRow);
+            if (appIndex + info.numApps > position) {
                 return rowCount + ((position - appIndex) / mNumAppsPerRow);
             }
-            appIndex += info.numAppsInSection;
+            appIndex += info.numApps;
             rowCount += numRowsInSection;
         }
         return appIndex;
@@ -445,7 +405,7 @@
         List<AlphabeticalAppsList.SectionInfo> sections = mApps.getSections();
         int rowCount = 0;
         for (AlphabeticalAppsList.SectionInfo info : sections) {
-            int numRowsInSection = (int) Math.ceil((float) info.numAppsInSection / mNumAppsPerRow);
+            int numRowsInSection = (int) Math.ceil((float) info.numApps / mNumAppsPerRow);
             rowCount += numRowsInSection;
         }
         return rowCount;
diff --git a/src/com/android/launcher3/AppsContainerSearchEditTextView.java b/src/com/android/launcher3/AppsContainerSearchEditTextView.java
new file mode 100644
index 0000000..c688237
--- /dev/null
+++ b/src/com/android/launcher3/AppsContainerSearchEditTextView.java
@@ -0,0 +1,65 @@
+/*
+ * 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.util.AttributeSet;
+import android.view.KeyEvent;
+import android.widget.EditText;
+
+
+/**
+ * The edit text for the search container
+ */
+public class AppsContainerSearchEditTextView extends EditText {
+
+    /**
+     * Implemented by listeners of the back key.
+     */
+    public interface OnBackKeyListener {
+        public void onBackKey();
+    }
+
+    private OnBackKeyListener mBackKeyListener;
+
+    public AppsContainerSearchEditTextView(Context context) {
+        this(context, null);
+    }
+
+    public AppsContainerSearchEditTextView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public AppsContainerSearchEditTextView(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+    }
+
+    public void setOnBackKeyListener(OnBackKeyListener listener) {
+        mBackKeyListener = listener;
+    }
+
+    @Override
+    public boolean onKeyPreIme(int keyCode, KeyEvent event) {
+        // If this is a back key, propagate the key back to the listener
+        if (keyCode == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_UP) {
+            if (mBackKeyListener != null) {
+                mBackKeyListener.onBackKey();
+            }
+            return false;
+        }
+        return super.onKeyPreIme(keyCode, event);
+    }
+}
diff --git a/src/com/android/launcher3/AppsContainerView.java b/src/com/android/launcher3/AppsContainerView.java
index 9122427..993f9c8 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,21 +39,22 @@
 
 
 /**
- * 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;
-    public static final boolean GRID_MERGE_SECTION_HEADERS = false;
     public static final boolean GRID_HIDE_SECTION_HEADERS = false;
 
     private static final boolean ALLOW_SINGLE_APP_LAUNCH = true;
     private static final boolean DYNAMIC_HEADER_ELEVATION = false;
+    private static final boolean DISMISS_SEARCH_ON_BACK = true;
     private static final float HEADER_ELEVATION_DP = 4;
     private static final int FADE_IN_DURATION = 175;
-    private static final int FADE_OUT_DURATION = 125;
+    private static final int FADE_OUT_DURATION = 100;
+    private static final int SEARCH_TRANSLATION_X_DP = 18;
 
     @Thunk Launcher mLauncher;
     @Thunk AlphabeticalAppsList mApps;
@@ -68,18 +68,14 @@
     private View mSearchBarContainerView;
     private View mSearchButtonView;
     private View mDismissSearchButtonView;
-    private EditText mSearchBarEditView;
+    private AppsContainerSearchEditTextView mSearchBarEditView;
 
     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 +95,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 +140,8 @@
      */
     public void hideHeaderBar() {
         mHeaderView.setVisibility(View.GONE);
-        updateBackgrounds();
-        updatePaddings();
+        onUpdateBackgrounds();
+        onUpdatePaddings();
     }
 
     /**
@@ -199,10 +193,19 @@
         mSearchBarContainerView = findViewById(R.id.app_search_container);
         mDismissSearchButtonView = mSearchBarContainerView.findViewById(R.id.dismiss_search_button);
         mDismissSearchButtonView.setOnClickListener(this);
-        mSearchBarEditView = (EditText) findViewById(R.id.app_search_box);
+        mSearchBarEditView = (AppsContainerSearchEditTextView) findViewById(R.id.app_search_box);
         if (mSearchBarEditView != null) {
             mSearchBarEditView.addTextChangedListener(this);
             mSearchBarEditView.setOnEditorActionListener(this);
+            if (DISMISS_SEARCH_ON_BACK) {
+                mSearchBarEditView.setOnBackKeyListener(
+                        new AppsContainerSearchEditTextView.OnBackKeyListener() {
+                            @Override
+                            public void onBackKey() {
+                                hideSearchField(true, true);
+                            }
+                        });
+            }
         }
         mAppsRecyclerView = (AppsContainerRecyclerView) findViewById(R.id.apps_list_view);
         mAppsRecyclerView.setApps(mApps);
@@ -225,46 +228,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,72 +569,20 @@
     }
 
     /**
-     * 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() {
         // Show the search bar and focus the search
+        final int translationX = DynamicGrid.pxFromDp(SEARCH_TRANSLATION_X_DP,
+                getContext().getResources().getDisplayMetrics());
         mSearchBarContainerView.setVisibility(View.VISIBLE);
         mSearchBarContainerView.setAlpha(0f);
-        mSearchBarContainerView.animate().alpha(1f).setDuration(FADE_IN_DURATION).withLayer()
+        mSearchBarContainerView.setTranslationX(translationX);
+        mSearchBarContainerView.animate()
+                .alpha(1f)
+                .translationX(0)
+                .setDuration(FADE_IN_DURATION)
+                .withLayer()
                 .withEndAction(new Runnable() {
                     @Override
                     public void run() {
@@ -605,38 +591,57 @@
                                 InputMethodManager.SHOW_IMPLICIT);
                     }
                 });
-        mSearchButtonView.animate().alpha(0f).setDuration(FADE_OUT_DURATION).withLayer();
+        mSearchButtonView.animate()
+                .alpha(0f)
+                .translationX(-translationX)
+                .setDuration(FADE_OUT_DURATION)
+                .withLayer();
     }
 
     /**
      * Hides the search field.
      */
     private void hideSearchField(boolean animated, final boolean returnFocusToRecyclerView) {
+        final boolean resetTextField = mSearchBarEditView.getText().toString().length() > 0;
+        final int translationX = DynamicGrid.pxFromDp(SEARCH_TRANSLATION_X_DP,
+                getContext().getResources().getDisplayMetrics());
         if (animated) {
             // Hide the search bar and focus the recycler view
-            mSearchBarContainerView.animate().alpha(0f).setDuration(FADE_IN_DURATION).withLayer()
+            mSearchBarContainerView.animate()
+                    .alpha(0f)
+                    .translationX(0)
+                    .setDuration(FADE_IN_DURATION)
+                    .withLayer()
                     .withEndAction(new Runnable() {
                         @Override
                         public void run() {
                             mSearchBarContainerView.setVisibility(View.INVISIBLE);
-                            mSearchBarEditView.setText("");
+                            if (resetTextField) {
+                                mSearchBarEditView.setText("");
+                            }
                             mApps.setFilter(null);
                             if (returnFocusToRecyclerView) {
                                 mAppsRecyclerView.requestFocus();
                             }
-                            scrollToTop();
                         }
                     });
-            mSearchButtonView.animate().alpha(1f).setDuration(FADE_OUT_DURATION).withLayer();
+            mSearchButtonView.setTranslationX(-translationX);
+            mSearchButtonView.animate()
+                    .alpha(1f)
+                    .translationX(0)
+                    .setDuration(FADE_OUT_DURATION)
+                    .withLayer();
         } else {
             mSearchBarContainerView.setVisibility(View.INVISIBLE);
-            mSearchBarEditView.setText("");
+            if (resetTextField) {
+                mSearchBarEditView.setText("");
+            }
             mApps.setFilter(null);
             mSearchButtonView.setAlpha(1f);
+            mSearchButtonView.setTranslationX(0f);
             if (returnFocusToRecyclerView) {
                 mAppsRecyclerView.requestFocus();
             }
-            scrollToTop();
         }
         getInputMethodManager().hideSoftInputFromWindow(getWindowToken(), 0);
     }
diff --git a/src/com/android/launcher3/AppsGridAdapter.java b/src/com/android/launcher3/AppsGridAdapter.java
index 62d9129..259740c 100644
--- a/src/com/android/launcher3/AppsGridAdapter.java
+++ b/src/com/android/launcher3/AppsGridAdapter.java
@@ -4,11 +4,10 @@
 import android.content.res.Resources;
 import android.graphics.Canvas;
 import android.graphics.Paint;
-import android.graphics.Point;
+import android.graphics.PointF;
 import android.graphics.Rect;
 import android.support.v7.widget.GridLayoutManager;
 import android.support.v7.widget.RecyclerView;
-import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
@@ -84,7 +83,7 @@
 
         private static final boolean FADE_OUT_SECTIONS = false;
 
-        private HashMap<String, Point> mCachedSectionBounds = new HashMap<>();
+        private HashMap<String, PointF> mCachedSectionBounds = new HashMap<>();
         private Rect mTmpBounds = new Rect();
 
         @Override
@@ -94,75 +93,75 @@
             }
 
             List<AlphabeticalAppsList.AdapterItem> items = mApps.getAdapterItems();
-            String lastSectionName = null;
-            int appIndexInSection = 0;
+            int childCount = parent.getChildCount();
             int lastSectionTop = 0;
             int lastSectionHeight = 0;
-            for (int i = 0; i < parent.getChildCount(); i++) {
+            for (int i = 0; i < childCount; i++) {
                 View child = parent.getChildAt(i);
                 ViewHolder holder = (ViewHolder) parent.getChildViewHolder(child);
                 if (shouldDrawItemSection(holder, child, i, items)) {
-                    int cellTopOffset = (2 * child.getPaddingTop());
+                    // At this point, we only draw sections for each section break;
+                    int viewTopOffset = (2 * child.getPaddingTop());
                     int pos = holder.getPosition();
                     AlphabeticalAppsList.AdapterItem item = items.get(pos);
-                    if (!item.sectionName.equals(lastSectionName)) {
-                        lastSectionName = item.sectionName;
+                    AlphabeticalAppsList.SectionInfo sectionInfo = item.sectionInfo;
+
+                    // Draw all the sections for this index
+                    String lastSectionName = item.sectionName;
+                    for (int j = item.sectionAppIndex; j < sectionInfo.numApps; j++, pos++) {
+                        AlphabeticalAppsList.AdapterItem nextItem = items.get(pos);
+                        String sectionName = nextItem.sectionName;
+                        if (nextItem.sectionInfo != sectionInfo) {
+                            break;
+                        }
+                        if (j > item.sectionAppIndex && sectionName.equals(lastSectionName)) {
+                            continue;
+                        }
 
                         // Find the section code points
-                        String sectionBegin = null;
-                        String sectionEnd = null;
-                        int charOffset = 0;
-                        while (charOffset < item.sectionName.length()) {
-                            int codePoint = item.sectionName.codePointAt(charOffset);
-                            int codePointSize = Character.charCount(codePoint);
-                            if (charOffset == 0) {
-                                // The first code point
-                                sectionBegin = item.sectionName.substring(charOffset, charOffset + codePointSize);
-                            } else if ((charOffset + codePointSize) >= item.sectionName.length()) {
-                                // The last code point
-                                sectionEnd = item.sectionName.substring(charOffset, charOffset + codePointSize);
-                            }
-                            charOffset += codePointSize;
+                        PointF sectionBounds = getAndCacheSectionBounds(sectionName);
+
+                        // Calculate where to draw the section
+                        int sectionBaseline = (int) (viewTopOffset + sectionBounds.y);
+                        int x = mIsRtl ? parent.getWidth() - mPaddingStart - mStartMargin :
+                                mPaddingStart;
+                        x += (int) ((mStartMargin - sectionBounds.x) / 2f);
+                        int y = child.getTop() + sectionBaseline;
+
+                        // Determine whether this is the last row with apps in that section, if
+                        // so, then fix the section to the row allowing it to scroll past the
+                        // baseline, otherwise, bound it to the baseline so it's in the viewport
+                        int appIndexInSection = items.get(pos).sectionAppIndex;
+                        int nextRowPos = Math.min(items.size() - 1,
+                                pos + mAppsPerRow - (appIndexInSection % mAppsPerRow));
+                        AlphabeticalAppsList.AdapterItem nextRowItem = items.get(nextRowPos);
+                        boolean fixedToRow = !sectionName.equals(nextRowItem.sectionName);
+                        if (!fixedToRow) {
+                            y = Math.max(sectionBaseline, y);
                         }
 
-                        Point sectionBeginBounds = getAndCacheSectionBounds(sectionBegin);
-                        int minTop = cellTopOffset + sectionBeginBounds.y;
-                        int top = child.getTop() + cellTopOffset + sectionBeginBounds.y;
-                        int left = mIsRtl ? parent.getWidth() - mPaddingStart - mStartMargin :
-                                mPaddingStart;
-                        int col = appIndexInSection % mAppsPerRow;
-                        int nextRowPos = Math.min(pos - col + mAppsPerRow, items.size() - 1);
-                        int alpha = 255;
-                        boolean fixedToRow = !items.get(nextRowPos).sectionName.equals(item.sectionName);
-                        if (fixedToRow) {
-                            alpha = Math.min(255, (int) (255 * (Math.max(0, top) / (float) minTop)));
-                        } else {
-                            // If we aren't fixed to the current row, then bound into the viewport
-                            top = Math.max(minTop, top);
+                        // In addition, if it overlaps with the last section that was drawn, then
+                        // offset it so that it does not overlap
+                        if (lastSectionHeight > 0 && y <= (lastSectionTop + lastSectionHeight)) {
+                            y += lastSectionTop - y + lastSectionHeight;
                         }
-                        if (lastSectionHeight > 0 && top <= (lastSectionTop + lastSectionHeight)) {
-                            top += lastSectionTop - top + lastSectionHeight;
-                        }
+
+                        // Draw the section header
                         if (FADE_OUT_SECTIONS) {
+                            int alpha = 255;
+                            if (fixedToRow) {
+                                alpha = Math.min(255,
+                                        (int) (255 * (Math.max(0, y) / (float) sectionBaseline)));
+                            }
                             mSectionTextPaint.setAlpha(alpha);
                         }
-                        if (sectionEnd != null) {
-                            Point sectionEndBounds = getAndCacheSectionBounds(sectionEnd);
-                            c.drawText(sectionBegin + "/" + sectionEnd,
-                                    left + (mStartMargin - sectionBeginBounds.x - sectionEndBounds.x) / 2, top,
-                                    mSectionTextPaint);
-                        } else {
-                            c.drawText(sectionBegin, left + (mStartMargin - sectionBeginBounds.x) / 2, top,
-                                    mSectionTextPaint);
-                        }
-                        lastSectionTop = top;
-                        lastSectionHeight = sectionBeginBounds.y + mSectionHeaderOffset;
+                        c.drawText(sectionName, x, y, mSectionTextPaint);
+
+                        lastSectionTop = y;
+                        lastSectionHeight = (int) (sectionBounds.y + mSectionHeaderOffset);
+                        lastSectionName = sectionName;
                     }
-                }
-                if (holder.mIsSectionHeader) {
-                    appIndexInSection = 0;
-                } else {
-                    appIndexInSection++;
+                    i += (sectionInfo.numApps - item.sectionAppIndex);
                 }
             }
         }
@@ -173,16 +172,22 @@
             // Do nothing
         }
 
-        private Point getAndCacheSectionBounds(String sectionName) {
-            Point bounds = mCachedSectionBounds.get(sectionName);
+        /**
+         * Given a section name, return the bounds of the given section name.
+         */
+        private PointF getAndCacheSectionBounds(String sectionName) {
+            PointF bounds = mCachedSectionBounds.get(sectionName);
             if (bounds == null) {
                 mSectionTextPaint.getTextBounds(sectionName, 0, sectionName.length(), mTmpBounds);
-                bounds = new Point(mTmpBounds.width(), mTmpBounds.height());
+                bounds = new PointF(mSectionTextPaint.measureText(sectionName), mTmpBounds.height());
                 mCachedSectionBounds.put(sectionName, bounds);
             }
             return bounds;
         }
 
+        /**
+         * Returns whether to draw the section for the given child.
+         */
         private boolean shouldDrawItemSection(ViewHolder holder, View child, int childIndex,
                 List<AlphabeticalAppsList.AdapterItem> items) {
             // Ensure item is not already removed
@@ -201,19 +206,12 @@
             }
             // Ensure we have a holder position
             int pos = holder.getPosition();
-            if (pos < 0 || pos >= items.size()) {
+            if (pos <= 0 || pos >= items.size()) {
                 return false;
             }
-            // Ensure this is not a section header
-            if (items.get(pos).isSectionHeader) {
-                return false;
-            }
-            // Only draw the header for the first item in a section, or whenever the sub-sections
-            // changes (if AppsContainerView.GRID_MERGE_SECTIONS is true, but
-            // AppsContainerView.GRID_MERGE_SECTION_HEADERS is false)
+            // Draw the section header for the first item in each section
             return (childIndex == 0) ||
-                    items.get(pos - 1).isSectionHeader && !items.get(pos).isSectionHeader ||
-                    (!items.get(pos - 1).sectionName.equals(items.get(pos).sectionName));
+                    (items.get(pos - 1).isSectionHeader && !items.get(pos).isSectionHeader);
         }
     }
 
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/CellLayout.java b/src/com/android/launcher3/CellLayout.java
index 65c6702..72eabf1 100644
--- a/src/com/android/launcher3/CellLayout.java
+++ b/src/com/android/launcher3/CellLayout.java
@@ -3051,4 +3051,21 @@
     public boolean findVacantCell(int spanX, int spanY, int[] outXY) {
         return Utilities.findVacantCell(outXY, spanX, spanY, mCountX, mCountY, mOccupied);
     }
+
+    public boolean isRegionVacant(int x, int y, int spanX, int spanY) {
+        int x2 = x + spanX - 1;
+        int y2 = y + spanY - 1;
+        if (x < 0 || y < 0 || x2 >= mCountX || y2 >= mCountY) {
+            return false;
+        }
+        for (int i = x; i <= x2; i++) {
+            for (int j = y; j <= y2; j++) {
+                if (mOccupied[i][j]) {
+                    return false;
+                }
+            }
+        }
+
+        return true;
+    }
 }
diff --git a/src/com/android/launcher3/DragLayer.java b/src/com/android/launcher3/DragLayer.java
index 91f97fa..2efdb06 100644
--- a/src/com/android/launcher3/DragLayer.java
+++ b/src/com/android/launcher3/DragLayer.java
@@ -38,7 +38,6 @@
 import android.widget.FrameLayout;
 import android.widget.TextView;
 
-import com.android.launcher3.InsettableFrameLayout.LayoutParams;
 import com.android.launcher3.util.Thunk;
 
 import java.util.ArrayList;
@@ -153,6 +152,14 @@
         return false;
     }
 
+    private boolean isEventOverDropTargetBar(MotionEvent ev) {
+        getDescendantRectRelativeToSelf(mLauncher.getSearchBar(), mHitRect);
+        if (mHitRect.contains((int) ev.getX(), (int) ev.getY())) {
+            return true;
+        }
+        return false;
+    }
+
     public void setBlockTouch(boolean block) {
         mBlockTouches = block;
     }
@@ -188,10 +195,16 @@
                 }
             }
 
-            getDescendantRectRelativeToSelf(currentFolder, hitRect);
             if (!isEventOverFolder(currentFolder, ev)) {
-                mLauncher.closeFolder();
-                return true;
+                if (isInAccessibleDrag()) {
+                    // Do not close the folder if in drag and drop.
+                    if (!isEventOverDropTargetBar(ev)) {
+                        return true;
+                    }
+                } else {
+                    mLauncher.closeFolder();
+                    return true;
+                }
             }
         }
         return false;
@@ -228,11 +241,12 @@
                         getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
             if (accessibilityManager.isTouchExplorationEnabled()) {
                 final int action = ev.getAction();
-                boolean isOverFolder;
+                boolean isOverFolderOrSearchBar;
                 switch (action) {
                     case MotionEvent.ACTION_HOVER_ENTER:
-                        isOverFolder = isEventOverFolder(currentFolder, ev);
-                        if (!isOverFolder) {
+                        isOverFolderOrSearchBar = isEventOverFolder(currentFolder, ev) ||
+                            (isInAccessibleDrag() && isEventOverDropTargetBar(ev));
+                        if (!isOverFolderOrSearchBar) {
                             sendTapOutsideFolderAccessibilityEvent(currentFolder.isEditingName());
                             mHoverPointClosesFolder = true;
                             return true;
@@ -240,12 +254,13 @@
                         mHoverPointClosesFolder = false;
                         break;
                     case MotionEvent.ACTION_HOVER_MOVE:
-                        isOverFolder = isEventOverFolder(currentFolder, ev);
-                        if (!isOverFolder && !mHoverPointClosesFolder) {
+                        isOverFolderOrSearchBar = isEventOverFolder(currentFolder, ev) ||
+                            (isInAccessibleDrag() && isEventOverDropTargetBar(ev));
+                        if (!isOverFolderOrSearchBar && !mHoverPointClosesFolder) {
                             sendTapOutsideFolderAccessibilityEvent(currentFolder.isEditingName());
                             mHoverPointClosesFolder = true;
                             return true;
-                        } else if (!isOverFolder) {
+                        } else if (!isOverFolderOrSearchBar) {
                             return true;
                         }
                         mHoverPointClosesFolder = false;
@@ -268,6 +283,12 @@
         }
     }
 
+    private boolean isInAccessibleDrag() {
+        LauncherAccessibilityDelegate delegate = LauncherAppState
+                .getInstance().getAccessibilityDelegate();
+        return delegate != null && delegate.isInAccessibleDrag();
+    }
+
     @Override
     public boolean onRequestSendAccessibilityEvent(View child, AccessibilityEvent event) {
         Folder currentFolder = mLauncher.getWorkspace().getOpenFolder();
@@ -275,6 +296,10 @@
             if (child == currentFolder) {
                 return super.onRequestSendAccessibilityEvent(child, event);
             }
+
+            if (isInAccessibleDrag() && child instanceof SearchDropTargetBar) {
+                return super.onRequestSendAccessibilityEvent(child, event);
+            }
             // Skip propagating onRequestSendAccessibilityEvent all for other children
             // when a folder is open
             return false;
@@ -288,6 +313,10 @@
         if (currentFolder != null) {
             // Only add the folder as a child for accessibility when it is open
             childrenForAccessibility.add(currentFolder);
+
+            if (isInAccessibleDrag()) {
+                childrenForAccessibility.add(mLauncher.getSearchBar());
+            }
         } else {
             super.addChildrenForAccessibility(childrenForAccessibility);
         }
diff --git a/src/com/android/launcher3/Folder.java b/src/com/android/launcher3/Folder.java
index d98b0eb..3e20375 100644
--- a/src/com/android/launcher3/Folder.java
+++ b/src/com/android/launcher3/Folder.java
@@ -285,6 +285,9 @@
         for (int i = 0; i < mContent.getChildCount(); i++) {
             mContent.getPageAt(i).enableAccessibleDrag(enable, CellLayout.FOLDER_ACCESSIBILITY_DRAG);
         }
+
+        mFooter.setImportantForAccessibility(enable ? IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS :
+            IMPORTANT_FOR_ACCESSIBILITY_AUTO);
         mLauncher.getWorkspace().setAddNewPageOnDrag(!enable);
     }
 
diff --git a/src/com/android/launcher3/FolderPagedView.java b/src/com/android/launcher3/FolderPagedView.java
index a1c909a..211bbfe 100644
--- a/src/com/android/launcher3/FolderPagedView.java
+++ b/src/com/android/launcher3/FolderPagedView.java
@@ -504,6 +504,10 @@
         }
     }
 
+    public int getAllocatedContentSize() {
+        return mAllocatedContentSize;
+    }
+
     /**
      * Reorders the items such that the {@param empty} spot moves to {@param target}
      */
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/LauncherAccessibilityDelegate.java b/src/com/android/launcher3/LauncherAccessibilityDelegate.java
index 8a9a050..3992e63 100644
--- a/src/com/android/launcher3/LauncherAccessibilityDelegate.java
+++ b/src/com/android/launcher3/LauncherAccessibilityDelegate.java
@@ -1,6 +1,9 @@
 package com.android.launcher3;
 
 import android.annotation.TargetApi;
+import android.app.AlertDialog;
+import android.appwidget.AppWidgetProviderInfo;
+import android.content.DialogInterface;
 import android.graphics.Rect;
 import android.os.Build;
 import android.os.Bundle;
@@ -28,6 +31,7 @@
     private static final int ADD_TO_WORKSPACE = R.id.action_add_to_workspace;
     private static final int MOVE = R.id.action_move;
     private static final int MOVE_TO_WORKSPACE = R.id.action_move_to_workspace;
+    private static final int RESIZE = R.id.action_resize;
 
     public enum DragType {
         ICON,
@@ -62,6 +66,8 @@
                 launcher.getText(R.string.action_move)));
         mActions.put(MOVE_TO_WORKSPACE, new AccessibilityAction(MOVE_TO_WORKSPACE,
                 launcher.getText(R.string.action_move_to_workspace)));
+        mActions.put(RESIZE, new AccessibilityAction(RESIZE,
+                        launcher.getText(R.string.action_resize)));
     }
 
     @Override
@@ -87,6 +93,10 @@
 
             if (item.container >= 0) {
                 info.addAction(mActions.get(MOVE_TO_WORKSPACE));
+            } else if (item instanceof LauncherAppWidgetInfo) {
+                if (!getSupportedResizeActions(host, (LauncherAppWidgetInfo) item).isEmpty()) {
+                    info.addAction(mActions.get(RESIZE));
+                }
             }
         } if ((item instanceof AppInfo) || (item instanceof PendingAddItemInfo)) {
             info.addAction(mActions.get(ADD_TO_WORKSPACE));
@@ -102,7 +112,7 @@
         return super.performAccessibilityAction(host, action, args);
     }
 
-    public boolean performAction(View host, final ItemInfo item, int action) {
+    public boolean performAction(final View host, final ItemInfo item, int action) {
         if (action == REMOVE) {
             if (DeleteDropTarget.removeWorkspaceOrFolderItem(mLauncher, item, host)) {
                 announceConfirmation(R.string.item_removed);
@@ -167,10 +177,97 @@
                     announceConfirmation(R.string.item_moved);
                 }
             });
+        } else if (action == RESIZE) {
+            final LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) item;
+            final ArrayList<Integer> actions = getSupportedResizeActions(host, info);
+            CharSequence[] labels = new CharSequence[actions.size()];
+            for (int i = 0; i < actions.size(); i++) {
+                labels[i] = mLauncher.getText(actions.get(i));
+            }
+
+            new AlertDialog.Builder(mLauncher)
+                .setTitle(R.string.action_resize)
+                .setItems(labels, new DialogInterface.OnClickListener() {
+
+                    @Override
+                    public void onClick(DialogInterface dialog, int which) {
+                        performResizeAction(actions.get(which), host, info);
+                        dialog.dismiss();
+                    }
+                })
+                .show();
         }
         return false;
     }
 
+    private ArrayList<Integer> getSupportedResizeActions(View host, LauncherAppWidgetInfo info) {
+        AppWidgetProviderInfo providerInfo = ((LauncherAppWidgetHostView) host).getAppWidgetInfo();
+        ArrayList<Integer> actions = new ArrayList<>();
+
+        CellLayout layout = (CellLayout) host.getParent().getParent();
+        if ((providerInfo.resizeMode & AppWidgetProviderInfo.RESIZE_HORIZONTAL) != 0) {
+            if (layout.isRegionVacant(info.cellX + info.spanX, info.cellY, 1, info.spanY) ||
+                    layout.isRegionVacant(info.cellX - 1, info.cellY, 1, info.spanY)) {
+                actions.add(R.string.action_increase_width);
+            }
+
+            if (info.spanX > info.minSpanX && info.spanX > 1) {
+                actions.add(R.string.action_decrease_width);
+            }
+        }
+
+        if ((providerInfo.resizeMode & AppWidgetProviderInfo.RESIZE_VERTICAL) != 0) {
+            if (layout.isRegionVacant(info.cellX, info.cellY + info.spanY, info.spanX, 1) ||
+                    layout.isRegionVacant(info.cellX, info.cellY - 1, info.spanX, 1)) {
+                actions.add(R.string.action_increase_height);
+            }
+
+            if (info.spanY > info.minSpanY && info.spanY > 1) {
+                actions.add(R.string.action_decrease_height);
+            }
+        }
+        return actions;
+    }
+
+    private void performResizeAction(int action, View host, LauncherAppWidgetInfo info) {
+        CellLayout.LayoutParams lp = (CellLayout.LayoutParams) host.getLayoutParams();
+        CellLayout layout = (CellLayout) host.getParent().getParent();
+        layout.markCellsAsUnoccupiedForView(host);
+
+        if (action == R.string.action_increase_width) {
+            if (((host.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL)
+                    && layout.isRegionVacant(info.cellX - 1, info.cellY, 1, info.spanY))
+                    || !layout.isRegionVacant(info.cellX + info.spanX, info.cellY, 1, info.spanY)) {
+                lp.cellX --;
+                info.cellX --;
+            }
+            lp.cellHSpan ++;
+            info.spanX ++;
+        } else if (action == R.string.action_decrease_width) {
+            lp.cellHSpan --;
+            info.spanX --;
+        } else if (action == R.string.action_increase_height) {
+            if (!layout.isRegionVacant(info.cellX, info.cellY + info.spanY, info.spanX, 1)) {
+                lp.cellY --;
+                info.cellY --;
+            }
+            lp.cellVSpan ++;
+            info.spanY ++;
+        } else if (action == R.string.action_decrease_height) {
+            lp.cellVSpan --;
+            info.spanY --;
+        }
+
+        layout.markCellsAsOccupiedForView(host);
+        Rect sizeRange = new Rect();
+        AppWidgetResizeFrame.getWidgetSizeRanges(mLauncher, info.spanX, info.spanY, sizeRange);
+        ((LauncherAppWidgetHostView) host).updateAppWidgetSize(null,
+                sizeRange.left, sizeRange.top, sizeRange.right, sizeRange.bottom);
+        host.requestLayout();
+        LauncherModel.updateItemInDatabase(mLauncher, info);
+        announceConfirmation(mLauncher.getString(R.string.widget_resized, info.spanX, info.spanY));
+    }
+
     @Thunk void announceConfirmation(int resId) {
         announceConfirmation(mLauncher.getResources().getString(resId));
     }
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index 7efdf32..e81c8c2 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -2810,6 +2810,8 @@
             } else {
                 mHandler.post(r);
             }
+            loadAndBindWidgetsAndShortcuts(mApp.getContext(), tryGetCallbacks(oldCallbacks),
+                    false /* refresh */);
         }
 
         private void loadAllApps() {
@@ -2871,8 +2873,6 @@
                     final Callbacks callbacks = tryGetCallbacks(oldCallbacks);
                     if (callbacks != null) {
                         callbacks.bindAllApplications(added);
-                        loadAndBindWidgetsAndShortcuts(mApp.getContext(), callbacks,
-                                true /* refresh */);
                         if (DEBUG_LOADERS) {
                             Log.d(TAG, "bound " + added.size() + " apps in "
                                 + (SystemClock.uptimeMillis() - bindTime) + "ms");
@@ -2885,6 +2885,8 @@
             // Cleanup any data stored for a deleted user.
             ManagedProfileHeuristic.processAllUsers(profiles, mContext);
 
+            loadAndBindWidgetsAndShortcuts(mApp.getContext(), tryGetCallbacks(oldCallbacks),
+                    true /* refresh */);
             if (DEBUG_LOADERS) {
                 Log.d(TAG, "Icons processed in "
                         + (SystemClock.uptimeMillis() - loadTime) + "ms");
diff --git a/src/com/android/launcher3/accessibility/FolderAccessibilityHelper.java b/src/com/android/launcher3/accessibility/FolderAccessibilityHelper.java
index fc105b4..ff99890 100644
--- a/src/com/android/launcher3/accessibility/FolderAccessibilityHelper.java
+++ b/src/com/android/launcher3/accessibility/FolderAccessibilityHelper.java
@@ -24,23 +24,29 @@
  * Implementation of {@link DragAndDropAccessibilityDelegate} to support DnD in a folder.
  */
 public class FolderAccessibilityHelper extends DragAndDropAccessibilityDelegate {
+
+    /**
+     * 0-index position for the first cell in {@link #mView} in {@link #mParent}.
+     */
     private final int mStartPosition;
 
+    private final FolderPagedView mParent;
+
     public FolderAccessibilityHelper(CellLayout layout) {
         super(layout);
-        FolderPagedView parent = (FolderPagedView) layout.getParent();
+        mParent = (FolderPagedView) layout.getParent();
 
-        int index = parent.indexOfChild(layout);
-        mStartPosition = 1 + index * layout.getCountX() * layout.getCountY();
+        int index = mParent.indexOfChild(layout);
+        mStartPosition = index * layout.getCountX() * layout.getCountY();
     }
     @Override
     protected int intersectsValidDropTarget(int id) {
-        return id;
+        return Math.min(id, mParent.getAllocatedContentSize() - mStartPosition - 1);
     }
 
     @Override
     protected String getLocationDescriptionForIconDrop(int id) {
-        return mContext.getString(R.string.move_to_position, id + mStartPosition);
+        return mContext.getString(R.string.move_to_position, id + mStartPosition + 1);
     }
 
     @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);
+        }
     }
 
     /**