Merge "Introduce scrollbar in widget tray" into ub-launcher3-burnaby
diff --git a/res/drawable-hdpi/ic_launcher_info_active.png b/res/drawable-hdpi/ic_launcher_info_active.png
deleted file mode 100644
index f7a3b68..0000000
--- a/res/drawable-hdpi/ic_launcher_info_active.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/ic_launcher_remove_active.png b/res/drawable-hdpi/ic_launcher_remove_active.png
deleted file mode 100644
index e53de0d..0000000
--- a/res/drawable-hdpi/ic_launcher_remove_active.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/ic_launcher_uninstall_active.png b/res/drawable-hdpi/ic_launcher_uninstall_active.png
deleted file mode 100644
index 22b97ee..0000000
--- a/res/drawable-hdpi/ic_launcher_uninstall_active.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/ic_launcher_info_active.png b/res/drawable-mdpi/ic_launcher_info_active.png
deleted file mode 100644
index ea71272..0000000
--- a/res/drawable-mdpi/ic_launcher_info_active.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/ic_launcher_remove_active.png b/res/drawable-mdpi/ic_launcher_remove_active.png
deleted file mode 100644
index f36cfdd..0000000
--- a/res/drawable-mdpi/ic_launcher_remove_active.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/ic_launcher_uninstall_active.png b/res/drawable-mdpi/ic_launcher_uninstall_active.png
deleted file mode 100644
index e4ee911..0000000
--- a/res/drawable-mdpi/ic_launcher_uninstall_active.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/ic_launcher_info_active.png b/res/drawable-xhdpi/ic_launcher_info_active.png
deleted file mode 100644
index b438f9e..0000000
--- a/res/drawable-xhdpi/ic_launcher_info_active.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/ic_launcher_remove_active.png b/res/drawable-xhdpi/ic_launcher_remove_active.png
deleted file mode 100644
index 14ac79d..0000000
--- a/res/drawable-xhdpi/ic_launcher_remove_active.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/ic_launcher_uninstall_active.png b/res/drawable-xhdpi/ic_launcher_uninstall_active.png
deleted file mode 100644
index 2c19b32..0000000
--- a/res/drawable-xhdpi/ic_launcher_uninstall_active.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_launcher_info_active.png b/res/drawable-xxhdpi/ic_launcher_info_active.png
deleted file mode 100644
index d354dd3..0000000
--- a/res/drawable-xxhdpi/ic_launcher_info_active.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_launcher_remove_active.png b/res/drawable-xxhdpi/ic_launcher_remove_active.png
deleted file mode 100644
index 9df4404..0000000
--- a/res/drawable-xxhdpi/ic_launcher_remove_active.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_launcher_uninstall_active.png b/res/drawable-xxhdpi/ic_launcher_uninstall_active.png
deleted file mode 100644
index db7d339..0000000
--- a/res/drawable-xxhdpi/ic_launcher_uninstall_active.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_launcher_info_active.png b/res/drawable-xxxhdpi/ic_launcher_info_active.png
deleted file mode 100644
index 162e23d..0000000
--- a/res/drawable-xxxhdpi/ic_launcher_info_active.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_launcher_remove_active.png b/res/drawable-xxxhdpi/ic_launcher_remove_active.png
deleted file mode 100644
index c0b8ea2..0000000
--- a/res/drawable-xxxhdpi/ic_launcher_remove_active.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_launcher_uninstall_active.png b/res/drawable-xxxhdpi/ic_launcher_uninstall_active.png
deleted file mode 100644
index 75896f3..0000000
--- a/res/drawable-xxxhdpi/ic_launcher_uninstall_active.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable/info_target_selector.xml b/res/drawable/info_target_selector.xml
deleted file mode 100644
index 51caece..0000000
--- a/res/drawable/info_target_selector.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2011, 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.
-*/
--->
-
-<transition xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:drawable="@drawable/ic_launcher_info_normal"  />
-    <item android:drawable="@drawable/ic_launcher_info_active"  />
-</transition>
diff --git a/res/drawable/remove_target_selector.xml b/res/drawable/remove_target_selector.xml
deleted file mode 100644
index 9025e8a..0000000
--- a/res/drawable/remove_target_selector.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2007, 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.
-*/
--->
-
-<transition xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:drawable="@drawable/ic_launcher_remove_normal"  />
-    <item android:drawable="@drawable/ic_launcher_remove_active"  />
-</transition>
diff --git a/res/drawable/uninstall_target_selector.xml b/res/drawable/uninstall_target_selector.xml
deleted file mode 100644
index 175cc20..0000000
--- a/res/drawable/uninstall_target_selector.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2007, 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.
-*/
--->
-
-<transition xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:drawable="@drawable/ic_launcher_uninstall_normal"  />
-    <item android:drawable="@drawable/ic_launcher_uninstall_active"  />
-</transition>
diff --git a/res/layout-land/launcher.xml b/res/layout-land/launcher.xml
index f0f7bbb..113b319 100644
--- a/res/layout-land/launcher.xml
+++ b/res/layout-land/launcher.xml
@@ -21,7 +21,6 @@
     android:id="@+id/launcher"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    android:background="@drawable/workspace_bg"
     android:fitsSystemWindows="true">
 
     <com.android.launcher3.DragLayer
diff --git a/res/layout-port/launcher.xml b/res/layout-port/launcher.xml
index 5a018c5..7202f7b 100644
--- a/res/layout-port/launcher.xml
+++ b/res/layout-port/launcher.xml
@@ -22,7 +22,6 @@
     android:id="@+id/launcher"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    android:background="@drawable/workspace_bg"
     android:fitsSystemWindows="true">
 
     <com.android.launcher3.DragLayer
diff --git a/res/layout-sw720dp/launcher.xml b/res/layout-sw720dp/launcher.xml
index a9601af..8a9f3e9 100644
--- a/res/layout-sw720dp/launcher.xml
+++ b/res/layout-sw720dp/launcher.xml
@@ -22,7 +22,6 @@
     android:id="@+id/launcher"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    android:background="@drawable/workspace_bg"
     android:fitsSystemWindows="true">
 
     <com.android.launcher3.DragLayer
diff --git a/res/layout/apps_list_view.xml b/res/layout/apps_list_view.xml
index 6f9be05..0404983 100644
--- a/res/layout/apps_list_view.xml
+++ b/res/layout/apps_list_view.xml
@@ -39,6 +39,8 @@
         android:layout_height="wrap_content"
         android:layout_marginTop="@dimen/apps_search_bar_height"
         android:orientation="horizontal"
+        android:descendantFocusability="afterDescendants"
+        android:focusable="true"
         android:visibility="invisible" >
     </LinearLayout>
 
diff --git a/res/layout/search_drop_target_bar.xml b/res/layout/search_drop_target_bar.xml
index b0435aa..4737ee1 100644
--- a/res/layout/search_drop_target_bar.xml
+++ b/res/layout/search_drop_target_bar.xml
@@ -36,8 +36,6 @@
             <com.android.launcher3.DeleteDropTarget
                 android:id="@+id/delete_target_text"
                 style="@style/DropTargetButton"
-                android:drawableLeft="@drawable/remove_target_selector"
-                android:drawableStart="@drawable/remove_target_selector"
                 android:text="@string/delete_target_label" />
         </FrameLayout>
 
@@ -50,8 +48,6 @@
             <com.android.launcher3.InfoDropTarget
                 android:id="@+id/info_target_text"
                 style="@style/DropTargetButton"
-                android:drawableLeft="@drawable/info_target_selector"
-                android:drawableStart="@drawable/info_target_selector"
                 android:text="@string/info_target_label" />
         </FrameLayout>
 
@@ -64,8 +60,6 @@
             <com.android.launcher3.UninstallDropTarget
                 android:id="@+id/uninstall_target_text"
                 style="@style/DropTargetButton"
-                android:drawableLeft="@drawable/uninstall_target_selector"
-                android:drawableStart="@drawable/uninstall_target_selector"
                 android:text="@string/delete_target_uninstall_label" />
         </FrameLayout>
     </LinearLayout>
