Merge "Set proper height of the widget tray container for preloading." into ub-launcher3-burnaby
diff --git a/res/layout/all_apps_button.xml b/res/layout/all_apps_button.xml
index 9d6d82b..68cc109 100644
--- a/res/layout/all_apps_button.xml
+++ b/res/layout/all_apps_button.xml
@@ -15,5 +15,5 @@
 -->
 
 <TextView xmlns:android="http://schemas.android.com/apk/res/android"
-   style="@style/WorkspaceIcon"
+   style="@style/Icon"
    android:focusable="true" />
diff --git a/res/layout/application.xml b/res/layout/application.xml
index c21dea0..831cee5 100644
--- a/res/layout/application.xml
+++ b/res/layout/application.xml
@@ -15,5 +15,5 @@
 -->
 
 <com.android.launcher3.BubbleTextView xmlns:android="http://schemas.android.com/apk/res/android"
-   style="@style/WorkspaceIcon"
+   style="@style/Icon"
    android:focusable="true" />
diff --git a/res/layout/apps_grid_row_icon_view.xml b/res/layout/apps_grid_icon_view.xml
similarity index 96%
rename from res/layout/apps_grid_row_icon_view.xml
rename to res/layout/apps_grid_icon_view.xml
index acb3da3..67d7d50 100644
--- a/res/layout/apps_grid_row_icon_view.xml
+++ b/res/layout/apps_grid_icon_view.xml
@@ -16,7 +16,7 @@
 <com.android.launcher3.BubbleTextView
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:launcher="http://schemas.android.com/apk/res-auto"
-    style="@style/WorkspaceIcon.AppsCustomize"
+    style="@style/Icon.AllApps"
     android:id="@+id/icon"
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"
diff --git a/res/layout/apps_list_view.xml b/res/layout/apps_list_view.xml
index ddcb639..ef20323 100644
--- a/res/layout/apps_list_view.xml
+++ b/res/layout/apps_list_view.xml
@@ -13,20 +13,37 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<LinearLayout
+<FrameLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/apps_list"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    android:orientation="vertical"
     android:elevation="15dp"
     android:visibility="gone"
     android:focusableInTouchMode="true">
+    <com.android.launcher3.AppsContainerRecyclerView
+        android:id="@+id/apps_list_view"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_marginTop="@dimen/apps_search_bar_height"
+        android:layout_gravity="center_horizontal|top"
+        android:clipToPadding="false"
+        android:focusable="true"
+        android:descendantFocusability="afterDescendants" />
+    <LinearLayout
+        android:id="@+id/prediction_bar"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="@dimen/apps_search_bar_height"
+        android:orientation="horizontal"
+        android:visibility="invisible">
+    </LinearLayout>
+
+    <!-- We always want the search bar on top, so it goes last. -->
     <FrameLayout
         android:id="@+id/header"
         android:layout_width="match_parent"
         android:layout_height="@dimen/apps_search_bar_height"
-        android:orientation="horizontal"
         android:background="@drawable/apps_search_bg">
         <LinearLayout
             android:id="@+id/app_search_container"
@@ -40,8 +57,8 @@
                 android:layout_height="wrap_content"
                 android:layout_gravity="start|center_vertical"
                 android:layout_marginStart="4dp"
-                android:paddingTop="12dp"
-                android:paddingBottom="12dp"
+                android:paddingTop="13dp"
+                android:paddingBottom="13dp"
                 android:contentDescription="@string/all_apps_button_label"
                 android:src="@drawable/ic_arrow_back_grey" />
             <com.android.launcher3.AppsContainerSearchEditTextView
@@ -69,19 +86,9 @@
             android:layout_height="wrap_content"
             android:layout_gravity="end|center_vertical"
             android:layout_marginEnd="6dp"
-            android:paddingTop="12dp"
-            android:paddingBottom="12dp"
+            android:paddingTop="13dp"
+            android:paddingBottom="13dp"
             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"
-        android:layout_height="match_parent"
-        android:layout_gravity="center"
-        android:paddingTop="12dp"
-        android:paddingBottom="12dp"
-        android:clipToPadding="false"
-        android:focusable="true"
-        android:descendantFocusability="afterDescendants" />
-</LinearLayout>
\ No newline at end of file
+</FrameLayout>
\ No newline at end of file
diff --git a/res/layout/apps_grid_row_icon_view.xml b/res/layout/apps_prediction_bar_icon_view.xml
similarity index 77%
copy from res/layout/apps_grid_row_icon_view.xml
copy to res/layout/apps_prediction_bar_icon_view.xml
index acb3da3..4a6f157 100644
--- a/res/layout/apps_grid_row_icon_view.xml
+++ b/res/layout/apps_prediction_bar_icon_view.xml
@@ -16,13 +16,12 @@
 <com.android.launcher3.BubbleTextView
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:launcher="http://schemas.android.com/apk/res-auto"
-    style="@style/WorkspaceIcon.AppsCustomize"
+    style="@style/Icon.AllApps"
     android:id="@+id/icon"
-    android:layout_width="wrap_content"
-    android:layout_height="wrap_content"
-    android:layout_gravity="left|center_vertical"
-    android:paddingTop="@dimen/apps_icon_top_bottom_padding"
-    android:paddingBottom="@dimen/apps_icon_top_bottom_padding"
+    android:layout_width="0dp"
+    android:layout_height="match_parent"
+    android:layout_gravity="center"
+    android:layout_weight="1"
     android:focusable="true"
     android:background="@drawable/focusable_view_bg"
     launcher:deferShadowGeneration="true"
diff --git a/res/layout/folder_application.xml b/res/layout/folder_application.xml
index b48b613..4d00331 100644
--- a/res/layout/folder_application.xml
+++ b/res/layout/folder_application.xml
@@ -15,5 +15,5 @@
 -->
 
 <com.android.launcher3.BubbleTextView xmlns:android="http://schemas.android.com/apk/res/android"
-   style="@style/WorkspaceIcon.Folder"
+   style="@style/Icon.Folder"
    android:focusable="true" />
diff --git a/res/layout/folder_icon.xml b/res/layout/folder_icon.xml
index fd45d76..d9a7671 100644
--- a/res/layout/folder_icon.xml
+++ b/res/layout/folder_icon.xml
@@ -28,7 +28,7 @@
         android:antialias="true"
         android:src="@drawable/portal_ring_inner_holo"/>
     <com.android.launcher3.BubbleTextView
-        style="@style/WorkspaceIcon"
+        style="@style/Icon"
         android:id="@+id/folder_icon_name"
         android:layout_gravity="top"
         android:layout_width="match_parent"
diff --git a/res/layout/user_folder.xml b/res/layout/user_folder.xml
index 5bacc96..ab34917 100644
--- a/res/layout/user_folder.xml
+++ b/res/layout/user_folder.xml
@@ -45,20 +45,16 @@
         android:id="@+id/folder_footer"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:orientation="vertical" >
-
-        <include
-            android:id="@+id/folder_page_indicator"
-            android:layout_width="wrap_content"
-            android:layout_height="12dp"
-            android:layout_gravity="center_horizontal"
-            layout="@layout/page_indicator" />
+        android:orientation="horizontal"
+        android:paddingLeft="8dp"
+        android:paddingRight="8dp" >
 
         <com.android.launcher3.FolderEditText
             android:id="@+id/folder_name"
-            android:layout_width="match_parent"
+            android:layout_width="0dp"
             android:layout_height="wrap_content"
             android:layout_gravity="center_vertical"
+            android:layout_weight="1"
             android:background="#00000000"
             android:fontFamily="sans-serif-condensed"
             android:gravity="center_horizontal"
@@ -72,6 +68,13 @@
             android:textColorHint="#ff808080"
             android:textCursorDrawable="@null"
             android:textSize="14sp" />
+
+        <include
+            android:id="@+id/folder_page_indicator"
+            android:layout_width="wrap_content"
+            android:layout_height="12dp"
+            android:layout_gravity="center_vertical"
+            layout="@layout/page_indicator" />
     </LinearLayout>
 
 </com.android.launcher3.Folder>
\ No newline at end of file
diff --git a/res/values-sw600dp/dimens.xml b/res/values-sw600dp/dimens.xml
index 2bdd4f0..4a869e5 100644
--- a/res/values-sw600dp/dimens.xml
+++ b/res/values-sw600dp/dimens.xml
@@ -21,7 +21,6 @@
     <dimen name="apps_container_inset">18dp</dimen>
     <dimen name="apps_grid_view_start_margin">0dp</dimen>
     <dimen name="apps_view_section_text_size">26sp</dimen>
-    <dimen name="apps_view_row_height">72dp</dimen>
     <dimen name="apps_icon_top_bottom_padding">12dp</dimen>
 
 <!-- AppsCustomize -->
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index f944d4b..1a92545 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -50,17 +50,17 @@
     <dimen name="apps_container_width">0dp</dimen>
     <dimen name="apps_container_height">0dp</dimen>
     <dimen name="apps_container_inset">8dp</dimen>
