Merge "Preventing overdraw. Drawing background directly at the window, instead of the rootview." into ub-launcher3-burnaby
diff --git a/proguard.flags b/proguard.flags
index 6eb5948..5e2c384 100644
--- a/proguard.flags
+++ b/proguard.flags
@@ -1,19 +1,3 @@
--keep class com.android.launcher3.Launcher {
-  public void previousScreen(android.view.View);
-  public void nextScreen(android.view.View);
-  public void launchHotSeat(android.view.View);
-  public void onClickSearchButton(android.view.View);
-  public void onClickVoiceButton(android.view.View);
-  public void onClickConfigureButton(android.view.View);
-  public void onClickAllAppsButton(android.view.View);
-  public void dismissFirstRunCling(android.view.View);
-  public void dismissMigrationClingCopyApps(android.view.View);
-  public void dismissMigrationClingUseDefault(android.view.View);
-  public void dismissMigrationWorkspaceCling(android.view.View);
-  public void dismissWorkspaceCling(android.view.View);
-  public void dismissAllAppsCling(android.view.View);
-}
-
 -keep class com.android.launcher3.CellLayout {
   public float getBackgroundAlpha();
   public void setBackgroundAlpha(float);
@@ -44,8 +28,6 @@
 -keep class com.android.launcher3.Workspace {
   public float getBackgroundAlpha();
   public void setBackgroundAlpha(float);
-  public float getChildrenOutlineAlpha();
-  public void setChildrenOutlineAlpha(float);
 }
 
 -keep class com.android.launcher3.MemoryDumpActivity {
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/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/layout/widgets_list_row_view.xml b/res/layout/widgets_list_row_view.xml
index dc1bcce..67b4acb 100644
--- a/res/layout/widgets_list_row_view.xml
+++ b/res/layout/widgets_list_row_view.xml
@@ -19,6 +19,7 @@
     android:id="@+id/widgets_cell_list_container"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
+    android:background="@color/widgets_cell_color"
     android:orientation="vertical"
     android:focusable="true"
     android:descendantFocusability="afterDescendants">
@@ -52,7 +53,7 @@
         android:id="@+id/widgets_scroll_container"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:scrollbars="none" >
+        android:scrollbars="none">
         <LinearLayout
             android:id="@+id/widgets_cell_list"
             android:layout_width="wrap_content"
@@ -61,7 +62,6 @@
             android:layout_marginLeft="@dimen/widget_row_padding"
             android:orientation="horizontal"
             android:divider="@drawable/widgets_row_divider"
-            android:showDividers="middle"
-            android:background="@color/widgets_cell_color"/>
+            android:showDividers="middle"/>
     </HorizontalScrollView>
 </LinearLayout>
diff --git a/res/layout/widgets_view.xml b/res/layout/widgets_view.xml
index 1857595..9d9fa10 100644
--- a/res/layout/widgets_view.xml
+++ b/res/layout/widgets_view.xml
@@ -34,19 +34,12 @@
         android:focusable="false"
         android:visibility="invisible" />
 
-    <LinearLayout
-        android:id="@+id/widgets_content"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:clipChildren="false"
-        android:elevation="15dp"
-        android:visibility="gone"
-        android:orientation="vertical" >
-
         <com.android.launcher3.widget.WidgetsContainerRecyclerView
-                android:id="@+id/widgets_list_view"
-                android:layout_width="match_parent"
-                android:layout_height="match_parent"
-                android:background="@drawable/quantum_panel_dark"/>
-    </LinearLayout>
+            android:id="@+id/widgets_list_view"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:background="@drawable/quantum_panel_dark"
+            android:elevation="15dp"
+            android:visibility="gone" />
+
 </com.android.launcher3.widget.WidgetsContainerView>
\ No newline at end of file
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..5e05d11 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,16 +195,16 @@
     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,
+    public AlphabeticalAppsList(Context context, AdapterChangedCallback auCb, int numAppsPerRow,
             int numPredictedAppsPerRow) {
         mContext = context;
         mIndexer = new AlphabeticIndexCompat(context);
         mAppNameComparator = new AppNameComparator(context);
-        mFilterChangedCallback = cb;
+        mAdapterChangedCallback = auCb;
         setNumAppsPerRow(numAppsPerRow, numPredictedAppsPerRow);
     }
 
@@ -248,7 +252,7 @@
      * Returns the current filtered list of applications broken down into their sections.
      */
     public List<AdapterItem> getAdapterItems() {
-        return mSectionedFilteredApps;
+        return mAdapterItems;
     }
 
     /**
@@ -278,11 +282,7 @@
     public void setFilter(Filter f) {
         if (mFilter != f) {
             mFilter = f;
-            onAppsUpdated();
-            mAdapter.notifyDataSetChanged();
-            if (mFilterChangedCallback != null){
-                mFilterChangedCallback.onFilterChanged();
-            }
+            updateAdapterItems();
         }
     }
 
@@ -294,7 +294,6 @@
         mPredictedAppComponents.clear();
         mPredictedAppComponents.addAll(apps);
         onAppsUpdated();
-        mAdapter.notifyDataSetChanged();
     }
 
     /**
@@ -311,7 +310,6 @@
         mApps.clear();
         mApps.addAll(apps);
         onAppsUpdated();
-        mAdapter.notifyDataSetChanged();
     }
 
     /**
@@ -320,10 +318,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 +332,10 @@
             if (index != -1) {
                 mApps.set(index, info);
             } else {
-                addApp(info);
+                mApps.add(info);
             }
         }
         onAppsUpdated();
-        mAdapter.notifyDataSetChanged();
     }
 
     /**
@@ -353,7 +349,6 @@
             }
         }
         onAppsUpdated();
-        mAdapter.notifyDataSetChanged();
     }
 
     /**
@@ -373,34 +368,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 +450,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 +478,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 +489,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 +524,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 +553,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..612c19c 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;
@@ -298,7 +299,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 +398,11 @@
                 icon.setVisibility(View.INVISIBLE);
             }
         }
+
+        if (mFocusPredictionBarOnFirstBind) {
+            mFocusPredictionBarOnFirstBind = false;
+            mPredictionBarView.requestFocus();
+        }
     }
 
     @Override
@@ -670,7 +686,7 @@
     }
 
     @Override
-    public void onFilterChanged() {
+    public void onAdapterItemsChanged() {
         updatePredictionBarVisibility();
     }
 
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index 3b3b9bf..d530009 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -38,7 +38,7 @@
 import android.widget.TextView;
 
 import com.android.launcher3.IconCache.IconLoadRequest;
-import com.android.launcher3.widget.PackageItemInfo;
+import com.android.launcher3.model.PackageItemInfo;
 
 /**
  * TextView that draws a bubble behind the text. We cannot use a LineBackgroundSpan
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/CellLayout.java b/src/com/android/launcher3/CellLayout.java
index edcdc11..1d211bf 100644
--- a/src/com/android/launcher3/CellLayout.java
+++ b/src/com/android/launcher3/CellLayout.java
@@ -103,8 +103,6 @@
     private float FOREGROUND_ALPHA_DAMPER = 0.65f;
     private int mForegroundAlpha = 0;
     private float mBackgroundAlpha;
-    private float mBackgroundAlphaMultiplier = 1.0f;
-    private boolean mDrawBackground = true;
 
     private Drawable mNormalBackground;
     private Drawable mActiveGlowBackground;
@@ -423,10 +421,6 @@
         }
     }
 
-    void disableBackground() {
-        mDrawBackground = false;
-    }
-
     void disableDragTarget() {
         mIsDragTarget = false;
     }
@@ -448,12 +442,16 @@
 
     @Override
     protected void onDraw(Canvas canvas) {
+        if (!mIsDragTarget) {
+            return;
+        }
+
         // When we're large, we are either drawn in a "hover" state (ie when dragging an item to
         // a neighboring page) or with just a normal background (if backgroundAlpha > 0.0f)
         // When we're small, we are either drawn normally or in the "accepts drops" state (during
         // a drag). However, we also drag the mini hover background *over* one of those two
         // backgrounds
-        if (mDrawBackground && mBackgroundAlpha > 0.0f) {
+        if (mBackgroundAlpha > 0.0f) {
             Drawable bg;
 
             if (mIsDragOverlapping) {
@@ -463,7 +461,7 @@
                 bg = mNormalBackground;
             }
 
-            bg.setAlpha((int) (mBackgroundAlpha * mBackgroundAlphaMultiplier * 255));
+            bg.setAlpha((int) (mBackgroundAlpha * 255));
             bg.setBounds(mBackgroundRect);
             bg.draw(canvas);
         }
@@ -951,17 +949,6 @@
         return mBackgroundAlpha;
     }
 
-    public void setBackgroundAlphaMultiplier(float multiplier) {
-        if (mBackgroundAlphaMultiplier != multiplier) {
-            mBackgroundAlphaMultiplier = multiplier;
-            invalidate();
-        }
-    }
-
-    public float getBackgroundAlphaMultiplier() {
-        return mBackgroundAlphaMultiplier;
-    }
-
     public void setBackgroundAlpha(float alpha) {
         if (mBackgroundAlpha != alpha) {
             mBackgroundAlpha = alpha;
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/FolderPagedView.java b/src/com/android/launcher3/FolderPagedView.java
index de30b60..06ed588 100644
--- a/src/com/android/launcher3/FolderPagedView.java
+++ b/src/com/android/launcher3/FolderPagedView.java
@@ -241,6 +241,11 @@
         return page;
     }
 
+    @Override
+    protected int getChildGap() {
+        return getPaddingLeft() + getPaddingRight();
+    }
+
     public void setFixedSize(int width, int height) {
         width -= (getPaddingLeft() + getPaddingRight());
         height -= (getPaddingTop() + getPaddingBottom());
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 fff07c6..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;
 
@@ -42,9 +43,9 @@
 import com.android.launcher3.compat.LauncherAppsCompat;
 import com.android.launcher3.compat.UserHandleCompat;
 import com.android.launcher3.compat.UserManagerCompat;
+import com.android.launcher3.model.PackageItemInfo;
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.Thunk;
-import com.android.launcher3.widget.PackageItemInfo;
 
 import java.util.HashMap;
 import java.util.HashSet;
@@ -52,6 +53,7 @@
 import java.util.List;
 import java.util.Locale;
 import java.util.Map.Entry;
+import java.util.Stack;
 
 /**
  * Cache of application icons.  Icons can be made from any thread.
@@ -69,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;
@@ -221,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>();
@@ -255,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);
@@ -279,14 +302,9 @@
             }
             if (app == null) {
                 itemsToRemove.add(c.getInt(rowIndex));
-                continue;
+            } else {
+                appsToUpdate.add(app);
             }
-            ContentValues values = updateCacheAndGetContentValues(app);
-            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()) {
@@ -296,21 +314,20 @@
         }
 
         // Insert remaining apps.
-        for (LauncherActivityInfoCompat app : componentMap.values()) {
-            PackageInfo info = pkgInfoMap.get(app.getComponentName().getPackageName());
-            if (info == null) {
-                continue;
-            }
-            addIconToDBAndMemCache(app, info, userSerial);
+        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,
             long userSerial) {
-        ContentValues values = updateCacheAndGetContentValues(app);
+        // Reuse the existing entry if it already exists in the DB. This ensures that we do not
+        // create bitmap if it was already created during loader.
+        ContentValues values = updateCacheAndGetContentValues(app, false);
         addIconToDB(values, app.getComponentName(), info, userSerial);
-        values.put(IconDB.COLUMN_COMPONENT, app.getComponentName().flattenToString());
     }
 
     /**
@@ -327,9 +344,21 @@
                 SQLiteDatabase.CONFLICT_REPLACE);
     }
 
-    private ContentValues updateCacheAndGetContentValues(LauncherActivityInfoCompat app) {
-        CacheEntry entry = new CacheEntry();
-        entry.icon = Utilities.createIconBitmap(app.getBadgedIcon(mIconDpi), mContext);
+    private ContentValues updateCacheAndGetContentValues(LauncherActivityInfoCompat app,
+            boolean replaceExisting) {
+        final ComponentKey key = new ComponentKey(app.getComponentName(), app.getUser());
+        CacheEntry entry = null;
+        if (!replaceExisting) {
+            entry = mCache.get(key);
+            // We can't reuse the entry if the high-res icon is not present.
+            if (entry == null || entry.isLowResIcon || entry.icon == null) {
+                entry = null;
+            }
+        }
+        if (entry == null) {
+            entry = new CacheEntry();
+            entry.icon = Utilities.createIconBitmap(app.getBadgedIcon(mIconDpi), mContext);
+        }
         entry.title = app.getLabel();
         entry.contentDescription = mUserManager.getBadgedLabelForUser(entry.title, app.getUser());
         mCache.put(new ComponentKey(app.getComponentName(), app.getUser()), entry);
@@ -671,6 +700,66 @@
         }
     }
 
+    /**
+     * 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 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 SerializedIconUpdateTask(long userSerial, HashMap<String, PackageInfo> pkgInfoMap,
+                Stack<LauncherActivityInfoCompat> appsToAdd,
+                Stack<LauncherActivityInfoCompat> appsToUpdate) {
+            mUserSerial = userSerial;
+            mPkgInfoMap = pkgInfoMap;
+            mAppsToAdd = appsToAdd;
+            mAppsToUpdate = appsToUpdate;
+        }
+
+        @Override
+        public void run() {
+            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) {
+                    synchronized (IconCache.this) {
+                        addIconToDBAndMemCache(app, info, mUserSerial);
+                    }
+                }
+
+                if (!mAppsToAdd.isEmpty()) {
+                    scheduleNext();
+                }
+            }
+        }
+
+        public void scheduleNext() {
+            mWorkerHandler.postAtTime(this, ICON_UPDATE_TOKEN, SystemClock.uptimeMillis() + 1);
+        }
+    }
+
     private static final class IconDB extends SQLiteOpenHelper {
         private final static int DB_VERSION = 4;
 
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/Launcher.java b/src/com/android/launcher3/Launcher.java
index d7282eb..e5539c0 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -103,6 +103,7 @@
 import com.android.launcher3.compat.LauncherAppsCompat;
 import com.android.launcher3.compat.UserHandleCompat;
 import com.android.launcher3.compat.UserManagerCompat;
+import com.android.launcher3.model.WidgetsModel;
 import com.android.launcher3.util.LongArrayMap;
 import com.android.launcher3.util.Thunk;
 import com.android.launcher3.widget.PendingAddWidgetInfo;
@@ -273,8 +274,9 @@
     // Main container view for the all apps screen.
     @Thunk AppsContainerView mAppsView;
 
-    // Main container view for the widget tray screen.
-    private WidgetsContainerView mWidgetsView;
+    // Main container view and the model for the widget tray screen.
+    @Thunk WidgetsContainerView mWidgetsView;
+    @Thunk WidgetsModel mWidgetsModel;
 
     private boolean mAutoAdvanceRunning = false;
     private AppWidgetHostView mQsb;
@@ -4379,23 +4381,22 @@
         }
     }
 
-    @Thunk ArrayList<Object> mWidgetsAndShortcuts;
     private Runnable mBindPackagesUpdatedRunnable = new Runnable() {
             public void run() {
-                bindAllPackages(mWidgetsAndShortcuts);
+                bindAllPackages(mWidgetsModel);
             }
         };
 
     @Override
-    public void bindAllPackages(final ArrayList<Object> widgetsAndShortcuts) {
+    public void bindAllPackages(final WidgetsModel model) {
         if (waitUntilResume(mBindPackagesUpdatedRunnable, true)) {
-            mWidgetsAndShortcuts = widgetsAndShortcuts;
+            mWidgetsModel = model;
             return;
         }
 
-        if (mWidgetsView != null && widgetsAndShortcuts != null) {
-            mWidgetsView.addWidgets(widgetsAndShortcuts, getPackageManager());
-            mWidgetsAndShortcuts = null;
+        if (mWidgetsView != null && model != null) {
+            mWidgetsView.addWidgets(model);
+            mWidgetsModel = null;
         }
     }
 
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index 3e05f57..d994dc7 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -58,6 +58,7 @@
 import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo;
 import com.android.launcher3.compat.UserHandleCompat;
 import com.android.launcher3.compat.UserManagerCompat;
+import com.android.launcher3.model.WidgetsModel;
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.LongArrayMap;
 import com.android.launcher3.util.ManagedProfileHeuristic;
@@ -203,7 +204,7 @@
         public void bindRestoreItemsChange(HashSet<ItemInfo> updates);
         public void bindComponentsRemoved(ArrayList<String> packageNames,
                         ArrayList<AppInfo> appInfos, UserHandleCompat user, int reason);
-        public void bindAllPackages(ArrayList<Object> widgetsAndShortcuts);
+        public void bindAllPackages(WidgetsModel model);
         public void bindSearchablesChanged();
         public boolean isAllAppsButtonRank(int rank);
         public void onPageBoundSynchronously(int page);
@@ -968,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);
@@ -2110,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;
@@ -2754,7 +2757,7 @@
                         return;
                     }
                 }
-                updateAllAppsIconsCache();
+                mIconCache.updateDbIcons();
                 synchronized (LoaderTask.this) {
                     if (mStopped) {
                         return;
@@ -2880,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);
@@ -2952,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);
     }
@@ -3344,18 +3337,19 @@
         runOnWorkerThread(new Runnable(){
             @Override
             public void run() {
-                final ArrayList<Object> list = getWidgetsAndShortcuts(context, refresh);
+                final WidgetsModel model = createWidgetsModel(context, refresh);
                 mHandler.post(new Runnable() {
                     @Override
                     public void run() {
                         Callbacks cb = getCallback();
                         if (callbacks == cb && cb != null) {
-                            callbacks.bindAllPackages(list);
+                            callbacks.bindAllPackages(model);
                         }
                     }
                 });
                 // update the Widget entries inside DB on the worker thread.
-                LauncherAppState.getInstance().getWidgetCache().removeObsoletePreviews(list);
+                LauncherAppState.getInstance().getWidgetCache().removeObsoletePreviews(
+                        model.getRawList());
             }
         });
     }
@@ -3365,13 +3359,15 @@
      *
      *  @see #loadAndBindWidgetsAndShortcuts
      */
-    private ArrayList<Object> getWidgetsAndShortcuts(Context context, boolean refresh) {
+    private WidgetsModel createWidgetsModel(Context context, boolean refresh) {
         PackageManager packageManager = context.getPackageManager();
         final ArrayList<Object> widgetsAndShortcuts = new ArrayList<Object>();
         widgetsAndShortcuts.addAll(getWidgetProviders(context, refresh));
         Intent shortcutsIntent = new Intent(Intent.ACTION_CREATE_SHORTCUT);
         widgetsAndShortcuts.addAll(packageManager.queryIntentActivities(shortcutsIntent, 0));
-        return widgetsAndShortcuts;
+        WidgetsModel model = new WidgetsModel(context);
+        model.addWidgetsAndShortcuts(widgetsAndShortcuts);
+        return model;
     }
 
     @Thunk static boolean isPackageDisabled(Context context, String packageName,
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index 62f1bc9..dda9a16 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -633,9 +633,6 @@
             if (mCurrentPage != getNextPage()) {
                 AccessibilityEvent ev =
                         AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_SCROLLED);
-                ev.setItemCount(getChildCount());
-                ev.setFromIndex(getNextPage());
-                ev.setToIndex(getNextPage());
 
                 sendAccessibilityEventUnchecked(ev);
             }
@@ -912,8 +909,7 @@
                     pageGap = getPaddingRight();
                 }
 
-                childLeft += childWidth + pageGap
-                        + (lp.isFullScreenPage ? 0 : (getPaddingLeft() + getPaddingRight()));
+                childLeft += childWidth + pageGap + getChildGap();
             }
         }
 
