Merge "Accessibility: Folder drag and drop fixes" into ub-launcher3-burnaby
diff --git a/res/drawable-hdpi/ic_arrow_back_grey.png b/res/drawable-hdpi/ic_arrow_back_grey.png
new file mode 100755
index 0000000..ccd3900
--- /dev/null
+++ b/res/drawable-hdpi/ic_arrow_back_grey.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_search_grey.png b/res/drawable-hdpi/ic_search_grey.png
new file mode 100755
index 0000000..f4c5e27
--- /dev/null
+++ b/res/drawable-hdpi/ic_search_grey.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_arrow_back_grey.png b/res/drawable-mdpi/ic_arrow_back_grey.png
new file mode 100755
index 0000000..11996ef
--- /dev/null
+++ b/res/drawable-mdpi/ic_arrow_back_grey.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_search_grey.png b/res/drawable-mdpi/ic_search_grey.png
new file mode 100755
index 0000000..e83891c
--- /dev/null
+++ b/res/drawable-mdpi/ic_search_grey.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_arrow_back_grey.png b/res/drawable-xhdpi/ic_arrow_back_grey.png
new file mode 100755
index 0000000..79b9b48
--- /dev/null
+++ b/res/drawable-xhdpi/ic_arrow_back_grey.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_search_grey.png b/res/drawable-xhdpi/ic_search_grey.png
new file mode 100755
index 0000000..bd5fdf4
--- /dev/null
+++ b/res/drawable-xhdpi/ic_search_grey.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_arrow_back_grey.png b/res/drawable-xxhdpi/ic_arrow_back_grey.png
new file mode 100755
index 0000000..8e42e09
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_arrow_back_grey.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_search_grey.png b/res/drawable-xxhdpi/ic_search_grey.png
new file mode 100755
index 0000000..1d5c913
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_search_grey.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_arrow_back_grey.png b/res/drawable-xxxhdpi/ic_arrow_back_grey.png
new file mode 100755
index 0000000..854a9bd
--- /dev/null
+++ b/res/drawable-xxxhdpi/ic_arrow_back_grey.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_search_grey.png b/res/drawable-xxxhdpi/ic_search_grey.png
new file mode 100755
index 0000000..28519fd
--- /dev/null
+++ b/res/drawable-xxxhdpi/ic_search_grey.png
Binary files differ
diff --git a/res/layout/apps_list_view.xml b/res/layout/apps_list_view.xml
index dfb7b58..a726cd8 100644
--- a/res/layout/apps_list_view.xml
+++ b/res/layout/apps_list_view.xml
@@ -22,22 +22,56 @@
     android:elevation="15dp"
     android:visibility="gone"
     android:focusableInTouchMode="true">
-    <EditText
-        android:id="@+id/app_search_box"
+    <FrameLayout
+        android:id="@+id/header"
         android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:padding="16dp"
-        android:hint="@string/apps_view_search_bar_hint"
-        android:maxLines="1"
-        android:singleLine="true"
-        android:scrollHorizontally="true"
-        android:gravity="fill_horizontal"
-        android:textSize="16sp"
-        android:textColor="#4c4c4c"
-        android:textColorHint="#9c9c9c"
-        android:imeOptions="actionDone|flagNoExtractUi"
-        android:background="@drawable/apps_search_bg"
-        android:elevation="4dp" />
+        android:layout_height="52dp"
+        android:orientation="horizontal"
+        android:background="@drawable/apps_search_bg">
+        <LinearLayout
+            android:id="@+id/app_search_container"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="horizontal"
+            android:visibility="invisible">
+            <ImageView
+                android:id="@+id/dismiss_search_button"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="start|center_vertical"
+                android:paddingTop="12dp"
+                android:paddingBottom="12dp"
+                android:contentDescription="@string/all_apps_button_label"
+                android:src="@drawable/ic_arrow_back_grey" />
+            <com.android.launcher3.AppsContainerSearchEditTextView
+                android:id="@+id/app_search_box"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:paddingTop="16dp"
+                android:paddingBottom="16dp"
+                android:paddingLeft="8dp"
+                android:hint="@string/apps_view_search_bar_hint"
+                android:maxLines="1"
+                android:singleLine="true"
+                android:scrollHorizontally="true"
+                android:gravity="fill_horizontal"
+                android:textSize="16sp"
+                android:textColor="#4c4c4c"
+                android:textColorHint="#9c9c9c"
+                android:imeOptions="actionDone|flagNoExtractUi"
+                android:focusableInTouchMode="true"
+                android:background="@android:color/transparent" />
+        </LinearLayout>
+        <ImageView
+            android:id="@+id/search_button"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="end|center_vertical"
+            android:paddingTop="12dp"
+            android:paddingBottom="12dp"
+            android:contentDescription="@string/apps_view_search_bar_hint"
+            android:src="@drawable/ic_search_grey" />
+    </FrameLayout>
     <com.android.launcher3.AppsContainerRecyclerView
         android:id="@+id/apps_list_view"
         android:layout_width="match_parent"
diff --git a/res/layout/widgets_view.xml b/res/layout/widgets_view.xml
index 0800f59..5cdf560 100644
--- a/res/layout/widgets_view.xml
+++ b/res/layout/widgets_view.xml
@@ -40,7 +40,7 @@
         android:clipChildren="false"
         android:orientation="vertical">
 
-        <android.support.v7.widget.RecyclerView
+        <com.android.launcher3.widget.WidgetsContainerRecyclerView
                 android:id="@+id/widgets_list_view"
                 android:layout_width="match_parent"
                 android:layout_height="match_parent"
diff --git a/res/values/config.xml b/res/values/config.xml
index 21e1d69..84ccef1 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -100,4 +100,7 @@
     <item type="id" name="action_add_to_workspace" />
     <item type="id" name="action_move" />
     <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 a57ae89..46830d6 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -50,9 +50,8 @@
     <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>
     <dimen name="apps_view_section_text_size">24sp</dimen>
     <dimen name="apps_view_fast_scroll_bar_width">6dp</dimen>
@@ -61,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 57f23ae..5962584 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -243,4 +243,32 @@
 
     <!-- Accessibility action to move an item from folder to workspace. [CHAR_LIMIT=30] [DO NOT TRANSLATE] -->
     <string name="action_move_to_workspace">Move to home screen</string>
+
+    <!-- Accessibility action to move an homescreen to the left. [CHAR_LIMIT=30] [DO NOT TRANSLATE] -->
+    <string name="action_move_screen_left">Move screen to left</string>
+
+    <!-- Accessibility action to move an homescreen to the right. [CHAR_LIMIT=30] [DO NOT TRANSLATE] -->
+    <string name="action_move_screen_right">Move screen to right</string>
+
+    <!-- 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/AllAppsList.java b/src/com/android/launcher3/AllAppsList.java
index dd646bb..3b25dca 100644
--- a/src/com/android/launcher3/AllAppsList.java
+++ b/src/com/android/launcher3/AllAppsList.java
@@ -24,6 +24,7 @@
 import com.android.launcher3.compat.UserHandleCompat;
 
 import java.util.ArrayList;
+import java.util.HashSet;
 import java.util.List;
 
 
@@ -117,6 +118,16 @@
         }
     }
 
+    public void updateIconsAndLabels(HashSet<String> packages, UserHandleCompat user,
+            ArrayList<AppInfo> outUpdates) {
+        for (AppInfo info : data) {
+            if (info.user.equals(user) && packages.contains(info.componentName.getPackageName())) {
+                mIconCache.updateTitleAndIcon(info);
+                outUpdates.add(info);
+            }
+        }
+    }
+
     /**
      * Add and remove icons for this package which has been updated.
      */
diff --git a/src/com/android/launcher3/AlphabeticalAppsList.java b/src/com/android/launcher3/AlphabeticalAppsList.java
index c7ee2e9..70e36a7 100644
--- a/src/com/android/launcher3/AlphabeticalAppsList.java
+++ b/src/com/android/launcher3/AlphabeticalAppsList.java
@@ -82,15 +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;
+        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;
         }
     }
 
@@ -98,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;
@@ -137,18 +161,35 @@
         public boolean retainApp(AppInfo info, String sectionName);
     }
 
+    // The maximum number of rows allowed in a merged section before we stop merging
+    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;
     private AppNameComparator mAppNameComparator;
+    private int mNumAppsPerRow;
+    // The maximum number of section merges we allow at a given time before we stop merging
+    private int mMaxAllowableMerges = Integer.MAX_VALUE;
 