-    <dimen name="apps_grid_view_start_margin">52dp</dimen>
+    <dimen name="apps_grid_view_start_margin">56dp</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>
+    <dimen name="apps_view_fast_scroll_bar_width">4dp</dimen>
     <dimen name="apps_view_fast_scroll_bar_min_height">64dp</dimen>
     <dimen name="apps_view_fast_scroll_scrubber_touch_inset">-16dp</dimen>
-    <dimen name="apps_view_fast_scroll_popup_size">64dp</dimen>
-    <dimen name="apps_view_fast_scroll_text_size">40dp</dimen>
+    <dimen name="apps_view_fast_scroll_popup_size">72dp</dimen>
+    <dimen name="apps_view_fast_scroll_text_size">48dp</dimen>
     <dimen name="apps_search_bar_height">52dp</dimen>
     <dimen name="apps_icon_top_bottom_padding">8dp</dimen>
+    <dimen name="apps_prediction_icon_top_bottom_padding">12dp</dimen>
 
     <!-- Note: This needs to match the fixed insets for the search box. -->
     <dimen name="container_fixed_bounds_inset">8dp</dimen>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 16d4cbe..78cc083 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -19,7 +19,7 @@
 
 <resources>
 
-    <style name="WorkspaceIcon">
+    <style name="Icon">
         <item name="android:layout_width">match_parent</item>
         <item name="android:layout_height">match_parent</item>
         <item name="android:layout_gravity">center</item>
@@ -32,11 +32,7 @@
         <item name="android:fontFamily">sans-serif-condensed</item>
     </style>
 
-    <style name="WorkspaceIcon.Portrait"></style>
-
-    <style name="WorkspaceIcon.Landscape"></style>
-
-    <style name="WorkspaceIcon.AppsCustomize">
+    <style name="Icon.AllApps">
         <item name="android:background">@null</item>
         <item name="android:textColor">@color/quantum_panel_text_color</item>
         <item name="android:drawablePadding">@dimen/dynamic_grid_icon_drawable_padding</item>
@@ -44,7 +40,7 @@
         <item name="customShadows">false</item>
     </style>
 
-    <style name="WorkspaceIcon.Folder">
+    <style name="Icon.Folder">
         <item name="android:background">@null</item>
         <item name="android:textColor">@color/quantum_panel_text_color</item>
         <item name="android:shadowRadius">0</item>
diff --git a/src/com/android/launcher3/AlphabeticalAppsList.java b/src/com/android/launcher3/AlphabeticalAppsList.java
index dc75637..eff7b06 100644
--- a/src/com/android/launcher3/AlphabeticalAppsList.java
+++ b/src/com/android/launcher3/AlphabeticalAppsList.java
@@ -151,11 +151,13 @@
      * Info about a particular adapter item (can be either section or app)
      */
     public static class AdapterItem {
-        /** Section & App properties */
+        /** Common 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 type of this item
+        public int viewType;
+
+        /** Section & App properties */
         // The section for this item
         public SectionInfo sectionInfo;
 
@@ -169,30 +171,33 @@
         public AppInfo appInfo = null;
         // The index of this app not including sections
         public int appIndex = -1;
-        // Whether or not this is a predicted app
-        public boolean isPredictedApp;
 
         public static AdapterItem asSectionBreak(int pos, SectionInfo section) {
             AdapterItem item = new AdapterItem();
+            item.viewType = AppsGridAdapter.SECTION_BREAK_VIEW_TYPE;
             item.position = pos;
-            item.isSectionHeader = true;
             item.sectionInfo = section;
             section.sectionBreakItem = item;
             return item;
         }
 
-        public static AdapterItem asApp(int pos, SectionInfo section, String sectionName,
-                                        int sectionAppIndex, AppInfo appInfo, int appIndex,
-                                        boolean isPredictedApp) {
+        public static AdapterItem asPredictionBarSpacer(int pos) {
             AdapterItem item = new AdapterItem();
+            item.viewType = AppsGridAdapter.PREDICTION_BAR_SPACER_TYPE;
             item.position = pos;
-            item.isSectionHeader = false;
+            return item;
+        }
+
+        public static AdapterItem asApp(int pos, SectionInfo section, String sectionName,
+                                        int sectionAppIndex, AppInfo appInfo, int appIndex) {
+            AdapterItem item = new AdapterItem();
+            item.viewType = AppsGridAdapter.ICON_VIEW_TYPE;
+            item.position = pos;
             item.sectionInfo = section;
             item.sectionName = sectionName;
             item.sectionAppIndex = sectionAppIndex;
             item.appInfo = appInfo;
             item.appIndex = appIndex;
-            item.isPredictedApp = isPredictedApp;
             return item;
         }
     }
@@ -205,6 +210,13 @@
     }
 
     /**
+     * A callback to notify of changes to the filter.
+     */
+    public interface FilterChangedCallback {
+        void onFilterChanged();
+    }
+
+    /**
      * Common interface for different merging strategies.
      */
     private interface MergeAlgorithm {
@@ -260,28 +272,31 @@
     private List<AdapterItem> mSectionedFilteredApps = new ArrayList<>();
     private List<SectionInfo> mSections = new ArrayList<>();
     private List<FastScrollSectionInfo> mFastScrollerSections = new ArrayList<>();
-    private List<ComponentName> mPredictedApps = new ArrayList<>();
+    private List<ComponentName> mPredictedAppComponents = new ArrayList<>();
+    private List<AppInfo> mPredictedApps = new ArrayList<>();
     private HashMap<CharSequence, String> mCachedSectionNames = new HashMap<>();
     private RecyclerView.Adapter mAdapter;
     private Filter mFilter;
     private AlphabeticIndexCompat mIndexer;
     private AppNameComparator mAppNameComparator;
     private MergeAlgorithm mMergeAlgorithm;
+    private FilterChangedCallback mFilterChangedCallback;
     private int mNumAppsPerRow;
+    private int mNumPredictedAppsPerRow;
 
-    public AlphabeticalAppsList(Context context, int numAppsPerRow) {
+    public AlphabeticalAppsList(Context context, FilterChangedCallback cb, int numAppsPerRow,
+            int numPredictedAppsPerRow) {
         mContext = context;
         mIndexer = new AlphabeticIndexCompat(context);
         mAppNameComparator = new AppNameComparator(context);
-        setNumAppsPerRow(numAppsPerRow);
+        mFilterChangedCallback = cb;
+        setNumAppsPerRow(numAppsPerRow, numPredictedAppsPerRow);
     }
 
     /**
      * Sets the number of apps per row.  Used only for AppsContainerView.SECTIONED_GRID_COALESCED.
      */
-    public void setNumAppsPerRow(int numAppsPerRow) {
-        mNumAppsPerRow = numAppsPerRow;
-
+    public void setNumAppsPerRow(int numAppsPerRow, int numPredictedAppsPerRow) {
         // Update the merge algorithm
         DeviceProfile grid = LauncherAppState.getInstance().getDynamicGrid().getDeviceProfile();
         if (grid.isPhone()) {
@@ -291,6 +306,9 @@
             mMergeAlgorithm = new TabletMergeAlgorithm();
         }
 
+        mNumAppsPerRow = numAppsPerRow;
+        mNumPredictedAppsPerRow = numPredictedAppsPerRow;
+
         onAppsUpdated();
     }
 
@@ -351,6 +369,9 @@
             mFilter = f;
             onAppsUpdated();
             mAdapter.notifyDataSetChanged();
+            if (mFilterChangedCallback != null){
+                mFilterChangedCallback.onFilterChanged();
+            }
         }
     }
 
@@ -359,13 +380,20 @@
      * of applications, we should merge the results only in onAppsUpdated() which is idempotent.
      */
     public void setPredictedApps(List<ComponentName> apps) {
-        mPredictedApps.clear();
-        mPredictedApps.addAll(apps);
+        mPredictedAppComponents.clear();
+        mPredictedAppComponents.addAll(apps);
         onAppsUpdated();
         mAdapter.notifyDataSetChanged();
     }
 
     /**
+     * Returns the current set of predicted apps.
+     */
+    public List<AppInfo> getPredictedApps() {
+        return mPredictedApps;
+    }
+
+    /**
      * Sets the current set of apps.
      */
     public void setApps(List<AppInfo> apps) {
@@ -450,6 +478,42 @@
         // Sort the list of apps
         Collections.sort(mApps, mAppNameComparator.getAppInfoComparator());
 
+        // Prepare to update the list of sections, filtered apps, etc.
+        mFilteredApps.clear();
+        mSections.clear();
+        mSectionedFilteredApps.clear();
+        mFastScrollerSections.clear();
+        SectionInfo lastSectionInfo = null;
+        String lastSectionName = null;
+        FastScrollSectionInfo lastFastScrollerSectionInfo = null;
+        int position = 0;
+        int appIndex = 0;
+        List<AppInfo> allApps = new ArrayList<>();
+
+
+        // Process the predicted app components
+        mPredictedApps.clear();
+        if (mPredictedAppComponents != null && !mPredictedAppComponents.isEmpty() && !hasFilter()) {
+            for (ComponentName cn : mPredictedAppComponents) {
+                for (AppInfo info : mApps) {
+                    if (cn.equals(info.componentName)) {
+                        mPredictedApps.add(info);
+                        break;
+                    }
+                }
+                // Stop at the number of predicted apps
+                if (mPredictedApps.size() == mNumPredictedAppsPerRow) {
+                    break;
+                }
+            }
+
+            if (!mPredictedApps.isEmpty()) {
+                // Create a new spacer for the prediction bar
+                AdapterItem sectionItem = AdapterItem.asPredictionBarSpacer(position++);
+                mSectionedFilteredApps.add(sectionItem);
+            }
+        }
+
         // As a special case for some languages (currently only Simplified Chinese), we may need to
         // coalesce sections
         Locale curLocale = mContext.getResources().getConfiguration().locale;
@@ -475,6 +539,11 @@
                 }
                 sectionApps.add(info);
             }