diff --git a/res/values/colors.xml b/res/values/colors.xml
index 1e89615..a5db2fc 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -20,9 +20,9 @@
 <resources>
     <!-- The color tints to apply to the text and drag view when hovering
          over the delete target or the info target -->
-    <color name="delete_target_hover_tint">#DAC1C1C1</color>
-    <color name="uninstall_target_hover_tint">#DAF0592B</color>
-    <color name="info_target_hover_tint">#DA009688</color>
+    <color name="delete_target_hover_tint">#FFC1C1C1</color>
+    <color name="uninstall_target_hover_tint">#FFF0592B</color>
+    <color name="info_target_hover_tint">#FF009688</color>
     <color name="cling_scrim_background">#80000000</color>
 
     <color name="focused_background">#80c6c5c5</color>
diff --git a/src/com/android/launcher3/AlphabeticalAppsList.java b/src/com/android/launcher3/AlphabeticalAppsList.java
index 82aaeb9..b75b3ef 100644
--- a/src/com/android/launcher3/AlphabeticalAppsList.java
+++ b/src/com/android/launcher3/AlphabeticalAppsList.java
@@ -5,14 +5,10 @@
 import android.support.v7.widget.RecyclerView;
 import android.util.Log;
 import com.android.launcher3.compat.AlphabeticIndexCompat;
-import com.android.launcher3.compat.UserHandleCompat;
-import com.android.launcher3.compat.UserManagerCompat;
 import com.android.launcher3.model.AppNameComparator;
 
-import java.text.Collator;
 import java.util.ArrayList;
 import java.util.Collections;
-import java.util.Comparator;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Locale;
@@ -121,10 +117,10 @@
     }
 
     /**
-     * A callback to notify of changes to the filter.
+     * Callback to notify when the set of adapter items have changed.
      */
