Merge "Ensure that clipping widget size is 20~40% of its width on every devices" into ub-launcher3-burnaby
diff --git a/res/layout/apps_grid_row_icon_view.xml b/res/layout/apps_grid_row_icon_view.xml
index 81e74b9..149e28e 100644
--- a/res/layout/apps_grid_row_icon_view.xml
+++ b/res/layout/apps_grid_row_icon_view.xml
@@ -21,8 +21,8 @@
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"
     android:layout_gravity="left|center_vertical"
-    android:paddingTop="8dp"
-    android:paddingBottom="8dp"
+    android:paddingTop="@dimen/apps_icon_top_bottom_padding"
+    android:paddingBottom="@dimen/apps_icon_top_bottom_padding"
     android:focusable="true"
     android:background="@drawable/focusable_view_bg"
     launcher:deferShadowGeneration="true" />
diff --git a/res/layout/apps_list_row_icon_view.xml b/res/layout/apps_list_row_icon_view.xml
deleted file mode 100644
index 867dbdc..0000000
--- a/res/layout/apps_list_row_icon_view.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-
-<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"
-    android:id="@+id/application_icon"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:focusable="true"
-    android:background="@drawable/focusable_view_bg"
-    launcher:iconPaddingOverride="24dp"
-    launcher:textSizeOverride="16dp"
-    launcher:layoutHorizontal="true"
-    launcher:deferShadowGeneration="true" />
diff --git a/res/layout/apps_list_view.xml b/res/layout/apps_list_view.xml
index e29cac5..ddcb639 100644
--- a/res/layout/apps_list_view.xml
+++ b/res/layout/apps_list_view.xml
@@ -25,7 +25,7 @@
     <FrameLayout
         android:id="@+id/header"
         android:layout_width="match_parent"
-        android:layout_height="52dp"
+        android:layout_height="@dimen/apps_search_bar_height"
         android:orientation="horizontal"
         android:background="@drawable/apps_search_bg">
         <LinearLayout
@@ -39,11 +39,12 @@
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
                 android:layout_gravity="start|center_vertical"
+                android:layout_marginStart="4dp"
                 android:paddingTop="12dp"
                 android:paddingBottom="12dp"
                 android:contentDescription="@string/all_apps_button_label"
                 android:src="@drawable/ic_arrow_back_grey" />
-            <EditText
+            <com.android.launcher3.AppsContainerSearchEditTextView
                 android:id="@+id/app_search_box"
                 android:layout_width="match_parent"
                 android:layout_height="match_parent"
@@ -59,6 +60,7 @@
                 android:textColor="#4c4c4c"
                 android:textColorHint="#9c9c9c"
                 android:imeOptions="actionDone|flagNoExtractUi"
+                android:focusableInTouchMode="true"
                 android:background="@android:color/transparent" />
         </LinearLayout>
         <ImageView
@@ -66,6 +68,7 @@
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:layout_gravity="end|center_vertical"
+            android:layout_marginEnd="6dp"
             android:paddingTop="12dp"
             android:paddingBottom="12dp"
             android:contentDescription="@string/apps_view_search_bar_hint"
diff --git a/res/layout/user_folder.xml b/res/layout/user_folder.xml
index cd3a051..5bacc96 100644
--- a/res/layout/user_folder.xml
+++ b/res/layout/user_folder.xml
@@ -19,7 +19,7 @@
     xmlns:launcher="http://schemas.android.com/apk/res-auto"
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"
-    android:background="@drawable/quantum_panel"
+    android:elevation="5dp"
     android:orientation="vertical" >
 
     <FrameLayout
diff --git a/res/values-sw600dp/dimens.xml b/res/values-sw600dp/dimens.xml
index 9ecd07dd..b184642 100644
--- a/res/values-sw600dp/dimens.xml
+++ b/res/values-sw600dp/dimens.xml
@@ -22,6 +22,7 @@
     <dimen name="apps_grid_view_start_margin">64dp</dimen>
     <dimen name="apps_view_section_text_size">26sp</dimen>
     <dimen name="apps_view_row_height">76dp</dimen>
+    <dimen name="apps_icon_top_bottom_padding">12dp</dimen>
 
 <!-- AppsCustomize -->
     <dimen name="widget_preview_label_margin_top">8dp</dimen>
diff --git a/res/values-sw720dp/dimens.xml b/res/values-sw720dp/dimens.xml
index 68fc1ec..cec6b7d 100644
--- a/res/values-sw720dp/dimens.xml
+++ b/res/values-sw720dp/dimens.xml
@@ -16,6 +16,8 @@
 
 <resources>
     <dimen name="app_icon_size">72dp</dimen>
+    <dimen name="apps_search_bar_height">56dp</dimen>
+    <dimen name="apps_icon_top_bottom_padding">16dp</dimen>
 
 <!-- QSB -->
     <dimen name="toolbar_button_vertical_padding">8dip</dimen>
diff --git a/res/values/config.xml b/res/values/config.xml
index 6ef8635..84ccef1 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -102,4 +102,5 @@
     <item type="id" name="action_move_to_workspace" />
     <item type="id" name="action_move_screen_backwards" />
     <item type="id" name="action_move_screen_forwards" />
+    <item type="id" name="action_resize" />
 </resources>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index fd5bff3..5447371 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -59,6 +59,8 @@
     <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_search_bar_height">52dp</dimen>
+    <dimen name="apps_icon_top_bottom_padding">8dp</dimen>
 
     <!-- Note: This needs to match the fixed insets for the search box. -->
     <dimen name="container_fixed_bounds_inset">8dp</dimen>
@@ -76,10 +78,14 @@
          or right while you're dragging. -->
     <dimen name="scroll_zone">20dp</dimen>
 
-    <!--  When dragging an item, how much bigger (fixed dps) the dragged view
-          should be. If 0, it will not be scaled at all. -->
+    <!-- When dragging an item, how much bigger (fixed dps) the dragged view
+         should be. If 0, it will not be scaled at all. -->
     <dimen name="dragViewScale">12dp</dimen>
 
+    <!-- Elevation for the drag view. It should be larger than elevation of all other drag sources
+         and drop targets like all-apps and folders -->
+    <dimen name="drag_elevation">30dp</dimen>
+
 <!-- Widget tray -->
     <dimen name="widget_container_inset">8dp</dimen>
     <dimen name="widget_preview_size">130dp</dimen>
@@ -108,6 +114,7 @@
     <!-- The amount that the preview contents are inset from the preview background -->
     <dimen name="folder_preview_padding">4dp</dimen>
     <dimen name="folder_name_padding">10dp</dimen>
+    <dimen name="folder_shadow_padding">8dp</dimen>
 
 <!-- Sizes for managed profile badges -->
     <dimen name="profile_badge_size">24dp</dimen>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 1681fc6..5962584 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -252,4 +252,23 @@
 
     <!-- Accessibility confirmation when a screen was moved [DO NOT TRANSLATE] -->
     <string name="screen_moved">Screen moved</string>
+
+    <!-- Accessibility action to resize a widget [DO NOT TRANSLATE] -->
+    <string name="action_resize">Resize</string>
+
+    <!-- Accessibility action to increase width of a widget [DO NOT TRANSLATE] -->
+    <string name="action_increase_width">Increase width</string>
+
+    <!-- Accessibility action to increase height of a widget [DO NOT TRANSLATE] -->
+    <string name="action_increase_height">Increase height</string>
+
+    <!-- Accessibility action to decrease width of a widget [DO NOT TRANSLATE] -->
+    <string name="action_decrease_width">Decrease width</string>
+
+    <!-- Accessibility action to decrease height of a widget [DO NOT TRANSLATE] -->
+    <string name="action_decrease_height">Decrease height</string>
+
+    <!-- Accessibility confirmation for widget resize [DO NOT TRANSLATE]-->
+    <string name="widget_resized">Widget resized to width <xliff:g id="number" example="2">%1$s</xliff:g> height <xliff:g id="number" example="1">%2$s</xliff:g></string>
+
 </resources>