+
+            // Add it to the list
+            for (Map.Entry<String, ArrayList<AppInfo>> entry : sectionMap.entrySet()) {
+                allApps.addAll(entry.getValue());
+            }
         } else {
             // Just compute the section headers for use below
             for (AppInfo info : mApps) {
@@ -485,44 +554,7 @@
                     mCachedSectionNames.put(info.title, sectionName);
                 }
             }
-        }
-
-        // Prepare to update the list of sections, filtered apps, etc.
-        mFilteredApps.clear();
-        mSections.clear();
-        mSectionedFilteredApps.clear();
-        mFastScrollerSections.clear();
-        SectionInfo lastSectionInfo = null;
-        String lastSectionName = null;
-        FastScrollSectionInfo lastFastScrollerSectionInfo = null;
-        int position = 0;
-        int appIndex = 0;
-        List<AppInfo> allApps = new ArrayList<>();
-
-        // Add the predicted apps to the combined list
-        int numPredictedApps = 0;
-        if (mPredictedApps != null && !mPredictedApps.isEmpty() && !hasFilter()) {
-            for (ComponentName cn : mPredictedApps) {
-                for (AppInfo info : mApps) {
-                    if (cn.equals(info.componentName)) {
-                        allApps.add(info);
-                        numPredictedApps++;
-                        break;
-                    }
-                }
-                // Stop at the number of predicted apps
-                if (numPredictedApps == mNumAppsPerRow) {
-                    break;
-                }
-            }
-        }
-
-        // Add all the other apps to the combined list
-        if (localeRequiresSectionSorting) {
-            for (Map.Entry<String, ArrayList<AppInfo>> entry : sectionMap.entrySet()) {
-                allApps.addAll(entry.getValue());
-            }
-        } else {
+            // Add it to the list
             allApps.addAll(mApps);
         }
 