@@ -961,6 +957,10 @@
         }
     }
 
+    protected int getChildGap() {
+        return 0;
+    }
+
     private void updateMaxScrollX() {
         int childCount = getChildCount();
         if (childCount > 0) {
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 6734fdc..1f8a6f2 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -44,6 +44,7 @@
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.PaintDrawable;
 import android.os.Build;
+import android.os.Process;
 import android.util.Log;
 import android.util.Pair;
 import android.util.SparseArray;
@@ -57,6 +58,8 @@
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
+import junit.framework.Assert;
+
 /**
  * Various utilities shared amongst the Launcher's classes.
  */
@@ -641,4 +644,10 @@
         return (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) &&
                 (res.getConfiguration().getLayoutDirection() == View.LAYOUT_DIRECTION_RTL);
     }
+
+    public static void assertWorkerThread() {
+        if (LauncherAppState.isDogfoodBuild()) {
+            Assert.assertTrue(LauncherModel.sWorkerThread.getThreadId() == Process.myTid());
+        }
+    }
 }
diff --git a/src/com/android/launcher3/WidgetPreviewLoader.java b/src/com/android/launcher3/WidgetPreviewLoader.java
index 8459673..5ee1f26 100644
--- a/src/com/android/launcher3/WidgetPreviewLoader.java
+++ b/src/com/android/launcher3/WidgetPreviewLoader.java
@@ -24,17 +24,16 @@
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
 import android.os.AsyncTask;