-    public interface FilterChangedCallback {
-        void onFilterChanged();
+    public interface AdapterChangedCallback {
+        void onAdapterItemsChanged();
     }
 
     /**
@@ -178,12 +174,20 @@
     private static final int MAX_NUM_MERGES_PHONE = 2;
 
     private Context mContext;
+
+    // The set of apps from the system not including predictions
     private List<AppInfo> mApps = new ArrayList<>();
+    // The set of filtered apps with the current filter
     private List<AppInfo> mFilteredApps = new ArrayList<>();
-    private List<AdapterItem> mSectionedFilteredApps = new ArrayList<>();
+    // The current set of adapter items
+    private List<AdapterItem> mAdapterItems = new ArrayList<>();
+    // The set of sections for the apps with the current filter
     private List<SectionInfo> mSections = new ArrayList<>();
+    // The set of sections that we allow fast-scrolling to (includes non-merged sections)
     private List<FastScrollSectionInfo> mFastScrollerSections = new ArrayList<>();
+    // The set of predicted app component names
     private List<ComponentName> mPredictedAppComponents = new ArrayList<>();
+    // The set of predicted apps resolved from the component names and the current set of apps
     private List<AppInfo> mPredictedApps = new ArrayList<>();
     private HashMap<CharSequence, String> mCachedSectionNames = new HashMap<>();
     private RecyclerView.Adapter mAdapter;
@@ -191,20 +195,25 @@
     private AlphabeticIndexCompat mIndexer;
     private AppNameComparator mAppNameComparator;
     private MergeAlgorithm mMergeAlgorithm;
-    private FilterChangedCallback mFilterChangedCallback;
+    private AdapterChangedCallback mAdapterChangedCallback;
     private int mNumAppsPerRow;
     private int mNumPredictedAppsPerRow;
 
-    public AlphabeticalAppsList(Context context, FilterChangedCallback cb, int numAppsPerRow,
-            int numPredictedAppsPerRow) {
+    public AlphabeticalAppsList(Context context, int numAppsPerRow, int numPredictedAppsPerRow) {
         mContext = context;
         mIndexer = new AlphabeticIndexCompat(context);
         mAppNameComparator = new AppNameComparator(context);
-        mFilterChangedCallback = cb;
         setNumAppsPerRow(numAppsPerRow, numPredictedAppsPerRow);
     }
 
     /**
+     * Sets the apps updated callback.
+     */
+    public void setAdapterChangedCallback(AdapterChangedCallback cb) {
+        mAdapterChangedCallback = cb;
+    }
+
+    /**
      * Sets the number of apps per row.  Used only for AppsContainerView.SECTIONED_GRID_COALESCED.
      */
     public void setNumAppsPerRow(int numAppsPerRow, int numPredictedAppsPerRow) {
@@ -248,7 +257,7 @@
      * Returns the current filtered list of applications broken down into their sections.
      */
     public List<AdapterItem> getAdapterItems() {
-        return mSectionedFilteredApps;
+        return mAdapterItems;
     }
 
     /**
@@ -278,11 +287,7 @@
     public void setFilter(Filter f) {
         if (mFilter != f) {
             mFilter = f;
-            onAppsUpdated();
-            mAdapter.notifyDataSetChanged();
-            if (mFilterChangedCallback != null){
-                mFilterChangedCallback.onFilterChanged();
-            }
+            updateAdapterItems();
         }
     }
 
@@ -294,7 +299,6 @@
         mPredictedAppComponents.clear();
         mPredictedAppComponents.addAll(apps);
         onAppsUpdated();
-        mAdapter.notifyDataSetChanged();
     }
 
     /**
@@ -311,7 +315,6 @@
         mApps.clear();
         mApps.addAll(apps);
         onAppsUpdated();
-        mAdapter.notifyDataSetChanged();
     }
 
     /**
@@ -320,10 +323,9 @@
     public void addApps(List<AppInfo> apps) {
         // We add it in place, in alphabetical order
         for (AppInfo info : apps) {
-            addApp(info);
+            mApps.add(info);
         }
         onAppsUpdated();
-        mAdapter.notifyDataSetChanged();
     }
 
     /**
@@ -335,11 +337,10 @@
             if (index != -1) {
                 mApps.set(index, info);
             } else {
-                addApp(info);
+                mApps.add(info);
             }
         }
         onAppsUpdated();
-        mAdapter.notifyDataSetChanged();
     }
 
     /**
@@ -353,7 +354,6 @@
             }
         }
         onAppsUpdated();
-        mAdapter.notifyDataSetChanged();
     }
 
     /**
@@ -373,34 +373,68 @@
     }
 
     /**
-     * Implementation to actually add an app to the alphabetic list, but does not notify.
-     */
-    private void addApp(AppInfo info) {
-        int index = Collections.binarySearch(mApps, info, mAppNameComparator.getAppInfoComparator());
-        if (index < 0) {
-            mApps.add(-(index + 1), info);
-        }
-    }
-
-    /**
      * Updates internals when the set of apps are updated.
      */
     private void onAppsUpdated() {
         // Sort the list of apps
         Collections.sort(mApps, mAppNameComparator.getAppInfoComparator());
 
-        // Prepare to update the list of sections, filtered apps, etc.
-        mFilteredApps.clear();
-        mSections.clear();
-        mSectionedFilteredApps.clear();
-        mFastScrollerSections.clear();
+        // As a special case for some languages (currently only Simplified Chinese), we may need to
+        // coalesce sections
+        Locale curLocale = mContext.getResources().getConfiguration().locale;
+        TreeMap<String, ArrayList<AppInfo>> sectionMap = null;
+        boolean localeRequiresSectionSorting = curLocale.equals(Locale.SIMPLIFIED_CHINESE);
+        if (localeRequiresSectionSorting) {
+            // Compute the section headers.  We use a TreeMap with the section name comparator to
+            // ensure that the sections are ordered when we iterate over it later
+            sectionMap = new TreeMap<>(mAppNameComparator.getSectionNameComparator());
+            for (AppInfo info : mApps) {
+                // Add the section to the cache
+                String sectionName = getAndUpdateCachedSectionName(info.title);
+
+                // Add it to the mapping
+                ArrayList<AppInfo> sectionApps = sectionMap.get(sectionName);
+                if (sectionApps == null) {
+                    sectionApps = new ArrayList<>();
+                    sectionMap.put(sectionName, sectionApps);
+                }
+                sectionApps.add(info);
+            }
+
+            // Add each of the section apps to the list in order
+            List<AppInfo> allApps = new ArrayList<>(mApps.size());
+            for (Map.Entry<String, ArrayList<AppInfo>> entry : sectionMap.entrySet()) {
+                allApps.addAll(entry.getValue());
+            }
+            mApps = allApps;
+        } else {
+            // Just compute the section headers for use below
+            for (AppInfo info : mApps) {
+                // Add the section to the cache
+                getAndUpdateCachedSectionName(info.title);
+            }
+        }
+
+        // Recompose the set of adapter items from the current set of apps
+        updateAdapterItems();
+    }
+
+    /**
+     * Updates the set of filtered apps with the current filter.  At this point, we expect
+     * mCachedSectionNames to have been calculated for the set of all apps in mApps.
+     */
+    private void updateAdapterItems() {
         SectionInfo lastSectionInfo = null;
         String lastSectionName = null;
         FastScrollSectionInfo lastFastScrollerSectionInfo = null;
         int position = 0;
         int appIndex = 0;
-        List<AppInfo> allApps = new ArrayList<>();
 
+        // Prepare to update the list of sections, filtered apps, etc.
+        mFilteredApps.clear();
+        mFastScrollerSections.clear();
+        mAdapterItems.clear();
+        mSections.clear();
 
         // Process the predicted app components
         mPredictedApps.clear();
@@ -421,61 +455,16 @@
             if (!mPredictedApps.isEmpty()) {
                 // Create a new spacer for the prediction bar
                 AdapterItem sectionItem = AdapterItem.asPredictionBarSpacer(position++);
-                mSectionedFilteredApps.add(sectionItem);
+                mAdapterItems.add(sectionItem);
             }
         }
 
-        // As a special case for some languages (currently only Simplified Chinese), we may need to
-        // coalesce sections
-        Locale curLocale = mContext.getResources().getConfiguration().locale;
-        TreeMap<String, ArrayList<AppInfo>> sectionMap = null;
-        boolean localeRequiresSectionSorting = curLocale.equals(Locale.SIMPLIFIED_CHINESE);
-        if (localeRequiresSectionSorting) {
-            // Compute the section headers.  We use a TreeMap with the section name comparator to
-            // ensure that the sections are ordered when we iterate over it later
-            sectionMap = new TreeMap<>(mAppNameComparator.getSectionNameComparator());
-            for (AppInfo info : mApps) {
-                // Add the section to the cache
-                String sectionName = mCachedSectionNames.get(info.title);
-                if (sectionName == null) {
-                    sectionName = mIndexer.computeSectionName(info.title);
-                    mCachedSectionNames.put(info.title, sectionName);
-                }
-
-                // Add it to the mapping
-                ArrayList<AppInfo> sectionApps = sectionMap.get(sectionName);
-                if (sectionApps == null) {
-                    sectionApps = new ArrayList<>();
-                    sectionMap.put(sectionName, sectionApps);
-                }
-                sectionApps.add(info);
-            }
-
-            // Add it to the list
-            for (Map.Entry<String, ArrayList<AppInfo>> entry : sectionMap.entrySet()) {
-                allApps.addAll(entry.getValue());
-            }
-        } else {
-            // Just compute the section headers for use below
-            for (AppInfo info : mApps) {
-                // Add the section to the cache
-                String sectionName = mCachedSectionNames.get(info.title);
-                if (sectionName == null) {
-                    sectionName = mIndexer.computeSectionName(info.title);
-                    mCachedSectionNames.put(info.title, sectionName);
-                }
-            }
-            // Add it to the list
-            allApps.addAll(mApps);
-        }
-
         // Recreate the filtered and sectioned apps (for convenience for the grid layout) from the
         // ordered set of sections
-        int numApps = allApps.size();
+        int numApps = mApps.size();
         for (int i = 0; i < numApps; i++) {
-            AppInfo info = allApps.get(i);
-            // The section name was computed above so this should be find
-            String sectionName = mCachedSectionNames.get(info.title);
+            AppInfo info = mApps.get(i);
+            String sectionName = getAndUpdateCachedSectionName(info.title);
 
             // Check if we want to retain this app
             if (mFilter != null && !mFilter.retainApp(info, sectionName)) {
@@ -494,7 +483,7 @@
                 // Create a new section item to break the flow of items in the list
                 if (!hasFilter()) {
                     AdapterItem sectionItem = AdapterItem.asSectionBreak(position++, lastSectionInfo);
-                    mSectionedFilteredApps.add(sectionItem);
+                    mAdapterItems.add(sectionItem);
                 }
             }
 
@@ -505,12 +494,21 @@
                 lastSectionInfo.firstAppItem = appItem;
                 lastFastScrollerSectionInfo.appItem = appItem;
             }
-            mSectionedFilteredApps.add(appItem);
+            mAdapterItems.add(appItem);
             mFilteredApps.add(info);
         }
 
         // Merge multiple sections together as requested by the merge strategy for this device
         mergeSections();
+
+        // Refresh the recycler view
+        if (mAdapter != null) {
+            mAdapter.notifyDataSetChanged();
+        }
+
+        if (mAdapterChangedCallback != null) {
+            mAdapterChangedCallback.onAdapterItemsChanged();
+        }
     }
 
     /**
@@ -531,20 +529,20 @@
                     SectionInfo nextSection = mSections.remove(i + 1);
 
                     // Remove the next section break
-                    mSectionedFilteredApps.remove(nextSection.sectionBreakItem);
-                    int pos = mSectionedFilteredApps.indexOf(section.firstAppItem);
+                    mAdapterItems.remove(nextSection.sectionBreakItem);
+                    int pos = mAdapterItems.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);
+                        AdapterItem item = mAdapterItems.get(j);
                         item.sectionInfo = section;
                         item.sectionAppIndex += section.numApps;
                     }
 
                     // Update the following adapter items of the removed section item
-                    pos = mSectionedFilteredApps.indexOf(nextSection.firstAppItem);
-                    for (int j = pos; j < mSectionedFilteredApps.size(); j++) {
-                        AdapterItem item = mSectionedFilteredApps.get(j);
+                    pos = mAdapterItems.indexOf(nextSection.firstAppItem);
+                    for (int j = pos; j < mAdapterItems.size(); j++) {
+                        AdapterItem item = mAdapterItems.get(j);
                         item.position--;
                     }
                     section.numApps += nextSection.numApps;
@@ -560,4 +558,17 @@
             }
         }
     }
+
+    /**
+     * Returns the cached section name for the given title, recomputing and updating the cache if
+     * the title has no cached section name.
+     */
+    private String getAndUpdateCachedSectionName(CharSequence title) {
+        String sectionName = mCachedSectionNames.get(title);
+        if (sectionName == null) {
+            sectionName = mIndexer.computeSectionName(title);
+            mCachedSectionNames.put(title, sectionName);
+        }
+        return sectionName;
+    }
 }
diff --git a/src/com/android/launcher3/AppsContainerView.java b/src/com/android/launcher3/AppsContainerView.java
index 5ccb6c6..26a7cd7 100644
--- a/src/com/android/launcher3/AppsContainerView.java
+++ b/src/com/android/launcher3/AppsContainerView.java
@@ -136,7 +136,7 @@
  */
 public class AppsContainerView extends BaseContainerView implements DragSource, Insettable,
         TextWatcher, TextView.OnEditorActionListener, LauncherTransitionable,