-    public AlphabeticalAppsList(Context context) {
+    public AlphabeticalAppsList(Context context, int numAppsPerRow) {
         mIndexer = new AlphabeticIndexCompat(context);
         mAppNameComparator = new AppNameComparator(context);
+        setNumAppsPerRow(numAppsPerRow);
+    }
+
+    /**
+     * Sets the number of apps per row.  Used only for AppsContainerView.SECTIONED_GRID_COALESCED.
+     */
+    public void setNumAppsPerRow(int numAppsPerRow) {
+        mNumAppsPerRow = numAppsPerRow;
+        mMaxAllowableMerges = (int) Math.ceil(numAppsPerRow / 2f);
+        onAppsUpdated();
     }
 
     /**
@@ -166,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() {
@@ -180,6 +228,13 @@
     }
 
     /**
+     * Returns whether there are is a filter set.
+     */
+    public boolean hasFilter() {
+        return (mFilter != null);
+    }
+
+    /**
      * Returns whether there are no filtered results.
      */
     public boolean hasNoFilteredResults() {
@@ -190,16 +245,17 @@
      * Sets the current filter for this list of apps.
      */
     public void setFilter(Filter f) {
-        mFilter = f;
-        onAppsUpdated();
-        mAdapter.notifyDataSetChanged();
+        if (mFilter != f) {
+            mFilter = f;
+            onAppsUpdated();
+            mAdapter.notifyDataSetChanged();
+        }
     }
 
     /**
      * Sets the current set of apps.
      */
     public void setApps(List<AppInfo> apps) {
-        Collections.sort(apps, mAppNameComparator.getComparator());
         mApps.clear();
         mApps.addAll(apps);
         onAppsUpdated();
@@ -214,6 +270,8 @@
         for (AppInfo info : apps) {
             addApp(info);
         }
+        onAppsUpdated();
+        mAdapter.notifyDataSetChanged();
     }
 
     /**
@@ -224,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();
     }
 
     /**
@@ -240,10 +298,10 @@
             int removeIndex = findAppByComponent(mApps, info);
             if (removeIndex != -1) {
                 mApps.remove(removeIndex);
-                onAppsUpdated();
-                mAdapter.notifyDataSetChanged();
             }
         }
+        onAppsUpdated();
+        mAdapter.notifyDataSetChanged();
     }
 
     /**
@@ -263,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();
         }
     }
 
@@ -278,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());
 
@@ -294,23 +357,78 @@
             }
 
             // 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
-                AdapterItem sectionItem = AdapterItem.asSection(position++, sectionName);
-                mSectionedFilteredApps.add(sectionItem);
+                // Create a new section item, this item is used to break the flow of items in the
+                // list
+                AdapterItem sectionItem = AdapterItem.asSectionBreak(position++, lastSectionInfo);
+                if (!AppsContainerView.GRID_HIDE_SECTION_HEADERS && !hasFilter()) {
+                    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);
         }
+
+        if (AppsContainerView.GRID_MERGE_SECTIONS && !hasFilter()) {
+            // Go through each section and try and merge some of the sections
+            int minNumAppsPerRow = (int) Math.ceil(mNumAppsPerRow / 2f);
+            int sectionAppCount = 0;
+            for (int i = 0; i < mSections.size(); i++) {
+                SectionInfo section = mSections.get(i);
+                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 &&
+                        (sectionAppCount / mNumAppsPerRow) < MAX_ROWS_IN_MERGED_SECTION &&
+                        (i + 1) < mSections.size()) {
+                    SectionInfo nextSection = mSections.remove(i + 1);
+
+                    // Remove the next section break
+                    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 item
+                    pos = mSectionedFilteredApps.indexOf(nextSection.firstAppItem);
+                    for (int j = pos; j < mSectionedFilteredApps.size(); j++) {
+                        AdapterItem item = mSectionedFilteredApps.get(j);
+                        item.position--;
+                    }
+                    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 f889712..6556cf9 100644
--- a/src/com/android/launcher3/AppsContainerRecyclerView.java
+++ b/src/com/android/launcher3/AppsContainerRecyclerView.java
@@ -36,8 +36,7 @@
  * 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;
 
@@ -132,6 +131,7 @@
 
     @Override
     protected void onFinishInflate() {
+        super.onFinishInflate();
         addOnItemTouchListener(this);
     }
 
@@ -156,10 +156,6 @@
         handleTouchEvent(ev);
     }
 
-    public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
-        // Do nothing
-    }
-
     /**
      * Handles the touch event and determines whether to show the fast scroller (or updates it if
      * it is already showing).
@@ -175,7 +171,9 @@
                 // Keep track of the down positions
                 mDownX = mLastX = x;
                 mDownY = mLastY = y;
-                stopScroll();
+                if (shouldStopScroll(ev)) {
+                    stopScroll();
+                }
                 break;
             case MotionEvent.ACTION_MOVE:
                 // Check if we are scrolling
@@ -256,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);
@@ -287,45 +286,43 @@
     }
 
     /**
-     * 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
-        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 = mApps.getAdapterItems().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 mApps.getAdapterItems().get(position).sectionName;
+        return lastScrollSection.sectionName;
     }
 
     /**
      * Returns the bounds for the scrollbar.
      */
     private void updateVerticalScrollbarBounds() {
+        List<AlphabeticalAppsList.AdapterItem> items = mApps.getAdapterItems();
+
         // Skip early if there are no items
-        if (mApps.getAdapterItems().isEmpty()) {
+        if (items.isEmpty()) {
             mVerticalScrollbarBounds.setEmpty();
             return;
         }
@@ -344,7 +341,7 @@
             View child = getChildAt(i);
             int position = getChildPosition(child);
             if (position != NO_POSITION) {
-                AlphabeticalAppsList.AdapterItem item = mApps.getAdapterItems().get(position);
+                AlphabeticalAppsList.AdapterItem item = items.get(position);
                 if (!item.isSectionHeader) {
                     rowIndex = findRowForAppIndex(item.appIndex);
                     rowTopOffset = getLayoutManager().getDecoratedTop(child);
@@ -391,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;
@@ -408,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 f7adaf8..993f9c8 100644
--- a/src/com/android/launcher3/AppsContainerView.java
+++ b/src/com/android/launcher3/AppsContainerView.java
@@ -31,48 +31,53 @@
 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;
 
 import java.util.List;
 
 
 /**
- * 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,
-        View.OnLongClickListener {
+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_HIDE_SECTION_HEADERS = false;
 
     private static final boolean ALLOW_SINGLE_APP_LAUNCH = true;
-
-    private static final int GRID_LAYOUT = 0;
-    private static final int LIST_LAYOUT = 1;
-    private static final int USE_LAYOUT = GRID_LAYOUT;
+    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 = 100;
+    private static final int SEARCH_TRANSLATION_X_DP = 18;
 
     @Thunk Launcher mLauncher;
     @Thunk AlphabeticalAppsList mApps;
-    private RecyclerView.Adapter mAdapter;
+    private AppsGridAdapter mAdapter;
     private RecyclerView.LayoutManager mLayoutManager;
     private RecyclerView.ItemDecoration mItemDecoration;
 
     private LinearLayout mContentView;
     @Thunk AppsContainerRecyclerView mAppsRecyclerView;
-    private EditText mSearchBarView;
-    
+    private View mHeaderView;
+    private View mSearchBarContainerView;
+    private View mSearchButtonView;
+    private View mDismissSearchButtonView;
+    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;
 
     public AppsContainerView(Context context) {
         this(context, null);
@@ -90,26 +95,15 @@
 
         mContainerInset = context.getResources().getDimensionPixelSize(
                 R.dimen.apps_container_inset);
-        mFixedBoundsContainerInset = context.getResources().getDimensionPixelSize(
-                R.dimen.apps_container_fixed_bounds_inset);
         mLauncher = (Launcher) context;
-        mApps = new AlphabeticalAppsList(context);
-        if (USE_LAYOUT == GRID_LAYOUT) {
-            mNumAppsPerRow = grid.appsViewNumCols;
-            AppsGridAdapter adapter = new AppsGridAdapter(context, mApps, mNumAppsPerRow, this,
-                    mLauncher, this);
-            adapter.setEmptySearchText(res.getString(R.string.loading_apps_message));
-            mLayoutManager = adapter.getLayoutManager(context);
-            mItemDecoration = adapter.getItemDecoration();
-            mAdapter = adapter;
-            mContentMarginStart = adapter.getContentMarginStart();
-        } else if (USE_LAYOUT == LIST_LAYOUT) {
-            mNumAppsPerRow = 1;
-            AppsListAdapter adapter = new AppsListAdapter(context, mApps, this, mLauncher, this);
-            adapter.setEmptySearchText(res.getString(R.string.loading_apps_message));
-            mLayoutManager = adapter.getLayoutManager(context);
-            mAdapter = adapter;
-        }
+        mNumAppsPerRow = grid.appsViewNumCols;
+        mApps = new AlphabeticalAppsList(context, mNumAppsPerRow);
+        mAdapter = new AppsGridAdapter(context, mApps, mNumAppsPerRow, this, mLauncher, this);
+        mAdapter.setEmptySearchText(res.getString(R.string.loading_apps_message));
+        mAdapter.setNumAppsPerRow(mNumAppsPerRow);
+        mLayoutManager = mAdapter.getLayoutManager();
+        mItemDecoration = mAdapter.getItemDecoration();
+        mContentMarginStart = mAdapter.getContentMarginStart();
         mApps.setAdapter(mAdapter);
     }
 
@@ -142,12 +136,12 @@
     }
 
     /**
-     * Hides the search bar
+     * Hides the header bar
      */
-    public void hideSearchBar() {
-        mSearchBarView.setVisibility(View.GONE);
-        updateBackgrounds();
-        updatePaddings();
+    public void hideHeaderBar() {
+        mHeaderView.setVisibility(View.GONE);
+        onUpdateBackgrounds();
+        onUpdatePaddings();
     }
 
     /**
@@ -155,6 +149,7 @@
      */
     public void scrollToTop() {
         mAppsRecyclerView.scrollToPosition(0);
+        mRecyclerViewScrollY = 0;
     }
 
     /**
@@ -175,9 +170,7 @@
     protected void onFinishInflate() {
         boolean isRtl = (getResources().getConfiguration().getLayoutDirection() ==
                 LAYOUT_DIRECTION_RTL);
-        if (USE_LAYOUT == GRID_LAYOUT) {
-            ((AppsGridAdapter) mAdapter).setRtl(isRtl);
-        }
+        mAdapter.setRtl(isRtl);
 
         // Work around the search box getting first focus and showing the cursor by
         // proxying the focus from the content view to the recycler view directly
@@ -190,10 +183,29 @@
                 }
             }
         });
-        mSearchBarView = (EditText) findViewById(R.id.app_search_box);
-        if (mSearchBarView != null) {
-            mSearchBarView.addTextChangedListener(this);
-            mSearchBarView.setOnEditorActionListener(this);
+        mHeaderView = findViewById(R.id.header);
+        mHeaderView.setOnClickListener(this);
+        if (Utilities.isLmpOrAbove() && !DYNAMIC_HEADER_ELEVATION) {
+            mHeaderView.setElevation(DynamicGrid.pxFromDp(HEADER_ELEVATION_DP,
+                getContext().getResources().getDisplayMetrics()));
+        }
+        mSearchButtonView = mHeaderView.findViewById(R.id.search_button);
+        mSearchBarContainerView = findViewById(R.id.app_search_container);
+        mDismissSearchButtonView = mSearchBarContainerView.findViewById(R.id.dismiss_search_button);
+        mDismissSearchButtonView.setOnClickListener(this);
+        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);
@@ -201,39 +213,96 @@
         mAppsRecyclerView.setLayoutManager(mLayoutManager);
         mAppsRecyclerView.setAdapter(mAdapter);
         mAppsRecyclerView.setHasFixedSize(true);
+        mAppsRecyclerView.setOnScrollListenerProxy(new RecyclerView.OnScrollListener() {
+            @Override
+            public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
+                // Do nothing
+            }
+
+            @Override
+            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
+                mRecyclerViewScrollY += dy;
+                onRecyclerViewScrolled();
+            }
+        });
         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);
-                if (USE_LAYOUT == GRID_LAYOUT) {
-                    ((AppsGridAdapter) mAdapter).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 (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);
         }
-        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
@@ -258,6 +327,15 @@
     }
 
     @Override
+    public void onClick(View v) {
+        if (v == mHeaderView) {
+            showSearchField();
+        } else if (v == mDismissSearchButtonView) {
+            hideSearchField(true, true);
+        }
+    }
+
+    @Override
     public boolean onLongClick(View v) {
         // Return early if this is not initiated from a touch
         if (!v.isInTouchMode()) return false;
@@ -356,24 +434,27 @@
             mApps.setFilter(null);
         } else {
             String formatStr = getResources().getString(R.string.apps_view_no_search_results);
-            if (USE_LAYOUT == GRID_LAYOUT) {
-                ((AppsGridAdapter) mAdapter).setEmptySearchText(String.format(formatStr,
-                        s.toString()));
-            } else {
-                ((AppsListAdapter) mAdapter).setEmptySearchText(String.format(formatStr,
-                        s.toString()));
-            }
+            mAdapter.setEmptySearchText(String.format(formatStr, s.toString()));
 
             final String filterText = s.toString().toLowerCase().replaceAll("\\s+", "");
             mApps.setFilter(new AlphabeticalAppsList.Filter() {
                 @Override
                 public boolean retainApp(AppInfo info, String sectionName) {
                     String title = info.title.toString();
-                    return sectionName.toLowerCase().contains(filterText) ||
-                            title.toLowerCase().replaceAll("\\s+", "").contains(filterText);
+                    if (sectionName.toLowerCase().contains(filterText)) {
+                        return true;
+                    }
+                    String[] words = title.toLowerCase().split("\\s+");
+                    for (int i = 0; i < words.length; i++) {
+                        if (words[i].startsWith(filterText)) {
+                            return true;
+                        }
+                    }
+                    return false;
                 }
             });
         }
+        scrollToTop();
     }
 
     @Override
@@ -389,9 +470,7 @@
                 AlphabeticalAppsList.AdapterItem item = items.get(i);
                 if (!item.isSectionHeader) {
                     mAppsRecyclerView.getChildAt(i).performClick();
-                    InputMethodManager imm = (InputMethodManager)
-                            getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
-                    imm.hideSoftInputFromWindow(getWindowToken(), 0);
+                    getInputMethodManager().hideSoftInputFromWindow(getWindowToken(), 0);
                     return true;
                 }
             }
@@ -421,10 +500,22 @@
 
     @Override
     public void onLauncherTransitionEnd(Launcher l, boolean animated, boolean toWorkspace) {
-        if (mSearchBarView != null) {
+        if (mSearchBarEditView != null) {
             if (toWorkspace) {
-                // Clear the search bar
-                mSearchBarView.setText("");
+                hideSearchField(false, false);
+            }
+        }
+    }
+
+    /**
+     * Updates the container when the recycler view is scrolled.
+     */
+    private void onRecyclerViewScrolled() {
+        if (DYNAMIC_HEADER_ELEVATION) {
+            int elevation = Math.min(mRecyclerViewScrollY, DynamicGrid.pxFromDp(HEADER_ELEVATION_DP,
+                    getContext().getResources().getDisplayMetrics()));
+            if (Float.compare(mHeaderView.getElevation(), elevation) != 0) {
+                mHeaderView.setElevation(elevation);
             }
         }
     }
@@ -478,61 +569,87 @@
     }
 
     /**
-     * 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.
+     * Shows the search field.
      */
-    private void updatePaddings() {
-        boolean isRtl = (getResources().getConfiguration().getLayoutDirection() ==
-                LAYOUT_DIRECTION_RTL);
-        boolean hasSearchBar = (mSearchBarView != null) &&
-                (mSearchBarView.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 search bar
-        if (hasSearchBar) {
-            LinearLayout.LayoutParams lp =
-                    (LinearLayout.LayoutParams) mSearchBarView.getLayoutParams();
-            lp.leftMargin = lp.rightMargin = inset;
-        }
+    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.setTranslationX(translationX);
+        mSearchBarContainerView.animate()
+                .alpha(1f)
+                .translationX(0)
+                .setDuration(FADE_IN_DURATION)
+                .withLayer()
+                .withEndAction(new Runnable() {
+                    @Override
+                    public void run() {
+                        mSearchBarEditView.requestFocus();
+                        getInputMethodManager().showSoftInput(mSearchBarEditView,
+                                InputMethodManager.SHOW_IMPLICIT);
+                    }
+                });
+        mSearchButtonView.animate()
+                .alpha(0f)
+                .translationX(-translationX)
+                .setDuration(FADE_OUT_DURATION)
+                .withLayer();
     }
 
     /**
-     * Update the background of the Apps view and children.
+     * Hides the search field.
      */
-    private void updateBackgrounds() {
-        int inset = mFixedBounds.isEmpty() ? mContainerInset : mFixedBoundsContainerInset;
-        boolean hasSearchBar = (mSearchBarView != null) &&
-                (mSearchBarView.getVisibility() == View.VISIBLE);
+    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)
+                    .translationX(0)
+                    .setDuration(FADE_IN_DURATION)
+                    .withLayer()
+                    .withEndAction(new Runnable() {
+                        @Override
+                        public void run() {
+                            mSearchBarContainerView.setVisibility(View.INVISIBLE);
+                            if (resetTextField) {
+                                mSearchBarEditView.setText("");
+                            }
+                            mApps.setFilter(null);
+                            if (returnFocusToRecyclerView) {
+                                mAppsRecyclerView.requestFocus();
+                            }
+                        }
+                    });
+            mSearchButtonView.setTranslationX(-translationX);
+            mSearchButtonView.animate()
+                    .alpha(1f)
+                    .translationX(0)
+                    .setDuration(FADE_OUT_DURATION)
+                    .withLayer();
+        } else {
+            mSearchBarContainerView.setVisibility(View.INVISIBLE);
+            if (resetTextField) {
+                mSearchBarEditView.setText("");
+            }
+            mApps.setFilter(null);
+            mSearchButtonView.setAlpha(1f);
+            mSearchButtonView.setTranslationX(0f);
+            if (returnFocusToRecyclerView) {
+                mAppsRecyclerView.requestFocus();
+            }
+        }
+        getInputMethodManager().hideSoftInputFromWindow(getWindowToken(), 0);
+    }
 
-        // 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));
+    /**
+     * Returns an input method manager.
+     */
+    private InputMethodManager getInputMethodManager() {
+        return (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
     }
 }
diff --git a/src/com/android/launcher3/AppsCustomizeCellLayout.java b/src/com/android/launcher3/AppsCustomizeCellLayout.java
deleted file mode 100644
index a50fb68..0000000
--- a/src/com/android/launcher3/AppsCustomizeCellLayout.java
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * Copyright (C) 2010 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.view.View;
-
-public class AppsCustomizeCellLayout extends CellLayout implements Page {
-
-    final FocusIndicatorView mFocusHandlerView;
-
-    public AppsCustomizeCellLayout(Context context) {
-        super(context);
-
-        mFocusHandlerView = new FocusIndicatorView(context);
-        addView(mFocusHandlerView, 0);
-        mFocusHandlerView.getLayoutParams().width = FocusIndicatorView.DEFAULT_LAYOUT_SIZE;
-        mFocusHandlerView.getLayoutParams().height = FocusIndicatorView.DEFAULT_LAYOUT_SIZE;
-    }
-
-    @Override
-    public void removeAllViewsOnPage() {
-        removeAllViews();
-        setLayerType(LAYER_TYPE_NONE, null);
-    }
-
-    @Override
-    public void removeViewOnPageAt(int index) {
-        removeViewAt(index);
-    }
-
-    @Override
-    public int getPageChildCount() {
-        return getChildCount();
-    }
-
-    @Override
-    public View getChildOnPageAt(int i) {
-        return getChildAt(i);
-    }
-
-    @Override
-    public int indexOfChildOnPage(View v) {
-        return indexOfChild(v);
-    }
-
-    /**
-     * Clears all the key listeners for the individual icons.
-     */
-    public void resetChildrenOnKeyListeners() {
-        ShortcutAndWidgetContainer children = getShortcutsAndWidgets();
-        int childCount = children.getChildCount();
-        for (int j = 0; j < childCount; ++j) {
-            children.getChildAt(j).setOnKeyListener(null);
-        }
-    }
-}
diff --git a/src/com/android/launcher3/AppsGridAdapter.java b/src/com/android/launcher3/AppsGridAdapter.java
index 5bc3981..259740c 100644
--- a/src/com/android/launcher3/AppsGridAdapter.java
+++ b/src/com/android/launcher3/AppsGridAdapter.java
@@ -4,6 +4,7 @@
 import android.content.res.Resources;
 import android.graphics.Canvas;
 import android.graphics.Paint;
+import android.graphics.PointF;
 import android.graphics.Rect;
 import android.support.v7.widget.GridLayoutManager;
 import android.support.v7.widget.RecyclerView;
@@ -11,9 +12,9 @@
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.TextView;
-
 import com.android.launcher3.util.Thunk;
 
+import java.util.HashMap;
 import java.util.List;
 
 
@@ -23,6 +24,7 @@
 class AppsGridAdapter extends RecyclerView.Adapter<AppsGridAdapter.ViewHolder> {
 
     public static final String TAG = "AppsGridAdapter";
+    private static final boolean DEBUG = false;
 
     private static final int SECTION_BREAK_VIEW_TYPE = 0;
     private static final int ICON_VIEW_TYPE = 1;
@@ -48,6 +50,12 @@
      * Helper class to size the grid items.
      */
     public class GridSpanSizer extends GridLayoutManager.SpanSizeLookup {
+
+        public GridSpanSizer() {
+            super();
+            setSpanIndexCacheEnabled(true);
+        }
+
         @Override
         public int getSpanSize(int position) {
             if (mApps.hasNoFilteredResults()) {
@@ -57,7 +65,11 @@
 
             if (mApps.getAdapterItems().get(position).isSectionHeader) {
                 // Section break spans full width
-                return mAppsPerRow;
+                if (AppsContainerView.GRID_HIDE_SECTION_HEADERS) {
+                    return 0;
+                } else {
+                    return mAppsPerRow;
+                }
             } else {
                 return 1;
             }
@@ -69,30 +81,87 @@
      */
     public class GridItemDecoration extends RecyclerView.ItemDecoration {
 
+        private static final boolean FADE_OUT_SECTIONS = false;
+
+        private HashMap<String, PointF> mCachedSectionBounds = new HashMap<>();
+        private Rect mTmpBounds = new Rect();
+
         @Override
         public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
+            if (mApps.hasFilter()) {
+                return;
+            }
+
             List<AlphabeticalAppsList.AdapterItem> items = mApps.getAdapterItems();
-            for (int i = 0; i < parent.getChildCount(); i++) {
+            int childCount = parent.getChildCount();
+            int lastSectionTop = 0;
+            int lastSectionHeight = 0;
+            for (int i = 0; i < childCount; i++) {
                 View child = parent.getChildAt(i);
                 ViewHolder holder = (ViewHolder) parent.getChildViewHolder(child);
-                if (shouldDrawItemSection(holder, child, items)) {
-                    // Draw at the parent
-                    AlphabeticalAppsList.AdapterItem item =
-                            items.get(holder.getPosition());
-                    String section = item.sectionName;
-                    mSectionTextPaint.getTextBounds(section, 0, section.length(),
-                            mTmpBounds);
-                    if (mIsRtl) {
-                        int left = parent.getWidth() - mPaddingStart - mStartMargin;
-                        c.drawText(section, left + (mStartMargin - mTmpBounds.width()) / 2,
-                                child.getTop() + (2 * child.getPaddingTop()) +
-                                        mTmpBounds.height(), mSectionTextPaint);
-                    } else {
-                        int left = mPaddingStart;
-                        c.drawText(section, left + (mStartMargin - mTmpBounds.width()) / 2,
-                            child.getTop() + (2 * child.getPaddingTop()) +
-                                    mTmpBounds.height(), mSectionTextPaint);
+                if (shouldDrawItemSection(holder, child, i, items)) {
+                    // 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);
+                    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
+                        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);
+                        }
+
+                        // 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;
+                        }
+
+                        // 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);
+                        }
+                        c.drawText(sectionName, x, y, mSectionTextPaint);
+
+                        lastSectionTop = y;
+                        lastSectionHeight = (int) (sectionBounds.y + mSectionHeaderOffset);
+                        lastSectionName = sectionName;
                     }
+                    i += (sectionInfo.numApps - item.sectionAppIndex);
                 }
             }
         }
@@ -103,7 +172,23 @@
             // Do nothing
         }
 
-        private boolean shouldDrawItemSection(ViewHolder holder, View child,
+        /**
+         * 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 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
             GridLayoutManager.LayoutParams lp = (GridLayoutManager.LayoutParams)
@@ -124,8 +209,9 @@
             if (pos <= 0 || pos >= items.size()) {
                 return false;
             }
-            // Only draw the first item in the section (the first one after the section header)
-            return items.get(pos - 1).isSectionHeader && !items.get(pos).isSectionHeader;
+            // Draw the section header for the first item in each section
+            return (childIndex == 0) ||
+                    (items.get(pos - 1).isSectionHeader && !items.get(pos).isSectionHeader);
         }
     }
 
@@ -144,8 +230,8 @@
     // Section drawing
     @Thunk int mPaddingStart;
     @Thunk int mStartMargin;
+    @Thunk int mSectionHeaderOffset;
     @Thunk Paint mSectionTextPaint;
-    @Thunk Rect mTmpBounds = new Rect();
 
 
     public AppsGridAdapter(Context context, AlphabeticalAppsList apps, int appsPerRow,
@@ -163,7 +249,10 @@
         mTouchListener = touchListener;
         mIconClickListener = iconClickListener;
         mIconLongClickListener = iconLongClickListener;
-        mStartMargin = res.getDimensionPixelSize(R.dimen.apps_grid_view_start_margin);
+        if (!AppsContainerView.GRID_HIDE_SECTION_HEADERS) {
+            mStartMargin = res.getDimensionPixelSize(R.dimen.apps_grid_view_start_margin);
+            mSectionHeaderOffset = res.getDimensionPixelSize(R.dimen.apps_grid_section_y_offset);
+        }
         mPaddingStart = res.getDimensionPixelSize(R.dimen.apps_container_inset);
         mSectionTextPaint = new Paint();
         mSectionTextPaint.setTextSize(res.getDimensionPixelSize(
@@ -197,7 +286,7 @@
     /**
      * Returns the grid layout manager.
      */
-    public GridLayoutManager getLayoutManager(Context context) {
+    public GridLayoutManager getLayoutManager() {
         return mGridLayoutMgr;
     }
 
@@ -205,7 +294,11 @@
      * Returns the item decoration for the recycler view.
      */
     public RecyclerView.ItemDecoration getItemDecoration() {
-        return mItemDecoration;
+        // We don't draw any headers when we are uncomfortably dense
+        if (!AppsContainerView.GRID_HIDE_SECTION_HEADERS) {
+            return mItemDecoration;
+        }
+        return null;
     }
 
     /**
diff --git a/src/com/android/launcher3/AppsListAdapter.java b/src/com/android/launcher3/AppsListAdapter.java
deleted file mode 100644
index ffd3092..0000000
--- a/src/com/android/launcher3/AppsListAdapter.java
+++ /dev/null
@@ -1,143 +0,0 @@
-package com.android.launcher3;
-
-import android.content.Context;
-import android.support.v7.widget.LinearLayoutManager;
-import android.support.v7.widget.RecyclerView;
-import android.view.Gravity;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-/**
- * The linear list view adapter for all the apps.
- */
-class AppsListAdapter extends RecyclerView.Adapter<AppsListAdapter.ViewHolder> {
-
-    /**
-     * ViewHolder for each row.
-     */
-    public static class ViewHolder extends RecyclerView.ViewHolder {
-        public View mContent;
-
-        public ViewHolder(View v) {
-            super(v);
-            mContent = v;
-        }
-    }
-
-    private static final int SECTION_BREAK_VIEW_TYPE = 0;
-    private static final int ICON_VIEW_TYPE = 1;
-    private static final int EMPTY_VIEW_TYPE = 2;
-
-    private LayoutInflater mLayoutInflater;
-    private AlphabeticalAppsList mApps;
-    private View.OnTouchListener mTouchListener;
-    private View.OnClickListener mIconClickListener;
-    private View.OnLongClickListener mIconLongClickListener;
-    private String mEmptySearchText;
-
-    public AppsListAdapter(Context context, AlphabeticalAppsList apps,
-            View.OnTouchListener touchListener, View.OnClickListener iconClickListener,
-            View.OnLongClickListener iconLongClickListener) {
-        mApps = apps;
-        mLayoutInflater = LayoutInflater.from(context);
-        mTouchListener = touchListener;
-        mIconClickListener = iconClickListener;
-        mIconLongClickListener = iconLongClickListener;
-    }
-
-    public RecyclerView.LayoutManager getLayoutManager(Context context) {
-        return new LinearLayoutManager(context);
-    }
-
-    /**
-     * Sets the text to show when there are no apps.
-     */
-    public void setEmptySearchText(String query) {
-        mEmptySearchText = query;
-    }
-
-    @Override
-    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
-        switch (viewType) {
-            case EMPTY_VIEW_TYPE:
-                return new ViewHolder(mLayoutInflater.inflate(R.layout.apps_empty_view, parent,
-                        false));
-            case SECTION_BREAK_VIEW_TYPE:
-                return new ViewHolder(new View(parent.getContext()));
-            case ICON_VIEW_TYPE:
-                // Inflate the row and all the icon children necessary
-                ViewGroup row = (ViewGroup) mLayoutInflater.inflate(R.layout.apps_list_row_view,
-                        parent, false);
-                BubbleTextView icon = (BubbleTextView) mLayoutInflater.inflate(
-                        R.layout.apps_list_row_icon_view, row, false);
-                LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(0,
-                        ViewGroup.LayoutParams.WRAP_CONTENT, 1);
-                lp.gravity = Gravity.CENTER_VERTICAL;
-                icon.setLayoutParams(lp);
-                icon.setOnTouchListener(mTouchListener);
-                icon.setOnClickListener(mIconClickListener);
-                icon.setOnLongClickListener(mIconLongClickListener);
-                icon.setFocusable(true);
-                row.addView(icon);
-                return new ViewHolder(row);
-            default:
-                throw new RuntimeException("Unexpected view type");
-        }
-    }
-
-    @Override
-    public void onBindViewHolder(ViewHolder holder, int position) {
-        switch (holder.getItemViewType()) {
-            case ICON_VIEW_TYPE:
-                AlphabeticalAppsList.AdapterItem item = mApps.getAdapterItems().get(position);
-                ViewGroup content = (ViewGroup) holder.mContent;
-                String sectionDescription = item.sectionName;
-
-                // Bind the section header
-                boolean showSectionHeader = true;
-                if (position > 0) {
-                    AlphabeticalAppsList.AdapterItem prevItem =
-                            mApps.getAdapterItems().get(position - 1);
-                    showSectionHeader = prevItem.isSectionHeader;
-                }
-                TextView tv = (TextView) content.findViewById(R.id.section);
-                if (showSectionHeader) {
-                    tv.setText(sectionDescription);
-                    tv.setVisibility(View.VISIBLE);
-                } else {
-                    tv.setVisibility(View.INVISIBLE);
-                }
-
-                // Bind the icon
-                BubbleTextView icon = (BubbleTextView) content.getChildAt(1);
-                icon.applyFromApplicationInfo(item.appInfo);
-                break;
-            case EMPTY_VIEW_TYPE:
-                TextView emptyViewText = (TextView) holder.mContent.findViewById(R.id.empty_text);
-                emptyViewText.setText(mEmptySearchText);
-                break;
-        }
-    }
-
-    @Override
-    public int getItemCount() {
-        if (mApps.hasNoFilteredResults()) {
-            // For the empty view
-            return 1;
-        }
-        return mApps.getAdapterItems().size();
-    }
-
-    @Override
-    public int getItemViewType(int position) {
-        if (mApps.hasNoFilteredResults()) {
-            return EMPTY_VIEW_TYPE;
-        } else if (mApps.getAdapterItems().get(position).isSectionHeader) {
-            return SECTION_BREAK_VIEW_TYPE;
-        }
-        return ICON_VIEW_TYPE;
-    }
-}
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/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index deb8075..918517e 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -428,6 +428,13 @@
     }
 
     public boolean updateAppsViewNumCols(Resources res, int containerWidth) {
+        if (AppsContainerView.GRID_HIDE_SECTION_HEADERS) {
+            if (appsViewNumCols != allAppsNumCols) {
+                appsViewNumCols = allAppsNumCols;
+                return true;
+            }
+            return false;
+        }
         int appsViewLeftMarginPx =
                 res.getDimensionPixelSize(R.dimen.apps_grid_view_start_margin);
         int availableAppsWidthPx = (containerWidth > 0) ? containerWidth : availableWidthPx;
diff --git a/src/com/android/launcher3/FolderPagedView.java b/src/com/android/launcher3/FolderPagedView.java
index 1d198ba..211bbfe 100644
--- a/src/com/android/launcher3/FolderPagedView.java
+++ b/src/com/android/launcher3/FolderPagedView.java
@@ -78,7 +78,6 @@
     public FolderPagedView(Context context, AttributeSet attrs) {
         super(context, attrs);
         LauncherAppState app = LauncherAppState.getInstance();
-        setDataIsReady();
 
         DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
         if (ALLOW_FOLDER_SCROLL) {
@@ -346,15 +345,6 @@
         }
     }
 
-    @Override
-    protected void loadAssociatedPages(int page, boolean immediateAndOnly) { }
-
-    @Override
-    public void syncPages() { }
-
-    @Override
-    public void syncPageItems(int page, boolean immediate) { }
-
     public int getDesiredWidth() {
         return getPageCount() > 0 ? getPageAt(0).getDesiredWidth() : 0;
     }
diff --git a/src/com/android/launcher3/IconCache.java b/src/com/android/launcher3/IconCache.java
index fd45714..6c2aa39 100644
--- a/src/com/android/launcher3/IconCache.java
+++ b/src/com/android/launcher3/IconCache.java
@@ -407,6 +407,20 @@
     }
 
     /**
+     * Updates {@param application} only if a valid entry is found.
+     */
+    public synchronized void updateTitleAndIcon(AppInfo application) {
+        CacheEntry entry = cacheLocked(application.componentName, null, application.user,
+                false, application.usingLowResIcon);
+        if (entry.icon != null && !isDefaultIcon(entry.icon, application.user)) {
+            application.title = entry.title;
+            application.iconBitmap = entry.icon;
+            application.contentDescription = entry.contentDescription;
+            application.usingLowResIcon = entry.isLowResIcon;
+        }
+    }
+
+    /**
      * Returns a high res icon for the given intent and user
      */
     public synchronized Bitmap getIcon(Intent intent, UserHandleCompat user) {
@@ -655,7 +669,7 @@
     }
 
     private static final class IconDB extends SQLiteOpenHelper {
-        private final static int DB_VERSION = 3;
+        private final static int DB_VERSION = 4;
 
         private final static String TABLE_NAME = "icons";
         private final static String COLUMN_ROWID = "rowid";
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 4533089..5645759 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -135,6 +135,9 @@
     static final String TAG = "Launcher";
     static final boolean LOGD = true;
 
+    // Temporary flag
+    static final boolean DISABLE_ALL_APPS_SEARCH_INTEGRATION = true;
+
     static final boolean PROFILE_STARTUP = false;
     static final boolean DEBUG_WIDGETS = true;
     static final boolean DEBUG_STRICT_MODE = false;
@@ -525,15 +528,18 @@
                 if (LOGD) {
                     Log.d(TAG, "onAllAppsBoundsChanged(Rect): " + bounds);
                 }
-                mAppsView.setFixedBounds(Launcher.this, bounds);
+                mAppsView.setFixedBounds(bounds);
+                mWidgetsView.setFixedBounds(bounds);
             }
 
             @Override
             public void dismissAllApps() {
-                // Dismiss All Apps if we aren't already paused/invisible
-                if (!mPaused) {
-                    showWorkspace(WorkspaceStateTransitionAnimation.SCROLL_TO_CURRENT_PAGE, true,
-                            null /* onCompleteRunnable */, false /* notifyLauncherCallbacks */);
+                if (!DISABLE_ALL_APPS_SEARCH_INTEGRATION) {
+                    // Dismiss All Apps if we aren't already paused/invisible
+                    if (!mPaused) {
+                        showWorkspace(WorkspaceStateTransitionAnimation.SCROLL_TO_CURRENT_PAGE, true,
+                                null /* onCompleteRunnable */, false /* notifyLauncherCallbacks */);
+                    }
                 }
             }
         });
@@ -1019,7 +1025,7 @@
         mOnResumeState = State.NONE;
 
         // Restore the apps state if we are in all apps
-        if (mState == State.APPS) {
+        if (!Launcher.DISABLE_ALL_APPS_SEARCH_INTEGRATION && mState == State.APPS) {
             if (mLauncherCallbacks != null) {
                 mLauncherCallbacks.onAllAppsShown();
             }
@@ -1453,8 +1459,8 @@
 
         // Setup Apps
         mAppsView = (AppsContainerView) findViewById(R.id.apps_view);
-        if (mLauncherCallbacks != null && mLauncherCallbacks.overrideAllAppsSearch()) {
-            mAppsView.hideSearchBar();
+        if (isAllAppsSearchOverridden()) {
+            mAppsView.hideHeaderBar();
         }
 
         // Setup AppsCustomize
@@ -2877,15 +2883,22 @@
 
     /** Updates the interaction state. */
     public void updateInteraction(Workspace.State fromState, Workspace.State toState) {
-        // Only update the interacting state if we are transitioning to/from a view without an
+        // Only update the interacting state if we are transitioning to/from a view with an
         // overlay
-        boolean fromStateWithoutOverlay = fromState != Workspace.State.NORMAL &&
-                fromState != Workspace.State.NORMAL_HIDDEN;
-        boolean toStateWithoutOverlay = toState != Workspace.State.NORMAL &&
-                toState != Workspace.State.NORMAL_HIDDEN;
-        if (toStateWithoutOverlay) {
+        boolean fromStateWithOverlay;
+        boolean toStateWithOverlay;
+        if (Launcher.DISABLE_ALL_APPS_SEARCH_INTEGRATION) {
+            fromStateWithOverlay = fromState != Workspace.State.NORMAL;
+            toStateWithOverlay = toState != Workspace.State.NORMAL;
+        } else {
+            fromStateWithOverlay = fromState != Workspace.State.NORMAL &&
+                    fromState != Workspace.State.NORMAL_HIDDEN;
+            toStateWithOverlay = toState != Workspace.State.NORMAL &&
+                    toState != Workspace.State.NORMAL_HIDDEN;
+        }
+        if (toStateWithOverlay) {
             onInteractionBegin();
-        } else if (fromStateWithoutOverlay) {
+        } else if (fromStateWithOverlay) {
             onInteractionEnd();
         }
     }
@@ -3219,8 +3232,7 @@
         // The hotseat touch handling does not go through Workspace, and we always allow long press
         // on hotseat items.
         final boolean inHotseat = isHotseatLayout(v);
-        boolean allowLongPress = inHotseat || mWorkspace.allowLongPress();
-        if (allowLongPress && !mDragController.isDragging()) {
+        if (!mDragController.isDragging()) {
             if (itemUnderLongClick == null) {
                 // User long pressed on empty space
                 mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS,
@@ -3367,7 +3379,7 @@
                     .sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
             if (notifyLauncherCallbacks) {
                 // Dismiss all apps when the workspace is shown
-                if (mLauncherCallbacks != null) {
+                if (!Launcher.DISABLE_ALL_APPS_SEARCH_INTEGRATION && mLauncherCallbacks != null) {
                     mLauncherCallbacks.onAllAppsHidden();
                 }
             }
@@ -3419,7 +3431,7 @@
 
         if (toState == State.APPS) {
             mStateTransitionAnimation.startAnimationToAllApps(animated);
-            if (mLauncherCallbacks != null) {
+            if (!Launcher.DISABLE_ALL_APPS_SEARCH_INTEGRATION && mLauncherCallbacks != null) {
                 mLauncherCallbacks.onAllAppsShown();
             }
         } else {
@@ -3472,7 +3484,7 @@
         if (successfulDrop) {
             // We need to trigger all apps hidden to notify search to update itself before the
             // delayed call to showWorkspace below
-            if (mLauncherCallbacks != null) {
+            if (!Launcher.DISABLE_ALL_APPS_SEARCH_INTEGRATION && mLauncherCallbacks != null) {
                 mLauncherCallbacks.onAllAppsHidden();
             }
         }
@@ -4166,11 +4178,28 @@
     }
 
     /**
+     * A runnable that we can dequeue and re-enqueue when all applications are bound (to prevent
+     * multiple calls to bind the same list.)
+     */
+    @Thunk ArrayList<AppInfo> mTmpAppsList;
+    private Runnable mBindAllApplicationsRunnable = new Runnable() {
+        public void run() {
+            bindAllApplications(mTmpAppsList);
+            mTmpAppsList = null;
+        }
+    };
+
+    /**
      * Add the icons for all apps.
      *
      * Implementation of the method from LauncherModel.Callbacks.
      */
     public void bindAllApplications(final ArrayList<AppInfo> apps) {
+        if (waitUntilResume(mBindAllApplicationsRunnable, true)) {
+            mTmpAppsList = apps;
+            return;
+        }
+
         if (mAppsView != null) {
             mAppsView.setApps(apps);
         }
@@ -4437,9 +4466,12 @@
 
     /**
      * Returns whether the launcher callbacks overrides search in all apps.
-     * @return
      */
     @Thunk boolean isAllAppsSearchOverridden() {
+        if (DISABLE_ALL_APPS_SEARCH_INTEGRATION) {
+            return false;
+        }
+
         if (mLauncherCallbacks != null) {
             return mLauncherCallbacks.overrideAllAppsSearch();
         }
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 95ff6a4..e81c8c2 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -107,6 +107,7 @@
     @Thunk DeferredHandler mHandler = new DeferredHandler();
     @Thunk LoaderTask mLoaderTask;
     @Thunk boolean mIsLoaderTaskRunning;
+    @Thunk boolean mHasLoaderCompletedOnce;
 
     private static final String MIGRATE_AUTHORITY = "com.android.launcher2.settings";
 
@@ -128,6 +129,12 @@
     // a normal load, we also clear this set of Runnables.
     static final ArrayList<Runnable> mDeferredBindRunnables = new ArrayList<Runnable>();
 
+    /**
+     * Set of runnables to be called on the background thread after the workspace binding
+     * is complete.
+     */
+    static final ArrayList<Runnable> mBindCompleteRunnables = new ArrayList<Runnable>();
+
     @Thunk WeakReference<Callbacks> mCallbacks;
 
     // < only access in worker thread >
@@ -263,6 +270,19 @@
         }
     }
 
+    /**
+     * Runs the specified runnable after the loader is complete
+     */
+    private void runAfterBindCompletes(Runnable r) {
+        if (isLoadingWorkspace() || !mHasLoaderCompletedOnce) {
+            synchronized (mBindCompleteRunnables) {
+                mBindCompleteRunnables.add(r);
+            }
+        } else {
+            runOnWorkerThread(r);
+        }
+    }
+
     boolean canMigrateFromOldLauncherDb(Launcher launcher) {
         return mOldContentProviderExists && !launcher.isLauncherPreinstalled() ;
     }
@@ -424,7 +444,7 @@
      * Find a position on the screen for the given size or adds a new screen.
      * @return screenId and the coordinates for the item.
      */
-    @Thunk static Pair<Long, int[]> findSpaceForItem(
+    @Thunk Pair<Long, int[]> findSpaceForItem(
             Context context,
             ArrayList<Long> workspaceScreens,
             ArrayList<Long> addedWorkspaceScreensFinal,
@@ -432,7 +452,7 @@
         LongSparseArray<ArrayList<ItemInfo>> screenItems = new LongSparseArray<>();
 
         // Use sBgItemsIdMap as all the items are already loaded.
-        // TODO: Throw exception is above condition is not met.
+        assertWorkspaceLoaded();
         synchronized (sBgLock) {
             for (ItemInfo info : sBgItemsIdMap) {
                 if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
@@ -875,12 +895,18 @@
         updateItemInDatabaseHelper(context, values, item, "updateItemInDatabase");
     }
 
+    private void assertWorkspaceLoaded() {
+        if (LauncherAppState.isDogfoodBuild() && (isLoadingWorkspace() || !mHasLoaderCompletedOnce)) {
+            throw new RuntimeException("Trying to add shortcut while loader is running");
+        }
+    }
+
     /**
      * Returns true if the shortcuts already exists on the workspace. This must be called after
      * the workspace has been loaded. We identify a shortcut by its intent.
-     * TODO: Throw exception is above condition is not met.
      */
-    @Thunk static boolean shortcutExists(Context context, Intent intent, UserHandleCompat user) {
+    @Thunk boolean shortcutExists(Context context, Intent intent, UserHandleCompat user) {
+        assertWorkspaceLoaded();
         final String intentWithPkg, intentWithoutPkg;
         final String packageName;
         if (intent.getComponent() != null) {
@@ -1390,6 +1416,16 @@
                 mHandler.post(r);
             }
         }
+
+        // Run all the bind complete runnables after workspace is bound.
+        if (!mBindCompleteRunnables.isEmpty()) {
+            synchronized (mBindCompleteRunnables) {
+                for (final Runnable r : mBindCompleteRunnables) {
+                    runOnWorkerThread(r);
+                }
+                mBindCompleteRunnables.clear();
+            }
+        }
     }
 
     public void stopLoader() {
@@ -1615,6 +1651,7 @@
                     mLoaderTask = null;
                 }
                 mIsLoaderTaskRunning = false;
+                mHasLoaderCompletedOnce = true;
             }
         }
 
@@ -2730,6 +2767,7 @@
             }
             if (!mAllAppsLoaded) {
                 loadAllApps();
+                updateAllAppsIconsCache();
                 synchronized (LoaderTask.this) {
                     if (mStopped) {
                         return;
@@ -2772,6 +2810,8 @@
             } else {
                 mHandler.post(r);
             }
+            loadAndBindWidgetsAndShortcuts(mApp.getContext(), tryGetCallbacks(oldCallbacks),
+                    false /* refresh */);
         }
 
         private void loadAllApps() {
@@ -2784,9 +2824,6 @@
                 return;
             }
 
-            final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
-            mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
-
             final List<UserHandleCompat> profiles = mUserManager.getUserProfiles();
 
             // Clear the list of apps
@@ -2794,7 +2831,7 @@
             for (UserHandleCompat user : profiles) {
                 // Query for the set of apps
                 final long qiaTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
-                List<LauncherActivityInfoCompat> apps = mLauncherApps.getActivityList(null, user);
+                final List<LauncherActivityInfoCompat> apps = mLauncherApps.getActivityList(null, user);
                 if (DEBUG_LOADERS) {
                     Log.d(TAG, "getActivityList took "
                             + (SystemClock.uptimeMillis()-qiaTime) + "ms for user " + user);
@@ -2806,42 +2843,6 @@
                     return;
                 }
 
-                // Update icon cache
-                HashSet<String> updatedPackages = mIconCache.updateDBIcons(user, apps);
-
-                // If any package icon has changed (app was updated while launcher was dead),
-                // update the corresponding shortcuts.
-                if (!updatedPackages.isEmpty()) {
-                    final ArrayList<ShortcutInfo> updates = new ArrayList<ShortcutInfo>();
-                    synchronized (sBgLock) {
-                        for (ItemInfo info : sBgItemsIdMap) {
-                            if (info instanceof ShortcutInfo && user.equals(info.user)
-                                    && info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
-                                ShortcutInfo si = (ShortcutInfo) info;
-                                ComponentName cn = si.getTargetComponent();
-                                if (cn != null && updatedPackages.contains(cn.getPackageName())) {
-                                    si.updateIcon(mIconCache);
-                                    updates.add(si);
-                                }
-                            }
-                        }
-                    }
-
-                    if (!updates.isEmpty()) {
-                        final UserHandleCompat userFinal = user;
-                        mHandler.post(new Runnable() {
-
-                            public void run() {
-                                Callbacks cb = getCallback();
-                                if (cb != null) {
-                                    cb.bindShortcutsChanged(
-                                            updates, new ArrayList<ShortcutInfo>(), userFinal);
-                                }
-                            }
-                        });
-                    }
-                }
-
                 // Create the ApplicationInfos
                 for (int i = 0; i < apps.size(); i++) {
                     LauncherActivityInfoCompat app = apps.get(i);
@@ -2849,11 +2850,15 @@
                     mBgAllAppsList.add(new AppInfo(mContext, app, user, mIconCache));
                 }
 
-                if (!user.equals(UserHandleCompat.myUserHandle())) {
-                    ManagedProfileHeuristic heuristic = ManagedProfileHeuristic.get(mContext, user);
-                    if (heuristic != null) {
-                        heuristic.processUserApps(apps);
-                    }
+                final ManagedProfileHeuristic heuristic = ManagedProfileHeuristic.get(mContext, user);
+                if (heuristic != null) {
+                    runAfterBindCompletes(new Runnable() {
+
+                        @Override
+                        public void run() {
+                            heuristic.processUserApps(apps);
+                        }
+                    });
                 }
             }
             // Huh? Shouldn't this be inside the Runnable below?
@@ -2868,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");
@@ -2882,12 +2885,76 @@
             // 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");
             }
         }
 
+        private void updateAllAppsIconsCache() {
+            final ArrayList<AppInfo> updatedApps = new ArrayList<>();
+
+            for (UserHandleCompat user : mUserManager.getUserProfiles()) {
+                // Query for the set of apps
+                final List<LauncherActivityInfoCompat> apps = mLauncherApps.getActivityList(null, user);
+                // Fail if we don't have any apps
+                // TODO: Fix this. Only fail for the current user.
+                if (apps == null || apps.isEmpty()) {
+                    return;
+                }
+
+                // Update icon cache
+                HashSet<String> updatedPackages = mIconCache.updateDBIcons(user, apps);
+
+                // If any package icon has changed (app was updated while launcher was dead),
+                // update the corresponding shortcuts.
+                if (!updatedPackages.isEmpty()) {
+                    final ArrayList<ShortcutInfo> updatedShortcuts = new ArrayList<>();
+                    synchronized (sBgLock) {
+                        for (ItemInfo info : sBgItemsIdMap) {
+                            if (info instanceof ShortcutInfo && user.equals(info.user)
+                                    && info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
+                                ShortcutInfo si = (ShortcutInfo) info;
+                                ComponentName cn = si.getTargetComponent();
+                                if (cn != null && updatedPackages.contains(cn.getPackageName())) {
+                                    si.updateIcon(mIconCache);
+                                    updatedShortcuts.add(si);
+                                }
+                            }
+                        }
+                        mBgAllAppsList.updateIconsAndLabels(updatedPackages, user, updatedApps);
+                    }
+
+                    if (!updatedShortcuts.isEmpty()) {
+                        final UserHandleCompat userFinal = user;
+                        mHandler.post(new Runnable() {
+
+                            public void run() {
+                                Callbacks cb = getCallback();
+                                if (cb != null) {
+                                    cb.bindShortcutsChanged(updatedShortcuts,
+                                            new ArrayList<ShortcutInfo>(), userFinal);
+                                }
+                            }
+                        });
+                    }
+                }
+            }
+            if (!updatedApps.isEmpty()) {
+                mHandler.post(new Runnable() {
+
+                    public void run() {
+                        Callbacks cb = getCallback();
+                        if (cb != null) {
+                            cb.bindAppsUpdated(updatedApps);
+                        }
+                    }
+                });
+            }
+        }
+
         public void dumpState() {
             synchronized (sBgLock) {
                 Log.d(TAG, "mLoaderTask.mContext=" + mContext);
@@ -2963,6 +3030,10 @@
         }
 
         public void run() {
+            if (!mHasLoaderCompletedOnce) {
+                // Loader has not yet run.
+                return;
+            }
             final Context context = mApp.getContext();
 
             final String[] packages = mPackages;
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index a4593ec..aa86567 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -22,13 +22,10 @@
 import android.animation.LayoutTransition;
 import android.animation.ObjectAnimator;
 import android.animation.TimeInterpolator;
-import android.animation.ValueAnimator;
-import android.animation.ValueAnimator.AnimatorUpdateListener;
 import android.content.Context;
 import android.content.res.TypedArray;
 import android.graphics.Canvas;
 import android.graphics.Matrix;
-import android.graphics.PointF;
 import android.graphics.Rect;
 import android.os.Bundle;
 import android.os.Parcel;
@@ -47,23 +44,12 @@
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityManager;
 import android.view.accessibility.AccessibilityNodeInfo;
-import android.view.animation.AnimationUtils;
-import android.view.animation.DecelerateInterpolator;
 import android.view.animation.Interpolator;
-import android.view.animation.LinearInterpolator;
 
 import com.android.launcher3.util.Thunk;
 
 import java.util.ArrayList;
 
-interface Page {
-    public int getPageChildCount();
-    public View getChildOnPageAt(int i);
-    public void removeAllViewsOnPage();
-    public void removeViewOnPageAt(int i);
-    public int indexOfChildOnPage(View v);
-}
-
 /**
  * An abstraction of the original Workspace which supports browsing through a
  * sequential list of "pages"
@@ -88,6 +74,8 @@
     // The page is moved more than halfway, automatically move to the next page on touch up.
     private static final float SIGNIFICANT_MOVE_THRESHOLD = 0.4f;
 
+    private static final float MAX_SCROLL_PROGRESS = 1.0f;
+
     // The following constants need to be scaled based on density. The scaled versions will be
     // assigned to the corresponding member variables below.
     private static final int FLING_THRESHOLD_VELOCITY = 500;
@@ -97,7 +85,6 @@
     // We are disabling touch interaction of the widget region for factory ROM.
     private static final boolean DISABLE_TOUCH_INTERACTION = false;
     private static final boolean DISABLE_TOUCH_SIDE_PAGES = true;
-    private static final boolean DISABLE_FLING_TO_DELETE = true;
 
     public static final int INVALID_RESTORE_PAGE = -1001;
 
@@ -170,7 +157,6 @@
     protected int mUnboundedScrollX;
     protected int[] mTempVisiblePagesRange = new int[2];
     protected boolean mForceDrawAllChildrenNextFrame;
-    private boolean mSpacePagesAutomatically = false;
 
     // mOverScrollX is equal to getScrollX() when we're within the normal scroll range. Otherwise
     // it is equal to the scaled overscroll position. We use a separate value so as to prevent
@@ -183,11 +169,6 @@
 
     private PageSwitchListener mPageSwitchListener;
 
-    protected ArrayList<Boolean> mDirtyPageContent;
-
-    // If true, syncPages and syncPageItems will be called to refresh pages
-    protected boolean mContentIsRefreshable = true;
-
     // If true, modify alpha of neighboring pages as user scrolls left/right
     protected boolean mFadeInAdjacentScreens = false;
 
@@ -198,22 +179,14 @@
     // If true, the subclass should directly update scrollX itself in its computeScroll method
     // (SmoothPagedView does this)
     protected boolean mDeferScrollUpdate = false;
-    protected boolean mDeferLoadAssociatedPagesUntilScrollCompletes = false;
 
     protected boolean mIsPageMoving = false;
 
-    // All syncs and layout passes are deferred until data is ready.
-    protected boolean mIsDataReady = false;
-
-    protected boolean mAllowLongPress = true;
-
     private boolean mWasInOverscroll = false;
 
     // Page Indicator
     @Thunk int mPageIndicatorViewId;
     @Thunk PageIndicator mPageIndicator;
-    private boolean mAllowPagedViewAnimations = true;
-
     // The viewport whether the pages are to be contained (the actual view may be larger than the
     // viewport)
     private Rect mViewport = new Rect();
@@ -221,10 +194,10 @@
     // Reordering
     // We use the min scale to determine how much to expand the actually PagedView measured
     // dimensions such that when we are zoomed out, the view is not clipped
-    private int REORDERING_DROP_REPOSITION_DURATION = 200;
-    protected int REORDERING_REORDER_REPOSITION_DURATION = 300;
-    protected int REORDERING_ZOOM_IN_OUT_DURATION = 250;
-    private int REORDERING_SIDE_PAGE_HOVER_TIMEOUT = 80;
+    private static int REORDERING_DROP_REPOSITION_DURATION = 200;
+    private static int REORDERING_REORDER_REPOSITION_DURATION = 300;
+    private static int REORDERING_SIDE_PAGE_HOVER_TIMEOUT = 80;
+
     private float mMinScale = 1f;
     private boolean mUseMinScale = false;
     protected View mDragView;
@@ -242,28 +215,10 @@
     private Runnable mPostReorderingPreZoomInRunnable;
 
     // Convenience/caching
-    private Matrix mTmpInvMatrix = new Matrix();
-    private float[] mTmpPoint = new float[2];
-    private int[] mTmpIntPoint = new int[2];
-    private Rect mTmpRect = new Rect();
-    private Rect mAltTmpRect = new Rect();
-
-    // Fling to delete
-    @Thunk int FLING_TO_DELETE_FADE_OUT_DURATION = 350;
-    private float FLING_TO_DELETE_FRICTION = 0.035f;
-    // The degrees specifies how much deviation from the up vector to still consider a fling "up"
-    private float FLING_TO_DELETE_MAX_FLING_DEGREES = 65f;
-    protected int mFlingToDeleteThresholdVelocity = -1400;
-    // Drag to delete
-    @Thunk boolean mDeferringForDelete = false;
-    @Thunk int DELETE_SLIDE_IN_SIDE_PAGE_DURATION = 250;
-    private int DRAG_TO_DELETE_FADE_OUT_DURATION = 350;
-
-    // Drop to delete
-    private View mDeleteDropTarget;
-
-    // Bouncer
-    private boolean mTopAlignPageWhenShrinkingForBouncer = false;
+    private static final Matrix sTmpInvMatrix = new Matrix();
+    private static final float[] sTmpPoint = new float[2];
+    private static final int[] sTmpIntPoint = new int[2];
+    private static final Rect sTmpRect = new Rect();
 
     protected final Rect mInsets = new Rect();
 
@@ -300,8 +255,6 @@
      * Initializes various states for this workspace.
      */
     protected void init() {
-        mDirtyPageContent = new ArrayList<Boolean>();
-        mDirtyPageContent.ensureCapacity(32);
         mScroller = new LauncherScroller(getContext());
         setDefaultInterpolator(new ScrollInterpolator());
         mCurrentPage = 0;
@@ -313,10 +266,6 @@
         mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
         mDensity = getResources().getDisplayMetrics().density;
 
-        // Scale the fling-to-delete threshold by the density
-        mFlingToDeleteThresholdVelocity =
-                (int) (mFlingToDeleteThresholdVelocity * mDensity);
-
         mFlingThresholdVelocity = (int) (FLING_THRESHOLD_VELOCITY * mDensity);
         mMinFlingVelocity = (int) (MIN_FLING_VELOCITY * mDensity);
         mMinSnapVelocity = (int) (MIN_SNAP_VELOCITY * mDensity);
@@ -336,7 +285,7 @@
         ViewGroup grandParent = (ViewGroup) parent.getParent();
         if (mPageIndicator == null && mPageIndicatorViewId > -1) {
             mPageIndicator = (PageIndicator) grandParent.findViewById(mPageIndicatorViewId);
-            mPageIndicator.removeAllMarkers(mAllowPagedViewAnimations);
+            mPageIndicator.removeAllMarkers(true);
 
             ArrayList<PageIndicator.PageMarkerResources> markers =
                     new ArrayList<PageIndicator.PageMarkerResources>();
@@ -344,7 +293,7 @@
                 markers.add(getPageIndicatorMarker(i));
             }
 
-            mPageIndicator.addMarkers(markers, mAllowPagedViewAnimations);
+            mPageIndicator.addMarkers(markers, true);
 
             OnClickListener listener = getPageIndicatorClickListener();
             if (listener != null) {
@@ -362,33 +311,31 @@
         return null;
     }
 
+    @Override
     protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
         // Unhook the page indicator
         mPageIndicator = null;
     }
 
-    void setDeleteDropTarget(View v) {
-        mDeleteDropTarget = v;
-    }
-
     // Convenience methods to map points from self to parent and vice versa
-    float[] mapPointFromViewToParent(View v, float x, float y) {
-        mTmpPoint[0] = x;
-        mTmpPoint[1] = y;
-        v.getMatrix().mapPoints(mTmpPoint);
-        mTmpPoint[0] += v.getLeft();
-        mTmpPoint[1] += v.getTop();
-        return mTmpPoint;
+    private float[] mapPointFromViewToParent(View v, float x, float y) {
+        sTmpPoint[0] = x;
+        sTmpPoint[1] = y;
+        v.getMatrix().mapPoints(sTmpPoint);
+        sTmpPoint[0] += v.getLeft();
+        sTmpPoint[1] += v.getTop();
+        return sTmpPoint;
     }
-    float[] mapPointFromParentToView(View v, float x, float y) {
-        mTmpPoint[0] = x - v.getLeft();
-        mTmpPoint[1] = y - v.getTop();
-        v.getMatrix().invert(mTmpInvMatrix);
-        mTmpInvMatrix.mapPoints(mTmpPoint);
-        return mTmpPoint;
+    private float[] mapPointFromParentToView(View v, float x, float y) {
+        sTmpPoint[0] = x - v.getLeft();
+        sTmpPoint[1] = y - v.getTop();
+        v.getMatrix().invert(sTmpInvMatrix);
+        sTmpInvMatrix.mapPoints(sTmpPoint);
+        return sTmpPoint;
     }
 
-    void updateDragViewTranslationDuringDrag() {
+    private void updateDragViewTranslationDuringDrag() {
         if (mDragView != null) {
             float x = (mLastMotionX - mDownMotionX) + (getScrollX() - mDownScrollX) +
                     (mDragViewBaselineLeft - mDragView.getLeft());
@@ -463,18 +410,6 @@
     }
 
     /**
-     * Called by subclasses to mark that data is ready, and that we can begin loading and laying
-     * out pages.
-     */
-    protected void setDataIsReady() {
-        mIsDataReady = true;
-    }
-
-    protected boolean isDataReady() {
-        return mIsDataReady;
-    }
-
-    /**
      * Returns the index of the currently displayed page.
      */
     int getCurrentPage() {
@@ -516,17 +451,6 @@
         forceFinishScroller();
     }
 
-    /**
-     * Called during AllApps/Home transitions to avoid unnecessary work. When that other animation
-     * {@link #updateCurrentPageScroll()} should be called, to correctly set the final state and
-     * re-enable scrolling.
-     */
-    void stopScrolling() {
-        mCurrentPage = getNextPage();
-        notifyPageSwitchListener();
-        forceFinishScroller();
-    }
-
     private void abortScrollerAnimation(boolean resetNextPage) {
         mScroller.abortAnimation();
         // We need to clean up the next page here to avoid computeScrollHelper from
@@ -559,7 +483,7 @@
     /**
      * Sets the current page.
      */
-    void setCurrentPage(int currentPage) {
+    public void setCurrentPage(int currentPage) {
         if (!mScroller.isFinished()) {
             abortScrollerAnimation(true);
         }
@@ -750,12 +674,6 @@
             mNextPage = INVALID_PAGE;
             notifyPageSwitchListener();
 
-            // Load the associated pages if necessary
-            if (mDeferLoadAssociatedPagesUntilScrollCompletes) {
-                loadAssociatedPages(mCurrentPage);
-                mDeferLoadAssociatedPagesUntilScrollCompletes = false;
-            }
-
             // We don't want to trigger a page end moving unless the page has settled
             // and the user has stopped scrolling
             if (mTouchState == TOUCH_STATE_REST) {
@@ -779,10 +697,6 @@
         computeScrollHelper();
     }
 
-    protected boolean shouldSetTopAlignedPivotForWidget(int childIndex) {
-        return mTopAlignPageWhenShrinkingForBouncer;
-    }
-
     public static class LayoutParams extends ViewGroup.LayoutParams {
         public boolean isFullScreenPage = false;
 
@@ -814,7 +728,7 @@
 
     @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-        if (!mIsDataReady || getChildCount() == 0) {
+        if (getChildCount() == 0) {
             super.onMeasure(widthMeasureSpec, heightMeasureSpec);
             return;
         }
@@ -918,27 +832,12 @@
                 child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
             }
         }
-        if (mSpacePagesAutomatically) {
-            int spacing = (getViewportWidth() - mInsets.left - mInsets.right
-                    - referenceChildWidth) / 2;
-            if (spacing >= 0) {
-                setPageSpacing(spacing);
-            }
-            mSpacePagesAutomatically = false;
-        }
         setMeasuredDimension(scaledWidthSize, scaledHeightSize);
     }
 
-    /**
-     * This method should be called once before first layout / measure pass.
-     */
-    protected void setSinglePageInViewport() {
-        mSpacePagesAutomatically = true;
-    }
-
     @Override
     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
-        if (!mIsDataReady || getChildCount() == 0) {
+        if (getChildCount() == 0) {
             return;
         }
 
@@ -1040,8 +939,7 @@
             updateMaxScrollX();
         }
 
-        if (mScroller.isFinished() && mChildCountOnLastLayout != childCount &&
-                !mDeferringForDelete) {
+        if (mScroller.isFinished() && mChildCountOnLastLayout != childCount) {
             if (mRestorePage != INVALID_RESTORE_PAGE) {
                 setCurrentPage(mRestorePage);
                 mRestorePage = INVALID_RESTORE_PAGE;
@@ -1087,14 +985,6 @@
         }
     }
 
-    protected void enablePagedViewAnimations() {
-        mAllowPagedViewAnimations = true;
-
-    }
-    protected void disablePagedViewAnimations() {
-        mAllowPagedViewAnimations = false;
-    }
-
     @Override
     public void onChildViewAdded(View parent, View child) {
         // Update the page indicator, we don't update the page indicator as we
@@ -1103,7 +993,7 @@
             int pageIndex = indexOfChild(child);
             mPageIndicator.addMarker(pageIndex,
                     getPageIndicatorMarker(pageIndex),
-                    mAllowPagedViewAnimations);
+                    true);
         }
 
         // This ensures that when children are added, they get the correct transforms / alphas
@@ -1124,7 +1014,7 @@
         // Update the page indicator, we don't update the page indicator as we
         // add/remove pages
         if (mPageIndicator != null && !isReordering(false)) {
-            mPageIndicator.removeMarker(index, mAllowPagedViewAnimations);
+            mPageIndicator.removeMarker(index, true);
         }
     }
 
@@ -1154,7 +1044,7 @@
         // Update the page indicator, we don't update the page indicator as we
         // add/remove pages
         if (mPageIndicator != null) {
-            mPageIndicator.removeAllMarkers(mAllowPagedViewAnimations);
+            mPageIndicator.removeAllMarkers(true);
         }
 
         super.removeAllViewsInLayout();
@@ -1175,7 +1065,7 @@
 
     protected void getVisiblePages(int[] range) {
         final int pageCount = getChildCount();
-        mTmpIntPoint[0] = mTmpIntPoint[1] = 0;
+        sTmpIntPoint[0] = sTmpIntPoint[1] = 0;
 
         range[0] = -1;
         range[1] = -1;
@@ -1188,9 +1078,9 @@
             for (int i = 0; i < count; i++) {
                 View currPage = getPageAt(i);
 
-                mTmpIntPoint[0] = 0;
-                Utilities.getDescendantCoordRelativeToParent(currPage, this, mTmpIntPoint, false);
-                if (mTmpIntPoint[0] > viewportWidth) {
+                sTmpIntPoint[0] = 0;
+                Utilities.getDescendantCoordRelativeToParent(currPage, this, sTmpIntPoint, false);
+                if (sTmpIntPoint[0] > viewportWidth) {
                     if (range[0] == -1) {
                         continue;
                     } else {
@@ -1198,9 +1088,9 @@
                     }
                 }
 
-                mTmpIntPoint[0] = currPage.getMeasuredWidth();
-                Utilities.getDescendantCoordRelativeToParent(currPage, this, mTmpIntPoint, false);
-                if (mTmpIntPoint[0] < 0) {
+                sTmpIntPoint[0] = currPage.getMeasuredWidth();
+                Utilities.getDescendantCoordRelativeToParent(currPage, this, sTmpIntPoint, false);
+                if (sTmpIntPoint[0] < 0) {
                     if (range[0] == -1) {
                         continue;
                     } else {
@@ -1397,9 +1287,9 @@
 
     /** Returns whether x and y originated within the buffered viewport */
     private boolean isTouchPointInViewportWithBuffer(int x, int y) {
-        mTmpRect.set(mViewport.left - mViewport.width() / 2, mViewport.top,
+        sTmpRect.set(mViewport.left - mViewport.width() / 2, mViewport.top,
                 mViewport.right + mViewport.width() / 2, mViewport.bottom);
-        return mTmpRect.contains(x, y);
+        return sTmpRect.contains(x, y);
     }
 
     @Override
@@ -1559,32 +1449,16 @@
         }
     }
 
-    protected float getMaxScrollProgress() {
-        return 1.0f;
-    }
-
     protected void cancelCurrentPageLongPress() {
-        if (mAllowLongPress) {
-            //mAllowLongPress = false;
-            // Try canceling the long press. It could also have been scheduled
-            // by a distant descendant, so use the mAllowLongPress flag to block
-            // everything
-            final View currentPage = getPageAt(mCurrentPage);
-            if (currentPage != null) {
-                currentPage.cancelLongPress();
-            }
+        // Try canceling the long press. It could also have been scheduled
+        // by a distant descendant, so use the mAllowLongPress flag to block
+        // everything
+        final View currentPage = getPageAt(mCurrentPage);
+        if (currentPage != null) {
+            currentPage.cancelLongPress();
         }
     }
 
-    protected float getBoundedScrollProgress(int screenCenter, View v, int page) {
-        final int halfScreenSize = getViewportWidth() / 2;
-
-        screenCenter = Math.min(getScrollX() + halfScreenSize, screenCenter);
-        screenCenter = Math.max(halfScreenSize,  screenCenter);
-
-        return getScrollProgress(screenCenter, v, page);
-    }
-
     protected float getScrollProgress(int screenCenter, View v, int page) {
         final int halfScreenSize = getViewportWidth() / 2;
 
@@ -1605,8 +1479,8 @@
         }
 
         float scrollProgress = delta / (totalDistance * 1.0f);
-        scrollProgress = Math.min(scrollProgress, getMaxScrollProgress());
-        scrollProgress = Math.max(scrollProgress, - getMaxScrollProgress());
+        scrollProgress = Math.min(scrollProgress, MAX_SCROLL_PROGRESS);
+        scrollProgress = Math.max(scrollProgress, - MAX_SCROLL_PROGRESS);
         return scrollProgress;
     }
 
@@ -1735,7 +1609,7 @@
         mAllowOverScroll = enable;
     }
 
-    int getNearestHoverOverPageIndex() {
+    private int getNearestHoverOverPageIndex() {
         if (mDragView != null) {
             int dragX = (int) (mDragView.getLeft() + (mDragView.getMeasuredWidth() / 2)
                     + mDragView.getTranslationX());
@@ -1842,19 +1716,13 @@
                 // Find the closest page to the touch point
                 final int dragViewIndex = indexOfChild(mDragView);
 
-                // Change the drag view if we are hovering over the drop target
-                boolean isHoveringOverDelete = isHoveringOverDeleteDropTarget(
-                        (int) mParentDownMotionX, (int) mParentDownMotionY);
-                setPageHoveringOverDeleteDropTarget(dragViewIndex, isHoveringOverDelete);
-
                 if (DEBUG) Log.d(TAG, "mLastMotionX: " + mLastMotionX);
                 if (DEBUG) Log.d(TAG, "mLastMotionY: " + mLastMotionY);
                 if (DEBUG) Log.d(TAG, "mParentDownMotionX: " + mParentDownMotionX);
                 if (DEBUG) Log.d(TAG, "mParentDownMotionY: " + mParentDownMotionY);
 
                 final int pageUnderPointIndex = getNearestHoverOverPageIndex();
-                if (pageUnderPointIndex > -1 && pageUnderPointIndex != indexOfChild(mDragView) &&
-                        !isHoveringOverDelete) {
+                if (pageUnderPointIndex > -1 && pageUnderPointIndex != indexOfChild(mDragView)) {
                     mTempVisiblePagesRange[0] = 0;
                     mTempVisiblePagesRange[1] = getPageCount() - 1;
                     getFreeScrollPageRange(mTempVisiblePagesRange);
@@ -1901,9 +1769,7 @@
                                 }
 
                                 removeView(mDragView);
-                                onRemoveView(mDragView, false);
                                 addView(mDragView, pageUnderPointIndex);
-                                onAddView(mDragView, pageUnderPointIndex);
                                 mSidePageHoverIndex = -1;
                                 if (mPageIndicator != null) {
                                     mPageIndicator.setActiveMarker(getNextPage());
@@ -2014,19 +1880,6 @@
                 mParentDownMotionX = pt[0];
                 mParentDownMotionY = pt[1];
                 updateDragViewTranslationDuringDrag();
-                boolean handledFling = false;
-                if (!DISABLE_FLING_TO_DELETE) {
-                    // Check the velocity and see if we are flinging-to-delete
-                    PointF flingToDeleteVector = isFlingingToDelete();
-                    if (flingToDeleteVector != null) {
-                        onFlingToDelete(flingToDeleteVector);
-                        handledFling = true;
-                    }
-                }
-                if (!handledFling && isHoveringOverDeleteDropTarget((int) mParentDownMotionX,
-                        (int) mParentDownMotionY)) {
-                    onDropToDelete();
-                }
             } else {
                 if (!mCancelTap) {
                     onUnhandledTap(ev);
@@ -2055,11 +1908,6 @@
         return true;
     }
 
-    public void onFlingToDelete(View v) {}
-    public void onRemoveView(View v, boolean deletePermanently) {}
-    public void onRemoveViewAnimationCompleted() {}
-    public void onAddView(View v, int index) {}
-
     private void resetTouchState() {
         releaseVelocityTracker();
         endReordering();
@@ -2155,22 +2003,6 @@
         }
     }
 
-    protected int getChildWidth(int index) {
-        return getPageAt(index).getMeasuredWidth();
-    }
-
-    int getPageNearestToPoint(float x) {
-        int index = 0;
-        for (int i = 0; i < getChildCount(); ++i) {
-            if (x < getChildAt(i).getRight() - getScrollX()) {
-                return index;
-            } else {
-                index++;
-            }
-        }
-        return Math.min(index, getChildCount() - 1);
-    }
-
     int getPageNearestToCenterOfScreen() {
         int minDistanceFromScreenCenter = Integer.MAX_VALUE;
         int minDistanceFromScreenCenterIndex = -1;
@@ -2330,9 +2162,6 @@
             computeScroll();
         }
 
-        // Defer loading associated pages until the scroll settles
-        mDeferLoadAssociatedPagesUntilScrollCompletes = true;
-
         mForceScreenScrolled = true;
         invalidate();
     }
@@ -2359,27 +2188,12 @@
         return result;
     }
 
-    /**
-     * @return True is long presses are still allowed for the current touch
-     */
-    public boolean allowLongPress() {
-        return mAllowLongPress;
-    }
-
     @Override
     public boolean performLongClick() {
         mCancelTap = true;
         return super.performLongClick();
     }
 
-    /**
-     * Set true to allow long-press events to be triggered, usually checked by
-     * {@link Launcher} to accept or block dpad-initiated long-presses.
-     */
-    public void setAllowLongPress(boolean allowLongPress) {
-        mAllowLongPress = allowLongPress;
-    }
-
     public static class SavedState extends BaseSavedState {
         int currentPage = -1;
 
@@ -2410,111 +2224,6 @@
         };
     }
 
-    protected void loadAssociatedPages(int page) {
-        loadAssociatedPages(page, false);
-    }
-    protected void loadAssociatedPages(int page, boolean immediateAndOnly) {
-        if (mContentIsRefreshable) {
-            final int count = getChildCount();
-            if (page < count) {
-                int lowerPageBound = getAssociatedLowerPageBound(page);
-                int upperPageBound = getAssociatedUpperPageBound(page);
-                if (DEBUG) Log.d(TAG, "loadAssociatedPages: " + lowerPageBound + "/"
-                        + upperPageBound);
-                // First, clear any pages that should no longer be loaded
-                for (int i = 0; i < count; ++i) {
-                    Page layout = (Page) getPageAt(i);
-                    if ((i < lowerPageBound) || (i > upperPageBound)) {
-                        if (layout.getPageChildCount() > 0) {
-                            layout.removeAllViewsOnPage();
-                        }
-                        mDirtyPageContent.set(i, true);
-                    }
-                }
-                // Next, load any new pages
-                for (int i = 0; i < count; ++i) {
-                    if ((i != page) && immediateAndOnly) {
-                        continue;
-                    }
-                    if (lowerPageBound <= i && i <= upperPageBound) {
-                        if (mDirtyPageContent.get(i)) {
-                            syncPageItems(i, (i == page) && immediateAndOnly);
-                            mDirtyPageContent.set(i, false);
-                        }
-                    }
-                }
-            }
-        }
-    }
-
-    protected int getAssociatedLowerPageBound(int page) {
-        return Math.max(0, page - 1);
-    }
-    protected int getAssociatedUpperPageBound(int page) {
-        final int count = getChildCount();
-        return Math.min(page + 1, count - 1);
-    }
-
-    /**
-     * This method is called ONLY to synchronize the number of pages that the paged view has.
-     * To actually fill the pages with information, implement syncPageItems() below.  It is
-     * guaranteed that syncPageItems() will be called for a particular page before it is shown,
-     * and therefore, individual page items do not need to be updated in this method.
-     */
-    public abstract void syncPages();
-
-    /**
-     * This method is called to synchronize the items that are on a particular page.  If views on
-     * the page can be reused, then they should be updated within this method.
-     */
-    public abstract void syncPageItems(int page, boolean immediate);
-
-    protected void invalidatePageData() {
-        invalidatePageData(-1, false);
-    }
-    protected void invalidatePageData(int currentPage) {
-        invalidatePageData(currentPage, false);
-    }
-    protected void invalidatePageData(int currentPage, boolean immediateAndOnly) {
-        if (!mIsDataReady) {
-            return;
-        }
-
-        if (mContentIsRefreshable) {
-            // Force all scrolling-related behavior to end
-            forceFinishScroller();
-
-            // Update all the pages
-            syncPages();
-
-            // We must force a measure after we've loaded the pages to update the content width and
-            // to determine the full scroll width
-            measure(MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY),
-                    MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY));
-
-            // Set a new page as the current page if necessary
-            if (currentPage > -1) {
-                setCurrentPage(Math.min(getPageCount() - 1, currentPage));
-            }
-
-            // Mark each of the pages as dirty
-            final int count = getChildCount();
-            mDirtyPageContent.clear();
-            for (int i = 0; i < count; ++i) {
-                mDirtyPageContent.add(true);
-            }
-
-            // Load any pages that are necessary for the current window of views
-            loadAssociatedPages(mCurrentPage, immediateAndOnly);
-            requestLayout();
-        }
-        if (isPageMoving()) {
-            // If the page is moving, then snap it to the final position to ensure we don't get
-            // stuck between pages
-            snapToDestination();
-        }
-    }
-
     // Animate the drag view back to the original position
     void animateDragViewToOriginalPosition() {
         if (mDragView != null) {
@@ -2535,7 +2244,7 @@
         }
     }
 
-    protected void onStartReordering() {
+    public void onStartReordering() {
         // Set the touch state to reordering (allows snapping to pages, dragging a child, etc.)
         mTouchState = TOUCH_STATE_REORDERING;
         mIsReordering = true;
@@ -2555,7 +2264,7 @@
         }
     }
 
-    protected void onEndReordering() {
+    public void onEndReordering() {
         mIsReordering = false;
     }
 
@@ -2605,280 +2314,24 @@
                 onEndReordering();
             }
         };
-        if (!mDeferringForDelete) {
-            mPostReorderingPreZoomInRunnable = new Runnable() {
-                public void run() {
-                    onCompleteRunnable.run();
-                    enableFreeScroll();
-                };
+
+        mPostReorderingPreZoomInRunnable = new Runnable() {
+            public void run() {
+                onCompleteRunnable.run();
+                enableFreeScroll();
             };
+        };
 
-            mPostReorderingPreZoomInRemainingAnimationCount =
-                    NUM_ANIMATIONS_RUNNING_BEFORE_ZOOM_OUT;
-            // Snap to the current page
-            snapToPage(indexOfChild(mDragView), 0);
-            // Animate the drag view back to the front position
-            animateDragViewToOriginalPosition();
-        } else {
-            // Handled in post-delete-animation-callbacks
-        }
+        mPostReorderingPreZoomInRemainingAnimationCount =
+                NUM_ANIMATIONS_RUNNING_BEFORE_ZOOM_OUT;
+        // Snap to the current page
+        snapToPage(indexOfChild(mDragView), 0);
+        // Animate the drag view back to the front position
+        animateDragViewToOriginalPosition();
     }
 
-    /*
-     * Flinging to delete - IN PROGRESS
-     */
-    private PointF isFlingingToDelete() {
-        ViewConfiguration config = ViewConfiguration.get(getContext());
-        mVelocityTracker.computeCurrentVelocity(1000, config.getScaledMaximumFlingVelocity());
-
-        if (mVelocityTracker.getYVelocity() < mFlingToDeleteThresholdVelocity) {
-            // Do a quick dot product test to ensure that we are flinging upwards
-            PointF vel = new PointF(mVelocityTracker.getXVelocity(),
-                    mVelocityTracker.getYVelocity());
-            PointF upVec = new PointF(0f, -1f);
-            float theta = (float) Math.acos(((vel.x * upVec.x) + (vel.y * upVec.y)) /
-                    (vel.length() * upVec.length()));
-            if (theta <= Math.toRadians(FLING_TO_DELETE_MAX_FLING_DEGREES)) {
-                return vel;
-            }
-        }
-        return null;
-    }
-
-    /**
-     * Creates an animation from the current drag view along its current velocity vector.
-     * For this animation, the alpha runs for a fixed duration and we update the position
-     * progressively.
-     */
-    private static class FlingAlongVectorAnimatorUpdateListener implements AnimatorUpdateListener {
-        private View mDragView;
-        private PointF mVelocity;
-        private Rect mFrom;
-        private long mPrevTime;
-        private float mFriction;
-
-        private final TimeInterpolator mAlphaInterpolator = new DecelerateInterpolator(0.75f);
-
-        public FlingAlongVectorAnimatorUpdateListener(View dragView, PointF vel, Rect from,
-                long startTime, float friction) {
-            mDragView = dragView;
-            mVelocity = vel;
-            mFrom = from;
-            mPrevTime = startTime;
-            mFriction = 1f - (mDragView.getResources().getDisplayMetrics().density * friction);
-        }
-
-        @Override
-        public void onAnimationUpdate(ValueAnimator animation) {
-            float t = ((Float) animation.getAnimatedValue()).floatValue();
-            long curTime = AnimationUtils.currentAnimationTimeMillis();
-
-            mFrom.left += (mVelocity.x * (curTime - mPrevTime) / 1000f);
-            mFrom.top += (mVelocity.y * (curTime - mPrevTime) / 1000f);
-
-            mDragView.setTranslationX(mFrom.left);
-            mDragView.setTranslationY(mFrom.top);
-            mDragView.setAlpha(1f - mAlphaInterpolator.getInterpolation(t));
-
-            mVelocity.x *= mFriction;
-            mVelocity.y *= mFriction;
-            mPrevTime = curTime;
-        }
-    };
-
     private static final int ANIM_TAG_KEY = 100;
 
-    private Runnable createPostDeleteAnimationRunnable(final View dragView) {
-        return new Runnable() {
-            @Override
-            public void run() {
-                int dragViewIndex = indexOfChild(dragView);
-
-                // For each of the pages around the drag view, animate them from the previous
-                // position to the new position in the layout (as a result of the drag view moving
-                // in the layout)
-                // NOTE: We can make an assumption here because we have side-bound pages that we
-                //       will always have pages to animate in from the left
-                getFreeScrollPageRange(mTempVisiblePagesRange);
-                boolean isLastWidgetPage = (mTempVisiblePagesRange[0] == mTempVisiblePagesRange[1]);
-                boolean slideFromLeft = (isLastWidgetPage ||
-                        dragViewIndex > mTempVisiblePagesRange[0]);
-
-                // Setup the scroll to the correct page before we swap the views
-                if (slideFromLeft) {
-                    snapToPageImmediately(dragViewIndex - 1);
-                }
-
-                int firstIndex = (isLastWidgetPage ? 0 : mTempVisiblePagesRange[0]);
-                int lastIndex = Math.min(mTempVisiblePagesRange[1], getPageCount() - 1);
-                int lowerIndex = (slideFromLeft ? firstIndex : dragViewIndex + 1 );
-                int upperIndex = (slideFromLeft ? dragViewIndex - 1 : lastIndex);
-                ArrayList<Animator> animations = new ArrayList<Animator>();
-                for (int i = lowerIndex; i <= upperIndex; ++i) {
-                    View v = getChildAt(i);
-                    // dragViewIndex < pageUnderPointIndex, so after we remove the
-                    // drag view all subsequent views to pageUnderPointIndex will
-                    // shift down.
-                    int oldX = 0;
-                    int newX = 0;
-                    if (slideFromLeft) {
-                        if (i == 0) {
-                            // Simulate the page being offscreen with the page spacing
-                            oldX = getViewportOffsetX() + getChildOffset(i) - getChildWidth(i)
-                                    - mPageSpacing;
-                        } else {
-                            oldX = getViewportOffsetX() + getChildOffset(i - 1);
-                        }
-                        newX = getViewportOffsetX() + getChildOffset(i);
-                    } else {
-                        oldX = getChildOffset(i) - getChildOffset(i - 1);
-                        newX = 0;
-                    }
-
-                    // Animate the view translation from its old position to its new
-                    // position
-                    AnimatorSet anim = (AnimatorSet) v.getTag();
-                    if (anim != null) {
-                        anim.cancel();
-                    }
-
-                    // Note: Hacky, but we want to skip any optimizations to not draw completely
-                    // hidden views
-                    v.setAlpha(Math.max(v.getAlpha(), 0.01f));
-                    v.setTranslationX(oldX - newX);
-                    anim = new AnimatorSet();
-                    anim.playTogether(
-                            ObjectAnimator.ofFloat(v, "translationX", 0f),
-                            ObjectAnimator.ofFloat(v, "alpha", 1f));
-                    animations.add(anim);
-                    v.setTag(ANIM_TAG_KEY, anim);
-                }
-
-                AnimatorSet slideAnimations = new AnimatorSet();
-                slideAnimations.playTogether(animations);
-                slideAnimations.setDuration(DELETE_SLIDE_IN_SIDE_PAGE_DURATION);
-                slideAnimations.addListener(new AnimatorListenerAdapter() {
-                    @Override
-                    public void onAnimationEnd(Animator animation) {
-                        mDeferringForDelete = false;
-                        onEndReordering();
-                        onRemoveViewAnimationCompleted();
-                    }
-                });
-                slideAnimations.start();
-
-                removeView(dragView);
-                onRemoveView(dragView, true);
-            }
-        };
-    }
-
-    public void onFlingToDelete(PointF vel) {
-        final long startTime = AnimationUtils.currentAnimationTimeMillis();
-
-        // NOTE: Because it takes time for the first frame of animation to actually be
-        // called and we expect the animation to be a continuation of the fling, we have
-        // to account for the time that has elapsed since the fling finished.  And since
-        // we don't have a startDelay, we will always get call to update when we call
-        // start() (which we want to ignore).
-        final TimeInterpolator tInterpolator = new TimeInterpolator() {
-            private int mCount = -1;
-            private long mStartTime;
-            private float mOffset;
-            /* Anonymous inner class ctor */ {
-                mStartTime = startTime;
-            }
-
-            @Override
-            public float getInterpolation(float t) {
-                if (mCount < 0) {
-                    mCount++;
-                } else if (mCount == 0) {
-                    mOffset = Math.min(0.5f, (float) (AnimationUtils.currentAnimationTimeMillis() -
-                            mStartTime) / FLING_TO_DELETE_FADE_OUT_DURATION);
-                    mCount++;
-                }
-                return Math.min(1f, mOffset + t);
-            }
-        };
-
-        final Rect from = new Rect();
-        final View dragView = mDragView;
-        from.left = (int) dragView.getTranslationX();
-        from.top = (int) dragView.getTranslationY();
-        AnimatorUpdateListener updateCb = new FlingAlongVectorAnimatorUpdateListener(dragView, vel,
-                from, startTime, FLING_TO_DELETE_FRICTION);
-
-        final Runnable onAnimationEndRunnable = createPostDeleteAnimationRunnable(dragView);
-
-        // Create and start the animation
-        ValueAnimator mDropAnim = new ValueAnimator();
-        mDropAnim.setInterpolator(tInterpolator);
-        mDropAnim.setDuration(FLING_TO_DELETE_FADE_OUT_DURATION);
-        mDropAnim.setFloatValues(0f, 1f);
-        mDropAnim.addUpdateListener(updateCb);
-        mDropAnim.addListener(new AnimatorListenerAdapter() {
-            public void onAnimationEnd(Animator animation) {
-                onAnimationEndRunnable.run();
-            }
-        });
-        mDropAnim.start();
-        mDeferringForDelete = true;
-    }
-
-    /* Drag to delete */
-    private boolean isHoveringOverDeleteDropTarget(int x, int y) {
-        if (mDeleteDropTarget != null) {
-            mAltTmpRect.set(0, 0, 0, 0);
-            View parent = (View) mDeleteDropTarget.getParent();
-            if (parent != null) {
-                parent.getGlobalVisibleRect(mAltTmpRect);
-            }
-            mDeleteDropTarget.getGlobalVisibleRect(mTmpRect);
-            mTmpRect.offset(-mAltTmpRect.left, -mAltTmpRect.top);
-            return mTmpRect.contains(x, y);
-        }
-        return false;
-    }
-
-    protected void setPageHoveringOverDeleteDropTarget(int viewIndex, boolean isHovering) {}
-
-    private void onDropToDelete() {
-        final View dragView = mDragView;
-
-        final float toScale = 0f;
-        final float toAlpha = 0f;
-
-        // Create and start the complex animation
-        ArrayList<Animator> animations = new ArrayList<Animator>();
-        AnimatorSet motionAnim = new AnimatorSet();
-        motionAnim.setInterpolator(new DecelerateInterpolator(2));
-        motionAnim.playTogether(
-                ObjectAnimator.ofFloat(dragView, "scaleX", toScale),
-                ObjectAnimator.ofFloat(dragView, "scaleY", toScale));
-        animations.add(motionAnim);
-
-        AnimatorSet alphaAnim = new AnimatorSet();
-        alphaAnim.setInterpolator(new LinearInterpolator());
-        alphaAnim.playTogether(
-                ObjectAnimator.ofFloat(dragView, "alpha", toAlpha));
-        animations.add(alphaAnim);
-
-        final Runnable onAnimationEndRunnable = createPostDeleteAnimationRunnable(dragView);
-
-        AnimatorSet anim = new AnimatorSet();
-        anim.playTogether(animations);
-        anim.setDuration(DRAG_TO_DELETE_FADE_OUT_DURATION);
-        anim.addListener(new AnimatorListenerAdapter() {
-            public void onAnimationEnd(Animator animation) {
-                onAnimationEndRunnable.run();
-            }
-        });
-        anim.start();
-
-        mDeferringForDelete = true;
-    }
-
     /* Accessibility */
     @Override
     public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index e7a41e0..5c2121a 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -63,6 +63,7 @@
 import com.android.launcher3.LauncherAccessibilityDelegate.AccessibilityDragSource;
 import com.android.launcher3.LauncherSettings.Favorites;
 import com.android.launcher3.UninstallDropTarget.UninstallSource;
+import com.android.launcher3.accessibility.OverviewScreenAccessibilityDelegate;
 import com.android.launcher3.compat.UserHandleCompat;
 import com.android.launcher3.util.LongArrayMap;
 import com.android.launcher3.util.Thunk;
@@ -94,7 +95,6 @@
     protected static final int FADE_EMPTY_SCREEN_DURATION = 150;
 
     private static final int ADJACENT_SCREEN_DROP_DURATION = 300;
-    private static final int FLING_THRESHOLD_VELOCITY = 500;
 
     static final boolean MAP_NO_RECURSE = false;
     static final boolean MAP_RECURSE = true;
@@ -277,6 +277,8 @@
     // Handles workspace state transitions
     private WorkspaceStateTransitionAnimation mStateTransitionAnimation;
 
+    private AccessibilityDelegate mPagesAccessibilityDelegate;
+
     private final Runnable mBindPages = new Runnable() {
         @Override
         public void run() {
@@ -303,13 +305,11 @@
      */
     public Workspace(Context context, AttributeSet attrs, int defStyle) {
         super(context, attrs, defStyle);
-        mContentIsRefreshable = false;
 
         mOutlineHelper = HolographicOutlineHelper.obtain(context);
 
         mDragEnforcer = new DropTarget.DragEnforcer(context);
         // With workspace, data is available straight from the get-go
-        setDataIsReady();
 
         mLauncher = (Launcher) context;
         mStateTransitionAnimation = new WorkspaceStateTransitionAnimation(mLauncher, this);
@@ -445,7 +445,6 @@
         display.getSize(mDisplaySize);
 
         mMaxDistanceForFolderCreation = (0.55f * grid.iconSizePx);
-        mFlingThresholdVelocity = (int) (FLING_THRESHOLD_VELOCITY * mDensity);
 
         // Set the wallpaper dimensions when Launcher starts up
         setWallpaperDimension();
@@ -2000,14 +1999,14 @@
         range[1] = Math.max(0,  end);
     }
 
-    protected void onStartReordering() {
+    public void onStartReordering() {
         super.onStartReordering();
         showOutlines();
         // Reordering handles its own animations, disable the automatic ones.
         disableLayoutTransitions();
     }
 
-    protected void onEndReordering() {
+    public void onEndReordering() {
         super.onEndReordering();
 
         if (mLauncher.isWorkspaceLoading()) {
@@ -2068,11 +2067,45 @@
         return mState;
     }
 
-    private void updateAccessibilityFlags() {
-        int accessible = mState == State.NORMAL ?
-                IMPORTANT_FOR_ACCESSIBILITY_NO :
-                IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS;
-        setImportantForAccessibility(accessible);
+    public void updateAccessibilityFlags() {
+        if (Utilities.isLmpOrAbove()) {
+            int total = getPageCount();
+            for (int i = numCustomPages(); i < total; i++) {
+                updateAccessibilityFlags((CellLayout) getPageAt(i), i);
+            }
+            if (mState == State.NORMAL) {
+                setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
+            } else {
+                setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
+            }
+        } else {
+            int accessible = mState == State.NORMAL ?
+                    IMPORTANT_FOR_ACCESSIBILITY_NO :
+                        IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS;
+            setImportantForAccessibility(accessible);
+        }
+    }
+
+    private void updateAccessibilityFlags(CellLayout page, int pageNo) {
+        if (mState == State.OVERVIEW) {
+            page.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
+            page.getShortcutsAndWidgets().setImportantForAccessibility(
+                    IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
+            page.setContentDescription(getPageDescription(pageNo));
+
+            if (mPagesAccessibilityDelegate == null) {
+                mPagesAccessibilityDelegate = new OverviewScreenAccessibilityDelegate(this);
+            }
+            page.setAccessibilityDelegate(mPagesAccessibilityDelegate);
+        } else {
+            int accessible = mState == State.NORMAL ?
+                    IMPORTANT_FOR_ACCESSIBILITY_NO :
+                        IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS;
+            page.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
+            page.getShortcutsAndWidgets().setImportantForAccessibility(accessible);
+            page.setContentDescription(null);
+            page.setAccessibilityDelegate(null);
+        }
     }
 
     @Override
@@ -4446,25 +4479,21 @@
         return super.getPageIndicatorMarker(pageIndex);
     }
 
-    @Override
-    public void syncPages() {
-    }
-
-    @Override
-    public void syncPageItems(int page, boolean immediate) {
-    }
-
     protected String getPageIndicatorDescription() {
         String settings = getResources().getString(R.string.settings_button_text);
         return getCurrentPageDescription() + ", " + settings;
     }
 
     protected String getCurrentPageDescription() {
-        int page = (mNextPage != INVALID_PAGE) ? mNextPage : mCurrentPage;
-        int delta = numCustomPages();
         if (hasCustomContent() && getNextPage() == 0) {
             return mCustomContentDescription;
         }
+        int page = (mNextPage != INVALID_PAGE) ? mNextPage : mCurrentPage;
+        return getPageDescription(page);
+    }
+
+    private String getPageDescription(int page) {
+        int delta = numCustomPages();
         return String.format(getContext().getString(R.string.workspace_scroll_format),
                 page + 1 - delta, getChildCount() - delta);
     }
diff --git a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
index a0cedeb..61a64e3 100644
--- a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
+++ b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
@@ -24,8 +24,11 @@
 import android.content.Context;
 import android.content.res.Resources;
 import android.view.View;
+import android.view.ViewGroup;
 import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.AccessibilityNodeInfo;
 import android.view.animation.DecelerateInterpolator;
+
 import com.android.launcher3.util.Thunk;
 
 import java.util.HashMap;
@@ -190,7 +193,7 @@
                               final HashMap<View, Integer> layerViews) {
         AccessibilityManager am = (AccessibilityManager)
                 mLauncher.getSystemService(Context.ACCESSIBILITY_SERVICE);
-        boolean accessibilityEnabled = am.isEnabled();
+        final boolean accessibilityEnabled = am.isEnabled();
 
         // Reinitialize animation arrays for the current workspace state
         reinitializeAnimationArrays();
@@ -301,7 +304,7 @@
         }
 
         final View searchBar = mLauncher.getOrCreateQsbBar();
-        final View overviewPanel = mLauncher.getOverviewPanel();
+        final ViewGroup overviewPanel = mLauncher.getOverviewPanel();
         final View hotseat = mLauncher.getHotseat();
         final View pageIndicator = mWorkspace.getPageIndicator();
         if (animated) {
@@ -424,6 +427,11 @@
                 @Override
                 public void onAnimationEnd(Animator animation) {
                     mStateAnimator = null;
+
+                    if (accessibilityEnabled && overviewPanel.getVisibility() == View.VISIBLE) {
+                        overviewPanel.getChildAt(0).performAccessibilityAction(
+                                AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null);
+                    }
                 }
             });
         } else {
@@ -443,6 +451,11 @@
             mWorkspace.setScaleX(mNewScale);
             mWorkspace.setScaleY(mNewScale);
             mWorkspace.setTranslationY(finalWorkspaceTranslationY);
+
+            if (accessibilityEnabled && overviewPanel.getVisibility() == View.VISIBLE) {
+                overviewPanel.getChildAt(0).performAccessibilityAction(
+                        AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null);
+            }
         }
 
         if (stateIsNormal) {
diff --git a/src/com/android/launcher3/accessibility/OverviewScreenAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/OverviewScreenAccessibilityDelegate.java
new file mode 100644
index 0000000..d3f5230
--- /dev/null
+++ b/src/com/android/launcher3/accessibility/OverviewScreenAccessibilityDelegate.java
@@ -0,0 +1,92 @@
+/*
+ * 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.accessibility;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.util.SparseArray;
+import android.view.View;
+import android.view.View.AccessibilityDelegate;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
+
+import com.android.launcher3.R;
+import com.android.launcher3.Workspace;
+
+public class OverviewScreenAccessibilityDelegate extends AccessibilityDelegate {
+
+    private static final int MOVE_BACKWARD = R.id.action_move_screen_backwards;
+    private static final int MOVE_FORWARD = R.id.action_move_screen_forwards;
+
+    private final SparseArray<AccessibilityAction> mActions = new SparseArray<>();
+    private final Workspace mWorkspace;
+
+    public OverviewScreenAccessibilityDelegate(Workspace workspace) {
+        mWorkspace = workspace;
+
+        Context context = mWorkspace.getContext();
+        boolean isRtl = mWorkspace.isLayoutRtl();
+        mActions.put(MOVE_BACKWARD, new AccessibilityAction(MOVE_BACKWARD,
+                context.getText(isRtl ? R.string.action_move_screen_right :
+                    R.string.action_move_screen_left)));
+        mActions.put(MOVE_FORWARD, new AccessibilityAction(MOVE_FORWARD,
+                context.getText(isRtl ? R.string.action_move_screen_left :
+                    R.string.action_move_screen_right)));
+    }
+
+    @Override
+    public boolean performAccessibilityAction(View host, int action, Bundle args) {
+        if (host != null) {
+            if (action == AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS ) {
+                int index = mWorkspace.indexOfChild(host);
+                mWorkspace.setCurrentPage(index);
+            } else if (action == MOVE_FORWARD) {
+                movePage(mWorkspace.indexOfChild(host) + 1, host);
+                return true;
+            } else if (action == MOVE_BACKWARD) {
+                movePage(mWorkspace.indexOfChild(host) - 1, host);
+                return true;
+            }
+        }
+
+        return super.performAccessibilityAction(host, action, args);
+    }
+
+    private void movePage(int finalIndex, View view) {
+        mWorkspace.onStartReordering();
+        mWorkspace.removeView(view);
+        mWorkspace.addView(view, finalIndex);
+        mWorkspace.onEndReordering();
+        mWorkspace.announceForAccessibility(mWorkspace.getContext().getText(R.string.screen_moved));
+
+        mWorkspace.updateAccessibilityFlags();
+        view.performAccessibilityAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null);
+    }
+
+    @Override
+    public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
+        super.onInitializeAccessibilityNodeInfo(host, info);
+
+        int index = mWorkspace.indexOfChild(host);
+        if (index < mWorkspace.getChildCount() - 1) {
+            info.addAction(mActions.get(MOVE_FORWARD));
+        }
+        if (index > mWorkspace.numCustomPages()) {
+            info.addAction(mActions.get(MOVE_BACKWARD));
+        }
+    }
+}
diff --git a/src/com/android/launcher3/widget/WidgetsContainerRecyclerView.java b/src/com/android/launcher3/widget/WidgetsContainerRecyclerView.java
new file mode 100644
index 0000000..6f15324
--- /dev/null
+++ b/src/com/android/launcher3/widget/WidgetsContainerRecyclerView.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.widget;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import com.android.launcher3.BaseContainerRecyclerView;
+
+/**
+ * The widgets recycler view container.
+ */
+public class WidgetsContainerRecyclerView extends BaseContainerRecyclerView {
+
+    public WidgetsContainerRecyclerView(Context context) {
+        this(context, null);
+    }
+
+    public WidgetsContainerRecyclerView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public WidgetsContainerRecyclerView(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+    }
+
+}
\ 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);
+        }
     }
 
     /**
diff --git a/src/com/android/launcher3/widget/WidgetsRowView.java b/src/com/android/launcher3/widget/WidgetsRowView.java
deleted file mode 100644
index 5466738..0000000
--- a/src/com/android/launcher3/widget/WidgetsRowView.java
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3.widget;
-
-import android.content.Context;
-import android.view.MotionEvent;
-import android.widget.FrameLayout;
-import android.widget.HorizontalScrollView;
-import android.widget.TextView;
-
-import com.android.launcher3.R;
-
-/**
- * Layout used for widget tray rows for each app. For performance, this view can be replaced with
- * a {@link RecyclerView} in the future if we settle on scrollable single row for the widgets.
- * If we decide on collapsable grid, then HorizontalScrollView can be replaced with a
- * {@link GridLayout}.
- */
-public class WidgetsRowView extends HorizontalScrollView {
-    static final String TAG = "WidgetsRow";
-
-    private Runnable mOnLayoutListener;
-    private String mAppName;
-
-    public WidgetsRowView(Context context, String appName) {
-        super(context, null, 0);
-        mAppName = appName;
-    }
-
-    /**
-     * Clears all the key listeners for the individual widgets.
-     */
-    public void resetChildrenOnKeyListeners() {
-        int childCount = getChildCount();
-        for (int j = 0; j < childCount; ++j) {
-            getChildAt(j).setOnKeyListener(null);
-        }
-    }
-
-    @Override
-    protected void onAttachedToWindow() {
-        super.onAttachedToWindow();
-        TextView tv = (TextView) findViewById(R.id.widget_name);
-        tv.setText(mAppName);
-    }
-
-    @Override
-    protected void onDetachedFromWindow() {
-        super.onDetachedFromWindow();
-        mOnLayoutListener = null;
-    }
-
-    public void setOnLayoutListener(Runnable r) {
-        mOnLayoutListener = r;
-    }
-
-    @Override
-    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
-        super.onLayout(changed, left, top, right, bottom);
-        if (mOnLayoutListener != null) {
-            mOnLayoutListener.run();
-        }
-    }
-
-    @Override
-    public boolean onTouchEvent(MotionEvent event) {
-        boolean result = super.onTouchEvent(event);
-        return result;
-    }
-
-    public static class LayoutParams extends FrameLayout.LayoutParams {
-        public LayoutParams(int width, int height) {
-            super(width, height);
-        }
-    }
-}