+import android.os.Handler;
 import android.os.Process;
 import android.util.Log;
 import android.util.LongSparseArray;
-
 import com.android.launcher3.compat.AppWidgetManagerCompat;
 import com.android.launcher3.compat.UserHandleCompat;
 import com.android.launcher3.compat.UserManagerCompat;
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.Thunk;
 import com.android.launcher3.widget.WidgetCell;
-
 import junit.framework.Assert;
 
 import java.util.ArrayList;
@@ -69,6 +68,7 @@
     private final CacheDb mDb;
 
     private final MainThreadExecutor mMainThreadExecutor = new MainThreadExecutor();
+    private final Handler mWorkerHandler;
 
     public WidgetPreviewLoader(Context context, IconCache iconCache) {
         mContext = context;
@@ -76,6 +76,7 @@
         mManager = AppWidgetManagerCompat.getInstance(context);
         mUserManager = UserManagerCompat.getInstance(context);
         mDb = new CacheDb(context);
+        mWorkerHandler = new Handler(LauncherModel.getWorkerLooper());
     }
 
     /**
@@ -83,8 +84,6 @@
      * called on UI thread
      *
      * @param o either {@link LauncherAppWidgetProviderInfo} or {@link ResolveInfo}
-     * @param immediateResult A bitmap array of size 1. If the result is already cached, it is
-     * set to the final result.
      * @return a request id which can be used to cancel the request.
      */
     public PreviewLoadRequest getPreview(final Object o, int previewWidth, int previewHeight,
@@ -207,8 +206,7 @@
      * This ensures that we remove entries for packages which changed while the launcher was dead.
      */
     public void removeObsoletePreviews(ArrayList<Object> list) {
-        // This method should always be called from the worker thread.
-        Assert.assertTrue(LauncherModel.sWorkerThread.getThreadId() == Process.myTid());
+        Utilities.assertWorkerThread();
 
         LongSparseArray<UserHandleCompat> userIdCache = new LongSparseArray<>();
         LongSparseArray<HashSet<String>> validPackages = new LongSparseArray<>();
@@ -289,7 +287,10 @@
         }
     }
 