-        AlphabeticalAppsList.FilterChangedCallback, AppsGridAdapter.PredictionBarSpacerCallbacks,
+        AlphabeticalAppsList.AdapterChangedCallback, AppsGridAdapter.PredictionBarSpacerCallbacks,
         View.OnTouchListener, View.OnClickListener, View.OnLongClickListener,
         ViewTreeObserver.OnPreDrawListener {
 
@@ -183,6 +183,7 @@
     private int mContainerInset;
     private int mPredictionBarHeight;
     private int mLastRecyclerViewScrollPos = -1;
+    private boolean mFocusPredictionBarOnFirstBind;
 
     private CheckLongPressHelper mPredictionIconCheckForLongPress;
     private View mPredictionIconUnderTouch;
@@ -209,7 +210,8 @@
         mLayoutInflater = LayoutInflater.from(context);
         mNumAppsPerRow = grid.appsViewNumCols;
         mNumPredictedAppsPerRow = grid.appsViewNumPredictiveCols;
-        mApps = new AlphabeticalAppsList(context, this, mNumAppsPerRow, mNumPredictedAppsPerRow);
+        mApps = new AlphabeticalAppsList(context, mNumAppsPerRow, mNumPredictedAppsPerRow);
+        mApps.setAdapterChangedCallback(this);
         mAdapter = new AppsGridAdapter(context, mApps, mNumAppsPerRow, this, this, mLauncher, this);
         mAdapter.setEmptySearchText(res.getString(R.string.loading_apps_message));
         mAdapter.setNumAppsPerRow(mNumAppsPerRow);
@@ -298,7 +300,17 @@
             @Override
             public void onFocusChange(View v, boolean hasFocus) {
                 if (v == mContentView && hasFocus) {
-                    mAppsRecyclerView.requestFocus();
+                    if (!mApps.getPredictedApps().isEmpty()) {
+                        // If the prediction bar is going to be bound, then defer focusing until
+                        // it is first bound
+                        if (mPredictionBarView.getChildCount() == 0) {
+                            mFocusPredictionBarOnFirstBind = true;
+                        } else {
+                            mPredictionBarView.requestFocus();
+                        }
+                    } else {
+                        mAppsRecyclerView.requestFocus();
+                    }
                 }
             }
         });
@@ -387,6 +399,11 @@
                 icon.setVisibility(View.INVISIBLE);
             }
         }
+
+        if (mFocusPredictionBarOnFirstBind) {
+            mFocusPredictionBarOnFirstBind = false;
+            mPredictionBarView.requestFocus();
+        }
     }
 
     @Override
@@ -670,7 +687,7 @@
     }
 
     @Override
-    public void onFilterChanged() {
+    public void onAdapterItemsChanged() {
         updatePredictionBarVisibility();
     }
 
diff --git a/src/com/android/launcher3/ButtonDropTarget.java b/src/com/android/launcher3/ButtonDropTarget.java
index b8214d1..4cd28c0 100644
--- a/src/com/android/launcher3/ButtonDropTarget.java
+++ b/src/com/android/launcher3/ButtonDropTarget.java
@@ -16,15 +16,20 @@
 
 package com.android.launcher3;
 
+import android.animation.AnimatorSet;
+import android.animation.FloatArrayEvaluator;
 import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
+import android.animation.ValueAnimator.AnimatorUpdateListener;
 import android.annotation.TargetApi;
 import android.content.Context;
 import android.content.res.ColorStateList;
 import android.content.res.Configuration;
+import android.graphics.ColorMatrix;
+import android.graphics.ColorMatrixColorFilter;
 import android.graphics.PointF;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
-import android.graphics.drawable.TransitionDrawable;
 import android.os.Build;
 import android.util.AttributeSet;
 import android.view.View;
@@ -56,9 +61,11 @@
     protected int mHoverColor = 0;
 
     protected ColorStateList mOriginalTextColor;
-    protected TransitionDrawable mDrawable;
+    protected Drawable mDrawable;
 
-    private ObjectAnimator mCurrentColorAnim;
+    private AnimatorSet mCurrentColorAnim;
+    private ColorMatrix mSrcFilter, mDstFilter, mCurrentFilter;
+
 
     public ButtonDropTarget(Context context, AttributeSet attrs) {
         this(context, attrs, 0);
@@ -85,21 +92,14 @@
 
     @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
     protected void setDrawable(int resId) {
-        // Get the hover color
-        mDrawable = (TransitionDrawable) getCurrentDrawable();
+        // We do not set the drawable in the xml as that inflates two drawables corresponding to
+        // drawableLeft and drawableStart.
+        mDrawable = getResources().getDrawable(resId);
 
-        if (mDrawable == null) {
-            // TODO: investigate why this is ever happening. Presently only on one known device.
-            mDrawable = (TransitionDrawable) getResources().getDrawable(resId);
-            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
-                setCompoundDrawablesRelativeWithIntrinsicBounds(mDrawable, null, null, null);
-            } else {
-                setCompoundDrawablesWithIntrinsicBounds(mDrawable, null, null, null);
-            }
-        }
-
-        if (null != mDrawable) {
-            mDrawable.setCrossFadeEnabled(true);
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+            setCompoundDrawablesRelativeWithIntrinsicBounds(mDrawable, null, null, null);
+        } else {
+            setCompoundDrawablesWithIntrinsicBounds(mDrawable, null, null, null);
         }
     }
 
@@ -111,16 +111,6 @@
         mSearchDropTargetBar = searchDropTargetBar;
     }
 
-    protected Drawable getCurrentDrawable() {
-        Drawable[] drawables = getCompoundDrawables();
-        for (int i = 0; i < drawables.length; ++i) {
-            if (drawables[i] != null) {
-                return drawables[i];
-            }
-        }
-        return null;
-    }
-
     @Override
     public void onFlingToDelete(DragObject d, PointF vec) { }
 
@@ -128,10 +118,13 @@
     public final void onDragEnter(DragObject d) {
         d.dragView.setColor(mHoverColor);
         if (Utilities.isLmpOrAbove()) {
-            mDrawable.startTransition(DragView.COLOR_CHANGE_DURATION);
             animateTextColor(mHoverColor);
         } else {
-            mDrawable.startTransition(0);
+            if (mCurrentFilter == null) {
+                mCurrentFilter = new ColorMatrix();
+            }
+            DragView.setColorScale(mHoverColor, mCurrentFilter);
+            mDrawable.setColorFilter(new ColorMatrixColorFilter(mCurrentFilter));
             setTextColor(mHoverColor);
         }
     }
@@ -141,12 +134,11 @@
         // Do nothing
     }
 
-    protected void resetHoverColor() {
+	protected void resetHoverColor() {
         if (Utilities.isLmpOrAbove()) {
-            mDrawable.reverseTransition(DragView.COLOR_CHANGE_DURATION);
             animateTextColor(mOriginalTextColor.getDefaultColor());
         } else {
-            mDrawable.resetTransition();
+            mDrawable.setColorFilter(null);
             setTextColor(mOriginalTextColor);
         }
     }
@@ -156,8 +148,32 @@
         if (mCurrentColorAnim != null) {
             mCurrentColorAnim.cancel();
         }
-        mCurrentColorAnim = ObjectAnimator.ofArgb(this, "textColor", targetColor);
+
+        mCurrentColorAnim = new AnimatorSet();
         mCurrentColorAnim.setDuration(DragView.COLOR_CHANGE_DURATION);
+
+        if (mSrcFilter == null) {
+            mSrcFilter = new ColorMatrix();
+            mDstFilter = new ColorMatrix();
+            mCurrentFilter = new ColorMatrix();
+        }
+
+        DragView.setColorScale(getTextColor(), mSrcFilter);
+        DragView.setColorScale(targetColor, mDstFilter);
+        ValueAnimator anim1 = ValueAnimator.ofObject(
+                new FloatArrayEvaluator(mCurrentFilter.getArray()),
+                mSrcFilter.getArray(), mDstFilter.getArray());
+        anim1.addUpdateListener(new AnimatorUpdateListener() {
+
+            @Override
+            public void onAnimationUpdate(ValueAnimator animation) {
+                mDrawable.setColorFilter(new ColorMatrixColorFilter(mCurrentFilter));
+                invalidate();
+            }
+        });
+
+        mCurrentColorAnim.play(anim1);
+        mCurrentColorAnim.play(ObjectAnimator.ofArgb(this, "textColor", targetColor));
         mCurrentColorAnim.start();
     }
 