diff --git a/src/com/android/launcher3/AlphabeticalAppsList.java b/src/com/android/launcher3/AlphabeticalAppsList.java
index e9a52d5..62cb237 100644
--- a/src/com/android/launcher3/AlphabeticalAppsList.java
+++ b/src/com/android/launcher3/AlphabeticalAppsList.java
@@ -29,8 +29,7 @@
         mAppNameComparator = new Comparator<AppInfo>() {
             public final int compare(AppInfo a, AppInfo b) {
                 // Order by the title
-                int result = collator.compare(a.title.toString().trim(),
-                        b.title.toString().trim());
+                int result = collator.compare(a.title.toString(), b.title.toString());
                 if (result == 0) {
                     // If two apps have the same title, then order by the component name
                     result = a.componentName.compareTo(b.componentName);
@@ -82,17 +81,30 @@
      * Info about a section in the alphabetic list
      */
     public static class SectionInfo {
-        // The name of this section
-        public String sectionName;
         // The number of applications in this section
-        public int numAppsInSection;
-        // The section AdapterItem for this section
-        public AdapterItem sectionItem;
+        public int numApps;
+        // The section break AdapterItem for this section
+        public AdapterItem sectionBreakItem;
         // The first app AdapterItem for this section
         public AdapterItem firstAppItem;
+    }
 
-        public SectionInfo(String name) {
-            sectionName = name;
+    /**
+     * Info about a fast scroller section, depending if sections are merged, the fast scroller
+     * sections will not be the same set as the section headers.
+     */
+    public static class FastScrollSectionInfo {
+        // The section name
+        public String sectionName;
+        // To map the touch (from 0..1) to the index in the app list to jump to in the fast
+        // scroller, we use the fraction in range (0..1) of the app index / total app count.
+        public float appRangeFraction;
+        // The AdapterItem to scroll to for this section
+        public AdapterItem appItem;
+
+        public FastScrollSectionInfo(String sectionName, float appRangeFraction) {
+            this.sectionName = sectionName;
+            this.appRangeFraction = appRangeFraction;
         }
     }
 
@@ -100,34 +112,48 @@
      * 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;
+        // Whether or not this is a predicted app
+        public boolean isPredictedApp;
+
+        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;
+            section.sectionBreakItem = item;
             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,
+                                        boolean isPredictedApp) {
             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;
+            item.isPredictedApp = isPredictedApp;
             return item;
         }
     }
@@ -140,12 +166,14 @@
     }
 
     // The maximum number of rows allowed in a merged section before we stop merging
-    private static final int MAX_ROWS_IN_MERGED_SECTION = Integer.MAX_VALUE;
+    private static final int MAX_ROWS_IN_MERGED_SECTION = 3;
 
     private List<AppInfo> mApps = new ArrayList<>();
     private List<AppInfo> mFilteredApps = new ArrayList<>();
     private List<AdapterItem> mSectionedFilteredApps = new ArrayList<>();
     private List<SectionInfo> mSections = new ArrayList<>();
+    private List<FastScrollSectionInfo> mFastScrollerSections = new ArrayList<>();
+    private List<ComponentName> mPredictedApps = new ArrayList<>();
     private RecyclerView.Adapter mAdapter;
     private Filter mFilter;
     private AlphabeticIndexCompat mIndexer;
@@ -184,6 +212,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() {
@@ -223,6 +258,17 @@
     }
 
     /**
+     * Sets the current set of predicted apps.  Since this can be called before we get the full set
+     * of applications, we should merge the results only in onAppsUpdated() which is idempotent.
+     */
+    public void setPredictedApps(List<ComponentName> apps) {
+        mPredictedApps.clear();
+        mPredictedApps.addAll(apps);
+        onAppsUpdated();
+        mAdapter.notifyDataSetChanged();
+    }
+
+    /**
      * Sets the current set of apps.
      */
     public void setApps(List<AppInfo> apps) {
@@ -307,85 +353,118 @@
         // Sort the list of apps
         Collections.sort(mApps, mAppNameComparator.getComparator());
 
-        // Recreate the filtered and sectioned apps (for convenience for the grid layout)
+        // 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;
-        for (AppInfo info : mApps) {
-            String sectionName = mIndexer.computeSectionName(info.title.toString().trim());
+        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
+        allApps.addAll(mApps);
+
+        // Recreate the filtered and sectioned apps (for convenience for the grid layout) from the
+        // combined list
+        int numApps = allApps.size();
+        for (int i = 0; i < numApps; i++) {
+            boolean isPredictedApp = i < numPredictedApps;
+            AppInfo info = allApps.get(i);
+            String sectionName = isPredictedApp ? "" : mIndexer.computeSectionName(info.title);
 
             // Check if we want to retain this app
             if (mFilter != null && !mFilter.retainApp(info, sectionName)) {
                 continue;
             }
 
-            // Create a new section if necessary
-            if (lastSectionInfo == null || !lastSectionInfo.sectionName.equals(sectionName)) {
-                lastSectionInfo = new SectionInfo(sectionName);
+            // Create a new section if the section names do not match
+            if (lastSectionInfo == null ||
+                    (!isPredictedApp && !sectionName.equals(lastSectionName))) {
+                lastSectionName = sectionName;
+                lastSectionInfo = new SectionInfo();
+                lastFastScrollerSectionInfo = new FastScrollSectionInfo(sectionName,
+                        (float) appIndex / numApps);
                 mSections.add(lastSectionInfo);
+                mFastScrollerSections.add(lastFastScrollerSectionInfo);
 
-                // Create a new section item, this item is used to break the flow of items in the
-                // list
-                AdapterItem sectionItem = AdapterItem.asSection(position++, sectionName);
+                // Create a new section item to break the flow of items in the list
                 if (!AppsContainerView.GRID_HIDE_SECTION_HEADERS && !hasFilter()) {
-                    lastSectionInfo.sectionItem = sectionItem;
+                    AdapterItem sectionItem = AdapterItem.asSectionBreak(position++, lastSectionInfo);
                     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++, isPredictedApp);
             if (lastSectionInfo.firstAppItem == null) {
                 lastSectionInfo.firstAppItem = appItem;
+                lastFastScrollerSectionInfo.appItem = appItem;
             }
             mSectionedFilteredApps.add(appItem);
             mFilteredApps.add(info);
         }
 
+        // Go through each section and try and merge some of the sections
         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);
-                String mergedSectionName = section.sectionName;
-                sectionAppCount = section.numAppsInSection;
+                sectionAppCount = section.numApps;
                 int mergeCount = 1;
+
                 // Merge rows if the last app in this section is in a column that is greater than
                 // 0, but less than the min number of apps per row.  In addition, apply the
                 // constraint to stop merging if the number of rows in the section is greater than
                 // some limit, and also if there are no lessons to merge.
                 while (0 < (sectionAppCount % mNumAppsPerRow) &&
                         (sectionAppCount % mNumAppsPerRow) < minNumAppsPerRow &&
-                        (int) Math.ceil(sectionAppCount / mNumAppsPerRow) < MAX_ROWS_IN_MERGED_SECTION &&
+                        (sectionAppCount / mNumAppsPerRow) < MAX_ROWS_IN_MERGED_SECTION &&
                         (i + 1) < mSections.size()) {
                     SectionInfo nextSection = mSections.remove(i + 1);
-                    // Merge the section names
-                    if (AppsContainerView.GRID_MERGE_SECTION_HEADERS) {
-                        mergedSectionName += nextSection.sectionName;
-                    }
+
                     // Remove the next section break
-                    mSectionedFilteredApps.remove(nextSection.sectionItem);
-                    if (AppsContainerView.GRID_MERGE_SECTION_HEADERS) {
-                        // Update the section names for the two sections
-                        int pos = mSectionedFilteredApps.indexOf(section.firstAppItem);
-                        for (int j = pos; j < (pos + section.numAppsInSection + nextSection.numAppsInSection); j++) {
-                            AdapterItem item = mSectionedFilteredApps.get(j);
-                            item.sectionName = mergedSectionName;
-                        }
+                    mSectionedFilteredApps.remove(nextSection.sectionBreakItem);
+                    int pos = mSectionedFilteredApps.indexOf(section.firstAppItem);
+                    // Point the section for these new apps to the merged section
+                    int nextPos = pos + section.numApps;
+                    for (int j = nextPos; j < (nextPos + nextSection.numApps); j++) {
+                        AdapterItem item = mSectionedFilteredApps.get(j);
+                        item.sectionInfo = section;
+                        item.sectionAppIndex += section.numApps;
                     }
-                    // Update the following adapter items of the removed section
-                    int pos = mSectionedFilteredApps.indexOf(nextSection.firstAppItem);
+
+                    // Update the following adapter items of the removed section item
+                    pos = mSectionedFilteredApps.indexOf(nextSection.firstAppItem);
                     for (int j = pos; j < mSectionedFilteredApps.size(); j++) {
                         AdapterItem item = mSectionedFilteredApps.get(j);
                         item.position--;
                     }
-                    section.numAppsInSection += nextSection.numAppsInSection;
-                    sectionAppCount += nextSection.numAppsInSection;
+                    section.numApps += nextSection.numApps;
+                    sectionAppCount += nextSection.numApps;
                     mergeCount++;
                     if (mergeCount >= mMaxAllowableMerges) {
                         break;
diff --git a/src/com/android/launcher3/AppInfo.java b/src/com/android/launcher3/AppInfo.java
index 7c6b066..58a57a1 100644
--- a/src/com/android/launcher3/AppInfo.java
+++ b/src/com/android/launcher3/AppInfo.java
@@ -105,7 +105,7 @@
     public AppInfo(AppInfo info) {
         super(info);
         componentName = info.componentName;
-        title = info.title.toString();
+        title = Utilities.trim(info.title);
         intent = new Intent(info.intent);
         flags = info.flags;
         firstInstallTime = info.firstInstallTime;
@@ -114,7 +114,7 @@
 
     @Override
     public String toString() {
-        return "ApplicationInfo(title=" + title.toString() + " id=" + this.id
+        return "ApplicationInfo(title=" + title + " id=" + this.id
                 + " type=" + this.itemType + " container=" + this.container
                 + " screen=" + screenId + " cellX=" + cellX + " cellY=" + cellY
                 + " spanX=" + spanX + " spanY=" + spanY + " dropPos=" + Arrays.toString(dropPos)
diff --git a/src/com/android/launcher3/AppsContainerRecyclerView.java b/src/com/android/launcher3/AppsContainerRecyclerView.java
index fb5f6d4..edb6f0c 100644
--- a/src/com/android/launcher3/AppsContainerRecyclerView.java
+++ b/src/com/android/launcher3/AppsContainerRecyclerView.java
@@ -229,7 +229,7 @@
      * Draws the fast scroller popup.
      */
     private void drawFastScrollerPopup(Canvas canvas) {
-        if (mFastScrollAlpha > 0f) {
+        if (mFastScrollAlpha > 0f && !mFastScrollSectionName.isEmpty()) {
             int x;
             int y;
             boolean isRtl = (getResources().getConfiguration().getLayoutDirection() ==
@@ -254,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);
@@ -285,38 +286,33 @@
     }
 
     /**
-     * Maps the progress (from 0..1) to the position that should be visible
+     * Maps the touch (from 0..1) to the adapter position that should be visible.
      */
-    private String scrollToPositionAtProgress(float progress) {
-        List<AlphabeticalAppsList.SectionInfo> sections = mApps.getSections();
-        if (sections.isEmpty()) {
+    private String scrollToPositionAtProgress(float touchFraction) {
+        // Ensure that we have any sections
+        List<AlphabeticalAppsList.FastScrollSectionInfo> fastScrollSections =
+                mApps.getFastScrollerSections();
+        if (fastScrollSections.isEmpty()) {
             return "";
         }
 
-        // Find the position of the first application in the section that contains the row at the
-        // current progress
-        List<AlphabeticalAppsList.AdapterItem> items = mApps.getAdapterItems();
-        int rowAtProgress = (int) (progress * getNumRows());
-        int rowCount = 0;
-        AlphabeticalAppsList.SectionInfo lastSectionInfo = null;
-        for (AlphabeticalAppsList.SectionInfo section : sections) {
-            int numRowsInSection = (int) Math.ceil((float) section.numAppsInSection / mNumAppsPerRow);
-            if (rowCount + numRowsInSection >= rowAtProgress) {
-                lastSectionInfo = section;
+        AlphabeticalAppsList.FastScrollSectionInfo lastScrollSection = fastScrollSections.get(0);
+        for (int i = 1; i < fastScrollSections.size(); i++) {
+            AlphabeticalAppsList.FastScrollSectionInfo scrollSection = fastScrollSections.get(i);
+            if (lastScrollSection.appRangeFraction <= touchFraction &&
+                    touchFraction < scrollSection.appRangeFraction) {
                 break;
             }
-            rowCount += numRowsInSection;
+            lastScrollSection = scrollSection;
         }
-        int position = items.indexOf(lastSectionInfo.firstAppItem);
 
         // Scroll the position into view, anchored at the top of the screen if possible. We call the
         // scroll method on the LayoutManager directly since it is not exposed by RecyclerView.
         LinearLayoutManager layoutManager = (LinearLayoutManager) getLayoutManager();
         stopScroll();
-        layoutManager.scrollToPositionWithOffset(position, 0);
+        layoutManager.scrollToPositionWithOffset(lastScrollSection.appItem.position, 0);
 
-        // Return the section name of the row
-        return lastSectionInfo.sectionName;
+        return lastScrollSection.sectionName;
     }
 
     /**
@@ -385,18 +381,18 @@
     }
 
     /**
-     * Returns the row index for a given position in the list.
+     * Returns the row index for a app index in the list.
      */
-    private int findRowForAppIndex(int position) {
+    private int findRowForAppIndex(int index) {
         List<AlphabeticalAppsList.SectionInfo> sections = mApps.getSections();
         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) {
-                return rowCount + ((position - appIndex) / mNumAppsPerRow);
+            int numRowsInSection = (int) Math.ceil((float) info.numApps / mNumAppsPerRow);
+            if (appIndex + info.numApps > index) {
+                return rowCount + ((index - appIndex) / mNumAppsPerRow);
             }
-            appIndex += info.numAppsInSection;
+            appIndex += info.numApps;
             rowCount += numRowsInSection;
         }
         return appIndex;
@@ -409,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 024e539..b8d30d0 100644
--- a/src/com/android/launcher3/AppsContainerView.java
+++ b/src/com/android/launcher3/AppsContainerView.java
@@ -15,6 +15,7 @@
  */
 package com.android.launcher3;
 
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.Point;
@@ -36,6 +37,7 @@
 import com.android.launcher3.util.Thunk;
 
 import java.util.List;
+import java.util.regex.Pattern;
 
 
 /**
@@ -46,14 +48,17 @@
         View.OnClickListener, View.OnLongClickListener {
 
     public static final boolean GRID_MERGE_SECTIONS = true;
-    public static final boolean GRID_MERGE_SECTION_HEADERS = false;
     public static final boolean GRID_HIDE_SECTION_HEADERS = false;
 
     private static final boolean ALLOW_SINGLE_APP_LAUNCH = true;
     private static final boolean DYNAMIC_HEADER_ELEVATION = false;
+    private static final boolean DISMISS_SEARCH_ON_BACK = true;
     private static final float HEADER_ELEVATION_DP = 4;
     private static final int FADE_IN_DURATION = 175;
-    private static final int FADE_OUT_DURATION = 125;
+    private static final int FADE_OUT_DURATION = 100;
+    private static final int SEARCH_TRANSLATION_X_DP = 18;
+
+    private static final Pattern SPLIT_PATTERN = Pattern.compile("[\\s|\\p{javaSpaceChar}]+");
 
     @Thunk Launcher mLauncher;
     @Thunk AlphabeticalAppsList mApps;
@@ -67,7 +72,7 @@
     private View mSearchBarContainerView;
     private View mSearchButtonView;
     private View mDismissSearchButtonView;
-    private EditText mSearchBarEditView;
+    private AppsContainerSearchEditTextView mSearchBarEditView;
 
     private int mNumAppsPerRow;
     private Point mLastTouchDownPos = new Point(-1, -1);
@@ -107,6 +112,13 @@
     }
 
     /**
+     * Sets the current set of predicted apps.
+     */
+    public void setPredictedApps(List<ComponentName> apps) {
+        mApps.setPredictedApps(apps);
+    }
+
+    /**
      * Sets the current set of apps.
      */
     public void setApps(List<AppInfo> apps) {
@@ -192,10 +204,19 @@
         mSearchBarContainerView = findViewById(R.id.app_search_container);
         mDismissSearchButtonView = mSearchBarContainerView.findViewById(R.id.dismiss_search_button);
         mDismissSearchButtonView.setOnClickListener(this);
-        mSearchBarEditView = (EditText) findViewById(R.id.app_search_box);
+        mSearchBarEditView = (AppsContainerSearchEditTextView) findViewById(R.id.app_search_box);
         if (mSearchBarEditView != null) {
             mSearchBarEditView.addTextChangedListener(this);
             mSearchBarEditView.setOnEditorActionListener(this);
+            if (DISMISS_SEARCH_ON_BACK) {
+                mSearchBarEditView.setOnBackKeyListener(
+                        new AppsContainerSearchEditTextView.OnBackKeyListener() {
+                            @Override
+                            public void onBackKey() {
+                                hideSearchField(true, true);
+                            }
+                        });
+            }
         }
         mAppsRecyclerView = (AppsContainerRecyclerView) findViewById(R.id.apps_list_view);
         mAppsRecyclerView.setApps(mApps);
@@ -420,23 +441,24 @@
 
     @Override
     public void afterTextChanged(final Editable s) {
-        if (s.toString().isEmpty()) {
+        String queryText = s.toString();
+        if (queryText.isEmpty()) {
             mApps.setFilter(null);
         } else {
             String formatStr = getResources().getString(R.string.apps_view_no_search_results);
-            mAdapter.setEmptySearchText(String.format(formatStr, s.toString()));
+            mAdapter.setEmptySearchText(String.format(formatStr, queryText));
 
-            final String filterText = s.toString().toLowerCase().replaceAll("\\s+", "");
+            final String queryTextLower = queryText.toLowerCase();
             mApps.setFilter(new AlphabeticalAppsList.Filter() {
                 @Override
                 public boolean retainApp(AppInfo info, String sectionName) {
-                    String title = info.title.toString();
-                    if (sectionName.toLowerCase().contains(filterText)) {
+                    if (sectionName.toLowerCase().contains(queryTextLower)) {
                         return true;
                     }
-                    String[] words = title.toLowerCase().split("\\s+");
+                    String title = info.title.toString();
+                    String[] words = SPLIT_PATTERN.split(title.toLowerCase());
                     for (int i = 0; i < words.length; i++) {
-                        if (words[i].startsWith(filterText)) {
+                        if (words[i].startsWith(queryTextLower)) {
                             return true;
                         }
                     }
@@ -563,9 +585,16 @@
      */
     private void showSearchField() {
         // Show the search bar and focus the search
+        final int translationX = DynamicGrid.pxFromDp(SEARCH_TRANSLATION_X_DP,
+                getContext().getResources().getDisplayMetrics());
         mSearchBarContainerView.setVisibility(View.VISIBLE);
         mSearchBarContainerView.setAlpha(0f);
-        mSearchBarContainerView.animate().alpha(1f).setDuration(FADE_IN_DURATION).withLayer()
+        mSearchBarContainerView.setTranslationX(translationX);
+        mSearchBarContainerView.animate()
+                .alpha(1f)
+                .translationX(0)
+                .setDuration(FADE_IN_DURATION)
+                .withLayer()
                 .withEndAction(new Runnable() {
                     @Override
                     public void run() {
@@ -574,38 +603,57 @@
                                 InputMethodManager.SHOW_IMPLICIT);
                     }
                 });
-        mSearchButtonView.animate().alpha(0f).setDuration(FADE_OUT_DURATION).withLayer();
+        mSearchButtonView.animate()
+                .alpha(0f)
+                .translationX(-translationX)
+                .setDuration(FADE_OUT_DURATION)
+                .withLayer();
     }
 
     /**
      * Hides the search field.
      */
     private void hideSearchField(boolean animated, final boolean returnFocusToRecyclerView) {
+        final boolean resetTextField = mSearchBarEditView.getText().toString().length() > 0;
+        final int translationX = DynamicGrid.pxFromDp(SEARCH_TRANSLATION_X_DP,
+                getContext().getResources().getDisplayMetrics());
         if (animated) {
             // Hide the search bar and focus the recycler view
-            mSearchBarContainerView.animate().alpha(0f).setDuration(FADE_IN_DURATION).withLayer()
+            mSearchBarContainerView.animate()
+                    .alpha(0f)
+                    .translationX(0)
+                    .setDuration(FADE_IN_DURATION)
+                    .withLayer()
                     .withEndAction(new Runnable() {
                         @Override
                         public void run() {
                             mSearchBarContainerView.setVisibility(View.INVISIBLE);
-                            mSearchBarEditView.setText("");
+                            if (resetTextField) {
+                                mSearchBarEditView.setText("");
+                            }
                             mApps.setFilter(null);
                             if (returnFocusToRecyclerView) {
                                 mAppsRecyclerView.requestFocus();
                             }
-                            scrollToTop();
                         }
                     });
-            mSearchButtonView.animate().alpha(1f).setDuration(FADE_OUT_DURATION).withLayer();
+            mSearchButtonView.setTranslationX(-translationX);
+            mSearchButtonView.animate()
+                    .alpha(1f)
+                    .translationX(0)
+                    .setDuration(FADE_OUT_DURATION)
+                    .withLayer();
         } else {
             mSearchBarContainerView.setVisibility(View.INVISIBLE);
-            mSearchBarEditView.setText("");
+            if (resetTextField) {
+                mSearchBarEditView.setText("");
+            }
             mApps.setFilter(null);
             mSearchButtonView.setAlpha(1f);
+            mSearchButtonView.setTranslationX(0f);
             if (returnFocusToRecyclerView) {
                 mAppsRecyclerView.requestFocus();
             }
-            scrollToTop();
         }
         getInputMethodManager().hideSoftInputFromWindow(getWindowToken(), 0);
     }
diff --git a/src/com/android/launcher3/AppsGridAdapter.java b/src/com/android/launcher3/AppsGridAdapter.java
index 62d9129..9ecb2ee 100644
--- a/src/com/android/launcher3/AppsGridAdapter.java
+++ b/src/com/android/launcher3/AppsGridAdapter.java
@@ -4,11 +4,10 @@
 import android.content.res.Resources;
 import android.graphics.Canvas;
 import android.graphics.Paint;
-import android.graphics.Point;
+import android.graphics.PointF;
 import android.graphics.Rect;
 import android.support.v7.widget.GridLayoutManager;
 import android.support.v7.widget.RecyclerView;
-import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
@@ -36,13 +35,11 @@
      */
     public static class ViewHolder extends RecyclerView.ViewHolder {
         public View mContent;
-        public boolean mIsSectionHeader;
         public boolean mIsEmptyRow;
 
-        public ViewHolder(View v, boolean isSectionHeader, boolean isEmptyRow) {
+        public ViewHolder(View v, boolean isEmptyRow) {
             super(v);
             mContent = v;
-            mIsSectionHeader = isSectionHeader;
             mIsEmptyRow = isEmptyRow;
         }
     }
@@ -84,7 +81,7 @@
 
         private static final boolean FADE_OUT_SECTIONS = false;
 
-        private HashMap<String, Point> mCachedSectionBounds = new HashMap<>();
+        private HashMap<String, PointF> mCachedSectionBounds = new HashMap<>();
         private Rect mTmpBounds = new Rect();
 
         @Override
@@ -94,75 +91,91 @@
             }
 
             List<AlphabeticalAppsList.AdapterItem> items = mApps.getAdapterItems();
-            String lastSectionName = null;
-            int appIndexInSection = 0;
+            boolean hasDrawnPredictedAppDivider = false;
+            int childCount = parent.getChildCount();
             int lastSectionTop = 0;
             int lastSectionHeight = 0;
-            for (int i = 0; i < parent.getChildCount(); i++) {
+            for (int i = 0; i < childCount; i++) {
                 View child = parent.getChildAt(i);
                 ViewHolder holder = (ViewHolder) parent.getChildViewHolder(child);
-                if (shouldDrawItemSection(holder, child, i, items)) {
-                    int cellTopOffset = (2 * child.getPaddingTop());
+                if (!isValidHolderAndChild(holder, child, items)) {
+                    continue;
+                }
+
+                if (shouldDrawItemDivider(holder, items) && !hasDrawnPredictedAppDivider) {
+                    // Draw the divider under the predicted app
+                    DeviceProfile grid = LauncherAppState.getInstance().getDynamicGrid().
+                            getDeviceProfile();
+                    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;
+
+                } else if (shouldDrawItemSection(holder, 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);
-                    if (!item.sectionName.equals(lastSectionName)) {
-                        lastSectionName = item.sectionName;
+                    AlphabeticalAppsList.SectionInfo sectionInfo = item.sectionInfo;
+
+                    // Draw all the sections for this index
+                    String lastSectionName = item.sectionName;
+                    for (int j = item.sectionAppIndex; j < sectionInfo.numApps; j++, pos++) {
+                        AlphabeticalAppsList.AdapterItem nextItem = items.get(pos);
+                        String sectionName = nextItem.sectionName;
+                        if (nextItem.sectionInfo != sectionInfo) {
+                            break;
+                        }
+                        if (j > item.sectionAppIndex && sectionName.equals(lastSectionName)) {
+                            continue;
+                        }
 
                         // Find the section code points
-                        String sectionBegin = null;
-                        String sectionEnd = null;
-                        int charOffset = 0;
-                        while (charOffset < item.sectionName.length()) {
-                            int codePoint = item.sectionName.codePointAt(charOffset);
-                            int codePointSize = Character.charCount(codePoint);
-                            if (charOffset == 0) {
-                                // The first code point
-                                sectionBegin = item.sectionName.substring(charOffset, charOffset + codePointSize);
-                            } else if ((charOffset + codePointSize) >= item.sectionName.length()) {
-                                // The last code point
-                                sectionEnd = item.sectionName.substring(charOffset, charOffset + codePointSize);
-                            }
-                            charOffset += codePointSize;
+                        PointF sectionBounds = getAndCacheSectionBounds(sectionName);
+
+                        // Calculate where to draw the section
+                        int sectionBaseline = (int) (viewTopOffset + sectionBounds.y);
+                        int x = mIsRtl ? parent.getWidth() - mPaddingStart - mStartMargin :
+                                mPaddingStart;
+                        x += (int) ((mStartMargin - sectionBounds.x) / 2f);
+                        int y = child.getTop() + sectionBaseline;
+
+                        // Determine whether this is the last row with apps in that section, if
+                        // so, then fix the section to the row allowing it to scroll past the
+                        // baseline, otherwise, bound it to the baseline so it's in the viewport
+                        int appIndexInSection = items.get(pos).sectionAppIndex;
+                        int nextRowPos = Math.min(items.size() - 1,
+                                pos + mAppsPerRow - (appIndexInSection % mAppsPerRow));
+                        AlphabeticalAppsList.AdapterItem nextRowItem = items.get(nextRowPos);
+                        boolean fixedToRow = !sectionName.equals(nextRowItem.sectionName);
+                        if (!fixedToRow) {
+                            y = Math.max(sectionBaseline, y);
                         }
 
-                        Point sectionBeginBounds = getAndCacheSectionBounds(sectionBegin);
-                        int minTop = cellTopOffset + sectionBeginBounds.y;
-                        int top = child.getTop() + cellTopOffset + sectionBeginBounds.y;
-                        int left = mIsRtl ? parent.getWidth() - mPaddingStart - mStartMargin :
-                                mPaddingStart;
-                        int col = appIndexInSection % mAppsPerRow;
-                        int nextRowPos = Math.min(pos - col + mAppsPerRow, items.size() - 1);
-                        int alpha = 255;
-                        boolean fixedToRow = !items.get(nextRowPos).sectionName.equals(item.sectionName);
-                        if (fixedToRow) {
-                            alpha = Math.min(255, (int) (255 * (Math.max(0, top) / (float) minTop)));
-                        } else {
-                            // If we aren't fixed to the current row, then bound into the viewport
-                            top = Math.max(minTop, top);
+                        // In addition, if it overlaps with the last section that was drawn, then
+                        // offset it so that it does not overlap
+                        if (lastSectionHeight > 0 && y <= (lastSectionTop + lastSectionHeight)) {
+                            y += lastSectionTop - y + lastSectionHeight;
                         }
-                        if (lastSectionHeight > 0 && top <= (lastSectionTop + lastSectionHeight)) {
-                            top += lastSectionTop - top + lastSectionHeight;
-                        }
+
+                        // Draw the section header
                         if (FADE_OUT_SECTIONS) {
+                            int alpha = 255;
+                            if (fixedToRow) {
+                                alpha = Math.min(255,
+                                        (int) (255 * (Math.max(0, y) / (float) sectionBaseline)));
+                            }
                             mSectionTextPaint.setAlpha(alpha);
                         }
-                        if (sectionEnd != null) {
-                            Point sectionEndBounds = getAndCacheSectionBounds(sectionEnd);
-                            c.drawText(sectionBegin + "/" + sectionEnd,
-                                    left + (mStartMargin - sectionBeginBounds.x - sectionEndBounds.x) / 2, top,
-                                    mSectionTextPaint);
-                        } else {
-                            c.drawText(sectionBegin, left + (mStartMargin - sectionBeginBounds.x) / 2, top,
-                                    mSectionTextPaint);
-                        }
-                        lastSectionTop = top;
-                        lastSectionHeight = sectionBeginBounds.y + mSectionHeaderOffset;
+                        c.drawText(sectionName, x, y, mSectionTextPaint);
+
+                        lastSectionTop = y;
+                        lastSectionHeight = (int) (sectionBounds.y + mSectionHeaderOffset);
+                        lastSectionName = sectionName;
                     }
-                }
-                if (holder.mIsSectionHeader) {
-                    appIndexInSection = 0;
-                } else {
-                    appIndexInSection++;
+                    i += (sectionInfo.numApps - item.sectionAppIndex);
                 }
             }
         }
@@ -173,17 +186,23 @@
             // Do nothing
         }
 
-        private Point getAndCacheSectionBounds(String sectionName) {
-            Point bounds = mCachedSectionBounds.get(sectionName);
+        /**
+         * Given a section name, return the bounds of the given section name.
+         */
+        private PointF getAndCacheSectionBounds(String sectionName) {
+            PointF bounds = mCachedSectionBounds.get(sectionName);
             if (bounds == null) {
                 mSectionTextPaint.getTextBounds(sectionName, 0, sectionName.length(), mTmpBounds);
-                bounds = new Point(mTmpBounds.width(), mTmpBounds.height());
+                bounds = new PointF(mSectionTextPaint.measureText(sectionName), mTmpBounds.height());
                 mCachedSectionBounds.put(sectionName, bounds);
             }
             return bounds;
         }
 
-        private boolean shouldDrawItemSection(ViewHolder holder, View child, int childIndex,
+        /**
+         * Returns whether we consider this a valid view holder for us to draw a divider or section for.
+         */
+        private boolean isValidHolderAndChild(ViewHolder holder, View child,
                 List<AlphabeticalAppsList.AdapterItem> items) {
             // Ensure item is not already removed
             GridLayoutManager.LayoutParams lp = (GridLayoutManager.LayoutParams)
@@ -195,25 +214,44 @@
             if (holder == null) {
                 return false;
             }
-            // Ensure it's not an empty row
-            if (holder.mIsEmptyRow) {
-                return false;
-            }
             // Ensure we have a holder position
             int pos = holder.getPosition();
             if (pos < 0 || pos >= items.size()) {
                 return false;
             }
-            // Ensure this is not a section header
-            if (items.get(pos).isSectionHeader) {
+            return true;
+        }
+
+        /**
+         * Returns whether to draw the divider for a given child.
+         */
+        private boolean shouldDrawItemDivider(ViewHolder holder, List<AlphabeticalAppsList.AdapterItem> items) {
+            int pos = holder.getPosition();
+            return items.get(pos).isPredictedApp;
+        }
+
+        /**
+         * Returns whether to draw the section for the given child.
+         */
+        private boolean shouldDrawItemSection(ViewHolder holder, int childIndex,
+                List<AlphabeticalAppsList.AdapterItem> items) {
+            int pos = holder.getPosition();
+            AlphabeticalAppsList.AdapterItem item = items.get(pos);
+
+            // Ensure it's not an empty row
+            if (holder.mIsEmptyRow) {
                 return false;
             }
-            // Only draw the header for the first item in a section, or whenever the sub-sections
-            // changes (if AppsContainerView.GRID_MERGE_SECTIONS is true, but
-            // AppsContainerView.GRID_MERGE_SECTION_HEADERS is false)
-            return (childIndex == 0) ||
-                    items.get(pos - 1).isSectionHeader && !items.get(pos).isSectionHeader ||
-                    (!items.get(pos - 1).sectionName.equals(items.get(pos).sectionName));
+            // Ensure this is not a section break
+            if (item.isSectionHeader) {
+                return false;
+            }
+            // Ensure this is not a predicted app
+            if (item.isPredictedApp) {
+                return false;
+            }
+            // Draw the section header for the first item in each section
+            return (childIndex == 0) || (items.get(pos - 1).isSectionHeader && !item.isSectionHeader);
         }
     }
 
@@ -234,6 +272,7 @@
     @Thunk int mStartMargin;
     @Thunk int mSectionHeaderOffset;
     @Thunk Paint mSectionTextPaint;
+    @Thunk Paint mPredictedAppsDividerPaint;
 
 
     public AppsGridAdapter(Context context, AlphabeticalAppsList apps, int appsPerRow,
@@ -256,11 +295,17 @@
             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(
                 R.dimen.apps_view_section_text_size));
         mSectionTextPaint.setColor(res.getColor(R.color.apps_view_section_text_color));
         mSectionTextPaint.setAntiAlias(true);
+
+        mPredictedAppsDividerPaint = new Paint();
+        mPredictedAppsDividerPaint.setStrokeWidth(DynamicGrid.pxFromDp(1.5f, res.getDisplayMetrics()));
+        mPredictedAppsDividerPaint.setColor(0x10000000);
+        mPredictedAppsDividerPaint.setAntiAlias(true);
     }
 
     /**
@@ -315,10 +360,9 @@
         switch (viewType) {
             case EMPTY_VIEW_TYPE:
                 return new ViewHolder(mLayoutInflater.inflate(R.layout.apps_empty_view, parent,
-                        false), false /* isSectionRow */, true /* isEmptyRow */);
+                        false), true /* isEmptyRow */);
             case SECTION_BREAK_VIEW_TYPE:
-                return new ViewHolder(new View(parent.getContext()), true /* isSectionRow */,
-                        false /* isEmptyRow */);
+                return new ViewHolder(new View(parent.getContext()), false /* isEmptyRow */);
             case ICON_VIEW_TYPE:
                 BubbleTextView icon = (BubbleTextView) mLayoutInflater.inflate(
                         R.layout.apps_grid_row_icon_view, parent, false);
@@ -326,7 +370,7 @@
                 icon.setOnClickListener(mIconClickListener);
                 icon.setOnLongClickListener(mIconLongClickListener);
                 icon.setFocusable(true);
-                return new ViewHolder(icon, false /* isSectionRow */, false /* isEmptyRow */);
+                return new ViewHolder(icon, false /* isEmptyRow */);
             default:
                 throw new RuntimeException("Unexpected view type");
         }
diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java
index 65c6702..72eabf1 100644
--- a/src/com/android/launcher3/CellLayout.java
+++ b/src/com/android/launcher3/CellLayout.java
@@ -3051,4 +3051,21 @@
     public boolean findVacantCell(int spanX, int spanY, int[] outXY) {
         return Utilities.findVacantCell(outXY, spanX, spanY, mCountX, mCountY, mOccupied);
     }
+
+    public boolean isRegionVacant(int x, int y, int spanX, int spanY) {
+        int x2 = x + spanX - 1;
+        int y2 = y + spanY - 1;
+        if (x < 0 || y < 0 || x2 >= mCountX || y2 >= mCountY) {
+            return false;
+        }
+        for (int i = x; i <= x2; i++) {
+            for (int j = y; j <= y2; j++) {
+                if (mOccupied[i][j]) {
+                    return false;
+                }
+            }
+        }
+
+        return true;
+    }
 }
diff --git a/src/com/android/launcher3/DragLayer.java b/src/com/android/launcher3/DragLayer.java
index 91f97fa..2efdb06 100644
--- a/src/com/android/launcher3/DragLayer.java
+++ b/src/com/android/launcher3/DragLayer.java
@@ -38,7 +38,6 @@
 import android.widget.FrameLayout;
 import android.widget.TextView;
 
-import com.android.launcher3.InsettableFrameLayout.LayoutParams;
 import com.android.launcher3.util.Thunk;
 
 import java.util.ArrayList;
@@ -153,6 +152,14 @@
         return false;
     }
 
+    private boolean isEventOverDropTargetBar(MotionEvent ev) {
+        getDescendantRectRelativeToSelf(mLauncher.getSearchBar(), mHitRect);
+        if (mHitRect.contains((int) ev.getX(), (int) ev.getY())) {
+            return true;
+        }
+        return false;
+    }
+
     public void setBlockTouch(boolean block) {
         mBlockTouches = block;
     }
@@ -188,10 +195,16 @@
                 }
             }
 
-            getDescendantRectRelativeToSelf(currentFolder, hitRect);
             if (!isEventOverFolder(currentFolder, ev)) {
-                mLauncher.closeFolder();
-                return true;
+                if (isInAccessibleDrag()) {
+                    // Do not close the folder if in drag and drop.
+                    if (!isEventOverDropTargetBar(ev)) {
+                        return true;
+                    }
+                } else {
+                    mLauncher.closeFolder();
+                    return true;
+                }
             }
         }
         return false;
@@ -228,11 +241,12 @@
                         getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
             if (accessibilityManager.isTouchExplorationEnabled()) {
                 final int action = ev.getAction();
-                boolean isOverFolder;
+                boolean isOverFolderOrSearchBar;
                 switch (action) {
                     case MotionEvent.ACTION_HOVER_ENTER:
-                        isOverFolder = isEventOverFolder(currentFolder, ev);
-                        if (!isOverFolder) {
+                        isOverFolderOrSearchBar = isEventOverFolder(currentFolder, ev) ||
+                            (isInAccessibleDrag() && isEventOverDropTargetBar(ev));
+                        if (!isOverFolderOrSearchBar) {
                             sendTapOutsideFolderAccessibilityEvent(currentFolder.isEditingName());
                             mHoverPointClosesFolder = true;
                             return true;
@@ -240,12 +254,13 @@
                         mHoverPointClosesFolder = false;
                         break;
                     case MotionEvent.ACTION_HOVER_MOVE:
-                        isOverFolder = isEventOverFolder(currentFolder, ev);
-                        if (!isOverFolder && !mHoverPointClosesFolder) {
+                        isOverFolderOrSearchBar = isEventOverFolder(currentFolder, ev) ||
+                            (isInAccessibleDrag() && isEventOverDropTargetBar(ev));
+                        if (!isOverFolderOrSearchBar && !mHoverPointClosesFolder) {
                             sendTapOutsideFolderAccessibilityEvent(currentFolder.isEditingName());
                             mHoverPointClosesFolder = true;
                             return true;
-                        } else if (!isOverFolder) {
+                        } else if (!isOverFolderOrSearchBar) {
                             return true;
                         }
                         mHoverPointClosesFolder = false;
@@ -268,6 +283,12 @@
         }
     }
 
+    private boolean isInAccessibleDrag() {
+        LauncherAccessibilityDelegate delegate = LauncherAppState
+                .getInstance().getAccessibilityDelegate();
+        return delegate != null && delegate.isInAccessibleDrag();
+    }
+
     @Override
     public boolean onRequestSendAccessibilityEvent(View child, AccessibilityEvent event) {
         Folder currentFolder = mLauncher.getWorkspace().getOpenFolder();
@@ -275,6 +296,10 @@
             if (child == currentFolder) {
                 return super.onRequestSendAccessibilityEvent(child, event);
             }
+
+            if (isInAccessibleDrag() && child instanceof SearchDropTargetBar) {
+                return super.onRequestSendAccessibilityEvent(child, event);
+            }
             // Skip propagating onRequestSendAccessibilityEvent all for other children
             // when a folder is open
             return false;
@@ -288,6 +313,10 @@
         if (currentFolder != null) {
             // Only add the folder as a child for accessibility when it is open
             childrenForAccessibility.add(currentFolder);
+
+            if (isInAccessibleDrag()) {
+                childrenForAccessibility.add(mLauncher.getSearchBar());
+            }
         } else {
             super.addChildrenForAccessibility(childrenForAccessibility);
         }
diff --git a/src/com/android/launcher3/DragView.java b/src/com/android/launcher3/DragView.java
index a4b6704..3eec3d9 100644
--- a/src/com/android/launcher3/DragView.java
+++ b/src/com/android/launcher3/DragView.java
@@ -129,6 +129,10 @@
         int ms = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
         measure(ms, ms);
         mPaint = new Paint(Paint.FILTER_BITMAP_FLAG);
+
+        if (Utilities.isLmpOrAbove()) {
+            setElevation(getResources().getDimension(R.dimen.drag_elevation));
+        }
     }
 
     /** Sets the scale of the view over the normal workspace icon size. */
diff --git a/src/com/android/launcher3/Folder.java b/src/com/android/launcher3/Folder.java
index a282805..6ca4888 100644
--- a/src/com/android/launcher3/Folder.java
+++ b/src/com/android/launcher3/Folder.java
@@ -27,6 +27,7 @@
 import android.graphics.Point;
 import android.graphics.PointF;
 import android.graphics.Rect;
+import android.graphics.drawable.InsetDrawable;
 import android.os.Build;
 import android.text.InputType;
 import android.text.Selection;
@@ -180,6 +181,15 @@
         // name is complete, we have something to focus on, thus hiding the cursor and giving
         // reliable behavior when clicking the text field (since it will always gain focus on click).
         setFocusableInTouchMode(true);
+
+        if (Utilities.isLmpOrAbove()) {
+            int padding = getResources().getDimensionPixelSize(R.dimen.folder_shadow_padding);
+            setBackground(new InsetDrawable(
+                    getResources().getDrawable(R.drawable.apps_list_bg),
+                    padding, padding, padding, padding));
+        } else {
+            setBackgroundResource(R.drawable.quantum_panel);
+        }
     }
 
     @Override
@@ -275,6 +285,9 @@
         for (int i = 0; i < mContent.getChildCount(); i++) {
             mContent.getPageAt(i).enableAccessibleDrag(enable, CellLayout.FOLDER_ACCESSIBILITY_DRAG);
         }
+
+        mFooter.setImportantForAccessibility(enable ? IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS :
+            IMPORTANT_FOR_ACCESSIBILITY_AUTO);
         mLauncher.getWorkspace().setAddNewPageOnDrag(!enable);
     }
 
@@ -296,13 +309,14 @@
         mFolderName.setHint(sHintText);
         // Convert to a string here to ensure that no other state associated with the text field
         // gets saved.
-        String newTitle = mFolderName.getText().toString();
+        CharSequence newTitle = mFolderName.getText();
         mInfo.setTitle(newTitle);
         LauncherModel.updateItemInDatabase(mLauncher, mInfo);
 
         if (commit) {
             sendCustomAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED,
-                    String.format(getContext().getString(R.string.folder_renamed), newTitle));
+                    String.format(getContext().getString(R.string.folder_renamed),
+                            newTitle.toString()));
         }
         // In order to clear the focus from the text field, we set the focus on ourself. This
         // ensures that every time the field is clicked, focus is gained, giving reliable behavior.
@@ -470,9 +484,15 @@
             PropertyValuesHolder tx = PropertyValuesHolder.ofFloat("translationX", transX, 0);
             PropertyValuesHolder ty = PropertyValuesHolder.ofFloat("translationY", transY, 0);
 
+            Animator drift = LauncherAnimUtils.ofPropertyValuesHolder(this, tx, ty);
+            drift.setDuration(mMaterialExpandDuration);
+            drift.setStartDelay(mMaterialExpandStagger);
+            drift.setInterpolator(new LogDecelerateInterpolator(100, 0));
+
             int rx = (int) Math.max(Math.max(width - getPivotX(), 0), getPivotX());
             int ry = (int) Math.max(Math.max(height - getPivotY(), 0), getPivotY());
             float radius = (float) Math.hypot(rx, ry);
+
             AnimatorSet anim = LauncherAnimUtils.createAnimatorSet();
             Animator reveal = LauncherAnimUtils.createCircularReveal(this, (int) getPivotX(),
                     (int) getPivotY(), 0, radius);
@@ -491,10 +511,6 @@
             textAlpha.setStartDelay(mMaterialExpandStagger);
             textAlpha.setInterpolator(new AccelerateInterpolator(1.5f));
 
-            Animator drift = LauncherAnimUtils.ofPropertyValuesHolder(this, tx, ty);
-            drift.setDuration(mMaterialExpandDuration);
-            drift.setStartDelay(mMaterialExpandStagger);
-            drift.setInterpolator(new LogDecelerateInterpolator(60, 0));
 
             anim.play(drift);
             anim.play(iconsAlpha);
@@ -504,10 +520,12 @@
             openFolderAnim = anim;
 
             mContentWrapper.setLayerType(LAYER_TYPE_HARDWARE, null);
+            mFooter.setLayerType(LAYER_TYPE_HARDWARE, null);
             onCompleteRunnable = new Runnable() {
                 @Override
                 public void run() {
                     mContentWrapper.setLayerType(LAYER_TYPE_NONE, null);
+                    mContentWrapper.setLayerType(LAYER_TYPE_NONE, null);
                 }
             };
         }
diff --git a/src/com/android/launcher3/FolderIcon.java b/src/com/android/launcher3/FolderIcon.java
index f5836c2..b161b1c 100644
--- a/src/com/android/launcher3/FolderIcon.java
+++ b/src/com/android/launcher3/FolderIcon.java
@@ -710,7 +710,7 @@
     }
 
     public void onTitleChanged(CharSequence title) {
-        mFolderName.setText(title.toString());
+        mFolderName.setText(title);
         setContentDescription(String.format(getContext().getString(R.string.folder_name_format),
                 title));
     }
diff --git a/src/com/android/launcher3/FolderInfo.java b/src/com/android/launcher3/FolderInfo.java
index 80b1564..9675371 100644
--- a/src/com/android/launcher3/FolderInfo.java
+++ b/src/com/android/launcher3/FolderInfo.java
@@ -87,7 +87,7 @@
     }
 
     public void setTitle(CharSequence title) {
-        this.title = title;
+        this.title = Utilities.trim(title);
         for (int i = 0; i < listeners.size(); i++) {
             listeners.get(i).onTitleChanged(title);
         }
diff --git a/src/com/android/launcher3/FolderPagedView.java b/src/com/android/launcher3/FolderPagedView.java
index a1c909a..211bbfe 100644
--- a/src/com/android/launcher3/FolderPagedView.java
+++ b/src/com/android/launcher3/FolderPagedView.java
@@ -504,6 +504,10 @@
         }
     }
 
+    public int getAllocatedContentSize() {
+        return mAllocatedContentSize;
+    }
+
     /**
      * Reorders the items such that the {@param empty} spot moves to {@param target}
      */
diff --git a/src/com/android/launcher3/IconCache.java b/src/com/android/launcher3/IconCache.java
index 6c2aa39..0596fbe 100644
--- a/src/com/android/launcher3/IconCache.java
+++ b/src/com/android/launcher3/IconCache.java
@@ -400,7 +400,7 @@
         UserHandleCompat user = info == null ? application.user : info.getUser();
         CacheEntry entry = cacheLocked(application.componentName, info, user,
                 false, useLowResIcon);
-        application.title = entry.title;
+        application.title = Utilities.trim(entry.title);
         application.iconBitmap = getNonNullIcon(entry, user);
         application.contentDescription = entry.contentDescription;
         application.usingLowResIcon = entry.isLowResIcon;
@@ -413,7 +413,7 @@
         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.title = Utilities.trim(entry.title);
             application.iconBitmap = entry.icon;
             application.contentDescription = entry.contentDescription;
             application.usingLowResIcon = entry.isLowResIcon;
@@ -464,7 +464,7 @@
             UserHandleCompat user, boolean usePkgIcon, boolean useLowResIcon) {
         CacheEntry entry = cacheLocked(component, info, user, usePkgIcon, useLowResIcon);
         shortcutInfo.setIcon(getNonNullIcon(entry, user));
-        shortcutInfo.title = entry.title;
+        shortcutInfo.title = Utilities.trim(entry.title);
         shortcutInfo.usingFallbackIcon = isDefaultIcon(entry.icon, user);
         shortcutInfo.usingLowResIcon = entry.isLowResIcon;
     }
@@ -477,7 +477,7 @@
             PackageItemInfo infoOut) {
         CacheEntry entry = getEntryForPackageLocked(packageName, user, useLowResIcon);
         infoOut.iconBitmap = getNonNullIcon(entry, user);
-        infoOut.title = entry.title;
+        infoOut.title = Utilities.trim(entry.title);
         infoOut.usingLowResIcon = entry.isLowResIcon;
         infoOut.contentDescription = entry.contentDescription;
     }
@@ -530,7 +530,7 @@
             }
 
             if (TextUtils.isEmpty(entry.title) && info != null) {
-                entry.title = info.getLabel().toString();
+                entry.title = info.getLabel();
                 entry.contentDescription = mUserManager.getBadgedLabelForUser(entry.title, user);
             }
         }
diff --git a/src/com/android/launcher3/InstallShortcutReceiver.java b/src/com/android/launcher3/InstallShortcutReceiver.java
index 23bcc85..115598f 100644
--- a/src/com/android/launcher3/InstallShortcutReceiver.java
+++ b/src/com/android/launcher3/InstallShortcutReceiver.java
@@ -247,7 +247,7 @@
             try {
                 PackageManager pm = context.getPackageManager();
                 ActivityInfo info = pm.getActivityInfo(intent.getComponent(), 0);
-                name = info.loadLabel(pm).toString();
+                name = info.loadLabel(pm);
             } catch (PackageManager.NameNotFoundException nnfe) {
                 return "";
             }
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 5645759..11b1e9a 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -64,6 +64,7 @@
 import android.os.Message;
 import android.os.StrictMode;
 import android.os.SystemClock;
+import android.preference.PreferenceManager;
 import android.text.Selection;
 import android.text.SpannableStringBuilder;
 import android.text.TextUtils;
@@ -1018,16 +1019,22 @@
         if (mOnResumeState == State.WORKSPACE) {
             showWorkspace(false);
         } else if (mOnResumeState == State.APPS) {
-            showAppsView(false /* animated */, false /* resetListToTop */);
+            // Don't update the predicted apps if the user is returning to launcher in the apps
+            // view as they may be depending on the UI to be static to switch to another app
+            showAppsView(false /* animated */, false /* resetListToTop */,
+                    false /* updatePredictedApps */);
         } else if (mOnResumeState == State.WIDGETS) {
             showWidgetsView(false, false);
         }
         mOnResumeState = State.NONE;
 
         // Restore the apps state if we are in all apps
-        if (!Launcher.DISABLE_ALL_APPS_SEARCH_INTEGRATION && mState == State.APPS) {
-            if (mLauncherCallbacks != null) {
-                mLauncherCallbacks.onAllAppsShown();
+        if (!Launcher.DISABLE_ALL_APPS_SEARCH_INTEGRATION) {
+            // Otherwise, notify the callbacks if we are in all apps mode
+            if (mState == State.APPS) {
+                if (mLauncherCallbacks != null) {
+                    mLauncherCallbacks.onAllAppsShown();
+                }
             }
         }
 
@@ -2619,7 +2626,9 @@
         if (isAppsViewVisible()) {
             showWorkspace(true);
         } else {
-            showAppsView(true /* animated */, false /* resetListToTop */);
+            // Try and refresh the set of predicted apps before we enter launcher
+            showAppsView(true /* animated */, false /* resetListToTop */,
+                    true /* updatePredictedApps */);
         }
     }
 
@@ -3397,10 +3406,13 @@
     /**
      * Shows the apps view.
      */
-    void showAppsView(boolean animated, boolean resetListToTop) {
+    void showAppsView(boolean animated, boolean resetListToTop, boolean updatePredictedApps) {
         if (resetListToTop) {
             mAppsView.scrollToTop();
         }
+        if (updatePredictedApps) {
+            tryAndUpdatePredictedApps();
+        }
         showAppsOrWidgets(animated, State.APPS);
     }
 
@@ -3509,12 +3521,26 @@
 
     void exitSpringLoadedDragMode() {
         if (mState == State.APPS_SPRING_LOADED) {
-            showAppsView(true, false);
+            showAppsView(true /* animated */, false /* resetListToTop */,
+                    false /* updatePredictedApps */);
         } else if (mState == State.WIDGETS_SPRING_LOADED) {
             showWidgetsView(true, false);
         }
     }
 
+    /**
+     * Updates the set of predicted apps if it hasn't been updated since the last time Launcher was
+     * resumed.
+     */
+    private void tryAndUpdatePredictedApps() {
+        if (mLauncherCallbacks != null) {
+            List<ComponentName> apps = mLauncherCallbacks.getPredictedApps();
+            if (!apps.isEmpty()) {
+                mAppsView.setPredictedApps(apps);
+            }
+        }
+    }
+
     void lockAllApps() {
         // TODO
     }
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/LauncherAppWidgetProviderInfo.java b/src/com/android/launcher3/LauncherAppWidgetProviderInfo.java
index bb4580c..af680f2 100644
--- a/src/com/android/launcher3/LauncherAppWidgetProviderInfo.java
+++ b/src/com/android/launcher3/LauncherAppWidgetProviderInfo.java
@@ -66,7 +66,7 @@
 
     public String getLabel(PackageManager packageManager) {
         if (isCustomWidget) {
-            return label.toString().trim();
+            return Utilities.trim(label);
         }
         return super.loadLabel(packageManager);
     }
diff --git a/src/com/android/launcher3/LauncherCallbacks.java b/src/com/android/launcher3/LauncherCallbacks.java
index 25c86c9..0124d1f 100644
--- a/src/com/android/launcher3/LauncherCallbacks.java
+++ b/src/com/android/launcher3/LauncherCallbacks.java
@@ -11,6 +11,7 @@
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.List;
 
 /**
  * LauncherCallbacks is an interface used to extend the Launcher activity. It includes many hooks
@@ -90,6 +91,7 @@
     public boolean overrideWallpaperDimensions();
     public boolean isLauncherPreinstalled();
     public boolean overrideAllAppsSearch();
+    public List<ComponentName> getPredictedApps();
 
     /**
      * Returning true will immediately result in a call to {@link #setLauncherOverlayView(ViewGroup,
diff --git a/src/com/android/launcher3/LauncherExtension.java b/src/com/android/launcher3/LauncherExtension.java
index 8174af0..14ad601 100644
--- a/src/com/android/launcher3/LauncherExtension.java
+++ b/src/com/android/launcher3/LauncherExtension.java
@@ -15,6 +15,7 @@
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.List;
 
 /**
  * This class represents a very trivial LauncherExtension. It primarily serves as a simple
@@ -259,6 +260,11 @@
         }
 
         @Override
+        public List<ComponentName> getPredictedApps() {
+            return new ArrayList<>();
+        }
+
+        @Override
         public boolean isLauncherPreinstalled() {
             return false;
         }
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index e81c8c2..3987c02 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -969,7 +969,7 @@
                         break;
                 }
 
-                folderInfo.title = c.getString(titleIndex);
+                folderInfo.title = Utilities.trim(c.getString(titleIndex));
                 folderInfo.id = id;
                 folderInfo.container = c.getInt(containerIndex);
                 folderInfo.screenId = c.getInt(screenIndex);
@@ -2144,7 +2144,7 @@
                                 id = c.getLong(idIndex);
                                 FolderInfo folderInfo = findOrMakeFolder(sBgFolders, id);
 
-                                folderInfo.title = c.getString(titleIndex);
+                                folderInfo.title = Utilities.trim(c.getString(titleIndex));
                                 folderInfo.id = id;
                                 container = c.getInt(containerIndex);
                                 folderInfo.container = container;
@@ -3199,7 +3199,7 @@
                                 if (appInfo != null && Intent.ACTION_MAIN.equals(si.intent.getAction())
                                         && si.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
                                     si.updateIcon(mIconCache);
-                                    si.title = appInfo.title.toString();
+                                    si.title = Utilities.trim(appInfo.title);
                                     si.contentDescription = appInfo.contentDescription;
                                     infoUpdated = true;
                                 }
@@ -3428,18 +3428,17 @@
         if ((promiseType & ShortcutInfo.FLAG_RESTORED_ICON) != 0) {
             String title = (cursor != null) ? cursor.getString(titleIndex) : null;
             if (!TextUtils.isEmpty(title)) {
-                info.title = title;
+                info.title = Utilities.trim(title);
             }
         } else if  ((promiseType & ShortcutInfo.FLAG_AUTOINTALL_ICON) != 0) {
             if (TextUtils.isEmpty(info.title)) {
-                info.title = (cursor != null) ? cursor.getString(titleIndex) : "";
+                info.title = (cursor != null) ? Utilities.trim(cursor.getString(titleIndex)) : "";
             }
         } else {
             throw new InvalidParameterException("Invalid restoreType " + promiseType);
         }
 
-        info.contentDescription = mUserManager.getBadgedLabelForUser(
-                info.title.toString(), info.user);
+        info.contentDescription = mUserManager.getBadgedLabelForUser(info.title, info.user);
         info.itemType = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
         info.promisedIntent = intent;
         info.status = promiseType;
@@ -3501,7 +3500,7 @@
 
         // from the db
         if (TextUtils.isEmpty(info.title) && c != null) {
-            info.title =  c.getString(titleIndex);
+            info.title =  Utilities.trim(c.getString(titleIndex));
         }
 
         // fall back to the class name of the activity
@@ -3511,8 +3510,7 @@
 
         info.itemType = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
         info.user = user;
-        info.contentDescription = mUserManager.getBadgedLabelForUser(
-                info.title.toString(), info.user);
+        info.contentDescription = mUserManager.getBadgedLabelForUser(info.title, info.user);
         if (lai != null) {
             info.flags = AppInfo.initFlags(lai);
         }
@@ -3578,7 +3576,7 @@
 
         // TODO: If there's an explicit component and we can't install that, delete it.
 
-        info.title = c.getString(titleIndex);
+        info.title = Utilities.trim(c.getString(titleIndex));
 
         int iconType = c.getInt(iconTypeIndex);
         switch (iconType) {
@@ -3656,9 +3654,8 @@
         }
         info.setIcon(icon);
 
-        info.title = name;
-        info.contentDescription = mUserManager.getBadgedLabelForUser(
-                info.title.toString(), info.user);
+        info.title = Utilities.trim(name);
+        info.contentDescription = mUserManager.getBadgedLabelForUser(info.title, info.user);
         info.intent = intent;
         info.customIcon = customIcon;
         info.iconResource = iconResource;
@@ -3699,16 +3696,16 @@
                 labelA = mLabelCache.get(a);
             } else {
                 labelA = (a instanceof LauncherAppWidgetProviderInfo)
-                        ? mManager.loadLabel((LauncherAppWidgetProviderInfo) a)
-                        : ((ResolveInfo) a).loadLabel(mPackageManager).toString().trim();
+                        ? Utilities.trim(mManager.loadLabel((LauncherAppWidgetProviderInfo) a))
+                        : Utilities.trim(((ResolveInfo) a).loadLabel(mPackageManager));
                 mLabelCache.put(a, labelA);
             }
             if (mLabelCache.containsKey(b)) {
                 labelB = mLabelCache.get(b);
             } else {
                 labelB = (b instanceof LauncherAppWidgetProviderInfo)
-                        ? mManager.loadLabel((LauncherAppWidgetProviderInfo) b)
-                        : ((ResolveInfo) b).loadLabel(mPackageManager).toString().trim();
+                        ? Utilities.trim(mManager.loadLabel((LauncherAppWidgetProviderInfo) b))
+                        : Utilities.trim(((ResolveInfo) b).loadLabel(mPackageManager));
                 mLabelCache.put(b, labelB);
             }
             return mCollator.compare(labelA, labelB);
diff --git a/src/com/android/launcher3/ShortcutInfo.java b/src/com/android/launcher3/ShortcutInfo.java
index 6354fcd..8be4872 100644
--- a/src/com/android/launcher3/ShortcutInfo.java
+++ b/src/com/android/launcher3/ShortcutInfo.java
@@ -153,7 +153,7 @@
             Bitmap icon, UserHandleCompat user) {
         this();
         this.intent = intent;
-        this.title = title;
+        this.title = Utilities.trim(title);
         this.contentDescription = contentDescription;
         mIcon = icon;
         this.user = user;
@@ -161,7 +161,7 @@
 
     public ShortcutInfo(Context context, ShortcutInfo info) {
         super(info);
-        title = info.title.toString();
+        title = Utilities.trim(info.title);
         intent = new Intent(info.intent);
         if (info.iconResource != null) {
             iconResource = new Intent.ShortcutIconResource();
@@ -179,7 +179,7 @@
     /** TODO: Remove this.  It's only called by ApplicationInfo.makeShortcut. */
     public ShortcutInfo(AppInfo info) {
         super(info);
-        title = info.title.toString();
+        title = Utilities.trim(info.title);
         intent = new Intent(info.intent);
         customIcon = false;
         flags = info.flags;
@@ -281,7 +281,7 @@
     public static ShortcutInfo fromActivityInfo(LauncherActivityInfoCompat info, Context context) {
         final ShortcutInfo shortcut = new ShortcutInfo();
         shortcut.user = info.getUser();
-        shortcut.title = info.getLabel().toString();
+        shortcut.title = Utilities.trim(info.getLabel());
         shortcut.contentDescription = UserManagerCompat.getInstance(context)
                 .getBadgedLabelForUser(info.getLabel(), info.getUser());
         shortcut.customIcon = false;
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index 2dbf078..2981747 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -54,6 +54,8 @@
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Comparator;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 /**
  * Various utilities shared amongst the Launcher's classes.
@@ -67,6 +69,9 @@
     private static final Rect sOldBounds = new Rect();
     private static final Canvas sCanvas = new Canvas();
 
+    private static final Pattern sTrimPattern =
+            Pattern.compile("^[\\s|\\p{javaSpaceChar}]*(.*)[\\s|\\p{javaSpaceChar}]*$");
+
     static {
         sCanvas.setDrawFilter(new PaintFlagsDrawFilter(Paint.DITHER_FLAG,
                 Paint.FILTER_BITMAP_FLAG));
@@ -616,4 +621,14 @@
 
         return false;
     }
+
+    /**
+     * Trims the string, removing all whitespace at the beginning and end of the string.
+     * Non-breaking whitespaces are also removed.
+     */
+    public static String trim(CharSequence s) {
+        // Just strip any sequence of whitespace or java space characters from the beginning and end
+        Matcher m = sTrimPattern.matcher(s);
+        return m.replaceAll("$1");
+    }
 }
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 5c2121a..55deb85 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -4185,7 +4185,7 @@
                             && packageNames.contains(cn.getPackageName())) {
                         shortcutInfo.isDisabled |= reason;
                         BubbleTextView shortcut = (BubbleTextView) v;
-                        shortcut.applyFromShortcutInfo(shortcutInfo, mIconCache, true, false);
+                        shortcut.applyFromShortcutInfo(shortcutInfo, mIconCache, false);
 
                         if (parent != null) {
                             parent.invalidate();
@@ -4371,7 +4371,7 @@
                     BubbleTextView shortcut = (BubbleTextView) v;
                     boolean oldPromiseState = getTextViewIcon(shortcut)
                             instanceof PreloadIconDrawable;
-                    shortcut.applyFromShortcutInfo(si, mIconCache, true,
+                    shortcut.applyFromShortcutInfo(si, mIconCache, false,
                             si.isPromise() != oldPromiseState);
 
                     if (parent != null) {
diff --git a/src/com/android/launcher3/accessibility/FolderAccessibilityHelper.java b/src/com/android/launcher3/accessibility/FolderAccessibilityHelper.java
index fc105b4..ff99890 100644
--- a/src/com/android/launcher3/accessibility/FolderAccessibilityHelper.java
+++ b/src/com/android/launcher3/accessibility/FolderAccessibilityHelper.java
@@ -24,23 +24,29 @@
  * Implementation of {@link DragAndDropAccessibilityDelegate} to support DnD in a folder.
  */
 public class FolderAccessibilityHelper extends DragAndDropAccessibilityDelegate {
+
+    /**
+     * 0-index position for the first cell in {@link #mView} in {@link #mParent}.
+     */
     private final int mStartPosition;
 
+    private final FolderPagedView mParent;
+
     public FolderAccessibilityHelper(CellLayout layout) {
         super(layout);
-        FolderPagedView parent = (FolderPagedView) layout.getParent();
+        mParent = (FolderPagedView) layout.getParent();
 
-        int index = parent.indexOfChild(layout);
-        mStartPosition = 1 + index * layout.getCountX() * layout.getCountY();
+        int index = mParent.indexOfChild(layout);
+        mStartPosition = index * layout.getCountX() * layout.getCountY();
     }
     @Override
     protected int intersectsValidDropTarget(int id) {
-        return id;
+        return Math.min(id, mParent.getAllocatedContentSize() - mStartPosition - 1);
     }
 
     @Override
     protected String getLocationDescriptionForIconDrop(int id) {
-        return mContext.getString(R.string.move_to_position, id + mStartPosition);
+        return mContext.getString(R.string.move_to_position, id + mStartPosition + 1);
     }
 
     @Override
diff --git a/src/com/android/launcher3/accessibility/WorkspaceAccessibilityHelper.java b/src/com/android/launcher3/accessibility/WorkspaceAccessibilityHelper.java
index 42e9e3c..6f89d0e 100644
--- a/src/com/android/launcher3/accessibility/WorkspaceAccessibilityHelper.java
+++ b/src/com/android/launcher3/accessibility/WorkspaceAccessibilityHelper.java
@@ -145,7 +145,7 @@
             if (info instanceof ShortcutInfo) {
                 return mContext.getString(R.string.create_folder_with, info.title);
             } else if (info instanceof FolderInfo) {
-                if (TextUtils.isEmpty(info.title.toString().trim())) {
+                if (TextUtils.isEmpty(info.title)) {
                     // Find the first item in the folder.
                     FolderInfo folder = (FolderInfo) info;
                     ShortcutInfo firstItem = null;
diff --git a/src/com/android/launcher3/compat/AlphabeticIndexCompat.java b/src/com/android/launcher3/compat/AlphabeticIndexCompat.java
index f890706..18cdc81 100644
--- a/src/com/android/launcher3/compat/AlphabeticIndexCompat.java
+++ b/src/com/android/launcher3/compat/AlphabeticIndexCompat.java
@@ -1,6 +1,7 @@
 package com.android.launcher3.compat;
 
 import android.content.Context;
+import com.android.launcher3.Utilities;
 
 import java.lang.reflect.Constructor;
 import java.lang.reflect.Method;
@@ -102,10 +103,11 @@
     /**
      * Computes the section name for an given string {@param s}.
      */
-    public String computeSectionName(String s) {
+    public String computeSectionName(CharSequence cs) {
+        String s = Utilities.trim(cs);
         String sectionName = getBucketLabel(getBucketIndex(s));
-        if (sectionName.trim().isEmpty() && s.length() > 0) {
-            boolean startsWithDigit = Character.isDigit(Character.codePointAt(s.trim(), 0));
+        if (Utilities.trim(sectionName).isEmpty() && s.length() > 0) {
+            boolean startsWithDigit = Character.isDigit(s.codePointAt(0));
             if (startsWithDigit) {
                 // Digit section
                 return "#";
diff --git a/src/com/android/launcher3/compat/AppWidgetManagerCompatV16.java b/src/com/android/launcher3/compat/AppWidgetManagerCompatV16.java
index 767f16f..967b53b 100644
--- a/src/com/android/launcher3/compat/AppWidgetManagerCompatV16.java
+++ b/src/com/android/launcher3/compat/AppWidgetManagerCompatV16.java
@@ -46,7 +46,7 @@
 
     @Override
     public String loadLabel(LauncherAppWidgetProviderInfo info) {
-        return info.label.trim();
+        return Utilities.trim(info.label);
     }
 
     @Override
diff --git a/src/com/android/launcher3/widget/PackageItemInfo.java b/src/com/android/launcher3/widget/PackageItemInfo.java
index 1a1de55..8f45a77 100644
--- a/src/com/android/launcher3/widget/PackageItemInfo.java
+++ b/src/com/android/launcher3/widget/PackageItemInfo.java
@@ -49,7 +49,7 @@
 
     @Override
     public String toString() {
-        return "PackageItemInfo(title=" + title.toString() + " id=" + this.id
+        return "PackageItemInfo(title=" + title + " id=" + this.id
                 + " type=" + this.itemType + " container=" + this.container
                 + " screen=" + screenId + " cellX=" + cellX + " cellY=" + cellY
                 + " spanX=" + spanX + " spanY=" + spanY + " dropPos=" + Arrays.toString(dropPos)
diff --git a/src/com/android/launcher3/widget/WidgetCell.java b/src/com/android/launcher3/widget/WidgetCell.java
index f5c44ab..8779371 100644
--- a/src/com/android/launcher3/widget/WidgetCell.java
+++ b/src/com/android/launcher3/widget/WidgetCell.java
@@ -248,10 +248,9 @@
      * Helper method to get the string info of the tag.
      */
     private String getTagToString() {
-        if (getTag() instanceof PendingAddWidgetInfo) {
-            return ((PendingAddWidgetInfo)getTag()).toString();
-        } else if (getTag() instanceof PendingAddShortcutInfo) {
-            return ((PendingAddShortcutInfo)getTag()).toString();
+        if (getTag() instanceof PendingAddWidgetInfo ||
+                getTag() instanceof PendingAddShortcutInfo) {
+            return getTag().toString();
         }
         return "";
     }