@@ -530,10 +562,9 @@
         // ordered set of sections
         int numApps = allApps.size();
         for (int i = 0; i < numApps; i++) {
-            boolean isPredictedApp = i < numPredictedApps;
             AppInfo info = allApps.get(i);
             // The section name was computed above so this should be find
-            String sectionName = isPredictedApp ? "" : mCachedSectionNames.get(info.title);
+            String sectionName = mCachedSectionNames.get(info.title);
 
             // Check if we want to retain this app
             if (mFilter != null && !mFilter.retainApp(info, sectionName)) {
@@ -541,8 +572,7 @@
             }
 
             // Create a new section if the section names do not match
-            if (lastSectionInfo == null ||
-                    (!isPredictedApp && !sectionName.equals(lastSectionName))) {
+            if (lastSectionInfo == null || !sectionName.equals(lastSectionName)) {
                 lastSectionName = sectionName;
                 lastSectionInfo = new SectionInfo();
                 lastFastScrollerSectionInfo = new FastScrollSectionInfo(sectionName,
@@ -559,7 +589,7 @@
 
             // Create an app item
             AdapterItem appItem = AdapterItem.asApp(position++, lastSectionInfo, sectionName,
-                    lastSectionInfo.numApps++, info, appIndex++, isPredictedApp);
+                    lastSectionInfo.numApps++, info, appIndex++);
             if (lastSectionInfo.firstAppItem == null) {
                 lastSectionInfo.firstAppItem = appItem;
                 lastFastScrollerSectionInfo.appItem = appItem;
@@ -568,6 +598,14 @@
             mFilteredApps.add(info);
         }
 
+        // Merge multiple sections together as requested by the merge strategy for this device
+        mergeSections();
+    }
+
+    /**
+     * Merges multiple sections to reduce visual raggedness.
+     */
+    private void mergeSections() {
         // Go through each section and try and merge some of the sections
         if (AppsContainerView.GRID_MERGE_SECTIONS && !hasFilter()) {
             int sectionAppCount = 0;
diff --git a/src/com/android/launcher3/AppsContainerRecyclerView.java b/src/com/android/launcher3/AppsContainerRecyclerView.java
index edb6f0c..3952923 100644
--- a/src/com/android/launcher3/AppsContainerRecyclerView.java
+++ b/src/com/android/launcher3/AppsContainerRecyclerView.java
@@ -38,10 +38,26 @@
  */
 public class AppsContainerRecyclerView extends BaseContainerRecyclerView {
 
+    /**
+     * The current scroll state of the recycler view.  We use this in updateVerticalScrollbarBounds()
+     * and scrollToPositionAtProgress() to determine the scroll position of the recycler view so
+     * that we can calculate what the scroll bar looks like, and where to jump to from the fast
+     * scroller.
+     */
+    private static class ScrollPositionState {
+        // The index of the first visible row
+        int rowIndex;
+        // The offset of the first visible row
+        int rowTopOffset;
+        // The height of a given row (they are currently all the same height)
+        int rowHeight;
+    }
+
     private static final float FAST_SCROLL_OVERLAY_Y_OFFSET_FACTOR = 1.5f;
 
     private AlphabeticalAppsList mApps;
     private int mNumAppsPerRow;
+    private int mNumPredictedAppsPerRow;
 
     private Drawable mScrollbar;
     private Drawable mFastScrollerBg;
@@ -51,6 +67,7 @@
     private Paint mFastScrollTextPaint;
     private Rect mFastScrollTextBounds = new Rect();
     private float mFastScrollAlpha;
+    private int mPredictionBarHeight;
     private int mDownX;
     private int mDownY;
     private int mLastX;
@@ -58,6 +75,8 @@
     private int mScrollbarWidth;
     private int mScrollbarMinHeight;
     private int mScrollbarInset;
+    private Rect mBackgroundPadding = new Rect();
+    private ScrollPositionState mScrollPosState = new ScrollPositionState();
 
     public AppsContainerRecyclerView(Context context) {
         this(context, null);
@@ -91,6 +110,7 @@
         mScrollbarInset =
                 res.getDimensionPixelSize(R.dimen.apps_view_fast_scroll_scrubber_touch_inset);
         setFastScrollerAlpha(getFastScrollerAlpha());
+        setOverScrollMode(View.OVER_SCROLL_NEVER);
     }
 
     /**
@@ -103,8 +123,22 @@
     /**
      * Sets the number of apps per row in this recycler view.
      */
-    public void setNumAppsPerRow(int rowSize) {
-        mNumAppsPerRow = rowSize;
+    public void setNumAppsPerRow(int numAppsPerRow, int numPredictedAppsPerRow) {
+        mNumAppsPerRow = numAppsPerRow;
+        mNumPredictedAppsPerRow = numPredictedAppsPerRow;
+    }
+
+    @Override
+    public void setBackground(Drawable background) {
+        super.setBackground(background);
+        background.getPadding(mBackgroundPadding);
+    }
+
+    /**
+     * Sets the prediction bar height.
+     */
+    public void setPredictionBarHeight(int height) {
+        mPredictionBarHeight = height;
     }
 
     /**
@@ -129,6 +163,14 @@
         return mScrollbarWidth;
     }
 
+    /**
+     * Scrolls this recycler view to the top.
+     */
+    public void scrollToTop() {
+        scrollToPosition(0);
+        updateScrollY(0);
+    }
+
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
@@ -238,7 +280,7 @@
             // Calculate the position for the fast scroller popup
             Rect bgBounds = mFastScrollerBg.getBounds();
             if (isRtl) {
-                x = getPaddingLeft() + getScrollBarSize();
+                x = mBackgroundPadding.left + getScrollBarSize();
             } else {
                 x = getWidth() - getPaddingRight() - getScrollBarSize() - bgBounds.width();
             }
@@ -281,7 +323,7 @@
      * Invalidates the fast scroller popup.
      */
     private void invalidateFastScroller() {
-        invalidate(getWidth() - getPaddingRight() - getScrollBarSize() -
+        invalidate(getWidth() - mBackgroundPadding.right - getScrollBarSize() -
                 mFastScrollerBg.getIntrinsicWidth(), 0, getWidth(), getHeight());
     }
 
@@ -296,6 +338,26 @@
             return "";
         }
 
+        // Stop the scroller if it is scrolling
+        LinearLayoutManager layoutManager = (LinearLayoutManager) getLayoutManager();
+        stopScroll();
+
+        // If there is a prediction bar, then capture the appropriate area for the prediction bar
+        float predictionBarFraction = 0f;
+        if (mPredictionBarHeight > 0) {
+            predictionBarFraction = (float) mNumPredictedAppsPerRow / mApps.getSize();
+            if (touchFraction <= predictionBarFraction) {
+                // Scroll to the top of the view, where the prediction bar is
+                layoutManager.scrollToPositionWithOffset(0, 0);
+                updateScrollY(0);
+                return "";
+            }
+        }
+
+        // Since the app ranges are from 0..1, we need to map the touch fraction back to 0..1 from
+        // predictionBarFraction..1
+        touchFraction = (touchFraction - predictionBarFraction) *
+                (1f / (1f - predictionBarFraction));
         AlphabeticalAppsList.FastScrollSectionInfo lastScrollSection = fastScrollSections.get(0);
         for (int i = 1; i < fastScrollSections.size(); i++) {
             AlphabeticalAppsList.FastScrollSectionInfo scrollSection = fastScrollSections.get(i);
@@ -306,10 +368,17 @@
             lastScrollSection = scrollSection;
         }
 
-        // 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();
+        // We need to workaround the RecyclerView to get the right scroll position
+        List<AlphabeticalAppsList.AdapterItem> items = mApps.getAdapterItems();
+        getCurScrollState(mScrollPosState, items);
+        if (mScrollPosState.rowIndex != -1) {
+            int scrollY = getPaddingTop() + (mScrollPosState.rowIndex * mScrollPosState.rowHeight) +
+                    mPredictionBarHeight - mScrollPosState.rowTopOffset;
+            updateScrollY(scrollY);
+        }
+
+        // Scroll to the view at the position, anchored at the top of the screen. We call the scroll
+        // method on the LayoutManager directly since it is not exposed by RecyclerView.
         layoutManager.scrollToPositionWithOffset(lastScrollSection.appItem.position, 0);
 
         return lastScrollSection.sectionName;
@@ -332,44 +401,28 @@
         int y;
         boolean isRtl = (getResources().getConfiguration().getLayoutDirection() ==
                 LAYOUT_DIRECTION_RTL);
-        int rowIndex = -1;
-        int rowTopOffset = -1;
-        int rowHeight = -1;
         int rowCount = getNumRows();
-        int childCount = getChildCount();
-        for (int i = 0; i < childCount; i++) {
-            View child = getChildAt(i);
-            int position = getChildPosition(child);
-            if (position != NO_POSITION) {
-                AlphabeticalAppsList.AdapterItem item = items.get(position);
-                if (!item.isSectionHeader) {
-                    rowIndex = findRowForAppIndex(item.appIndex);
-                    rowTopOffset = getLayoutManager().getDecoratedTop(child);
-                    rowHeight = child.getHeight();
-                    break;
-                }
-            }
-        }
-
-        if (rowIndex != -1) {
+        getCurScrollState(mScrollPosState, items);
+        if (mScrollPosState.rowIndex != -1) {
             int height = getHeight() - getPaddingTop() - getPaddingBottom();
-            int totalScrollHeight = rowCount * rowHeight;
+            int totalScrollHeight = rowCount * mScrollPosState.rowHeight + mPredictionBarHeight;
             if (totalScrollHeight > height) {
                 int scrollbarHeight = Math.max(mScrollbarMinHeight,
                         (int) (height / ((float) totalScrollHeight / height)));
 
                 // Calculate the position and size of the scroll bar
                 if (isRtl) {
-                    x = getPaddingLeft();
+                    x = mBackgroundPadding.left;
                 } else {
-                    x = getWidth() - getPaddingRight() - mScrollbarWidth;
+                    x = getWidth() - mBackgroundPadding.right - mScrollbarWidth;
                 }
 
                 // To calculate the offset, we compute the percentage of the total scrollable height
                 // that the user has already scrolled and then map that to the scroll bar bounds
                 int availableY = totalScrollHeight - height;
                 int availableScrollY = height - scrollbarHeight;
-                y = (rowIndex * rowHeight) - rowTopOffset;
+                y = (mScrollPosState.rowIndex * mScrollPosState.rowHeight) + mPredictionBarHeight
+                        - mScrollPosState.rowTopOffset;
                 y = getPaddingTop() +
                         (int) (((float) (getPaddingTop() + y) / availableY) * availableScrollY);
 
@@ -410,4 +463,28 @@
         }
         return rowCount;
     }
+
+    /**
+     * Returns the current scroll state.
+     */
+    private void getCurScrollState(ScrollPositionState stateOut,
+            List<AlphabeticalAppsList.AdapterItem> items) {
+        stateOut.rowIndex = -1;
+        stateOut.rowTopOffset = -1;
+        stateOut.rowHeight = -1;
+        int childCount = getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            View child = getChildAt(i);
+            int position = getChildPosition(child);
+            if (position != NO_POSITION) {
+                AlphabeticalAppsList.AdapterItem item = items.get(position);
+                if (item.viewType == AppsGridAdapter.ICON_VIEW_TYPE) {
+                    stateOut.rowIndex = findRowForAppIndex(item.appIndex);
+                    stateOut.rowTopOffset = getLayoutManager().getDecoratedTop(child);
+                    stateOut.rowHeight = child.getHeight();
+                    break;
+                }
+            }
+        }
+    }
 }
diff --git a/src/com/android/launcher3/AppsContainerView.java b/src/com/android/launcher3/AppsContainerView.java
index 8a5c660..692c23f 100644
--- a/src/com/android/launcher3/AppsContainerView.java
+++ b/src/com/android/launcher3/AppsContainerView.java
@@ -26,12 +26,14 @@
 import android.text.TextWatcher;
 import android.util.AttributeSet;
 import android.view.KeyEvent;
+import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewConfiguration;
+import android.view.ViewGroup;
 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;
@@ -44,16 +46,18 @@
  * The all apps view container.
  */
 public class AppsContainerView extends BaseContainerView implements DragSource, Insettable,
-        TextWatcher, TextView.OnEditorActionListener, LauncherTransitionable, View.OnTouchListener,
-        View.OnClickListener, View.OnLongClickListener {
+        TextWatcher, TextView.OnEditorActionListener, LauncherTransitionable,
+        AlphabeticalAppsList.FilterChangedCallback, AppsGridAdapter.PredictionBarSpacerCallbacks,
+        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 boolean DYNAMIC_HEADER_ELEVATION = false;
+    private static final boolean DYNAMIC_HEADER_ELEVATION = true;
     private static final boolean DISMISS_SEARCH_ON_BACK = true;
     private static final float HEADER_ELEVATION_DP = 4;
+    // How far the user has to scroll in order to reach the full elevation
+    private static final float HEADER_SCROLL_TO_ELEVATION_DP = 16;
     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;
@@ -62,12 +66,14 @@
 
     @Thunk Launcher mLauncher;
     @Thunk AlphabeticalAppsList mApps;
+    private LayoutInflater mLayoutInflater;
     private AppsGridAdapter mAdapter;
     private RecyclerView.LayoutManager mLayoutManager;
     private RecyclerView.ItemDecoration mItemDecoration;
 
-    private LinearLayout mContentView;
+    private FrameLayout mContentView;
     @Thunk AppsContainerRecyclerView mAppsRecyclerView;
+    private ViewGroup mPredictionBarView;
     private View mHeaderView;
     private View mSearchBarContainerView;
     private View mSearchButtonView;
@@ -75,11 +81,13 @@
     private AppsContainerSearchEditTextView mSearchBarEditView;
 
     private int mNumAppsPerRow;
+    private int mNumPredictedAppsPerRow;
     private Point mLastTouchDownPos = new Point(-1, -1);
     private Point mLastTouchPos = new Point();
     private int mContentMarginStart;
     // Normal container insets
     private int mContainerInset;
+    private int mPredictionBarHeight;
     // RecyclerView scroll position
     @Thunk int mRecyclerViewScrollY;
 
@@ -99,12 +107,17 @@
 
         mContainerInset = context.getResources().getDimensionPixelSize(
                 R.dimen.apps_container_inset);
+        mPredictionBarHeight = grid.allAppsCellHeightPx +
+                2 * res.getDimensionPixelSize(R.dimen.apps_prediction_icon_top_bottom_padding);
         mLauncher = (Launcher) context;
+        mLayoutInflater = LayoutInflater.from(context);
         mNumAppsPerRow = grid.appsViewNumCols;
-        mApps = new AlphabeticalAppsList(context, mNumAppsPerRow);
-        mAdapter = new AppsGridAdapter(context, mApps, mNumAppsPerRow, this, mLauncher, this);
+        mNumPredictedAppsPerRow = grid.appsViewNumPredictiveCols;
+        mApps = new AlphabeticalAppsList(context, this, mNumAppsPerRow, mNumPredictedAppsPerRow);
+        mAdapter = new AppsGridAdapter(context, mApps, mNumAppsPerRow, this, this, mLauncher, this);
         mAdapter.setEmptySearchText(res.getString(R.string.loading_apps_message));
         mAdapter.setNumAppsPerRow(mNumAppsPerRow);
+        mAdapter.setPredictionRowHeight(mPredictionBarHeight);
         mLayoutManager = mAdapter.getLayoutManager();
         mItemDecoration = mAdapter.getItemDecoration();
         mContentMarginStart = mAdapter.getContentMarginStart();
@@ -159,8 +172,7 @@
      * Scrolls this list view to the top.
      */
     public void scrollToTop() {
-        mAppsRecyclerView.scrollToPosition(0);
-        mRecyclerViewScrollY = 0;
+        mAppsRecyclerView.scrollToTop();
     }
 
     /**
@@ -185,7 +197,7 @@
 
         // 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
-        mContentView = (LinearLayout) findViewById(R.id.apps_list);
+        mContentView = (FrameLayout) findViewById(R.id.apps_list);
         mContentView.setOnFocusChangeListener(new View.OnFocusChangeListener() {
             @Override
             public void onFocusChange(View v, boolean hasFocus) {
@@ -194,12 +206,20 @@
                 }
             }
         });
+
+        // Fix the header view elevation if not dynamically calculating it
         mHeaderView = findViewById(R.id.header);
         mHeaderView.setOnClickListener(this);
         if (Utilities.isLmpOrAbove() && !DYNAMIC_HEADER_ELEVATION) {
             mHeaderView.setElevation(DynamicGrid.pxFromDp(HEADER_ELEVATION_DP,
                 getContext().getResources().getDisplayMetrics()));
         }
+
+        // Fix the prediction bar size
+        mPredictionBarView = (ViewGroup) findViewById(R.id.prediction_bar);
+        FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) mPredictionBarView.getLayoutParams();
+        lp.height = mPredictionBarHeight;
+
         mSearchButtonView = mHeaderView.findViewById(R.id.search_button);
         mSearchBarContainerView = findViewById(R.id.app_search_container);
         mDismissSearchButtonView = mSearchBarContainerView.findViewById(R.id.dismiss_search_button);
@@ -226,22 +246,19 @@
         }
         mAppsRecyclerView = (AppsContainerRecyclerView) findViewById(R.id.apps_list_view);
         mAppsRecyclerView.setApps(mApps);
-        mAppsRecyclerView.setNumAppsPerRow(mNumAppsPerRow);
+        mAppsRecyclerView.setNumAppsPerRow(mNumAppsPerRow, mNumPredictedAppsPerRow);
+        mAppsRecyclerView.setPredictionBarHeight(mPredictionBarHeight);
         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();
-            }
-        });
+        mAppsRecyclerView.setOnScrollListenerProxy(
+                new BaseContainerRecyclerView.OnScrollToListener() {
+                    @Override
+                    public void onScrolledTo(int x, int y) {
+                        mRecyclerViewScrollY = y;
+                        onRecyclerViewScrolled();
+                    }
+                });
         if (mItemDecoration != null) {
             mAppsRecyclerView.addItemDecoration(mItemDecoration);
         }
@@ -250,15 +267,52 @@
     }
 
     @Override
+    public void onBindPredictionBar() {
+        if (!updatePredictionBarVisibility()) {
+            return;
+        }
+
+        List<AppInfo> predictedApps = mApps.getPredictedApps();
+        int childCount = mPredictionBarView.getChildCount();
+        for (int i = 0; i < mNumPredictedAppsPerRow; i++) {
+            BubbleTextView icon;
+            if (i < childCount) {
+                // If a child at that index exists, then get that child
+                icon = (BubbleTextView) mPredictionBarView.getChildAt(i);
+            } else {
+                // Otherwise, inflate a new icon
+                icon = (BubbleTextView) mLayoutInflater.inflate(
+                        R.layout.apps_prediction_bar_icon_view, mPredictionBarView, false);
+                icon.setOnTouchListener(this);
+                icon.setOnClickListener(mLauncher);
+                icon.setOnLongClickListener(this);
+                icon.setFocusable(true);
+                mPredictionBarView.addView(icon);
+            }
+
+            // Either apply the app info to the child, or hide the view
+            if (i < predictedApps.size()) {
+                if (icon.getVisibility() != View.VISIBLE) {
+                    icon.setVisibility(View.VISIBLE);
+                }
+                icon.applyFromApplicationInfo(predictedApps.get(i));
+            } else {
+                icon.setVisibility(View.INVISIBLE);
+            }
+        }
+    }
+
+    @Override
     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);
+            mNumPredictedAppsPerRow = grid.appsViewNumPredictiveCols;
+            mAppsRecyclerView.setNumAppsPerRow(mNumAppsPerRow, mNumPredictedAppsPerRow);
             mAdapter.setNumAppsPerRow(mNumAppsPerRow);
-            mApps.setNumAppsPerRow(mNumAppsPerRow);
+            mApps.setNumAppsPerRow(mNumAppsPerRow, mNumPredictedAppsPerRow);
         }
     }
 
@@ -291,17 +345,25 @@
         int startMargin = grid.isPhone() ? mContentMarginStart : 0;
         int inset = mFixedBounds.isEmpty() ? mContainerInset : mFixedBoundsContainerInset;
         if (isRtl) {
-            mAppsRecyclerView.setPadding(inset, inset, inset + startMargin, inset);
+            mAppsRecyclerView.setPadding(inset + mAppsRecyclerView.getScrollbarWidth(), inset,
+                    inset + startMargin, inset);
         } else {
-            mAppsRecyclerView.setPadding(inset + startMargin, inset, inset, inset);
+            mAppsRecyclerView.setPadding(inset + startMargin, inset,
+                    inset + mAppsRecyclerView.getScrollbarWidth(), inset);
         }
 
         // Update the header bar
         if (hasSearchBar) {
-            LinearLayout.LayoutParams lp =
-                    (LinearLayout.LayoutParams) mHeaderView.getLayoutParams();
+            FrameLayout.LayoutParams lp =
+                    (FrameLayout.LayoutParams) mHeaderView.getLayoutParams();
             lp.leftMargin = lp.rightMargin = inset;
+            mHeaderView.requestLayout();
         }
+
+        FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) mPredictionBarView.getLayoutParams();
+        lp.leftMargin = inset + mAppsRecyclerView.getScrollbarWidth();
+        lp.rightMargin = inset + mAppsRecyclerView.getScrollbarWidth();
+        mPredictionBarView.requestLayout();
     }
 
     /**
@@ -456,7 +518,10 @@
             String formatStr = getResources().getString(R.string.apps_view_no_search_results);
             mAdapter.setEmptySearchText(String.format(formatStr, queryText));
 
+            // Do an intersection of the words in the query and each title, and filter out all the
+            // apps that don't match all of the words in the query.
             final String queryTextLower = queryText.toLowerCase();
+            final String[] queryWords = SPLIT_PATTERN.split(queryTextLower);
             mApps.setFilter(new AlphabeticalAppsList.Filter() {
                 @Override
                 public boolean retainApp(AppInfo info, String sectionName) {
@@ -465,12 +530,21 @@
                     }
                     String title = info.title.toString();
                     String[] words = SPLIT_PATTERN.split(title.toLowerCase());
-                    for (int i = 0; i < words.length; i++) {
-                        if (words[i].startsWith(queryTextLower)) {
-                            return true;
+                    for (int qi = 0; qi < queryWords.length; qi++) {
+                        boolean foundMatch = false;
+                        for (int i = 0; i < words.length; i++) {
+                            if (words[i].startsWith(queryWords[qi])) {
+                                foundMatch = true;
+                                break;
+                            }
+                        }
+                        if (!foundMatch) {
+                            // If there is a word in the query that does not match any words in this
+                            // title, so skip it.
+                            return false;
                         }
                     }
-                    return false;
+                    return true;
                 }
             });
         }
@@ -488,7 +562,7 @@
             List<AlphabeticalAppsList.AdapterItem> items = mApps.getAdapterItems();
             for (int i = 0; i < items.size(); i++) {
                 AlphabeticalAppsList.AdapterItem item = items.get(i);
-                if (!item.isSectionHeader) {
+                if (item.viewType == AppsGridAdapter.ICON_VIEW_TYPE) {
                     mAppsRecyclerView.getChildAt(i).performClick();
                     getInputMethodManager().hideSoftInputFromWindow(getWindowToken(), 0);
                     return true;
@@ -499,6 +573,11 @@
     }
 
     @Override
+    public void onFilterChanged() {
+        updatePredictionBarVisibility();
+    }
+
+    @Override
     public View getContent() {
         return null;
     }
@@ -531,13 +610,20 @@
      * 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);
+        if (DYNAMIC_HEADER_ELEVATION && Utilities.isLmpOrAbove()) {
+            int elevation = DynamicGrid.pxFromDp(HEADER_ELEVATION_DP,
+                    getContext().getResources().getDisplayMetrics());
+            int scrollToElevation = DynamicGrid.pxFromDp(HEADER_SCROLL_TO_ELEVATION_DP,
+                    getContext().getResources().getDisplayMetrics());
+            float elevationPct = (float) Math.min(mRecyclerViewScrollY, scrollToElevation) /
+                    scrollToElevation;
+            float newElevation = elevation * elevationPct;
+            if (Float.compare(mHeaderView.getElevation(), newElevation) != 0) {
+                mHeaderView.setElevation(newElevation);
             }
         }
+
+        mPredictionBarView.setTranslationY(-mRecyclerViewScrollY + mAppsRecyclerView.getPaddingTop());
     }
 
     /**
@@ -667,6 +753,21 @@
     }
 
     /**
+     * Updates the visibility of the prediction bar.
+     * @return whether the prediction bar is visible
+     */
+    private boolean updatePredictionBarVisibility() {
+        boolean showPredictionBar = !mApps.getPredictedApps().isEmpty() && (!mApps.hasFilter() ||
+                mSearchBarEditView.getEditableText().toString().isEmpty());
+        if (showPredictionBar) {
+            mPredictionBarView.setVisibility(View.VISIBLE);
+        } else if (!showPredictionBar) {
+            mPredictionBarView.setVisibility(View.INVISIBLE);
+        }
+        return showPredictionBar;
+    }
+
+    /**
      * Returns an input method manager.
      */
     private InputMethodManager getInputMethodManager() {
diff --git a/src/com/android/launcher3/AppsGridAdapter.java b/src/com/android/launcher3/AppsGridAdapter.java
index a6902d5..dfbfa01 100644
--- a/src/com/android/launcher3/AppsGridAdapter.java
+++ b/src/com/android/launcher3/AppsGridAdapter.java
@@ -6,6 +6,7 @@
 import android.graphics.Paint;
 import android.graphics.PointF;
 import android.graphics.Rect;
+import android.os.Handler;
 import android.support.v7.widget.GridLayoutManager;
 import android.support.v7.widget.RecyclerView;
 import android.view.LayoutInflater;
@@ -26,21 +27,31 @@
     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;
-    private static final int EMPTY_VIEW_TYPE = 2;
+    // A section break in the grid
+    public static final int SECTION_BREAK_VIEW_TYPE = 0;
+    // A normal icon
+    public static final int ICON_VIEW_TYPE = 1;
+    // The message shown when there are no filtered results
+    public static final int EMPTY_VIEW_TYPE = 2;
+    // The spacer used for the prediction bar
+    public static final int PREDICTION_BAR_SPACER_TYPE = 3;
+
+    /**
+     * Callback for when the prediction bar spacer is bound.
+     */
+    public interface PredictionBarSpacerCallbacks {
+        void onBindPredictionBar();
+    }
 
     /**
      * ViewHolder for each icon.
      */
     public static class ViewHolder extends RecyclerView.ViewHolder {
         public View mContent;
-        public boolean mIsEmptyRow;
 
-        public ViewHolder(View v, boolean isEmptyRow) {
+        public ViewHolder(View v) {
             super(v);
             mContent = v;
-            mIsEmptyRow = isEmptyRow;
         }
     }
 
@@ -61,8 +72,8 @@
                 return mAppsPerRow;
             }
 
-            if (mApps.getAdapterItems().get(position).isSectionHeader) {
-                // Section break spans full width
+            if (mApps.getAdapterItems().get(position).viewType != AppsGridAdapter.ICON_VIEW_TYPE) {
+                // Both the section breaks and predictive bar span the full width
                 return mAppsPerRow;
             } else {
                 return 1;
@@ -88,7 +99,7 @@
 
             DeviceProfile grid = LauncherAppState.getInstance().getDynamicGrid().getDeviceProfile();
             List<AlphabeticalAppsList.AdapterItem> items = mApps.getAdapterItems();
-            boolean hasDrawnPredictedAppDivider = false;
+            boolean hasDrawnPredictedAppsDivider = false;
             int childCount = parent.getChildCount();
             int lastSectionTop = 0;
             int lastSectionHeight = 0;
@@ -99,14 +110,13 @@
                     continue;
                 }
 
-                if (shouldDrawItemDivider(holder, items) && !hasDrawnPredictedAppDivider) {
-                    // Draw the divider under the predicted app
+                if (shouldDrawItemDivider(holder, items) && !hasDrawnPredictedAppsDivider) {
+                    // Draw the divider under the predicted apps
+                    parent.getBackground().getPadding(mTmpBounds);
                     int top = child.getTop() + child.getHeight();
-                    int left = parent.getPaddingLeft();
-                    int right = parent.getWidth() - parent.getPaddingRight();
-                    int iconInset = (((right - left) / mAppsPerRow) - grid.allAppsIconSizePx) / 2;
-                    c.drawLine(left + iconInset, top, right - iconInset, top, mPredictedAppsDividerPaint);
-                    hasDrawnPredictedAppDivider = true;
+                    c.drawLine(mTmpBounds.left, top, parent.getWidth() - mTmpBounds.right, top,
+                            mPredictedAppsDividerPaint);
+                    hasDrawnPredictedAppsDivider = true;
 
                 } else if (grid.isPhone() && shouldDrawItemSection(holder, i, items)) {
                     // At this point, we only draw sections for each section break;
@@ -221,9 +231,10 @@
         /**
          * Returns whether to draw the divider for a given child.
          */
-        private boolean shouldDrawItemDivider(ViewHolder holder, List<AlphabeticalAppsList.AdapterItem> items) {
+        private boolean shouldDrawItemDivider(ViewHolder holder,
+                List<AlphabeticalAppsList.AdapterItem> items) {
             int pos = holder.getPosition();
-            return items.get(pos).isPredictedApp;
+            return items.get(pos).viewType == AppsGridAdapter.PREDICTION_BAR_SPACER_TYPE;
         }
 
         /**
@@ -234,31 +245,27 @@
             int pos = holder.getPosition();
             AlphabeticalAppsList.AdapterItem item = items.get(pos);
 
-            // Ensure it's not an empty row
-            if (holder.mIsEmptyRow) {
-                return false;
-            }
-            // Ensure this is not a section break
-            if (item.isSectionHeader) {
-                return false;
-            }
-            // Ensure this is not a predicted app
-            if (item.isPredictedApp) {
+            // Ensure it's an icon
+            if (item.viewType != AppsGridAdapter.ICON_VIEW_TYPE) {
                 return false;
             }
             // Draw the section header for the first item in each section
-            return (childIndex == 0) || (items.get(pos - 1).isSectionHeader && !item.isSectionHeader);
+            return (childIndex == 0) ||
+                    (items.get(pos - 1).viewType == AppsGridAdapter.SECTION_BREAK_VIEW_TYPE);
         }
     }
 
+    private Handler mHandler;
     private LayoutInflater mLayoutInflater;
     @Thunk AlphabeticalAppsList mApps;
     private GridLayoutManager mGridLayoutMgr;
     private GridSpanSizer mGridSizer;
     private GridItemDecoration mItemDecoration;
+    private PredictionBarSpacerCallbacks mPredictionBarCb;
     private View.OnTouchListener mTouchListener;
     private View.OnClickListener mIconClickListener;
     private View.OnLongClickListener mIconLongClickListener;
+    @Thunk int mPredictionBarHeight;
     @Thunk int mAppsPerRow;
     @Thunk boolean mIsRtl;
     private String mEmptySearchText;
@@ -272,11 +279,13 @@
 
 
     public AppsGridAdapter(Context context, AlphabeticalAppsList apps, int appsPerRow,
-            View.OnTouchListener touchListener, View.OnClickListener iconClickListener,
-            View.OnLongClickListener iconLongClickListener) {
+            PredictionBarSpacerCallbacks pbCb, View.OnTouchListener touchListener,
+            View.OnClickListener iconClickListener, View.OnLongClickListener iconLongClickListener) {
         Resources res = context.getResources();
+        mHandler = new Handler();
         mApps = apps;
         mAppsPerRow = appsPerRow;
+        mPredictionBarCb = pbCb;
         mGridSizer = new GridSpanSizer();
         mGridLayoutMgr = new GridLayoutManager(context, appsPerRow, GridLayoutManager.VERTICAL,
                 false);
@@ -297,8 +306,8 @@
         mSectionTextPaint.setAntiAlias(true);
 
         mPredictedAppsDividerPaint = new Paint();
-        mPredictedAppsDividerPaint.setStrokeWidth(DynamicGrid.pxFromDp(1.5f, res.getDisplayMetrics()));
-        mPredictedAppsDividerPaint.setColor(0x10000000);
+        mPredictedAppsDividerPaint.setStrokeWidth(DynamicGrid.pxFromDp(1f, res.getDisplayMetrics()));
+        mPredictedAppsDividerPaint.setColor(0x1E000000);
         mPredictedAppsDividerPaint.setAntiAlias(true);
     }
 
@@ -311,6 +320,13 @@
     }
 
     /**
+     * Sets the prediction row height.
+     */
+    public void setPredictionRowHeight(int height) {
+        mPredictionBarHeight = height;
+    }
+
+    /**
      * Sets whether we are in RTL mode.
      */
     public void setRtl(boolean rtl) {
@@ -351,17 +367,24 @@
         switch (viewType) {
             case EMPTY_VIEW_TYPE:
                 return new ViewHolder(mLayoutInflater.inflate(R.layout.apps_empty_view, parent,
-                        false), true /* isEmptyRow */);
+                        false));
             case SECTION_BREAK_VIEW_TYPE:
-                return new ViewHolder(new View(parent.getContext()), false /* isEmptyRow */);
+                return new ViewHolder(new View(parent.getContext()));
+            case PREDICTION_BAR_SPACER_TYPE:
+                // Create a view of a specific height to match the floating prediction bar
+                View v = new View(parent.getContext());
+                ViewGroup.LayoutParams lp = new ViewGroup.LayoutParams(
+                        ViewGroup.LayoutParams.MATCH_PARENT, mPredictionBarHeight);
+                v.setLayoutParams(lp);
+                return new ViewHolder(v);
             case ICON_VIEW_TYPE:
                 BubbleTextView icon = (BubbleTextView) mLayoutInflater.inflate(
-                        R.layout.apps_grid_row_icon_view, parent, false);
+                        R.layout.apps_grid_icon_view, parent, false);
                 icon.setOnTouchListener(mTouchListener);
                 icon.setOnClickListener(mIconClickListener);
                 icon.setOnLongClickListener(mIconLongClickListener);
                 icon.setFocusable(true);
-                return new ViewHolder(icon, false /* isEmptyRow */);
+                return new ViewHolder(icon);
             default:
                 throw new RuntimeException("Unexpected view type");
         }
@@ -375,6 +398,16 @@
                 BubbleTextView icon = (BubbleTextView) holder.mContent;
                 icon.applyFromApplicationInfo(info);
                 break;
+            case PREDICTION_BAR_SPACER_TYPE:
+                mHandler.post(new Runnable() {
+                    @Override
+                    public void run() {
+                        if (mPredictionBarCb != null) {
+                            mPredictionBarCb.onBindPredictionBar();
+                        }
+                    }
+                });
+                break;
             case EMPTY_VIEW_TYPE:
                 TextView emptyViewText = (TextView) holder.mContent.findViewById(R.id.empty_text);
                 emptyViewText.setText(mEmptySearchText);
@@ -395,9 +428,9 @@
     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;
+
+        AlphabeticalAppsList.AdapterItem item = mApps.getAdapterItems().get(position);
+        return item.viewType;
     }
 }
diff --git a/src/com/android/launcher3/BaseContainerRecyclerView.java b/src/com/android/launcher3/BaseContainerRecyclerView.java
index 5b30e3d..59e20ca 100644
--- a/src/com/android/launcher3/BaseContainerRecyclerView.java
+++ b/src/com/android/launcher3/BaseContainerRecyclerView.java
@@ -29,12 +29,20 @@
 public class BaseContainerRecyclerView extends RecyclerView
         implements RecyclerView.OnItemTouchListener {
 
+    /**
+     * Listener to get notified when the absolute scroll changes.
+     */
+    public interface OnScrollToListener {
+        void onScrolledTo(int x, int y);
+    }
+
     private static final int SCROLL_DELTA_THRESHOLD_DP = 4;
 
     /** Keeps the last known scrolling delta/velocity along y-axis. */
     @Thunk int mDy = 0;
+    @Thunk int mScrollY;
     private float mDeltaThreshold;
-    private RecyclerView.OnScrollListener mScrollListenerProxy;
+    private OnScrollToListener mScrollToListener;
 
     public BaseContainerRecyclerView(Context context) {
         this(context, null);
@@ -60,8 +68,9 @@
         @Override
         public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
             mDy = dy;
-            if (mScrollListenerProxy != null) {
-                mScrollListenerProxy.onScrolled(recyclerView, dx, dy);
+            mScrollY += dy;
+            if (mScrollToListener != null) {
+                mScrollToListener.onScrolledTo(0, mScrollY);
             }
         }
     }
@@ -69,8 +78,8 @@
     /**
      * Sets an additional scroll listener, only needed for LMR1 version of the support lib.
      */
-    public void setOnScrollListenerProxy(RecyclerView.OnScrollListener listener) {
-        mScrollListenerProxy = listener;
+    public void setOnScrollListenerProxy(OnScrollToListener listener) {
+        mScrollToListener = listener;
     }
 
     @Override
@@ -97,6 +106,17 @@
     }
 
     /**
+     * Updates the scroll position, used to workaround a RecyclerView issue with scrolling to
+     * position.
+     */
+    protected void updateScrollY(int scrollY) {
+        mScrollY = scrollY;
+        if (mScrollToListener != null) {
+            mScrollToListener.onScrolledTo(0, mScrollY);
+        }
+    }
+
+    /**
      * Returns whether this {@link MotionEvent} should trigger the scroll to be stopped.
      */
     protected boolean shouldStopScroll(MotionEvent ev) {
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 3bbf0e7..dc6de07 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -127,6 +127,7 @@
     int allAppsNumRows;
     int allAppsNumCols;
     int appsViewNumCols;
+    int appsViewNumPredictiveCols;
     int searchBarSpaceWidthPx;
     int searchBarSpaceHeightPx;
     int pageIndicatorHeightPx;
@@ -411,7 +412,7 @@
 
         // All Apps
         allAppsCellWidthPx = allAppsIconSizePx;
-        allAppsCellHeightPx = allAppsIconSizePx + drawablePadding + iconTextSizePx;
+        allAppsCellHeightPx = allAppsIconSizePx + drawablePadding + allAppsIconTextSizePx;
         int maxLongEdgeCellCount =
                 res.getInteger(R.integer.config_dynamic_grid_max_long_edge_cell_count);
         int maxShortEdgeCellCount =
@@ -440,10 +441,13 @@
         int appsViewLeftMarginPx =
                 res.getDimensionPixelSize(R.dimen.apps_grid_view_start_margin);
         int availableAppsWidthPx = (containerWidth > 0) ? containerWidth : availableWidthPx;
-        int numCols = (availableAppsWidthPx - appsViewLeftMarginPx) /
+        int numAppsCols = (availableAppsWidthPx - appsViewLeftMarginPx) /
                 (allAppsCellWidthPx + 2 * allAppsCellPaddingPx);
-        if (numCols != appsViewNumCols) {
-            appsViewNumCols = numCols;
+        int numPredictiveAppCols = isPhone() ? numColumns : numAppsCols;
+        if ((numAppsCols != appsViewNumCols) ||
+                (numPredictiveAppCols != appsViewNumPredictiveCols)) {
+            appsViewNumCols = numAppsCols;
+            appsViewNumPredictiveCols = numPredictiveAppCols;
             return true;
         }
         return false;
diff --git a/src/com/android/launcher3/Folder.java b/src/com/android/launcher3/Folder.java
index a955b27..377e8ee 100644
--- a/src/com/android/launcher3/Folder.java
+++ b/src/com/android/launcher3/Folder.java
@@ -90,6 +90,8 @@
      */
     private static final float ICON_OVERSCROLL_WIDTH_FACTOR = 0.45f;
 
+    public static final int FOOTER_ANIMATION_DURATION = 200;
+
     private static final int REORDER_DELAY = 250;
     private static final int ON_EXIT_CLOSE_DELAY = 400;
     private static final Rect sTempRect = new Rect();
@@ -211,10 +213,7 @@
                 InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS | InputType.TYPE_TEXT_FLAG_CAP_WORDS);
 
         mFooter = findViewById(R.id.folder_footer);
-        updateFooterHeight();
-    }
 
-    public void updateFooterHeight() {
         // We find out how tall footer wants to be (it is set to wrap_content), so that
         // we can allocate the appropriate amount of space for it.
         int measureSpec = MeasureSpec.UNSPECIFIED;
@@ -547,6 +546,36 @@
                 mContent.setFocusOnFirstChild();
             }
         });
+
+        // Footer animation
+        if (mContent.getPageCount() > 1 && !mInfo.hasOption(FolderInfo.FLAG_MULTI_PAGE_ANIMATION)) {
+            int footerWidth = mContent.getDesiredWidth()
+                    - mFooter.getPaddingLeft() - mFooter.getPaddingRight();
+
+            float textWidth =  mFolderName.getPaint().measureText(mFolderName.getText().toString());
+            mFolderName.setTranslationX((footerWidth - textWidth) / 2);
+            mContent.setMarkerScale(0);
+
+            // Do not update the flag if we are in drag mode. The flag will be updated, when we
+            // actually drop the icon.
+            final boolean updateAnimationFlag = !mDragInProgress;
+            openFolderAnim.addListener(new AnimatorListenerAdapter() {
+
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    mFolderName.animate().setDuration(FOOTER_ANIMATION_DURATION).translationX(0);
+                    mContent.animateMarkers();
+
+                    if (updateAnimationFlag) {
+                        mInfo.setOption(FolderInfo.FLAG_MULTI_PAGE_ANIMATION, true, mLauncher);
+                    }
+                }
+            });
+        } else {
+            mFolderName.setTranslationX(0);
+            mContent.setMarkerScale(1);
+        }
+
         openFolderAnim.start();
 
         // Make sure the folder picks up the last drag move even if the finger doesn't move.
@@ -823,6 +852,14 @@
         // Reordering may have occured, and we need to save the new item locations. We do this once
         // at the end to prevent unnecessary database operations.
         updateItemLocationsInDatabaseBatch();
+
+        // Use the item count to check for multi-page as the folder UI may not have
+        // been refreshed yet.
+        if (getItemCount() <= mContent.itemsPerPage()) {
+            // Show the animation, next time something is added to the folder.
+            mInfo.setOption(FolderInfo.FLAG_MULTI_PAGE_ANIMATION, false, mLauncher);
+        }
+
     }
 
     @Override
@@ -1200,6 +1237,11 @@
         // Clear the drag info, as it is no longer being dragged.
         mCurrentDragInfo = null;
         mDragInProgress = false;
+
+        if (mContent.getPageCount() > 1) {
+            // The animation has already been shown while opening the folder.
+            mInfo.setOption(FolderInfo.FLAG_MULTI_PAGE_ANIMATION, true, mLauncher);
+        }
     }
 
     // This is used so the item doesn't immediately appear in the folder when added. In one case
@@ -1214,6 +1256,7 @@
         v.setVisibility(VISIBLE);
     }
 
+    @Override
     public void onAdd(ShortcutInfo item) {
         // If the item was dropped onto this open folder, we have done the work associated
         // with adding the item to the folder, as indicated by mSuppressOnAdd being set
diff --git a/src/com/android/launcher3/FolderInfo.java b/src/com/android/launcher3/FolderInfo.java
index 9675371..930f911 100644
--- a/src/com/android/launcher3/FolderInfo.java
+++ b/src/com/android/launcher3/FolderInfo.java
@@ -42,6 +42,11 @@
     public static final int FLAG_WORK_FOLDER = 0x00000002;
 
     /**
+     * The multi-page animation has run for this folder
+     */
+    public static final int FLAG_MULTI_PAGE_ANIMATION = 0x00000004;
+
+    /**
      * Whether this folder has been opened
      */
     boolean opened;
diff --git a/src/com/android/launcher3/FolderPagedView.java b/src/com/android/launcher3/FolderPagedView.java
index 05b2bbf..a6494d2 100644
--- a/src/com/android/launcher3/FolderPagedView.java
+++ b/src/com/android/launcher3/FolderPagedView.java
@@ -20,9 +20,11 @@
 import android.content.Context;
 import android.util.AttributeSet;
 import android.util.Log;
+import android.view.Gravity;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.animation.DecelerateInterpolator;
+import android.view.animation.OvershootInterpolator;
 
 import com.android.launcher3.FocusHelper.PagedFolderKeyEventListener;
 import com.android.launcher3.PageIndicator.PageMarkerResources;
@@ -44,6 +46,8 @@
     private static final int START_VIEW_REORDER_DELAY = 30;
     private static final float VIEW_REORDER_DELAY_FACTOR = 0.9f;
 
+    private static final int PAGE_INDICATOR_ANIMATION_DELAY = 150;
+
     /**
      * Fraction of the width to scroll when showing the next page hint.
      */
@@ -70,7 +74,7 @@
     private FocusIndicatorView mFocusIndicatorView;
     private PagedFolderKeyEventListener mKeyListener;
 
-    private View mPageIndicator;
+    private PageIndicator mPageIndicator;
 
     public FolderPagedView(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -93,7 +97,7 @@
         mFolder = folder;
         mFocusIndicatorView = (FocusIndicatorView) folder.findViewById(R.id.focus_indicator);
         mKeyListener = new PagedFolderKeyEventListener(folder);
-        mPageIndicator = folder.findViewById(R.id.folder_page_indicator);
+        mPageIndicator = (PageIndicator) folder.findViewById(R.id.folder_page_indicator);
     }
 
     /**
@@ -330,11 +334,8 @@
         setEnableOverscroll(getPageCount() > 1);
 
         // Update footer
-        int indicatorVisibility = mPageIndicator.getVisibility();
         mPageIndicator.setVisibility(getPageCount() > 1 ? View.VISIBLE : View.GONE);
-        if (indicatorVisibility != mPageIndicator.getVisibility()) {
-            mFolder.updateFooterHeight();
-        }
+        mFolder.mFolderName.setGravity(getPageCount() > 1 ? Gravity.START : Gravity.CENTER_HORIZONTAL);
     }
 
     public int getDesiredWidth() {
@@ -624,4 +625,29 @@
             }
         }
     }
+
+    public void setMarkerScale(float scale) {
+        int count  = mPageIndicator.getChildCount();
+        for (int i = 0; i < count; i++) {
+            View marker = mPageIndicator.getChildAt(i);
+            marker.animate().cancel();
+            marker.setScaleX(scale);
+            marker.setScaleY(scale);
+        }
+    }
+
+    public void animateMarkers() {
+        int count  = mPageIndicator.getChildCount();
+        OvershootInterpolator interpolator = new OvershootInterpolator(4);
+        for (int i = 0; i < count; i++) {
+            mPageIndicator.getChildAt(i).animate().scaleX(1).scaleY(1)
+                .setInterpolator(interpolator)
+                .setDuration(Folder.FOOTER_ANIMATION_DURATION)
+                .setStartDelay(PAGE_INDICATOR_ANIMATION_DELAY * i);
+        }
+    }
+
+    public int itemsPerPage() {
+        return mMaxItemsPerPage;
+    }
 }
diff --git a/src/com/android/launcher3/LauncherStateTransitionAnimation.java b/src/com/android/launcher3/LauncherStateTransitionAnimation.java
index 51f84bf..73ae51c 100644
--- a/src/com/android/launcher3/LauncherStateTransitionAnimation.java
+++ b/src/com/android/launcher3/LauncherStateTransitionAnimation.java
@@ -378,11 +378,12 @@
                     dispatchOnLauncherTransitionStart(toView, animated, false);
 
                     // Enable all necessary layers
+                    boolean isLmpOrAbove = Utilities.isLmpOrAbove();
                     for (View v : layerViews.keySet()) {
                         if (layerViews.get(v) == BUILD_AND_SET_LAYER) {
                             v.setLayerType(View.LAYER_TYPE_HARDWARE, null);
                         }
-                        if (Utilities.isViewAttachedToWindow(v)) {
+                        if (isLmpOrAbove && Utilities.isViewAttachedToWindow(v)) {
                             v.buildLayer();
                         }
                     }
@@ -697,11 +698,12 @@
                     dispatchOnLauncherTransitionStart(toView, animated, false);
 
                     // Enable all necessary layers
+                    boolean isLmpOrAbove = Utilities.isLmpOrAbove();
                     for (View v : layerViews.keySet()) {
                         if (layerViews.get(v) == BUILD_AND_SET_LAYER) {
                             v.setLayerType(View.LAYER_TYPE_HARDWARE, null);
                         }
-                        if (Utilities.isLmpOrAbove()) {
+                        if (isLmpOrAbove && Utilities.isViewAttachedToWindow(v)) {
                             v.buildLayer();
                         }
                     }
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index aa86567..c8e7d9c 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -636,21 +636,15 @@
         AccessibilityManager am =
                 (AccessibilityManager) getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
         if (am.isEnabled()) {
-            AccessibilityEvent ev =
-                    AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_SCROLLED);
-            ev.setItemCount(getChildCount());
-            ev.setFromIndex(mCurrentPage);
-            ev.setToIndex(getNextPage());
+            if (mCurrentPage != getNextPage()) {
+                AccessibilityEvent ev =
+                        AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_SCROLLED);
+                ev.setItemCount(getChildCount());
+                ev.setFromIndex(getNextPage());
+                ev.setToIndex(getNextPage());
 
-            final int action;
-            if (getNextPage() >= mCurrentPage) {
-                action = AccessibilityNodeInfo.ACTION_SCROLL_FORWARD;
-            } else {
-                action = AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD;
+                sendAccessibilityEventUnchecked(ev);
             }
-
-            ev.setAction(action);
-            sendAccessibilityEventUnchecked(ev);
         }
     }
 
@@ -2133,8 +2127,6 @@
             focusedChild.clearFocus();
         }
 
-        sendScrollAccessibilityEvent();
-
         pageBeginMoving();
         awakenScrollBars(duration);
         if (immediate) {