@@ -172,10 +188,10 @@
         }
     }
 
-    @Override
+	@Override
     public final void onDragStart(DragSource source, Object info, int dragAction) {
         mActive = supportsDrop(source, info);
-        mDrawable.resetTransition();
+        mDrawable.setColorFilter(null);
         if (mCurrentColorAnim != null) {
             mCurrentColorAnim.cancel();
             mCurrentColorAnim = null;
diff --git a/src/com/android/launcher3/DeleteDropTarget.java b/src/com/android/launcher3/DeleteDropTarget.java
index 08186f5..fa6e74f 100644
--- a/src/com/android/launcher3/DeleteDropTarget.java
+++ b/src/com/android/launcher3/DeleteDropTarget.java
@@ -43,7 +43,7 @@
         // Get the hover color
         mHoverColor = getResources().getColor(R.color.delete_target_hover_tint);
 
-        setDrawable(R.drawable.remove_target_selector);
+        setDrawable(R.drawable.ic_launcher_remove_normal);
     }
 
     public static boolean supportsDrop(Object info) {
diff --git a/src/com/android/launcher3/DragView.java b/src/com/android/launcher3/DragView.java
index 120299e..b332338 100644
--- a/src/com/android/launcher3/DragView.java
+++ b/src/com/android/launcher3/DragView.java
@@ -38,7 +38,7 @@
 import java.util.Arrays;
 
 public class DragView extends View {
-    public static int COLOR_CHANGE_DURATION = 200;
+    public static int COLOR_CHANGE_DURATION = 120;
 
     @Thunk static float sDragAlpha = 1f;
 
@@ -249,8 +249,7 @@
             m1.setSaturation(0);
 
             ColorMatrix m2 = new ColorMatrix();
-            m2.setScale(Color.red(color) / 255f, Color.green(color) / 255f,
-                    Color.blue(color) / 255f, Color.alpha(color) / 255f);
+            setColorScale(color, m2);
             m1.postConcat(m2);
 
             if (Utilities.isLmpOrAbove()) {
@@ -355,4 +354,9 @@
             mDragLayer.removeView(DragView.this);
         }
     }
+
+    public static void setColorScale(int color, ColorMatrix target) {
+        target.setScale(Color.red(color) / 255f, Color.green(color) / 255f,
+                Color.blue(color) / 255f, Color.alpha(color) / 255f);
+    }
 }
diff --git a/src/com/android/launcher3/FocusHelper.java b/src/com/android/launcher3/FocusHelper.java
index c77d416..678ed0f 100644
--- a/src/com/android/launcher3/FocusHelper.java
+++ b/src/com/android/launcher3/FocusHelper.java
@@ -314,12 +314,14 @@
         // with the hotseat.
         if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN && !profile.isVerticalBarLayout()) {
             matrix = FocusLogic.createSparseMatrix(iconLayout, hotseatLayout, true /* horizontal */,
-                    hotseat.getAllAppsButtonRank(), false /* all apps icon is ignored */);
+                    hotseat.getAllAppsButtonRank(),
+                    !hotseat.hasIcons() /* ignore all apps icon, unless there are no other icons */);
             countY = countY + 1;
         } else if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT &&
                 profile.isVerticalBarLayout()) {
             matrix = FocusLogic.createSparseMatrix(iconLayout, hotseatLayout, false /* horizontal */,
-                    hotseat.getAllAppsButtonRank(), false /* all apps icon is ignored */);
+                    hotseat.getAllAppsButtonRank(),
+                    !hotseat.hasIcons() /* ignore all apps icon, unless there are no other icons */);
             countX = countX + 1;
         } else if (keyCode == KeyEvent.KEYCODE_DEL || keyCode == KeyEvent.KEYCODE_FORWARD_DEL) {
             workspace.removeWorkspaceItem(v);
diff --git a/src/com/android/launcher3/FolderInfo.java b/src/com/android/launcher3/FolderInfo.java
index 930f911..aea21c9 100644
--- a/src/com/android/launcher3/FolderInfo.java
+++ b/src/com/android/launcher3/FolderInfo.java
@@ -92,7 +92,7 @@
     }
 
     public void setTitle(CharSequence title) {
-        this.title = Utilities.trim(title);
+        this.title = title;
         for (int i = 0; i < listeners.size(); i++) {
             listeners.get(i).onTitleChanged(title);
         }
diff --git a/src/com/android/launcher3/Hotseat.java b/src/com/android/launcher3/Hotseat.java
index b614bc6..b8337b6 100644
--- a/src/com/android/launcher3/Hotseat.java
+++ b/src/com/android/launcher3/Hotseat.java
@@ -65,6 +65,13 @@
     }
 
     /**
+     * Returns whether there are other icons than the all apps button in the hotseat.
+     */
+    public boolean hasIcons() {
+        return mContent.getShortcutsAndWidgets().getChildCount() > 1;
+    }
+
+    /**
      * Registers the specified listener on the cell layout of the hotseat.
      */
     @Override
@@ -98,25 +105,6 @@
         return rank == mAllAppsButtonRank;
     }
 
-    /** This returns the coordinates of an app in a given cell, relative to the DragLayer */
-    Rect getCellCoordinates(int cellX, int cellY) {
-        Rect coords = new Rect();
-        mContent.cellToRect(cellX, cellY, 1, 1, coords);
-        int[] hotseatInParent = new int[2];
-        Utilities.getDescendantCoordRelativeToParent(this, mLauncher.getDragLayer(),
-                hotseatInParent, false);
-        coords.offset(hotseatInParent[0], hotseatInParent[1]);
-
-        // Center the icon
-        int cWidth = mContent.getShortcutsAndWidgets().getCellContentWidth();
-        int cHeight = mContent.getShortcutsAndWidgets().getCellContentHeight();
-        int cellPaddingX = (int) Math.max(0, ((coords.width() - cWidth) / 2f));
-        int cellPaddingY = (int) Math.max(0, ((coords.height() - cHeight) / 2f));
-        coords.offset(cellPaddingX, cellPaddingY);
-
-        return coords;
-    }
-
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
diff --git a/src/com/android/launcher3/IconCache.java b/src/com/android/launcher3/IconCache.java
index a387579..8b5f747 100644
--- a/src/com/android/launcher3/IconCache.java
+++ b/src/com/android/launcher3/IconCache.java
@@ -35,6 +35,7 @@
 import android.graphics.Canvas;
 import android.graphics.drawable.Drawable;
 import android.os.Handler;
+import android.os.SystemClock;
 import android.text.TextUtils;
 import android.util.Log;
 
@@ -46,7 +47,6 @@
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.Thunk;
 
-import java.util.Collection;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
@@ -71,6 +71,8 @@
 
     private static final int LOW_RES_SCALE_FACTOR = 8;
 
+    private static final Object ICON_UPDATE_TOKEN = new Object();
+
     @Thunk static class CacheEntry {
         public Bitmap icon;
         public CharSequence title;
@@ -223,13 +225,32 @@
                 new String[] {packageName + "/%", Long.toString(userSerial)});
     }
 
+    public void updateDbIcons() {
+        // Remove all active icon update tasks.
+        mWorkerHandler.removeCallbacksAndMessages(ICON_UPDATE_TOKEN);
+
+        mIconDb.updateSystemStateString(mContext);
+        for (UserHandleCompat user : mUserManager.getUserProfiles()) {
+            // Query for the set of apps
+            final List<LauncherActivityInfoCompat> apps = mLauncherApps.getActivityList(null, user);
+            // Fail if we don't have any apps
+            // TODO: Fix this. Only fail for the current user.
+            if (apps == null || apps.isEmpty()) {
+                return;
+            }
+
+            // Update icon cache. This happens in segments and {@link #onPackageIconsUpdated}
+            // is called by the icon cache when the job is complete.
+            updateDBIcons(user, apps);
+        }
+    }
+
     /**
      * Updates the persistent DB, such that only entries corresponding to {@param apps} remain in
      * the DB and are updated.
      * @return The set of packages for which icons have updated.
      */