-    private Bitmap readFromDb(WidgetCacheKey key, Bitmap recycle) {
+    /**
+     * Reads the preview bitmap from the DB or null if the preview is not in the DB.
+     */
+    private Bitmap readFromDb(WidgetCacheKey key, Bitmap recycle, PreviewLoadTask loadTask) {
         Cursor cursor = null;
         try {
             cursor = mDb.getReadableDatabase().query(
@@ -302,12 +303,18 @@
                             key.size
                     },
                     null, null, null);
+            // If cancelled, skip getting the blob and decoding it into a bitmap
+            if (loadTask.isCancelled()) {
+                return null;
+            }
             if (cursor.moveToNext()) {
                 byte[] blob = cursor.getBlob(0);
                 BitmapFactory.Options opts = new BitmapFactory.Options();
                 opts.inBitmap = recycle;
                 try {
-                    return BitmapFactory.decodeByteArray(blob, 0, blob.length, opts);
+                    if (!loadTask.isCancelled()) {
+                        return BitmapFactory.decodeByteArray(blob, 0, blob.length, opts);
+                    }
                 } catch (Exception e) {
                     return null;
                 }
@@ -545,15 +552,17 @@
                 mTask.cancel(true);
             }
 
-            if (mTask.mBitmap == null) {
-                return;
+            // This only handles the case where the PreviewLoadTask is cancelled after the task has
+            // successfully completed (including having written to disk when necessary).  In the
+            // other cases where it is cancelled while the task is running, it will be cleaned up
+            // in the tasks's onCancelled() call, and if cancelled while the task is writing to
+            // disk, it will be cancelled in the task's onPostExecute() call.
+            if (mTask.mBitmapToRecycle != null) {
+                synchronized (mUnusedBitmaps) {
+                    mUnusedBitmaps.add(mTask.mBitmapToRecycle);
+                }
+                mTask.mBitmapToRecycle = null;
             }
-
-            // The preview is no longer bound to any view, move it to {@link WeakReference} list.
-            synchronized(mUnusedBitmaps) {
-                mUnusedBitmaps.add(mTask.mBitmap);
-            }
-            mTask.mBitmap = null;
         }
     }
 
@@ -564,7 +573,8 @@
         private final int mPreviewHeight;
         private final int mPreviewWidth;
         private final WidgetCell mCaller;
-        private Bitmap mBitmap;
+        private long[] mVersions;
+        private Bitmap mBitmapToRecycle;
 
         PreviewLoadTask(WidgetCacheKey key, Object info, int previewWidth,
                 int previewHeight, WidgetCell caller) {
@@ -584,6 +594,10 @@
             Bitmap unusedBitmap = null;
 
             synchronized (mUnusedBitmaps) {
+                // If already cancelled before this gets to run in the background, then return early
+                if (isCancelled()) {
+                    return null;
+                }
                 // Check if we can use a bitmap
                 for (Bitmap candidate : mUnusedBitmaps) {
                     if (candidate != null && candidate.isMutable() &&
@@ -600,31 +614,64 @@
                     mUnusedBitmaps.remove(unusedBitmap);
                 }
             }
+            // If cancelled now, don't bother reading the preview from the DB
             if (isCancelled()) {
-                return null;
+                return unusedBitmap;
             }
-            Bitmap preview = readFromDb(mKey, unusedBitmap);
+            Bitmap preview = readFromDb(mKey, unusedBitmap, this);
+            // Only consider generating the preview if we have not cancelled the task already
             if (!isCancelled() && preview == null) {
                 // Fetch the version info before we generate the preview, so that, in-case the
                 // app was updated while we are generating the preview, we use the old version info,
                 // which would gets re-written next time.
-                long[] versions = getPackageVersion(mKey.componentName.getPackageName());
+                mVersions = getPackageVersion(mKey.componentName.getPackageName());
 
                 // it's not in the db... we need to generate it
                 preview = generatePreview(mInfo, unusedBitmap, mPreviewWidth, mPreviewHeight);
-
-                if (!isCancelled()) {
-                    writeToDb(mKey, versions, preview);
-                }
             }
-
             return preview;
         }
 
         @Override
-        protected void onPostExecute(Bitmap result) {
-            mBitmap = result;
-            mCaller.applyPreview(result);
+        protected void onPostExecute(final Bitmap preview) {
+            mCaller.applyPreview(preview);
+
+            // Write the generated preview to the DB in the worker thread
+            if (mVersions != null) {
+                mWorkerHandler.post(new Runnable() {
+                    @Override
+                    public void run() {
+                        if (!isCancelled()) {
+                            // If we are still using this preview, then write it to the DB and then
+                            // let the normal clear mechanism recycle the bitmap
+                            writeToDb(mKey, mVersions, preview);
+                            mBitmapToRecycle = preview;
+                        } else {
+                            // If we've already cancelled, then skip writing the bitmap to the DB
+                            // and manually add the bitmap back to the recycled set
+                            synchronized (mUnusedBitmaps) {
+                                mUnusedBitmaps.add(preview);
+                            }
+                        }
+                    }
+                });
+            } else {
+                // If we don't need to write to disk, then ensure the preview gets recycled by
+                // the normal clear mechanism
+                mBitmapToRecycle = preview;
+            }
+        }
+
+        @Override
+        protected void onCancelled(Bitmap preview) {
+            // If we've cancelled while the task is running, then can return the bitmap to the
+            // recycled set immediately. Otherwise, it will be recycled after the preview is written
+            // to disk.
+            if (preview != null) {
+                synchronized (mUnusedBitmaps) {
+                    mUnusedBitmaps.add(preview);
+                }
+            }
         }
     }
 
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 8c1c7d6..55742f6 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -89,10 +89,6 @@
         Insettable, UninstallSource, AccessibilityDragSource {
     private static final String TAG = "Launcher.Workspace";
 
-    private static final int CHILDREN_OUTLINE_FADE_OUT_DELAY = 0;
-    private static final int CHILDREN_OUTLINE_FADE_OUT_DURATION = 375;
-    private static final int CHILDREN_OUTLINE_FADE_IN_DURATION = 100;
-
     protected static final int SNAP_OFF_EMPTY_SCREEN_DURATION = 400;
     protected static final int FADE_EMPTY_SCREEN_DURATION = 150;
 
@@ -101,11 +97,6 @@
     static final boolean MAP_NO_RECURSE = false;
     static final boolean MAP_RECURSE = true;
 
-    // These animators are used to fade the children's outlines
-    private ObjectAnimator mChildrenOutlineFadeInAnimation;
-    private ObjectAnimator mChildrenOutlineFadeOutAnimation;
-    private float mChildrenOutlineAlpha = 0;
-
     private static final long CUSTOM_CONTENT_GESTURE_DELAY = 200;
     private long mTouchDownTime = -1;
     private long mCustomContentShowTime = -1;
@@ -388,7 +379,6 @@
         updateChildrenLayersEnabled(false);
         mLauncher.lockScreenOrientation();
         mLauncher.onInteractionBegin();
-        setChildrenBackgroundAlphaMultipliers(1f);
         // Prevent any Un/InstallShortcutReceivers from updating the db while we are dragging
         InstallShortcutReceiver.enableInstallQueue();
         post(new Runnable() {
@@ -578,7 +568,6 @@
     public void createCustomContentContainer() {
         CellLayout customScreen = (CellLayout)
                 mLauncher.getLayoutInflater().inflate(R.layout.workspace_screen, this, false);
-        customScreen.disableBackground();
         customScreen.disableDragTarget();
 
         mWorkspaceScreens.put(CUSTOM_CONTENT_SCREEN_ID, customScreen);
@@ -1533,45 +1522,12 @@
         }
     }
 
-    void showOutlines() {
-        if (!workspaceInModalState() && !mIsSwitchingState) {
-            if (mChildrenOutlineFadeOutAnimation != null) mChildrenOutlineFadeOutAnimation.cancel();
-            if (mChildrenOutlineFadeInAnimation != null) mChildrenOutlineFadeInAnimation.cancel();
-            mChildrenOutlineFadeInAnimation = LauncherAnimUtils.ofFloat(this, "childrenOutlineAlpha", 1.0f);
-            mChildrenOutlineFadeInAnimation.setDuration(CHILDREN_OUTLINE_FADE_IN_DURATION);
-            mChildrenOutlineFadeInAnimation.start();
-        }
-    }
-
-    void hideOutlines() {
-        if (!workspaceInModalState() && !mIsSwitchingState) {
-            if (mChildrenOutlineFadeInAnimation != null) mChildrenOutlineFadeInAnimation.cancel();
-            if (mChildrenOutlineFadeOutAnimation != null) mChildrenOutlineFadeOutAnimation.cancel();
-            mChildrenOutlineFadeOutAnimation = LauncherAnimUtils.ofFloat(this, "childrenOutlineAlpha", 0.0f);
-            mChildrenOutlineFadeOutAnimation.setDuration(CHILDREN_OUTLINE_FADE_OUT_DURATION);
-            mChildrenOutlineFadeOutAnimation.setStartDelay(CHILDREN_OUTLINE_FADE_OUT_DELAY);
-            mChildrenOutlineFadeOutAnimation.start();
-        }
-    }
-
     public void showOutlinesTemporarily() {
         if (!mIsPageMoving && !isTouchActive()) {
             snapToPage(mCurrentPage);
         }
     }
 
-    public void setChildrenOutlineAlpha(float alpha) {
-        mChildrenOutlineAlpha = alpha;
-        for (int i = 0; i < getChildCount(); i++) {
-            CellLayout cl = (CellLayout) getChildAt(i);
-            cl.setBackgroundAlpha(alpha);
-        }
-    }
-
-    public float getChildrenOutlineAlpha() {
-        return mChildrenOutlineAlpha;
-    }
-
     float backgroundAlphaInterpolator(float r) {
         float pivotA = 0.1f;
         float pivotB = 0.4f;
@@ -1601,13 +1557,6 @@
         }
     }
 
-    private void setChildrenBackgroundAlphaMultipliers(float a) {
-        for (int i = 0; i < getChildCount(); i++) {
-            CellLayout child = (CellLayout) getChildAt(i);
-            child.setBackgroundAlphaMultiplier(a);
-        }
-    }
-
     @TargetApi(Build.VERSION_CODES.LOLLIPOP)
     @Override
     public void enableAccessibleDrag(boolean enable) {
@@ -2004,7 +1953,6 @@
 
     public void onStartReordering() {
         super.onStartReordering();
-        showOutlines();
         // Reordering handles its own animations, disable the automatic ones.
         disableLayoutTransitions();
     }
@@ -2017,7 +1965,6 @@
             return;
         }
 
-        hideOutlines();
         mScreenOrder.clear();
         int count = getChildCount();
         for (int i = 0; i < count; i++) {
@@ -2969,9 +2916,6 @@
 
         mSpringLoadedDragController.cancel();
 
-        if (!mIsPageMoving) {
-            hideOutlines();
-        }
         mLauncher.getDragLayer().hidePageHints();
     }
 
diff --git a/src/com/android/launcher3/widget/PackageItemInfo.java b/src/com/android/launcher3/model/PackageItemInfo.java
similarity index 97%
rename from src/com/android/launcher3/widget/PackageItemInfo.java
rename to src/com/android/launcher3/model/PackageItemInfo.java
index 8f45a77..0f0134a 100644
--- a/src/com/android/launcher3/widget/PackageItemInfo.java
+++ b/src/com/android/launcher3/model/PackageItemInfo.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.launcher3.widget;
+package com.android.launcher3.model;
 
 import android.content.ComponentName;
 import android.graphics.Bitmap;
diff --git a/src/com/android/launcher3/model/WidgetsAndShortcutNameComparator.java b/src/com/android/launcher3/model/WidgetsAndShortcutNameComparator.java
index 7c4e806..61e8952 100644
--- a/src/com/android/launcher3/model/WidgetsAndShortcutNameComparator.java
+++ b/src/com/android/launcher3/model/WidgetsAndShortcutNameComparator.java
@@ -1,6 +1,5 @@
 package com.android.launcher3.model;
 
-import android.appwidget.AppWidgetProviderInfo;
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
@@ -8,6 +7,7 @@
 import com.android.launcher3.LauncherAppWidgetProviderInfo;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.compat.AppWidgetManagerCompat;
+import com.android.launcher3.compat.UserHandleCompat;
 
 import java.text.Collator;
 import java.util.Comparator;
@@ -18,12 +18,14 @@
     private final PackageManager mPackageManager;
     private final HashMap<Object, String> mLabelCache;
     private final Collator mCollator;
+    private final UserHandleCompat mMainHandle;
 
     public WidgetsAndShortcutNameComparator(Context context) {
         mManager = AppWidgetManagerCompat.getInstance(context);
         mPackageManager = context.getPackageManager();
         mLabelCache = new HashMap<Object, String>();
         mCollator = Collator.getInstance();
+        mMainHandle = UserHandleCompat.myUserHandle();
     }
 
     @Override
@@ -45,19 +47,22 @@
                     : Utilities.trim(((ResolveInfo) b).loadLabel(mPackageManager));
             mLabelCache.put(b, labelB);
         }
-        int result = mCollator.compare(labelA, labelB);
-        if (result == 0 && a instanceof AppWidgetProviderInfo &&
-                b instanceof AppWidgetProviderInfo) {
-            AppWidgetProviderInfo aInfo = (AppWidgetProviderInfo) a;
-            AppWidgetProviderInfo bInfo = (AppWidgetProviderInfo) b;
 
-            // prioritize main user's widgets against work profile widgets.
-            if (aInfo.getProfile().equals(android.os.Process.myUserHandle())) {
-                return -1;
-            } else if (bInfo.getProfile().equals(android.os.Process.myUserHandle())) {
-                return 1;
-            }
+        // Currently, there is no work profile shortcuts, hence only considering the widget cases.
+
+        boolean aWorkProfile = (a instanceof LauncherAppWidgetProviderInfo) &&
+                !mMainHandle.equals(mManager.getUser((LauncherAppWidgetProviderInfo) a));
+        boolean bWorkProfile = (b instanceof LauncherAppWidgetProviderInfo) &&
+                !mMainHandle.equals(mManager.getUser((LauncherAppWidgetProviderInfo) b));
+
+        // Independent of how the labels compare, if only one of the two widget info belongs to
+        // work profile, put that one in the back.
+        if (aWorkProfile && !bWorkProfile) {
+            return 1;
         }
-        return result;
+        if (!aWorkProfile && bWorkProfile) {
+            return -1;
+        }
+        return mCollator.compare(labelA, labelB);
     }
 };
diff --git a/src/com/android/launcher3/widget/WidgetsModel.java b/src/com/android/launcher3/model/WidgetsModel.java
similarity index 81%
rename from src/com/android/launcher3/widget/WidgetsModel.java
rename to src/com/android/launcher3/model/WidgetsModel.java
index 5a920e8..b72b981 100644
--- a/src/com/android/launcher3/widget/WidgetsModel.java
+++ b/src/com/android/launcher3/model/WidgetsModel.java
@@ -1,18 +1,19 @@
 
-package com.android.launcher3.widget;
+package com.android.launcher3.model;
 
 import android.content.Context;
-import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
-import android.support.v7.widget.RecyclerView;
+import android.os.Handler;
+import android.os.Process;
 import android.util.Log;
 
 import com.android.launcher3.IconCache;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherAppWidgetProviderInfo;
+
+import com.android.launcher3.LauncherModel;
+import com.android.launcher3.Utilities;
 import com.android.launcher3.compat.UserHandleCompat;
-import com.android.launcher3.model.AppNameComparator;
-import com.android.launcher3.model.WidgetsAndShortcutNameComparator;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -37,19 +38,19 @@
     /* Map of widgets and shortcuts that are tracked per package. */
     private Map<PackageItemInfo, ArrayList<Object>> mWidgetsList = new HashMap<>();
 
-    /* Notifies the adapter when data changes. */
-    private RecyclerView.Adapter mAdapter;
+    private ArrayList<Object> mRawList;
 
-    private Comparator mWidgetAndShortcutNameComparator;
-    private Comparator mAppNameComparator;
+    private final Comparator mWidgetAndShortcutNameComparator;
+    private final Comparator mAppNameComparator;
 
-    private IconCache mIconCache;
+    private final IconCache mIconCache;
+    private final Handler mWorkerHandler;
 
-    public WidgetsModel(Context context, RecyclerView.Adapter adapter) {
-        mAdapter = adapter;
+    public WidgetsModel(Context context) {
         mWidgetAndShortcutNameComparator = new WidgetsAndShortcutNameComparator(context);
         mAppNameComparator = (new AppNameComparator(context)).getAppInfoComparator();
         mIconCache = LauncherAppState.getInstance().getIconCache();
+        mWorkerHandler = new Handler(LauncherModel.getWorkerLooper());
     }
 
     // Access methods that may be deleted if the private fields are made package-private.
@@ -66,9 +67,15 @@
         return mWidgetsList.get(mPackageItemInfos.get(pos));
     }
 
-    public void addWidgetsAndShortcuts(ArrayList<Object> widgetsShortcuts, PackageManager pm) {
+    public ArrayList<Object> getRawList() {
+        return mRawList;
+    }
+
+    public void addWidgetsAndShortcuts(ArrayList<Object> rawWidgetsShortcuts) {
+        Utilities.assertWorkerThread();
+        mRawList = rawWidgetsShortcuts;
         if (DEBUG) {
-            Log.d(TAG, "addWidgetsAndShortcuts, widgetsShortcuts#=" + widgetsShortcuts.size());
+            Log.d(TAG, "addWidgetsAndShortcuts, widgetsShortcuts#=" + rawWidgetsShortcuts.size());
         }
 
         // Temporary list for {@link PackageItemInfos} to avoid having to go through
@@ -80,7 +87,7 @@
         mPackageItemInfos.clear();
 
         // add and update.
-        for (Object o: widgetsShortcuts) {
+        for (Object o: rawWidgetsShortcuts) {
             String packageName = "";
             if (o instanceof LauncherAppWidgetProviderInfo) {
                 LauncherAppWidgetProviderInfo widgetInfo = (LauncherAppWidgetProviderInfo) o;
@@ -100,10 +107,9 @@
             } else {
                 widgetsShortcutsList = new ArrayList<Object>();
                 widgetsShortcutsList.add(o);
-
                 pInfo = new PackageItemInfo(packageName);
                 mIconCache.getTitleAndIconForApp(packageName, UserHandleCompat.myUserHandle(),
-                        true /* useLowResIcon */, pInfo);
+                        true /* userLowResIcon */, pInfo);
                 mWidgetsList.put(pInfo, widgetsShortcutsList);
                 tmpPackageItemInfos.put(packageName,  pInfo);
                 mPackageItemInfos.add(pInfo);
@@ -115,8 +121,5 @@
         for (PackageItemInfo p: mPackageItemInfos) {
             Collections.sort(mWidgetsList.get(p), mWidgetAndShortcutNameComparator);
         }
-
-        // notify.
-        mAdapter.notifyDataSetChanged();
     }
 }
\ No newline at end of file
diff --git a/src/com/android/launcher3/widget/WidgetsContainerView.java b/src/com/android/launcher3/widget/WidgetsContainerView.java
index 181c08a..5a879fa 100644
--- a/src/com/android/launcher3/widget/WidgetsContainerView.java
+++ b/src/com/android/launcher3/widget/WidgetsContainerView.java
@@ -17,7 +17,6 @@
 package com.android.launcher3.widget;
 
 import android.content.Context;
-import android.content.pm.PackageManager;
 import android.graphics.Bitmap;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
@@ -36,6 +35,7 @@
 import com.android.launcher3.DragController;
 import com.android.launcher3.DragSource;
 import com.android.launcher3.DropTarget.DragObject;
+import com.android.launcher3.model.WidgetsModel;
 import com.android.launcher3.Folder;
 import com.android.launcher3.IconCache;
 import com.android.launcher3.ItemInfo;
@@ -47,8 +47,6 @@
 import com.android.launcher3.WidgetPreviewLoader;
 import com.android.launcher3.Workspace;
 
-import java.util.ArrayList;
-
 /**
  * The widgets list view container.
  */
@@ -68,9 +66,6 @@
     private DragController mDragController;
     private IconCache mIconCache;
 
-    /* Data model for the widget */
-    private WidgetsModel mWidgets;
-
     /* Recycler view related member variables */
     private RecyclerView mView;
     private WidgetsListAdapter mAdapter;
@@ -98,8 +93,6 @@
         mDragController = mLauncher.getDragController();
         mWidgetHostViewLoader = new WidgetHostViewLoader(mLauncher);
         mAdapter = new WidgetsListAdapter(context, this, this, mLauncher);
-        mWidgets = new WidgetsModel(context, mAdapter);
-        mAdapter.setWidgetsModel(mWidgets);
         mIconCache = (LauncherAppState.getInstance()).getIconCache();
 
         if (DEBUG) {
@@ -109,10 +102,6 @@
 
     @Override
     protected void onFinishInflate() {
-        if (DEBUG) {
-            Log.d(TAG, String.format("onFinishInflate [widgets size=%d]",
-                    mWidgets.getPackageSize()));
-        }
         mView = (RecyclerView) findViewById(R.id.widgets_list_view);
         mView.setAdapter(mAdapter);
 
@@ -135,7 +124,7 @@
     //
 
     public View getContentView() {
-        return findViewById(R.id.widgets_content);
+        return mView;
     }
 
     public View getRevealView() {
@@ -145,10 +134,6 @@
 
     public void scrollToTop() {
         mView.scrollToPosition(0);
-        if (DEBUG) {
-            Log.d(TAG, String.format("scrollToTop, [widgets size=%d]",
-                    mWidgets.getPackageSize()));
-        }
     }
 
     //
@@ -374,8 +359,9 @@
     /**
      * Initialize the widget data model.
      */
-    public void addWidgets(ArrayList<Object> widgetsShortcuts, PackageManager pm) {
-        mWidgets.addWidgetsAndShortcuts(widgetsShortcuts, pm);
+    public void addWidgets(WidgetsModel model) {
+        mAdapter.setWidgetsModel(model);
+        mAdapter.notifyDataSetChanged();
     }
 
     private WidgetPreviewLoader getWidgetPreviewLoader() {
diff --git a/src/com/android/launcher3/widget/WidgetsListAdapter.java b/src/com/android/launcher3/widget/WidgetsListAdapter.java
index 2f733dc..918ec1b 100644
--- a/src/com/android/launcher3/widget/WidgetsListAdapter.java
+++ b/src/com/android/launcher3/widget/WidgetsListAdapter.java
@@ -38,6 +38,8 @@
 import com.android.launcher3.LauncherAppWidgetProviderInfo;
 import com.android.launcher3.R;
 import com.android.launcher3.WidgetPreviewLoader;
+import com.android.launcher3.model.PackageItemInfo;
+import com.android.launcher3.model.WidgetsModel;
 
 import java.util.List;
 
@@ -183,7 +185,6 @@
         }
     }
 
-    @Override
     public boolean onFailedToRecycleView(WidgetsRowViewHolder holder) {
         // If child views are animating, then the RecyclerView may choose not to recycle the view,
         // causing extraneous onCreateViewHolder() calls.  It is safe in this case to continue