-    public HashSet<String> updateDBIcons(UserHandleCompat user, List<LauncherActivityInfoCompat> apps) {
-        mIconDb.updateSystemStateString(mContext);
+    private void updateDBIcons(UserHandleCompat user, List<LauncherActivityInfoCompat> apps) {
         long userSerial = mUserManager.getSerialNumberForUser(user);
         PackageManager pm = mContext.getPackageManager();
         HashMap<String, PackageInfo> pkgInfoMap = new HashMap<String, PackageInfo>();
@@ -257,7 +278,7 @@
         final int systemStateIndex = c.getColumnIndex(IconDB.COLUMN_SYSTEM_STATE);
 
         HashSet<Integer> itemsToRemove = new HashSet<Integer>();
-        HashSet<String> updatedPackages = new HashSet<String>();
+        Stack<LauncherActivityInfoCompat> appsToUpdate = new Stack<>();
 
         while (c.moveToNext()) {
             String cn = c.getString(indexComponent);
@@ -281,14 +302,9 @@
             }
             if (app == null) {
                 itemsToRemove.add(c.getInt(rowIndex));
-                continue;
+            } else {
+                appsToUpdate.add(app);
             }
-            ContentValues values = updateCacheAndGetContentValues(app, true);
-            mIconDb.getWritableDatabase().update(IconDB.TABLE_NAME, values,
-                    IconDB.COLUMN_COMPONENT + " = ? AND " + IconDB.COLUMN_USER + " = ?",
-                    new String[] {cn, Long.toString(userSerial)});
-
-            updatedPackages.add(component.getPackageName());
         }
         c.close();
         if (!itemsToRemove.isEmpty()) {
@@ -298,11 +314,12 @@
         }
 
         // Insert remaining apps.
-        if (!componentMap.isEmpty()) {
-            mWorkerHandler.post(new SerializedIconAdditionTask(userSerial, pkgInfoMap,
-                    componentMap.values()));
+        if (!componentMap.isEmpty() || !appsToUpdate.isEmpty()) {
+            Stack<LauncherActivityInfoCompat> appsToAdd = new Stack<>();
+            appsToAdd.addAll(componentMap.values());
+            new SerializedIconUpdateTask(userSerial, pkgInfoMap,
+                    appsToAdd, appsToUpdate).scheduleNext();
         }
-        return updatedPackages;
     }
 
     private void addIconToDBAndMemCache(LauncherActivityInfoCompat app, PackageInfo info,
@@ -684,25 +701,46 @@
     }
 
     /**
-     * A runnable that adds icons in the DB for the provided LauncherActivityInfoCompat list.
-     * Items are added one at a time, to that the worker thread does not get blocked.
+     * A runnable that updates invalid icons and adds missing icons in the DB for the provided
+     * LauncherActivityInfoCompat list. Items are updated/added one at a time, so that the
+     * worker thread doesn't get blocked.
      */
-    private class SerializedIconAdditionTask implements Runnable {
+    private class SerializedIconUpdateTask implements Runnable {
         private final long mUserSerial;
         private final HashMap<String, PackageInfo> mPkgInfoMap;
         private final Stack<LauncherActivityInfoCompat> mAppsToAdd;
+        private final Stack<LauncherActivityInfoCompat> mAppsToUpdate;
+        private final HashSet<String> mUpdatedPackages = new HashSet<String>();
 
-        private SerializedIconAdditionTask(long userSerial, HashMap<String, PackageInfo> pkgInfoMap,
-                Collection<LauncherActivityInfoCompat> appsToAdd) {
+        private SerializedIconUpdateTask(long userSerial, HashMap<String, PackageInfo> pkgInfoMap,
+                Stack<LauncherActivityInfoCompat> appsToAdd,
+                Stack<LauncherActivityInfoCompat> appsToUpdate) {
             mUserSerial = userSerial;
             mPkgInfoMap = pkgInfoMap;
-            mAppsToAdd = new Stack<LauncherActivityInfoCompat>();
-            mAppsToAdd.addAll(appsToAdd);
+            mAppsToAdd = appsToAdd;
+            mAppsToUpdate = appsToUpdate;
         }
 
         @Override
         public void run() {
-            if (!mAppsToAdd.isEmpty()) {
+            if (!mAppsToUpdate.isEmpty()) {
+                LauncherActivityInfoCompat app = mAppsToUpdate.pop();
+                String cn = app.getComponentName().flattenToString();
+                ContentValues values = updateCacheAndGetContentValues(app, true);
+                mIconDb.getWritableDatabase().update(IconDB.TABLE_NAME, values,
+                        IconDB.COLUMN_COMPONENT + " = ? AND " + IconDB.COLUMN_USER + " = ?",
+                        new String[] {cn, Long.toString(mUserSerial)});
+                mUpdatedPackages.add(app.getComponentName().getPackageName());
+
+                if (mAppsToUpdate.isEmpty() && !mUpdatedPackages.isEmpty()) {
+                    // No more app to update. Notify model.
+                    LauncherAppState.getInstance().getModel().onPackageIconsUpdated(
+                            mUpdatedPackages, mUserManager.getUserForSerialNumber(mUserSerial));
+                }
+
+                // Let it run one more time.
+                scheduleNext();
+            } else if (!mAppsToAdd.isEmpty()) {
                 LauncherActivityInfoCompat app = mAppsToAdd.pop();
                 PackageInfo info = mPkgInfoMap.get(app.getComponentName().getPackageName());
                 if (info != null) {
@@ -710,10 +748,15 @@
                         addIconToDBAndMemCache(app, info, mUserSerial);
                     }
                 }
+
+                if (!mAppsToAdd.isEmpty()) {
+                    scheduleNext();
+                }
             }
-            if (!mAppsToAdd.isEmpty()) {
-                mWorkerHandler.post(this);
-            }
+        }
+
+        public void scheduleNext() {
+            mWorkerHandler.postAtTime(this, ICON_UPDATE_TOKEN, SystemClock.uptimeMillis() + 1);
         }
     }
 
diff --git a/src/com/android/launcher3/InfoDropTarget.java b/src/com/android/launcher3/InfoDropTarget.java
index f3383cc..0f139fa 100644
--- a/src/com/android/launcher3/InfoDropTarget.java
+++ b/src/com/android/launcher3/InfoDropTarget.java
@@ -41,7 +41,7 @@
         // Get the hover color
         mHoverColor = getResources().getColor(R.color.info_target_hover_tint);
 
-        setDrawable(R.drawable.info_target_selector);
+        setDrawable(R.drawable.ic_launcher_info_normal);
     }
 
     public static void startDetailsActivityForInfo(Object info, Launcher launcher) {
diff --git a/src/com/android/launcher3/InstallShortcutReceiver.java b/src/com/android/launcher3/InstallShortcutReceiver.java
index 115598f..448cc1d 100644
--- a/src/com/android/launcher3/InstallShortcutReceiver.java
+++ b/src/com/android/launcher3/InstallShortcutReceiver.java
@@ -156,6 +156,16 @@
         queuePendingShortcutInfo(info, context);
     }
 
+    public static ShortcutInfo fromShortcutIntent(Context context, Intent data) {
+        PendingInstallShortcutInfo info = new PendingInstallShortcutInfo(data, context);
+        if (info.launchIntent == null || info.label == null) {
+            if (DBG) Log.e(TAG, "Invalid install shortcut intent");
+            return null;
+        }
+        info = convertToLauncherActivityIfPossible(info);
+        return info.getShortcutInfo();
+    }
+
     static void queueInstallShortcut(LauncherActivityInfoCompat info, Context context) {
         queuePendingShortcutInfo(new PendingInstallShortcutInfo(info, context), context);
     }
@@ -215,30 +225,6 @@
     }
 
     /**
-     * Returns true if the intent is a valid launch intent for a shortcut.
-     * This is used to identify shortcuts which are different from the ones exposed by the
-     * applications' manifest file.
-     *
-     * When DISABLE_ALL_APPS is true, shortcuts exposed via the app's manifest should never be
-     * duplicated or removed(unless the app is un-installed).
-     *
-     * @param launchIntent The intent that will be launched when the shortcut is clicked.
-     */
-    static boolean isValidShortcutLaunchIntent(Intent launchIntent) {
-        if (launchIntent != null
-                && Intent.ACTION_MAIN.equals(launchIntent.getAction())
-                && launchIntent.getComponent() != null
-                && launchIntent.getCategories() != null
-                && launchIntent.getCategories().size() == 1
-                && launchIntent.hasCategory(Intent.CATEGORY_LAUNCHER)
-                && launchIntent.getExtras() == null
-                && TextUtils.isEmpty(launchIntent.getDataString())) {
-            return false;
-        }
-        return true;
-    }
-
-    /**
      * Ensures that we have a valid, non-null name.  If the provided name is null, we will return
      * the application name instead.
      */
@@ -428,7 +414,7 @@
             // Already an activity target
             return original;
         }
-        if (isValidShortcutLaunchIntent(original.launchIntent)
+        if (!Utilities.isLauncherAppTarget(original.launchIntent)
                 || !original.user.equals(UserHandleCompat.myUserHandle())) {
             // We can only convert shortcuts which point to a main activity in the current user.
             return original;
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 59ca978..a305d10 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -55,6 +55,7 @@
 import android.graphics.Color;
 import android.graphics.PorterDuff;
 import android.graphics.Rect;
+import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
 import android.net.Uri;
 import android.os.AsyncTask;
@@ -157,6 +158,10 @@
     private static final int REQUEST_BIND_APPWIDGET = 11;
     private static final int REQUEST_RECONFIGURE_APPWIDGET = 12;
 
+    private static final int WORKSPACE_BACKGROUND_GRADIENT = 0;
+    private static final int WORKSPACE_BACKGROUND_TRANSPARENT = 1;
+    private static final int WORKSPACE_BACKGROUND_BLACK = 2;
+
     /**
      * IntentStarter uses request codes starting with this. This must be greater than all activity
      * request codes used internally.
@@ -806,7 +811,7 @@
         }
 
         boolean isWidgetDrop = (requestCode == REQUEST_PICK_APPWIDGET ||
-                requestCode == REQUEST_CREATE_APPWIDGET || requestCode == REQUEST_CREATE_SHORTCUT);
+                requestCode == REQUEST_CREATE_APPWIDGET);
 
         final boolean workspaceLocked = isWorkspaceLocked();
         // We have special handling for widgets
@@ -1042,8 +1047,9 @@
             }
         }
 
-        // Background was set to gradient in onPause(), restore to black if in all apps.
-        setWorkspaceBackground(mState == State.WORKSPACE);
+        // Background was set to gradient in onPause(), restore to transparent if in all apps.
+        setWorkspaceBackground(mState == State.WORKSPACE ? WORKSPACE_BACKGROUND_TRANSPARENT
+                : WORKSPACE_BACKGROUND_GRADIENT);
 
         mPaused = false;
         if (mRestoring || mOnResumeNeedsLoad) {
@@ -1550,14 +1556,13 @@
         int[] touchXY = mPendingAddInfo.dropPos;
         CellLayout layout = getCellLayout(container, screenId);
 
-        boolean foundCellSpan = false;
-
-        ShortcutInfo info = mModel.infoFromShortcutIntent(this, data);
+        ShortcutInfo info = InstallShortcutReceiver.fromShortcutIntent(this, data);
         if (info == null) {
             return;
         }
         final View view = createShortcut(info);
 
+        boolean foundCellSpan = false;
         // First we check if we already know the exact location where we want to add this item.
         if (cellX >= 0 && cellY >= 0) {
             cellXY[0] = cellX;
@@ -3296,9 +3301,17 @@
         return (mState == State.WIDGETS) || (mOnResumeState == State.WIDGETS);
     }
 
-    private void setWorkspaceBackground(boolean workspace) {
-        mLauncherView.setBackground(workspace ?
-                mWorkspaceBackgroundDrawable : null);
+    private void setWorkspaceBackground(int background) {
+        switch (background) {
+            case WORKSPACE_BACKGROUND_TRANSPARENT:
+                getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
+                break;
+            case WORKSPACE_BACKGROUND_BLACK:
+                getWindow().setBackgroundDrawable(null);
+                break;
+            default:
+                getWindow().setBackgroundDrawable(mWorkspaceBackgroundDrawable);
+        }
     }
 
     protected void changeWallpaperVisiblity(boolean visible) {
@@ -3308,7 +3321,7 @@
         if (wpflags != curflags) {
             getWindow().setFlags(wpflags, WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER);
         }
-        setWorkspaceBackground(visible);
+        setWorkspaceBackground(visible ? WORKSPACE_BACKGROUND_GRADIENT : WORKSPACE_BACKGROUND_BLACK);
     }
 
     @Override
diff --git a/src/com/android/launcher3/LauncherBackupAgentHelper.java b/src/com/android/launcher3/LauncherBackupAgentHelper.java
index 5f7173f..e607a0f 100644
--- a/src/com/android/launcher3/LauncherBackupAgentHelper.java
+++ b/src/com/android/launcher3/LauncherBackupAgentHelper.java
@@ -94,6 +94,7 @@
             // TODO: Update the backup set to include rank.
             if (mHelper.restoredBackupVersion <= 2) {
                 LauncherAppState.getLauncherProvider().updateFolderItemsRank();
+                LauncherAppState.getLauncherProvider().convertShortcutsToLauncherActivities();
             }
         } else {
             if (VERBOSE) Log.v(TAG, "Nothing was restored, clearing DB");
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index 5002538..1b92602 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -969,7 +969,8 @@
                         break;
                 }
 
-                folderInfo.title = Utilities.trim(c.getString(titleIndex));
+                // Do not trim the folder label, as is was set by the user.
+                folderInfo.title = c.getString(titleIndex);
                 folderInfo.id = id;
                 folderInfo.container = c.getInt(containerIndex);
                 folderInfo.screenId = c.getInt(screenIndex);
@@ -2026,7 +2027,7 @@
                                                 "constructing info for partially restored package",
                                                 true);
                                         info = getRestoredItemInfo(c, titleIndex, intent,
-                                                promiseType, useLowResIcon);
+                                                promiseType);
                                         intent = getRestoredItemIntent(c, context, intent);
                                     } else {
                                         // Don't restore items for other profiles.
@@ -2111,7 +2112,8 @@
                                 id = c.getLong(idIndex);
                                 FolderInfo folderInfo = findOrMakeFolder(sBgFolders, id);
 
-                                folderInfo.title = Utilities.trim(c.getString(titleIndex));
+                                // Do not trim the folder label, as is was set by the user.
+                                folderInfo.title = c.getString(titleIndex);
                                 folderInfo.id = id;
                                 container = c.getInt(containerIndex);
                                 folderInfo.container = container;
@@ -2755,7 +2757,7 @@
                         return;
                     }
                 }
-                updateAllAppsIconsCache();
+                mIconCache.updateDbIcons();
                 synchronized (LoaderTask.this) {
                     if (mStopped) {
                         return;
@@ -2881,68 +2883,6 @@
             }
         }
 
-        private void updateAllAppsIconsCache() {
-            final ArrayList<AppInfo> updatedApps = new ArrayList<>();
-
-            for (UserHandleCompat user : mUserManager.getUserProfiles()) {
-                // Query for the set of apps
-                final List<LauncherActivityInfoCompat> apps = mLauncherApps.getActivityList(null, user);
-                // Fail if we don't have any apps
-                // TODO: Fix this. Only fail for the current user.
-                if (apps == null || apps.isEmpty()) {
-                    return;
-                }
-
-                // Update icon cache
-                HashSet<String> updatedPackages = mIconCache.updateDBIcons(user, apps);
-
-                // If any package icon has changed (app was updated while launcher was dead),
-                // update the corresponding shortcuts.
-                if (!updatedPackages.isEmpty()) {
-                    final ArrayList<ShortcutInfo> updatedShortcuts = new ArrayList<>();
-                    synchronized (sBgLock) {
-                        for (ItemInfo info : sBgItemsIdMap) {
-                            if (info instanceof ShortcutInfo && user.equals(info.user)
-                                    && info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
-                                ShortcutInfo si = (ShortcutInfo) info;
-                                ComponentName cn = si.getTargetComponent();
-                                if (cn != null && updatedPackages.contains(cn.getPackageName())) {
-                                    si.updateIcon(mIconCache);
-                                    updatedShortcuts.add(si);
-                                }
-                            }
-                        }
-                        mBgAllAppsList.updateIconsAndLabels(updatedPackages, user, updatedApps);
-                    }
-
-                    if (!updatedShortcuts.isEmpty()) {
-                        final UserHandleCompat userFinal = user;
-                        mHandler.post(new Runnable() {
-
-                            public void run() {
-                                Callbacks cb = getCallback();
-                                if (cb != null) {
-                                    cb.bindShortcutsChanged(updatedShortcuts,
-                                            new ArrayList<ShortcutInfo>(), userFinal);
-                                }
-                            }
-                        });
-                    }
-                }
-            }
-            if (!updatedApps.isEmpty()) {
-                mHandler.post(new Runnable() {
-
-                    public void run() {
-                        Callbacks cb = getCallback();
-                        if (cb != null) {
-                            cb.bindAppsUpdated(updatedApps);
-                        }
-                    }
-                });
-            }
-        }
-
         public void dumpState() {
             synchronized (sBgLock) {
                 Log.d(TAG, "mLoaderTask.mContext=" + mContext);
@@ -2953,6 +2893,58 @@
         }
     }
 
+    /**
+     * Called when the icons for packages have been updated in the icon cache.
+     */
+    public void onPackageIconsUpdated(HashSet<String> updatedPackages, UserHandleCompat user) {
+        final Callbacks callbacks = getCallback();
+        final ArrayList<AppInfo> updatedApps = new ArrayList<>();
+        final ArrayList<ShortcutInfo> updatedShortcuts = new ArrayList<>();
+
+        // If any package icon has changed (app was updated while launcher was dead),
+        // update the corresponding shortcuts.
+        synchronized (sBgLock) {
+            for (ItemInfo info : sBgItemsIdMap) {
+                if (info instanceof ShortcutInfo && user.equals(info.user)
+                        && info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
+                    ShortcutInfo si = (ShortcutInfo) info;
+                    ComponentName cn = si.getTargetComponent();
+                    if (cn != null && updatedPackages.contains(cn.getPackageName())) {
+                        si.updateIcon(mIconCache);
+                        updatedShortcuts.add(si);
+                    }
+                }
+            }
+            mBgAllAppsList.updateIconsAndLabels(updatedPackages, user, updatedApps);
+        }
+
+        if (!updatedShortcuts.isEmpty()) {
+            final UserHandleCompat userFinal = user;
+            mHandler.post(new Runnable() {
+
+                public void run() {
+                    Callbacks cb = getCallback();
+                    if (cb != null && callbacks == cb) {
+                        cb.bindShortcutsChanged(updatedShortcuts,
+                                new ArrayList<ShortcutInfo>(), userFinal);
+                    }
+                }
+            });
+        }
+
+        if (!updatedApps.isEmpty()) {
+            mHandler.post(new Runnable() {
+
+                public void run() {
+                    Callbacks cb = getCallback();
+                    if (cb != null && callbacks == cb) {
+                        cb.bindAppsUpdated(updatedApps);
+                    }
+                }
+            });
+        }
+    }
+
     void enqueuePackageUpdated(PackageUpdatedTask task) {
         sWorker.post(task);
     }
@@ -3410,10 +3402,10 @@
      * to a package that is not yet installed on the system.
      */
     public ShortcutInfo getRestoredItemInfo(Cursor cursor, int titleIndex, Intent intent,
-            int promiseType, boolean useLowResIcon) {
+            int promiseType) {
         final ShortcutInfo info = new ShortcutInfo();
         info.user = UserHandleCompat.myUserHandle();
-        mIconCache.getTitleAndIcon(info, intent, info.user, useLowResIcon);
+        mIconCache.getTitleAndIcon(info, intent, info.user, false /* useLowResIcon */);
 
         if ((promiseType & ShortcutInfo.FLAG_RESTORED_ICON) != 0) {
             String title = (cursor != null) ? cursor.getString(titleIndex) : null;
diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java
index 8bc8988..8fef32d 100644
--- a/src/com/android/launcher3/LauncherProvider.java
+++ b/src/com/android/launcher3/LauncherProvider.java
@@ -65,7 +65,7 @@
     private static final String TAG = "Launcher.LauncherProvider";
     private static final boolean LOGD = false;
 
-    private static final int DATABASE_VERSION = 25;
+    private static final int DATABASE_VERSION = 26;
 
     static final String OLD_AUTHORITY = "com.android.launcher2.settings";
     static final String AUTHORITY = ProviderConfig.AUTHORITY;
@@ -378,6 +378,11 @@
         mOpenHelper.updateFolderItemsRank(mOpenHelper.getWritableDatabase(), false);
     }
 
+    public void convertShortcutsToLauncherActivities() {
+        mOpenHelper.convertShortcutsToLauncherActivities(mOpenHelper.getWritableDatabase());
+    }
+
+
     public void deleteDatabase() {
         // Are you sure? (y/n)
         final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
@@ -623,10 +628,12 @@
                     }
                 }
                 case 23:
-                    convertShortcutsToLauncherActivities(db);
+                    // No-op
                 case 24:
                     ManagedProfileHeuristic.markExistingUsersForNoFolderCreation(mContext);
-                case 25: {
+                case 25:
+                    convertShortcutsToLauncherActivities(db);
+                case 26: {
                     // DB Upgraded successfully
                     return;
                 }
diff --git a/src/com/android/launcher3/UninstallDropTarget.java b/src/com/android/launcher3/UninstallDropTarget.java
index 0fc8f32..4199390 100644
--- a/src/com/android/launcher3/UninstallDropTarget.java
+++ b/src/com/android/launcher3/UninstallDropTarget.java
@@ -28,7 +28,7 @@
         // Get the hover color
         mHoverColor = getResources().getColor(R.color.uninstall_target_hover_tint);
 
-        setDrawable(R.drawable.uninstall_target_selector);
+        setDrawable(R.drawable.ic_launcher_uninstall_normal);
     }
 
     @Override
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index 1f8a6f2..9f47e13 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -45,12 +45,15 @@
 import android.graphics.drawable.PaintDrawable;
 import android.os.Build;
 import android.os.Process;
+import android.text.TextUtils;
 import android.util.Log;
 import android.util.Pair;
 import android.util.SparseArray;
 import android.view.View;
 import android.widget.Toast;
 
+import junit.framework.Assert;
+
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.util.ArrayList;
@@ -58,8 +61,6 @@
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
-import junit.framework.Assert;
-
 /**
  * Various utilities shared amongst the Launcher's classes.
  */
@@ -650,4 +651,22 @@
             Assert.assertTrue(LauncherModel.sWorkerThread.getThreadId() == Process.myTid());
         }
     }
+
+    /**
+     * Returns true if the intent is a valid launch intent for a launcher activity of an app.
+     * This is used to identify shortcuts which are different from the ones exposed by the
+     * applications' manifest file.
+     *
+     * @param launchIntent The intent that will be launched when the shortcut is clicked.
+     */
+    public static boolean isLauncherAppTarget(Intent launchIntent) {
+        return launchIntent != null
+                && Intent.ACTION_MAIN.equals(launchIntent.getAction())
+                && launchIntent.getComponent() != null
+                && launchIntent.getCategories() != null
+                && launchIntent.getCategories().size() == 1
+                && launchIntent.hasCategory(Intent.CATEGORY_LAUNCHER)
+                && launchIntent.getExtras() == null
+                && TextUtils.isEmpty(launchIntent.getDataString());
+    }
 }