am ea9ad5ce: Merge remote-tracking branch \'origin/ub-launcher3-burnaby\' into mnc-dev

* commit 'ea9ad5cead9ad894fb670476bb5381198cdcf2de': (76 commits)
  Restoring provider behavior for reloading app on old devices   > For older devices, launcher will only reload in case of inserts with specific query parameters   > For older devices, launcehr will notify content observers of any internal inserts   > Chaning TAG for Launcher provider as max logging tag is only 23 characters
  Removing items which are on invalid screen
  Preventing null pointer crash when opening a folder
  Revert workaround for move to default screen on home intent.
  Fixing NPE in recycler view scroll bar.
  Adding workaround for regression caused by ag/752175
  Adding gradle script for Android Studio
  Override the overscroll color for the widget rows.
  Adding graphic for all apps empty search screen.
  Using GET_UNINSTALLED_PACKAGES flag when getting packageInfo for a managed profile app
  Revert "Adding viewId for the QSB"
  Adding viewId for the QSB
  Fixing issue with missing scroll bar after fast-scrolling and searching.
  Fixing an issue where you would inadvertently start fastscrolling.
  Pending bind callbacks should be cleared before starting the loader, similar to startBinding
  Fixing widgets container inactive scroll bar color.
  Making the detached scrollbar catch up faster to the actual scroll position.
  Updating theme to use the light theme by default, instead of wallpaper theme   > This allows us to use all the goodness of material theme   > Cursor in folder edit text is no longer 1px wide
  Updating the target sdk to launcher
  Using the usermanager api to get creation time
  ...
diff --git a/.gitignore b/.gitignore
index aea5d61..7240e48 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,3 +8,9 @@
 WallpaperPicker/gen/
 WallpaperPicker/.project.properties
 bin/
+.idea/
+.gradle/
+local.properties
+gradle/
+build/
+gradlew*
\ No newline at end of file
diff --git a/Android.mk b/Android.mk
index aade48a..3a6442a 100644
--- a/Android.mk
+++ b/Android.mk
@@ -23,16 +23,25 @@
 
 LOCAL_MODULE_TAGS := optional
 
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    android-support-v4 \
+    android-support-v7-recyclerview
+
 LOCAL_SRC_FILES := $(call all-java-files-under, src) \
     $(call all-java-files-under, WallpaperPicker/src) \
     $(call all-proto-files-under, protos)
-LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/WallpaperPicker/res $(LOCAL_PATH)/res
+
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/WallpaperPicker/res \
+    $(LOCAL_PATH)/res \
+    $(LOCAL_PATH)/../../../prebuilts/sdk/current/support/v7/recyclerview/res
+
 LOCAL_PROGUARD_FLAG_FILES := proguard.flags
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-v4 android-support-v7-recyclerview
 
 LOCAL_PROTOC_OPTIMIZE_TYPE := nano
 LOCAL_PROTOC_FLAGS := --proto_path=$(LOCAL_PATH)/protos/
-LOCAL_AAPT_FLAGS := --auto-add-overlay
+LOCAL_AAPT_FLAGS := \
+    --auto-add-overlay \
+    --extra-packages android.support.v7.recyclerview
 
 LOCAL_SDK_VERSION := current
 LOCAL_PACKAGE_NAME := Launcher3
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 1fb8e8d..99a6ad9 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -20,7 +20,7 @@
 <manifest
     xmlns:android="http://schemas.android.com/apk/res/android"
     package="com.android.launcher3">
-    <uses-sdk android:targetSdkVersion="21" android:minSdkVersion="16"/>
+    <uses-sdk android:targetSdkVersion="23" android:minSdkVersion="16"/>
 
     <permission
         android:name="com.android.launcher.permission.INSTALL_SHORTCUT"
@@ -53,7 +53,6 @@
     <uses-permission android:name="android.permission.SET_WALLPAPER_HINTS" />
     <uses-permission android:name="android.permission.VIBRATE" />
     <uses-permission android:name="android.permission.BIND_APPWIDGET" />
-    <uses-permission android:name="android.permission.GET_ACCOUNTS" />
     <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
     <uses-permission android:name="android.permission.BROADCAST_STICKY"/>
     <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
diff --git a/WallpaperPicker/res/values-v19/styles.xml b/WallpaperPicker/res/values-v19/styles.xml
index 136cf01..15fb0ea 100644
--- a/WallpaperPicker/res/values-v19/styles.xml
+++ b/WallpaperPicker/res/values-v19/styles.xml
@@ -25,7 +25,7 @@
         <item name="android:windowTranslucentNavigation">true</item>
     </style>
 
-    <style name="Theme" parent="@android:style/Theme.DeviceDefault.Wallpaper.NoTitleBar">
+    <style name="Theme" parent="@style/BaseWallpaperTheme">
         <item name="android:windowTranslucentStatus">true</item>
         <item name="android:windowTranslucentNavigation">true</item>
     </style>
diff --git a/WallpaperPicker/res/values-v21/styles.xml b/WallpaperPicker/res/values-v21/styles.xml
index 582ab8f..70220ed 100644
--- a/WallpaperPicker/res/values-v21/styles.xml
+++ b/WallpaperPicker/res/values-v21/styles.xml
@@ -33,4 +33,11 @@
         <item name="android:background">?android:attr/selectableItemBackgroundBorderless</item>
     </style>
 
+    <style name="Theme" parent="@style/BaseWallpaperTheme">
+        <item name="android:windowTranslucentStatus">true</item>
+        <item name="android:windowTranslucentNavigation">true</item>
+        <item name="android:colorControlActivated">@color/launcher_accent_color</item>
+        <item name="android:colorAccent">@color/launcher_accent_color</item>
+        <item name="android:colorPrimary">@color/launcher_accent_color</item>
+    </style>
 </resources>
\ No newline at end of file
diff --git a/WallpaperPicker/res/values/colors.xml b/WallpaperPicker/res/values/colors.xml
index adae7cf..6ba32f0 100644
--- a/WallpaperPicker/res/values/colors.xml
+++ b/WallpaperPicker/res/values/colors.xml
@@ -19,4 +19,6 @@
 -->
 <resources>
     <color name="wallpaper_picker_translucent_gray">#66000000</color>
+
+    <color name="launcher_accent_color">#ff009688</color>
 </resources>
diff --git a/WallpaperPicker/res/values/styles.xml b/WallpaperPicker/res/values/styles.xml
index 74aeab9..d1c945a 100644
--- a/WallpaperPicker/res/values/styles.xml
+++ b/WallpaperPicker/res/values/styles.xml
@@ -35,9 +35,15 @@
         <item name="android:background">#88000000</item>
     </style>
 
-    <style name="Theme" parent="@android:style/Theme.DeviceDefault.Wallpaper.NoTitleBar">
+    <style name="BaseWallpaperTheme" parent="@android:style/Theme.DeviceDefault.Light.NoActionBar">
+        <item name="android:windowBackground">@android:color/transparent</item>
+        <item name="android:colorBackgroundCacheHint">@null</item>
+        <item name="android:windowShowWallpaper">true</item>
+        <item name="android:windowNoTitle">true</item>
     </style>
 
+    <style name="Theme" parent="@style/BaseWallpaperTheme"></style>
+
     <style name="ActionBarSetWallpaperStyle" parent="@android:style/Widget.DeviceDefault.ActionButton">
         <item name="android:textColor">#ffffffff</item>
         <item name="android:background">?android:attr/selectableItemBackground</item>
diff --git a/WallpaperPicker/src/com/android/launcher3/WallpaperCropActivity.java b/WallpaperPicker/src/com/android/launcher3/WallpaperCropActivity.java
index f2bb509..f2459dd 100644
--- a/WallpaperPicker/src/com/android/launcher3/WallpaperCropActivity.java
+++ b/WallpaperPicker/src/com/android/launcher3/WallpaperCropActivity.java
@@ -223,14 +223,12 @@
 
     @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
     protected boolean isActivityDestroyed() {
-        return (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1)
-                && isDestroyed();
+        return Utilities.ATLEAST_JB_MR1 && isDestroyed();
     }
 
     @Thunk void addReusableBitmap(TileSource src) {
         synchronized (mReusableBitmaps) {
-            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT
-                    && src instanceof BitmapRegionTileSource) {
+            if (Utilities.ATLEAST_KITKAT && src instanceof BitmapRegionTileSource) {
                 Bitmap preview = ((BitmapRegionTileSource) src).getBitmap();
                 if (preview != null && preview.isMutable()) {
                     mReusableBitmaps.add(preview);
diff --git a/WallpaperPicker/src/com/android/launcher3/WallpaperPickerActivity.java b/WallpaperPicker/src/com/android/launcher3/WallpaperPickerActivity.java
index 88dc3e2..5985850 100644
--- a/WallpaperPicker/src/com/android/launcher3/WallpaperPickerActivity.java
+++ b/WallpaperPicker/src/com/android/launcher3/WallpaperPickerActivity.java
@@ -100,6 +100,7 @@
 
     @Thunk LinearLayout mWallpapersView;
     @Thunk HorizontalScrollView mWallpaperScrollContainer;
+    @Thunk View mWallpaperStrip;
 
     @Thunk ActionMode.Callback mActionModeCallback;
     @Thunk ActionMode mActionMode;
@@ -379,6 +380,7 @@
 
         mProgressView = findViewById(R.id.loading);
         mWallpaperScrollContainer = (HorizontalScrollView) findViewById(R.id.wallpaper_scroll_container);
+        mWallpaperStrip = findViewById(R.id.wallpaper_strip);
         mCropView.setTouchCallback(new CropView.TouchCallback() {
             ViewPropertyAnimator mAnim;
             @Override
@@ -386,15 +388,15 @@
                 if (mAnim != null) {
                     mAnim.cancel();
                 }
-                if (mWallpaperScrollContainer.getAlpha() == 1f) {
+                if (mWallpaperStrip.getAlpha() == 1f) {
                     mIgnoreNextTap = true;
                 }
-                mAnim = mWallpaperScrollContainer.animate();
+                mAnim = mWallpaperStrip.animate();
                 mAnim.alpha(0f)
                     .setDuration(150)
                     .withEndAction(new Runnable() {
                         public void run() {
-                            mWallpaperScrollContainer.setVisibility(View.INVISIBLE);
+                            mWallpaperStrip.setVisibility(View.INVISIBLE);
                         }
                     });
                 mAnim.setInterpolator(new AccelerateInterpolator(0.75f));
@@ -412,8 +414,8 @@
                     if (mAnim != null) {
                         mAnim.cancel();
                     }
-                    mWallpaperScrollContainer.setVisibility(View.VISIBLE);
-                    mAnim = mWallpaperScrollContainer.animate();
+                    mWallpaperStrip.setVisibility(View.VISIBLE);
+                    mAnim = mWallpaperStrip.animate();
                     mAnim.alpha(1f)
                          .setDuration(150)
                          .setInterpolator(new DecelerateInterpolator(0.75f));
@@ -548,7 +550,12 @@
                 new View.OnClickListener() {
                     @Override
                     public void onClick(View v) {
-                        if (mSelectedTile != null) {
+                        // Ensure that a tile is slelected and loaded.
+                        if (mSelectedTile != null && mCropView.getTileSource() != null) {
+                            // Prevent user from selecting any new tile.
+                            mWallpaperStrip.setVisibility(View.GONE);
+                            actionBar.hide();
+
                             WallpaperTileInfo info = (WallpaperTileInfo) mSelectedTile.getTag();
                             info.onSave(WallpaperPickerActivity.this);
                         } else {
@@ -713,10 +720,10 @@
 
     public void onStop() {
         super.onStop();
-        mWallpaperScrollContainer = (HorizontalScrollView) findViewById(R.id.wallpaper_scroll_container);
-        if (mWallpaperScrollContainer.getAlpha() < 1f) {
-            mWallpaperScrollContainer.setAlpha(1f);
-            mWallpaperScrollContainer.setVisibility(View.VISIBLE);
+        mWallpaperStrip = findViewById(R.id.wallpaper_strip);
+        if (mWallpaperStrip.getAlpha() < 1f) {
+            mWallpaperStrip.setAlpha(1f);
+            mWallpaperStrip.setVisibility(View.VISIBLE);
         }
     }
 
@@ -970,10 +977,8 @@
 
         if (partner == null || !partner.hideDefaultWallpaper()) {
             // Add an entry for the default wallpaper (stored in system resources)
-            WallpaperTileInfo defaultWallpaperInfo =
-                    (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT)
-                    ? getPreKKDefaultWallpaperInfo()
-                    : getDefaultWallpaper();
+            WallpaperTileInfo defaultWallpaperInfo = Utilities.ATLEAST_KITKAT
+                    ? getDefaultWallpaper() : getPreKKDefaultWallpaperInfo();
             if (defaultWallpaperInfo != null) {
                 bundled.add(0, defaultWallpaperInfo);
             }
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000..d971755
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,55 @@
+buildscript {
+    repositories {
+        mavenCentral()
+    }
+    dependencies {
+        classpath 'com.android.tools.build:gradle:1.3.0'
+        classpath 'com.google.protobuf:protobuf-gradle-plugin:0.7.0'
+    }
+}
+
+apply plugin: 'com.android.application'
+apply plugin: 'com.google.protobuf'
+
+android {
+    compileSdkVersion 23
+    buildToolsVersion "22.0.1"
+
+    defaultConfig {
+        applicationId "com.android.launcher3"
+        minSdkVersion 16
+        targetSdkVersion 23
+        versionCode 1
+        versionName "1.0"
+    }
+    buildTypes {
+        debug {
+            minifyEnabled false
+        }
+    }
+    sourceSets {
+        main {
+            res.srcDirs = ['res', 'WallpaperPicker/res']
+            main.java.srcDirs = ['src', 'WallpaperPicker/src']
+            manifest.srcFile 'AndroidManifest.xml'
+            proto.srcDirs 'protos/'
+        }
+    }
+}
+
+repositories {
+    mavenCentral()
+}
+
+dependencies {
+    compile 'com.android.support:support-v4:+'
+    compile 'com.android.support:recyclerview-v7:+'
+    compile 'com.google.protobuf.nano:protobuf-javanano:3.0.0-alpha-2'
+}
+
+protobuf {
+    // Configure the protoc executable
+    protoc {
+        artifact = 'com.google.protobuf:protoc:3.0.0-alpha-3'
+    }
+}
diff --git a/proguard.flags b/proguard.flags
index 6a9d6f3..d6b9ba3 100644
--- a/proguard.flags
+++ b/proguard.flags
@@ -1,8 +1,13 @@
+-keep class com.android.launcher3.allapps.AllAppsBackgroundDrawable {
+  public void setAlpha(int);
+  public int getAlpha();
+}
+
 -keep class com.android.launcher3.BaseRecyclerViewFastScrollBar {
-  public void setWidth(int);
-  public int getWidth();
-  public void setTrackAlpha(int);
-  public int getTrackAlpha();
+  public void setThumbWidth(int);
+  public int getThumbWidth();
+  public void setTrackWidth(int);
+  public int getTrackWidth();
 }
 
 -keep class com.android.launcher3.BaseRecyclerViewFastScrollPopup {
@@ -63,4 +68,4 @@
 -keep class com.android.launcher3.Workspace {
   public float getBackgroundAlpha();
   public void setBackgroundAlpha(float);
-}
\ No newline at end of file
+}
diff --git a/protos/backup.proto b/protos/backup.proto
index d8d94e8..6704e08 100644
--- a/protos/backup.proto
+++ b/protos/backup.proto
@@ -101,7 +101,10 @@
   optional string iconPackage = 16;
   optional string iconResource = 17;
   optional bytes icon = 18;
+
+  // Added in backup version 4
   optional TargetType targetType = 19 [default = TARGET_NONE];
+  optional int32 rank = 20;
 }
 
 message Screen {
@@ -121,6 +124,7 @@
   optional Resource icon = 4;
   optional Resource preview = 5;
 
+  // Added in backup version 3
   // Assume that a widget is resizable upto 2x2 if no data is available
   optional int32 minSpanX = 6 [default = 2];
   optional int32 minSpanY = 7 [default = 2];
diff --git a/res/drawable-hdpi/ic_all_apps_bg_hand.png b/res/drawable-hdpi/ic_all_apps_bg_hand.png
new file mode 100644
index 0000000..64f50df
--- /dev/null
+++ b/res/drawable-hdpi/ic_all_apps_bg_hand.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_all_apps_bg_icon_1.png b/res/drawable-hdpi/ic_all_apps_bg_icon_1.png
new file mode 100644
index 0000000..df3e2de
--- /dev/null
+++ b/res/drawable-hdpi/ic_all_apps_bg_icon_1.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_all_apps_bg_icon_2.png b/res/drawable-hdpi/ic_all_apps_bg_icon_2.png
new file mode 100644
index 0000000..7138ee8
--- /dev/null
+++ b/res/drawable-hdpi/ic_all_apps_bg_icon_2.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_all_apps_bg_icon_3.png b/res/drawable-hdpi/ic_all_apps_bg_icon_3.png
new file mode 100644
index 0000000..ed88199
--- /dev/null
+++ b/res/drawable-hdpi/ic_all_apps_bg_icon_3.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_all_apps_bg_icon_4.png b/res/drawable-hdpi/ic_all_apps_bg_icon_4.png
new file mode 100644
index 0000000..0ff9453
--- /dev/null
+++ b/res/drawable-hdpi/ic_all_apps_bg_icon_4.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_arrow_back_grey.png b/res/drawable-hdpi/ic_arrow_back_grey.png
index ccd3900..c7c0088 100755
--- a/res/drawable-hdpi/ic_arrow_back_grey.png
+++ b/res/drawable-hdpi/ic_arrow_back_grey.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_search_grey.png b/res/drawable-hdpi/ic_search_grey.png
index f4c5e27..bd20ba0 100755
--- a/res/drawable-hdpi/ic_search_grey.png
+++ b/res/drawable-hdpi/ic_search_grey.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_all_apps_bg_hand.png b/res/drawable-mdpi/ic_all_apps_bg_hand.png
new file mode 100644
index 0000000..d94bb7a
--- /dev/null
+++ b/res/drawable-mdpi/ic_all_apps_bg_hand.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_all_apps_bg_icon_1.png b/res/drawable-mdpi/ic_all_apps_bg_icon_1.png
new file mode 100644
index 0000000..76d973f
--- /dev/null
+++ b/res/drawable-mdpi/ic_all_apps_bg_icon_1.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_all_apps_bg_icon_2.png b/res/drawable-mdpi/ic_all_apps_bg_icon_2.png
new file mode 100644
index 0000000..0257f8c
--- /dev/null
+++ b/res/drawable-mdpi/ic_all_apps_bg_icon_2.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_all_apps_bg_icon_3.png b/res/drawable-mdpi/ic_all_apps_bg_icon_3.png
new file mode 100644
index 0000000..67545f5
--- /dev/null
+++ b/res/drawable-mdpi/ic_all_apps_bg_icon_3.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_all_apps_bg_icon_4.png b/res/drawable-mdpi/ic_all_apps_bg_icon_4.png
new file mode 100644
index 0000000..3e36e27b
--- /dev/null
+++ b/res/drawable-mdpi/ic_all_apps_bg_icon_4.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_arrow_back_grey.png b/res/drawable-mdpi/ic_arrow_back_grey.png
index 11996ef..5892c77 100755
--- a/res/drawable-mdpi/ic_arrow_back_grey.png
+++ b/res/drawable-mdpi/ic_arrow_back_grey.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_search_grey.png b/res/drawable-mdpi/ic_search_grey.png
index e83891c..c386dbb 100755
--- a/res/drawable-mdpi/ic_search_grey.png
+++ b/res/drawable-mdpi/ic_search_grey.png
Binary files differ
diff --git a/res/drawable-v21/all_apps_search_market_bg.xml b/res/drawable-v21/all_apps_search_market_bg.xml
new file mode 100644
index 0000000..7bd2f88
--- /dev/null
+++ b/res/drawable-v21/all_apps_search_market_bg.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+    android:color="@color/all_apps_search_market_button_focused_bg_color">
+    <item android:drawable="@color/quantum_panel_bg_color" />
+</ripple>
diff --git a/res/drawable-xhdpi/ic_all_apps_bg_hand.png b/res/drawable-xhdpi/ic_all_apps_bg_hand.png
new file mode 100644
index 0000000..5dde7f3
--- /dev/null
+++ b/res/drawable-xhdpi/ic_all_apps_bg_hand.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_all_apps_bg_icon_1.png b/res/drawable-xhdpi/ic_all_apps_bg_icon_1.png
new file mode 100644
index 0000000..f5bd32a
--- /dev/null
+++ b/res/drawable-xhdpi/ic_all_apps_bg_icon_1.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_all_apps_bg_icon_2.png b/res/drawable-xhdpi/ic_all_apps_bg_icon_2.png
new file mode 100644
index 0000000..fb07956
--- /dev/null
+++ b/res/drawable-xhdpi/ic_all_apps_bg_icon_2.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_all_apps_bg_icon_3.png b/res/drawable-xhdpi/ic_all_apps_bg_icon_3.png
new file mode 100644
index 0000000..c7d687e
--- /dev/null
+++ b/res/drawable-xhdpi/ic_all_apps_bg_icon_3.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_all_apps_bg_icon_4.png b/res/drawable-xhdpi/ic_all_apps_bg_icon_4.png
new file mode 100644
index 0000000..e22b962
--- /dev/null
+++ b/res/drawable-xhdpi/ic_all_apps_bg_icon_4.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_arrow_back_grey.png b/res/drawable-xhdpi/ic_arrow_back_grey.png
index 79b9b48..11996ef 100755
--- a/res/drawable-xhdpi/ic_arrow_back_grey.png
+++ b/res/drawable-xhdpi/ic_arrow_back_grey.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_search_grey.png b/res/drawable-xhdpi/ic_search_grey.png
index bd5fdf4..e83891c 100755
--- a/res/drawable-xhdpi/ic_search_grey.png
+++ b/res/drawable-xhdpi/ic_search_grey.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_all_apps_bg_hand.png b/res/drawable-xxhdpi/ic_all_apps_bg_hand.png
new file mode 100644
index 0000000..e107c2e
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_all_apps_bg_hand.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_all_apps_bg_icon_1.png b/res/drawable-xxhdpi/ic_all_apps_bg_icon_1.png
new file mode 100644
index 0000000..7482830
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_all_apps_bg_icon_1.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_all_apps_bg_icon_2.png b/res/drawable-xxhdpi/ic_all_apps_bg_icon_2.png
new file mode 100644
index 0000000..028c7f4
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_all_apps_bg_icon_2.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_all_apps_bg_icon_3.png b/res/drawable-xxhdpi/ic_all_apps_bg_icon_3.png
new file mode 100644
index 0000000..dce7b67
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_all_apps_bg_icon_3.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_all_apps_bg_icon_4.png b/res/drawable-xxhdpi/ic_all_apps_bg_icon_4.png
new file mode 100644
index 0000000..811a6b3
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_all_apps_bg_icon_4.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_arrow_back_grey.png b/res/drawable-xxhdpi/ic_arrow_back_grey.png
index 8e42e09..ccd3900 100755
--- a/res/drawable-xxhdpi/ic_arrow_back_grey.png
+++ b/res/drawable-xxhdpi/ic_arrow_back_grey.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_search_grey.png b/res/drawable-xxhdpi/ic_search_grey.png
index 1d5c913..f4c5e27 100755
--- a/res/drawable-xxhdpi/ic_search_grey.png
+++ b/res/drawable-xxhdpi/ic_search_grey.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_all_apps_bg_hand.png b/res/drawable-xxxhdpi/ic_all_apps_bg_hand.png
new file mode 100644
index 0000000..c638456
--- /dev/null
+++ b/res/drawable-xxxhdpi/ic_all_apps_bg_hand.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_all_apps_bg_icon_1.png b/res/drawable-xxxhdpi/ic_all_apps_bg_icon_1.png
new file mode 100644
index 0000000..511a02a
--- /dev/null
+++ b/res/drawable-xxxhdpi/ic_all_apps_bg_icon_1.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_all_apps_bg_icon_2.png b/res/drawable-xxxhdpi/ic_all_apps_bg_icon_2.png
new file mode 100644
index 0000000..2cc18f8
--- /dev/null
+++ b/res/drawable-xxxhdpi/ic_all_apps_bg_icon_2.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_all_apps_bg_icon_3.png b/res/drawable-xxxhdpi/ic_all_apps_bg_icon_3.png
new file mode 100644
index 0000000..c32f8ff
--- /dev/null
+++ b/res/drawable-xxxhdpi/ic_all_apps_bg_icon_3.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_all_apps_bg_icon_4.png b/res/drawable-xxxhdpi/ic_all_apps_bg_icon_4.png
new file mode 100644
index 0000000..7bead8a
--- /dev/null
+++ b/res/drawable-xxxhdpi/ic_all_apps_bg_icon_4.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_arrow_back_grey.png b/res/drawable-xxxhdpi/ic_arrow_back_grey.png
index 854a9bd..79b9b48 100755
--- a/res/drawable-xxxhdpi/ic_arrow_back_grey.png
+++ b/res/drawable-xxxhdpi/ic_arrow_back_grey.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_search_grey.png b/res/drawable-xxxhdpi/ic_search_grey.png
index 28519fd..bd5fdf4 100755
--- a/res/drawable-xxxhdpi/ic_search_grey.png
+++ b/res/drawable-xxxhdpi/ic_search_grey.png
Binary files differ
diff --git a/res/drawable/all_apps_search_market_bg.xml b/res/drawable/all_apps_search_market_bg.xml
new file mode 100644
index 0000000..5278e00
--- /dev/null
+++ b/res/drawable/all_apps_search_market_bg.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_focused="true" android:drawable="@color/all_apps_search_market_button_focused_bg_color" />
+    <item android:state_pressed="true" android:drawable="@color/all_apps_search_market_button_focused_bg_color" />
+    <item android:drawable="@android:color/transparent" />
+</selector>
diff --git a/res/drawable/horizontal_line.xml b/res/drawable/horizontal_line.xml
new file mode 100644
index 0000000..3f3f17e3
--- /dev/null
+++ b/res/drawable/horizontal_line.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle">
+    <size android:height="1dp" />
+    <solid android:color="#ddd" />
+</shape>
diff --git a/res/layout/all_apps_empty_search.xml b/res/layout/all_apps_empty_search.xml
index f60c4a0..5439111 100644
--- a/res/layout/all_apps_empty_search.xml
+++ b/res/layout/all_apps_empty_search.xml
@@ -18,11 +18,14 @@
     android:id="@+id/empty_text"
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"
-    android:gravity="center"
-    android:paddingTop="24dp"
-    android:paddingBottom="24dp"
-    android:paddingRight="@dimen/all_apps_grid_view_start_margin"
-    android:textSize="16sp"
-    android:textColor="#4c4c4c"
+    android:gravity="start"
+    android:paddingTop="@dimen/all_apps_empty_search_message_top_offset"
+    android:paddingBottom="8dp"
+    android:paddingLeft="16dp"
+    android:paddingRight="16dp"
+    android:fontFamily="sans-serif-medium"
+    android:textSize="14sp"
+    android:textColor="#212121"
+    android:alpha="0.56"
     android:focusable="false" />
 
diff --git a/res/layout/all_apps_search_bar.xml b/res/layout/all_apps_search_bar.xml
index cf30eac..69a66c8 100644
--- a/res/layout/all_apps_search_bar.xml
+++ b/res/layout/all_apps_search_bar.xml
@@ -32,14 +32,13 @@
             android:id="@+id/dismiss_search_button"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:layout_marginLeft="4dp"
-            android:layout_marginStart="4dp"
+            android:layout_gravity="center_vertical"
+            android:layout_marginLeft="16dp"
+            android:layout_marginStart="16dp"
             android:contentDescription="@string/all_apps_button_label"
-            android:paddingBottom="13dp"
-            android:paddingTop="13dp"
             android:src="@drawable/ic_arrow_back_grey" />
 
-        <com.android.launcher3.allapps.AllAppsSearchEditView
+        <com.android.launcher3.ExtendedEditText
             android:id="@+id/search_box_input"
             android:layout_width="match_parent"
             android:layout_height="match_parent"
@@ -48,7 +47,7 @@
             android:gravity="fill_horizontal|center_vertical"
             android:hint="@string/all_apps_search_bar_hint"
             android:inputType="text|textNoSuggestions|textCapWords"
-            android:imeOptions="actionDone|flagNoExtractUi"
+            android:imeOptions="actionSearch|flagNoExtractUi"
             android:maxLines="1"
             android:paddingLeft="8dp"
             android:scrollHorizontally="true"
@@ -63,10 +62,8 @@
         android:layout_width="wrap_content"
         android:layout_height="@dimen/all_apps_search_bar_height"
         android:layout_gravity="end|center_vertical"
-        android:layout_marginEnd="4dp"
-        android:layout_marginRight="4dp"
+        android:layout_marginEnd="16dp"
+        android:layout_marginRight="16dp"
         android:contentDescription="@string/all_apps_search_bar_hint"
-        android:paddingBottom="13dp"
-        android:paddingTop="13dp"
         android:src="@drawable/ic_search_grey" />
 </FrameLayout>
\ No newline at end of file
diff --git a/res/layout/all_apps_search_market.xml b/res/layout/all_apps_search_market.xml
new file mode 100644
index 0000000..1ed5088
--- /dev/null
+++ b/res/layout/all_apps_search_market.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<TextView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/search_market_text"
+    android:layout_width="wrap_content"
+    android:layout_height="48dp"
+    android:gravity="start|center_vertical"
+    android:paddingLeft="16dp"
+    android:paddingRight="16dp"
+    android:fontFamily="sans-serif-medium"
+    android:textSize="14sp"
+    android:textColor="@color/launcher_accent_color"
+    android:textAllCaps="true"
+    android:focusable="false"
+    android:background="@drawable/all_apps_search_market_bg" />
diff --git a/res/layout/all_apps_search_market_divider.xml b/res/layout/all_apps_search_market_divider.xml
new file mode 100644
index 0000000..3909781
--- /dev/null
+++ b/res/layout/all_apps_search_market_divider.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<ImageView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:gravity="center"
+    android:paddingTop="16dp"
+    android:paddingBottom="8dp"
+    android:paddingLeft="16dp"
+    android:paddingRight="16dp"
+    android:focusable="false"
+    android:scaleType="matrix"
+    android:src="@drawable/horizontal_line" />
\ No newline at end of file
diff --git a/res/layout/overview_panel.xml b/res/layout/overview_panel.xml
index 4b7423e..1f02dce 100644
--- a/res/layout/overview_panel.xml
+++ b/res/layout/overview_panel.xml
@@ -32,6 +32,7 @@
         android:gravity="center_horizontal"
         android:text="@string/wallpaper_button_text"
         android:textAllCaps="true"
+        android:textColor="@android:color/white"
         android:textSize="12sp" />
 
     <TextView
@@ -45,6 +46,7 @@
         android:gravity="center_horizontal"
         android:text="@string/widget_button_text"
         android:textAllCaps="true"
+        android:textColor="@android:color/white"
         android:textSize="12sp" />
 
     <TextView
@@ -58,6 +60,7 @@
         android:gravity="center_horizontal"
         android:text="@string/settings_button_text"
         android:textAllCaps="true"
+        android:textColor="@android:color/white"
         android:textSize="12sp" />
 
 </LinearLayout>
\ No newline at end of file
diff --git a/res/layout/user_folder.xml b/res/layout/user_folder.xml
index ecf7def..252ebf0 100644
--- a/res/layout/user_folder.xml
+++ b/res/layout/user_folder.xml
@@ -53,7 +53,7 @@
         android:paddingLeft="8dp"
         android:paddingRight="8dp" >
 
-        <com.android.launcher3.FolderEditText
+        <com.android.launcher3.ExtendedEditText
             android:id="@+id/folder_name"
             android:layout_width="0dp"
             android:layout_height="wrap_content"
@@ -70,7 +70,6 @@
             android:textColor="#ff777777"
             android:textColorHighlight="#ffCCCCCC"
             android:textColorHint="#ff808080"
-            android:textCursorDrawable="@null"
             android:textSize="14sp" />
 
         <include
diff --git a/res/layout/widgets_list_row_view.xml b/res/layout/widgets_list_row_view.xml
index ced5648..1d593df 100644
--- a/res/layout/widgets_list_row_view.xml
+++ b/res/layout/widgets_list_row_view.xml
@@ -51,6 +51,7 @@
 
     <HorizontalScrollView
         android:id="@+id/widgets_scroll_container"
+        android:theme="@style/Theme.Dark.CustomOverscroll"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:scrollbars="none">
diff --git a/res/values-af/strings.xml b/res/values-af/strings.xml
index 7004524..78be3f4 100644
--- a/res/values-af/strings.xml
+++ b/res/values-af/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Deursoek programme"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Laai tans programme …"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"Geen programme gevind wat met \"<xliff:g id="QUERY">%1$s</xliff:g>\" ooreenstem nie"</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"Gaan na <xliff:g id="QUERY">%1$s</xliff:g>"</string>
     <string name="out_of_space" msgid="4691004494942118364">"Niks meer spasie op die tuisskerm nie."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Geen plek meer in die Gunstelinge-laai nie"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"Programme"</string>
diff --git a/res/values-am/strings.xml b/res/values-am/strings.xml
index d7fed9d..b5d982e 100644
--- a/res/values-am/strings.xml
+++ b/res/values-am/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"መተግበሪያዎችን ይፈልጉ"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"መተግበሪያዎችን በመጫን ላይ..."</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"ከ«<xliff:g id="QUERY">%1$s</xliff:g>» ጋር የሚዛመዱ ምንም መተግበሪያዎች አልተገኙም"</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"ወደ <xliff:g id="QUERY">%1$s</xliff:g> ሂድ"</string>
     <string name="out_of_space" msgid="4691004494942118364">"በዚህ መነሻ ማያ ገጽ ላይ ምንም ቦታ የለም።"</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"በተወዳጆች መሣቢያ ውስጥ ተጨማሪ ቦታ የለም"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"መተግበሪያዎች"</string>
diff --git a/res/values-ar/strings.xml b/res/values-ar/strings.xml
index b7cd901..619e11d 100644
--- a/res/values-ar/strings.xml
+++ b/res/values-ar/strings.xml
@@ -32,12 +32,13 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"البحث في التطبيقات"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"جارٍ تحميل التطبيقات…"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"لم يتم العثور على أية تطبيقات تتطابق مع \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"الانتقال إلى <xliff:g id="QUERY">%1$s</xliff:g>"</string>
     <string name="out_of_space" msgid="4691004494942118364">"ليس هناك مساحة أخرى في هذه الشاشة الرئيسية."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"لا يوجد المزيد من الحقول في علبة المفضلة"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"التطبيقات"</string>
     <string name="all_apps_home_button_label" msgid="252062713717058851">"الرئيسية"</string>
     <string name="delete_target_label" msgid="1822697352535677073">"إزالة"</string>
-    <string name="delete_target_uninstall_label" msgid="5100785476250872595">"إزالة"</string>
+    <string name="delete_target_uninstall_label" msgid="5100785476250872595">"إلغاء التثبيت"</string>
     <string name="info_target_label" msgid="8053346143994679532">"معلومات عن التطبيق"</string>
     <string name="permlab_install_shortcut" msgid="5632423390354674437">"تثبيت اختصارات"</string>
     <string name="permdesc_install_shortcut" msgid="923466509822011139">"للسماح لتطبيق ما بإضافة اختصارات بدون تدخل المستخدم."</string>
diff --git a/res/values-az-rAZ/strings.xml b/res/values-az-rAZ/strings.xml
index 099a220..b9ea83f 100644
--- a/res/values-az-rAZ/strings.xml
+++ b/res/values-az-rAZ/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Tətbiq Axtarın"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Tətbiqlər endirilir..."</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"\"<xliff:g id="QUERY">%1$s</xliff:g>\" sorğusuna uyğun Tətbiqlər tapılmadı"</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"<xliff:g id="QUERY">%1$s</xliff:g> daxil olun"</string>
     <string name="out_of_space" msgid="4691004494942118364">"Bu Əsas ekranda boş yer yoxdur."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Favoritlər-də yer yoxdur"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"Tətbiqlər"</string>
diff --git a/res/values-bg/strings.xml b/res/values-bg/strings.xml
index eeaa1ed..214c64b 100644
--- a/res/values-bg/strings.xml
+++ b/res/values-bg/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Търсене в приложенията"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Приложенията се зареждат…"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"Няма намерени приложения, съответстващи на „<xliff:g id="QUERY">%1$s</xliff:g>“"</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"Отваряне на <xliff:g id="QUERY">%1$s</xliff:g>"</string>
     <string name="out_of_space" msgid="4691004494942118364">"На този начален екран няма повече място."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Няма повече място в областта с любимите"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"Приложения"</string>
diff --git a/res/values-bn-rBD/strings.xml b/res/values-bn-rBD/strings.xml
index 57f7b51..129d250 100644
--- a/res/values-bn-rBD/strings.xml
+++ b/res/values-bn-rBD/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"অ্যাপ্লিকেশানগুলি অনুসন্ধান করুন"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"অ্যাপ্লিকেশানগুলি লোড হচ্ছে..."</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"\"<xliff:g id="QUERY">%1$s</xliff:g>\" এর সাথে মেলে এমন কোনো অ্যাপ্লিকেশান পাওয়া যায়নি"</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"<xliff:g id="QUERY">%1$s</xliff:g> এ যান"</string>
     <string name="out_of_space" msgid="4691004494942118364">"এই হোম স্ক্রীনে আর কোনো জায়গা নেই৷"</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"পছন্দসই ট্রে-তে আর কোনো জায়গা নেই"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"অ্যাপ্লিকেশানগুলি"</string>
diff --git a/res/values-ca/strings.xml b/res/values-ca/strings.xml
index 83f284a..cf874a4 100644
--- a/res/values-ca/strings.xml
+++ b/res/values-ca/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Cerca a les aplicacions"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"S\'estan carregant les aplicacions..."</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"No s\'ha trobat cap aplicació que coincideixi amb <xliff:g id="QUERY">%1$s</xliff:g>"</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"Vés a <xliff:g id="QUERY">%1$s</xliff:g>"</string>
     <string name="out_of_space" msgid="4691004494942118364">"Ja no queda espai en aquesta pantalla d\'inici."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"No hi ha més espai a la safata Preferits."</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"Aplicacions"</string>
diff --git a/res/values-cs/strings.xml b/res/values-cs/strings.xml
index c99a7ed..c2f9f42 100644
--- a/res/values-cs/strings.xml
+++ b/res/values-cs/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Hledat aplikace"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Načítání aplikací…"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"Dotazu „<xliff:g id="QUERY">%1$s</xliff:g>“ neodpovídají žádné aplikace"</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"Přejít na <xliff:g id="QUERY">%1$s</xliff:g>"</string>
     <string name="out_of_space" msgid="4691004494942118364">"Na této ploše již není místo."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Na panelu Oblíbené položky již není místo."</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"Aplikace"</string>
diff --git a/res/values-da/strings.xml b/res/values-da/strings.xml
index eeab92e..a948705 100644
--- a/res/values-da/strings.xml
+++ b/res/values-da/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Søg i Apps"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Indlæser apps…"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"Der blev ikke fundet nogen apps, som matcher \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"Gå til <xliff:g id="QUERY">%1$s</xliff:g>"</string>
     <string name="out_of_space" msgid="4691004494942118364">"Der er ikke mere plads på denne startskærm."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Der er ikke mere plads i bakken Foretrukne"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"Apps"</string>
diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml
index ac6f76b..96ae74b 100644
--- a/res/values-de/strings.xml
+++ b/res/values-de/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"In Apps suchen"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Apps werden geladen..."</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"Keine Apps für \"<xliff:g id="QUERY">%1$s</xliff:g>\" gefunden"</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"Gehe zu <xliff:g id="QUERY">%1$s</xliff:g>"</string>
     <string name="out_of_space" msgid="4691004494942118364">"Auf diesem Startbildschirm ist kein Platz mehr vorhanden."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Ablage \"Favoriten\" ist voll."</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"Apps"</string>
diff --git a/res/values-el/strings.xml b/res/values-el/strings.xml
index a60458a..81b43e2 100644
--- a/res/values-el/strings.xml
+++ b/res/values-el/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Αναζήτηση εφαρμογών"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Φόρτωση εφαρμογών…"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"Δεν βρέθηκαν εφαρμογές για το ερώτημα \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"Μετάβαση σε <xliff:g id="QUERY">%1$s</xliff:g>"</string>
     <string name="out_of_space" msgid="4691004494942118364">"Δεν υπάρχει χώρος σε αυτήν την αρχική οθόνη."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Δεν υπάρχει επιπλέον χώρος στην περιοχή Αγαπημένα"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"Εφαρμογές"</string>
diff --git a/res/values-en-rAU/strings.xml b/res/values-en-rAU/strings.xml
index 09963e4..f2a6eab 100644
--- a/res/values-en-rAU/strings.xml
+++ b/res/values-en-rAU/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Search Apps"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Loading Apps…"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"No Apps found matching \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"Go to <xliff:g id="QUERY">%1$s</xliff:g>"</string>
     <string name="out_of_space" msgid="4691004494942118364">"No more room on this Home screen."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"No more room in the Favourites tray"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"Apps"</string>
diff --git a/res/values-en-rGB/strings.xml b/res/values-en-rGB/strings.xml
index 09963e4..f2a6eab 100644
--- a/res/values-en-rGB/strings.xml
+++ b/res/values-en-rGB/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Search Apps"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Loading Apps…"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"No Apps found matching \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"Go to <xliff:g id="QUERY">%1$s</xliff:g>"</string>
     <string name="out_of_space" msgid="4691004494942118364">"No more room on this Home screen."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"No more room in the Favourites tray"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"Apps"</string>
diff --git a/res/values-en-rIN/strings.xml b/res/values-en-rIN/strings.xml
index 09963e4..f2a6eab 100644
--- a/res/values-en-rIN/strings.xml
+++ b/res/values-en-rIN/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Search Apps"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Loading Apps…"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"No Apps found matching \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"Go to <xliff:g id="QUERY">%1$s</xliff:g>"</string>
     <string name="out_of_space" msgid="4691004494942118364">"No more room on this Home screen."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"No more room in the Favourites tray"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"Apps"</string>
diff --git a/res/values-es-rUS/strings.xml b/res/values-es-rUS/strings.xml
index 6366742..c754493 100644
--- a/res/values-es-rUS/strings.xml
+++ b/res/values-es-rUS/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Buscar aplicaciones"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Cargando aplicaciones…"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"No hay aplicaciones que coincidan con <xliff:g id="QUERY">%1$s</xliff:g>."</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"Ir a <xliff:g id="QUERY">%1$s</xliff:g>"</string>
     <string name="out_of_space" msgid="4691004494942118364">"No hay más espacio en esta pantalla principal."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"La bandeja de favoritos está llena."</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"Aplicaciones"</string>
diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml
index 34331fc..16cd84f 100644
--- a/res/values-es/strings.xml
+++ b/res/values-es/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Busca aplicaciones"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Cargando aplicaciones…"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"No se han encontrado aplicaciones que contengan \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"Ir a <xliff:g id="QUERY">%1$s</xliff:g>"</string>
     <string name="out_of_space" msgid="4691004494942118364">"No queda espacio en la pantalla de inicio."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"La bandeja de favoritos está completa"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"Aplicaciones"</string>
diff --git a/res/values-et-rEE/strings.xml b/res/values-et-rEE/strings.xml
index c4ba823..84ca45a 100644
--- a/res/values-et-rEE/strings.xml
+++ b/res/values-et-rEE/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Otsige rakendustest"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Rakenduste laadimine ..."</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"Päringule „<xliff:g id="QUERY">%1$s</xliff:g>” ei vastanud ükski rakendus"</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"Mine: <xliff:g id="QUERY">%1$s</xliff:g>"</string>
     <string name="out_of_space" msgid="4691004494942118364">"Sellel avaekraanil pole enam ruumi."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Salves Lemmikud pole rohkem ruumi"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"Rakendused"</string>
diff --git a/res/values-eu-rES/strings.xml b/res/values-eu-rES/strings.xml
index 8bfca6c..096a4f9 100644
--- a/res/values-eu-rES/strings.xml
+++ b/res/values-eu-rES/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Bilatu aplikazioetan"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Aplikazioak kargatzen…"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"Ez da aurkitu \"<xliff:g id="QUERY">%1$s</xliff:g>\" bilaketarekin bat datorren aplikaziorik"</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"Joan hona: <xliff:g id="QUERY">%1$s</xliff:g>"</string>
     <string name="out_of_space" msgid="4691004494942118364">"Hasierako pantaila honetan ez dago toki gehiago."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Ez dago toki gehiago Gogokoak erretiluan"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"Aplikazioak"</string>
diff --git a/res/values-fa/strings.xml b/res/values-fa/strings.xml
index 701d59d..3b6d01a 100644
--- a/res/values-fa/strings.xml
+++ b/res/values-fa/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"جستجوی برنامه‌ها"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"در حال بارگیری برنامه‌ها..."</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"هیچ برنامه‌ای مطابق با «<xliff:g id="QUERY">%1$s</xliff:g>» پیدا نشد"</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"رفتن به <xliff:g id="QUERY">%1$s</xliff:g>"</string>
     <string name="out_of_space" msgid="4691004494942118364">"فضای بیشتری در این صفحه اصلی موجود نیست."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"فضای بیشتری در سینی موارد دلخواه وجود ندارد"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"برنامه‌ها"</string>
diff --git a/res/values-fi/strings.xml b/res/values-fi/strings.xml
index f0766d6..47b580e 100644
--- a/res/values-fi/strings.xml
+++ b/res/values-fi/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Sovellushaku"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Ladataan sovelluksia…"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"”<xliff:g id="QUERY">%1$s</xliff:g>” ei palauttanut sovelluksia."</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"Siirry: <xliff:g id="QUERY">%1$s</xliff:g>"</string>
     <string name="out_of_space" msgid="4691004494942118364">"Tässä aloitusruudussa ei ole enää tilaa."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Suosikit-valikossa ei ole enää tilaa"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"Sovellukset"</string>
diff --git a/res/values-fr-rCA/strings.xml b/res/values-fr-rCA/strings.xml
index 0c19ef2..91271f7 100644
--- a/res/values-fr-rCA/strings.xml
+++ b/res/values-fr-rCA/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Rechercher des applications"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Chargement des applications en cours..."</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"Aucune application trouvée correspondant à « <xliff:g id="QUERY">%1$s</xliff:g> »"</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"Aller à « <xliff:g id="QUERY">%1$s</xliff:g> »"</string>
     <string name="out_of_space" msgid="4691004494942118364">"Pas d\'espace libre sur l\'écran d\'accueil."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Il n\'y a plus d\'espace dans la zone des favoris"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"Applications"</string>
diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml
index 662bcf1..84e1594 100644
--- a/res/values-fr/strings.xml
+++ b/res/values-fr/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Rechercher dans les applications"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Chargement des applications en cours…"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"Aucune application ne correspond à la requête \"<xliff:g id="QUERY">%1$s</xliff:g>\"."</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"Accéder à <xliff:g id="QUERY">%1$s</xliff:g>"</string>
     <string name="out_of_space" msgid="4691004494942118364">"Pas d\'espace libre sur cet écran d\'accueil."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Plus d\'espace disponible dans la zone de favoris."</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"Applications"</string>
diff --git a/res/values-gl-rES/strings.xml b/res/values-gl-rES/strings.xml
index 6cf76a7..ccfe21e 100644
--- a/res/values-gl-rES/strings.xml
+++ b/res/values-gl-rES/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Aplicacións de busca"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Cargando aplicacións..."</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"Non se atoparon aplicacións que coincidan con \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"Ir a <xliff:g id="QUERY">%1$s</xliff:g>"</string>
     <string name="out_of_space" msgid="4691004494942118364">"Non hai máis espazo nesta pantalla de inicio."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Non hai máis espazo na bandexa de favoritos"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"Aplicacións"</string>
diff --git a/res/values-gu-rIN/strings.xml b/res/values-gu-rIN/strings.xml
index 4a2274f..db056a0 100644
--- a/res/values-gu-rIN/strings.xml
+++ b/res/values-gu-rIN/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"શોધ એપ્લિકેશનો"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"એપ્લિકેશનો લોડ કરી રહ્યું છે…"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"\"<xliff:g id="QUERY">%1$s</xliff:g>\" થી મેળ ખાતી કોઈ એપ્લિકેશનો મળી નથી"</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"<xliff:g id="QUERY">%1$s</xliff:g> પર જાઓ"</string>
     <string name="out_of_space" msgid="4691004494942118364">"આ હોમ સ્ક્રીન પર વધુ જગ્યા નથી."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"મનપસંદ ટ્રે પર વધુ જગ્યા નથી"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"એપ્લિકેશનો"</string>
diff --git a/res/values-hi/strings.xml b/res/values-hi/strings.xml
index 6fdbf24..7cf4f33 100644
--- a/res/values-hi/strings.xml
+++ b/res/values-hi/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"ऐप्‍स खोजें"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"ऐप्स लोड हो रहे हैं..."</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"\"<xliff:g id="QUERY">%1$s</xliff:g>\" से मिलान करने वाला कोई ऐप नहीं मिला"</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"<xliff:g id="QUERY">%1$s</xliff:g> पर जाएं"</string>
     <string name="out_of_space" msgid="4691004494942118364">"इस होम स्‍क्रीन पर स्थान शेष नहीं है."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"पसंदीदा ट्रे में और स्थान नहीं है"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"ऐप्लिकेशन"</string>
diff --git a/res/values-hr/strings.xml b/res/values-hr/strings.xml
index a54a7e4..2679570 100644
--- a/res/values-hr/strings.xml
+++ b/res/values-hr/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Pretraži aplikacije"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Učitavanje aplikacija…"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"Nema aplikacija podudarnih s upitom \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"Idite na <xliff:g id="QUERY">%1$s</xliff:g>"</string>
     <string name="out_of_space" msgid="4691004494942118364">"Na ovom početnom zaslonu više nema mjesta."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Nema više prostora na traci Favoriti"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"Aplikacije"</string>
diff --git a/res/values-hu/strings.xml b/res/values-hu/strings.xml
index e9fc574..41c38b2 100644
--- a/res/values-hu/strings.xml
+++ b/res/values-hu/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Alkalmazások keresése"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Alkalmazások betöltése…"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"Egy alkalmazás sem található a(z) „<xliff:g id="QUERY">%1$s</xliff:g>” lekérdezésre."</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"Keresse fel ezt: <xliff:g id="QUERY">%1$s</xliff:g>"</string>
     <string name="out_of_space" msgid="4691004494942118364">"Nincs több hely ezen a kezdőképernyőn."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Nincs több hely a Kedvencek tálcán"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"Alkalmazások"</string>
diff --git a/res/values-hy-rAM/strings.xml b/res/values-hy-rAM/strings.xml
index de4aed1..49a9d18 100644
--- a/res/values-hy-rAM/strings.xml
+++ b/res/values-hy-rAM/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Հավելվածների որոնում"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Հավելվածների բեռնում…"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"«<xliff:g id="QUERY">%1$s</xliff:g>» հարցմանը համապատասխանող հավելվածներ չեն գտնվել"</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"Գնալ <xliff:g id="QUERY">%1$s</xliff:g>"</string>
     <string name="out_of_space" msgid="4691004494942118364">"Այլևս տեղ չկա այս հիմնական էկրանին:"</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Ընտրյալների ցուցակում այլևս ազատ տեղ չկա"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"Ծրագրեր"</string>
@@ -59,11 +60,11 @@
     <string name="workspace_cling_longpress_title" msgid="9173998993909018310">"Պաստառներ, վիջեթներ և կարգավորումներ"</string>
     <string name="workspace_cling_longpress_description" msgid="4119994475505235248">"Հարմարեցնելու համար հպեք և պահեք հետնաշերտի վրա"</string>
     <string name="workspace_cling_longpress_dismiss" msgid="368660286867640874">"ՀԱՍԿԱՆԱԼԻ Է"</string>
-    <string name="folder_opened" msgid="94695026776264709">"Թղթապանակը բաց է, <xliff:g id="WIDTH">%1$d</xliff:g>-ից <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
+    <string name="folder_opened" msgid="94695026776264709">"Պանակը բաց է, <xliff:g id="WIDTH">%1$d</xliff:g>-ից <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
     <string name="folder_tap_to_close" msgid="1884479294466410023">"Հպեք՝ պանակը փակելու համար"</string>
     <string name="folder_tap_to_rename" msgid="9191075570492871147">"Հպեք՝ վերանվանումը պահելու համար"</string>
-    <string name="folder_closed" msgid="4100806530910930934">"Թղթապանակը փակ է"</string>
-    <string name="folder_renamed" msgid="1794088362165669656">"Թղթապանակը վերանվանվեց <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="folder_closed" msgid="4100806530910930934">"Պանակը փակ է"</string>
+    <string name="folder_renamed" msgid="1794088362165669656">"Պանակը վերանվանվեց <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="folder_name_format" msgid="6629239338071103179">"Թղթապանակ՝ <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="widget_button_text" msgid="2880537293434387943">"Վիջեթներ"</string>
     <string name="wallpaper_button_text" msgid="8404103075899945851">"Պաստառներ"</string>
@@ -87,7 +88,7 @@
     <string name="add_to_folder_with_app" msgid="4534929978967147231">"Ավելացնել «<xliff:g id="NAME">%1$s</xliff:g>» պանակին"</string>
     <string name="added_to_folder" msgid="4793259502305558003">"Տարրն ավելացվեց թղթապանակում"</string>
     <string name="create_folder_with" msgid="4050141361160214248">"Ստեղծել թղթապանակ, օգտագործելով՝ <xliff:g id="NAME">%1$s</xliff:g>"</string>
-    <string name="folder_created" msgid="6409794597405184510">"Թղթապանակը ստեղծվեց"</string>
+    <string name="folder_created" msgid="6409794597405184510">"Պանակը ստեղծվեց"</string>
     <string name="action_move_to_workspace" msgid="1603837886334246317">"Տեղափոխել Հիմնական էկրան"</string>
     <string name="action_move_screen_left" msgid="8854216831569401665">"Տեղափոխել էկրանը ձախ"</string>
     <string name="action_move_screen_right" msgid="329334910274311123">"Տեղափոխել էկրանը աջ"</string>
diff --git a/res/values-in/strings.xml b/res/values-in/strings.xml
index c65254a..21c2080 100644
--- a/res/values-in/strings.xml
+++ b/res/values-in/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Telusuri Apps"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Memuat Aplikasi..."</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"Tidak ditemukan Aplikasi yang cocok dengan \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"Buka <xliff:g id="QUERY">%1$s</xliff:g>"</string>
     <string name="out_of_space" msgid="4691004494942118364">"Tidak ada ruang lagi pada layar Utama ini."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Tidak ada ruang tersisa di baki Favorit"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"Aplikasi"</string>
diff --git a/res/values-is-rIS/strings.xml b/res/values-is-rIS/strings.xml
index 1d46cdc..7422eaf 100644
--- a/res/values-is-rIS/strings.xml
+++ b/res/values-is-rIS/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Leita í forritum"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Hleður forrit…"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"Ekki fundust forrit sem samsvara „<xliff:g id="QUERY">%1$s</xliff:g>“"</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"Fara í <xliff:g id="QUERY">%1$s</xliff:g>"</string>
     <string name="out_of_space" msgid="4691004494942118364">"Ekki meira pláss á þessum heimaskjá."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Ekki meira pláss í bakka fyrir uppáhald"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"Forrit"</string>
diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml
index 02057a1..e1e75b3 100644
--- a/res/values-it/strings.xml
+++ b/res/values-it/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Cerca app"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Caricamento di app…"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"Nessuna app trovata corrispondente a \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"Vai a <xliff:g id="QUERY">%1$s</xliff:g>"</string>
     <string name="out_of_space" msgid="4691004494942118364">"Spazio nella schermata Home esaurito."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Spazio esaurito nella barra dei Preferiti"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"App"</string>
diff --git a/res/values-iw/strings.xml b/res/values-iw/strings.xml
index a8039f7..3686abd 100644
--- a/res/values-iw/strings.xml
+++ b/res/values-iw/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"חפש אפליקציות"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"טוען אפליקציות…"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"לא נמצאו אפליקציות התואמות ל-\"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"עבור אל <xliff:g id="QUERY">%1$s</xliff:g>"</string>
     <string name="out_of_space" msgid="4691004494942118364">"אין עוד מקום במסך דף הבית הזה."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"אין עוד מקום במגש המועדפים"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"אפליקציות"</string>
diff --git a/res/values-ja/strings.xml b/res/values-ja/strings.xml
index ba58d88..87b3fa4 100644
--- a/res/values-ja/strings.xml
+++ b/res/values-ja/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"アプリを検索"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"アプリを読み込んでいます…"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"「<xliff:g id="QUERY">%1$s</xliff:g>」に一致するアプリは見つかりませんでした"</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"<xliff:g id="QUERY">%1$s</xliff:g>にアクセス"</string>
     <string name="out_of_space" msgid="4691004494942118364">"このホーム画面に空きスペースがありません。"</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"お気に入りトレイに空きスペースがありません"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"アプリ"</string>
@@ -57,7 +58,7 @@
     <string name="migration_cling_copy_apps" msgid="946331230090919440">"アイコンをコピー"</string>
     <string name="migration_cling_use_default" msgid="2626475813981258626">"初期状態にリセットする"</string>
     <string name="workspace_cling_longpress_title" msgid="9173998993909018310">"壁紙、ウィジェット、設定"</string>
-    <string name="workspace_cling_longpress_description" msgid="4119994475505235248">"カスタマイズするにはバックグラウンドを押し続けます"</string>
+    <string name="workspace_cling_longpress_description" msgid="4119994475505235248">"カスタマイズするには背景を押し続けます"</string>
     <string name="workspace_cling_longpress_dismiss" msgid="368660286867640874">"OK"</string>
     <string name="folder_opened" msgid="94695026776264709">"フォルダが開いています。<xliff:g id="WIDTH">%1$d</xliff:g>x<xliff:g id="HEIGHT">%2$d</xliff:g>の大きさです"</string>
     <string name="folder_tap_to_close" msgid="1884479294466410023">"タップしてフォルダを閉じます"</string>
diff --git a/res/values-ka-rGE/strings.xml b/res/values-ka-rGE/strings.xml
index 07030d8..2900330 100644
--- a/res/values-ka-rGE/strings.xml
+++ b/res/values-ka-rGE/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"აპების ძიება"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"აპები იტვირთება..."</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"„<xliff:g id="QUERY">%1$s</xliff:g>“-ის თანხვედრი აპები არ მოიძებნა"</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"გადადი <xliff:g id="QUERY">%1$s</xliff:g>-ში"</string>
     <string name="out_of_space" msgid="4691004494942118364">"ამ მთავარ ეკრანზე ადგილი აღარ არის."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"რჩეულების თაროზე ადგილი არ არის"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"აპები"</string>
diff --git a/res/values-kk-rKZ/strings.xml b/res/values-kk-rKZ/strings.xml
index e79c9ce..cae5e5e 100644
--- a/res/values-kk-rKZ/strings.xml
+++ b/res/values-kk-rKZ/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Қолданбаларды іздеу"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Қолданбалар жүктелуде…"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"«<xliff:g id="QUERY">%1$s</xliff:g>» сұрауына сәйкес келетін қолданбалар жоқ"</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"<xliff:g id="QUERY">%1$s</xliff:g> сұрауына өту"</string>
     <string name="out_of_space" msgid="4691004494942118364">"Бұл Негізгі экранда орын қалмады."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Қалаулылар науасында орын қалмады"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"Қолданбалар"</string>
diff --git a/res/values-km-rKH/strings.xml b/res/values-km-rKH/strings.xml
index 9f7f537..5ffbf76 100644
--- a/res/values-km-rKH/strings.xml
+++ b/res/values-km-rKH/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"ស្វែងរកកម្មវិធី"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"កំពុងដំណើរការកម្មវិធី..."</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"គ្មានកម្មវិធីដែលត្រូវជាមួយ \"<xliff:g id="QUERY">%1$s</xliff:g>\" ទេ"</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"ចូលទៅកាន់ <xliff:g id="QUERY">%1$s</xliff:g>"</string>
     <string name="out_of_space" msgid="4691004494942118364">"គ្មាន​បន្ទប់​នៅ​លើ​អេក្រង់​ដើម​នេះ​ទៀត​ទេ។"</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"គ្មាន​បន្ទប់​​ក្នុង​ថាស​និយម​ប្រើ"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"កម្មវិធី"</string>
diff --git a/res/values-kn-rIN/strings.xml b/res/values-kn-rIN/strings.xml
index 5cd8792..a1aab7b 100644
--- a/res/values-kn-rIN/strings.xml
+++ b/res/values-kn-rIN/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"ಅಪ್ಲಿಕೇಷನ್‌ಗಳನ್ನು ಹುಡುಕಿ"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"ಅಪ್ಲಿಕೇಶನ್‌ಗಳನ್ನು ಲೋಡ್ ಮಾಡಲಾಗುತ್ತಿದೆ..."</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"\"<xliff:g id="QUERY">%1$s</xliff:g>\" ಹೊಂದಿಕೆಯ ಯಾವುದೇ ಅಪ್ಲಿಕೇಶನ್‌ಗಳು ಕಂಡುಬಂದಿಲ್ಲ"</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"<xliff:g id="QUERY">%1$s</xliff:g> ಗೆ ಹೋಗಿ"</string>
     <string name="out_of_space" msgid="4691004494942118364">"ಈ ಮುಖಪುಟದ ಪರದೆಯಲ್ಲಿ ಹೆಚ್ಚು ಸ್ಥಳಾವಕಾಶವಿಲ್ಲ."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"ಮೆಚ್ಚಿನವುಗಳ ಟ್ರೇನಲ್ಲಿ ಹೆಚ್ಚಿನ ಸ್ಥಳಾವಕಾಶವಿಲ್ಲ"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"ಅಪ್ಲಿಕೇಶನ್‌ಗಳು"</string>
diff --git a/res/values-ko/strings.xml b/res/values-ko/strings.xml
index fa445ac..e069712 100644
--- a/res/values-ko/strings.xml
+++ b/res/values-ko/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"앱 검색"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"앱 로드 중..."</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"\'<xliff:g id="QUERY">%1$s</xliff:g>\'와(과) 일치하는 앱이 없습니다."</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"<xliff:g id="QUERY">%1$s</xliff:g>(으)로 이동"</string>
     <string name="out_of_space" msgid="4691004494942118364">"홈 화면에 더 이상 공간이 없습니다."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"즐겨찾기 트레이에 더 이상 공간이 없습니다."</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"앱"</string>
diff --git a/res/values-ky-rKG/strings.xml b/res/values-ky-rKG/strings.xml
index 2078748..c530b5c 100644
--- a/res/values-ky-rKG/strings.xml
+++ b/res/values-ky-rKG/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Колдонмолорду издөө"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Колдонмолор жүктөлүүдө…"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"\"<xliff:g id="QUERY">%1$s</xliff:g>\" дал келген колдонмолор табылган жок"</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"<xliff:g id="QUERY">%1$s</xliff:g> сурамына өтүңүз"</string>
     <string name="out_of_space" msgid="4691004494942118364">"Бул Үй экранында бош орун жок."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Тандамалдар тайпасында орун калган жок"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"Колдонмолор"</string>
diff --git a/res/values-lo-rLA/strings.xml b/res/values-lo-rLA/strings.xml
index dff2612..005f026 100644
--- a/res/values-lo-rLA/strings.xml
+++ b/res/values-lo-rLA/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"ຊອກຫາແອັບ"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"​ກຳ​ລັງ​ໂຫລດ​ແອັບ..."</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"ບໍ່​ພົບ​ແອັບ​ໃດ​ທີ່​ກົງ​ກັນ \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"ໄປ​ທີ່ <xliff:g id="QUERY">%1$s</xliff:g>"</string>
     <string name="out_of_space" msgid="4691004494942118364">"ບໍ່ມີຫ້ອງເຫຼືອໃນໜ້າຈໍຫຼັກນີ້."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"ບໍ່ມີບ່ອນຫວ່າງໃນຖາດສຳລັບເກັບສິ່ງທີ່ໃຊ້ເປັນປະຈຳ"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"ແອັບຯ"</string>
diff --git a/res/values-lt/strings.xml b/res/values-lt/strings.xml
index b5c0832..442874d 100644
--- a/res/values-lt/strings.xml
+++ b/res/values-lt/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Ieškoti programų"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Įkeliamos programos..."</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"Nerasta jokių užklausą „<xliff:g id="QUERY">%1$s</xliff:g>“ atitinkančių programų"</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"Eiti į <xliff:g id="QUERY">%1$s</xliff:g>"</string>
     <string name="out_of_space" msgid="4691004494942118364">"Šiame pagrindiniame ekrane vietos nebėra."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Mėgstamiausių dėkle nebėra vietos"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"Programos"</string>
diff --git a/res/values-lv/strings.xml b/res/values-lv/strings.xml
index 432bda4..1bef22e 100644
--- a/res/values-lv/strings.xml
+++ b/res/values-lv/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Meklēt lietotnes"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Notiek lietotņu ielāde…"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"Vaicājumam “<xliff:g id="QUERY">%1$s</xliff:g>” neatbilda neviena lietotne."</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"Doties uz: <xliff:g id="QUERY">%1$s</xliff:g>"</string>
     <string name="out_of_space" msgid="4691004494942118364">"Šajā sākuma ekrānā vairs nav vietas."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Izlases joslā vairs nav vietas."</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"Lietotnes"</string>
diff --git a/res/values-mk-rMK/strings.xml b/res/values-mk-rMK/strings.xml
index 8c276e3..0a6704b 100644
--- a/res/values-mk-rMK/strings.xml
+++ b/res/values-mk-rMK/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Пребарување апликации"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Се вчитуваат апликации…"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"Не се најдени апликации што одговараат на „<xliff:g id="QUERY">%1$s</xliff:g>“"</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"Оди на <xliff:g id="QUERY">%1$s</xliff:g>"</string>
     <string name="out_of_space" msgid="4691004494942118364">"Нема повеќе простор на овој екран на почетната страница."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Нема повеќе простор на лентата „Омилени“"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"Апликации"</string>
diff --git a/res/values-ml-rIN/strings.xml b/res/values-ml-rIN/strings.xml
index 59ad153..2bd80d3 100644
--- a/res/values-ml-rIN/strings.xml
+++ b/res/values-ml-rIN/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"ആപ്പ്‌സ് തിരയുക"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"ആപ്പ്‌സ് ലോഡുചെയ്യുന്നു..."</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"\"<xliff:g id="QUERY">%1$s</xliff:g>\" എന്നതുമായി പൊരുത്തപ്പെടുന്ന ആപ്പ്‌സൊന്നും കണ്ടെത്തിയില്ല"</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"<xliff:g id="QUERY">%1$s</xliff:g> എന്നതിലേക്ക് പോവുക"</string>
     <string name="out_of_space" msgid="4691004494942118364">"ഈ ഹോം സ്‌ക്രീനിൽ ഒഴിവൊന്നുമില്ല."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"പ്രിയപ്പെട്ടവയുടെ ട്രേയിൽ ഒഴിവൊന്നുമില്ല"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"അപ്ലിക്കേഷനുകൾ"</string>
diff --git a/res/values-mn-rMN/strings.xml b/res/values-mn-rMN/strings.xml
index fafb37b..19ba66f 100644
--- a/res/values-mn-rMN/strings.xml
+++ b/res/values-mn-rMN/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Апп хайх"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Аппликейшныг ачаалж байна..."</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"\"<xliff:g id="QUERY">%1$s</xliff:g>\"-тай нийцэх апп олдсонгүй"</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"<xliff:g id="QUERY">%1$s</xliff:g> руу очих"</string>
     <string name="out_of_space" msgid="4691004494942118364">"Энэ Нүүр дэлгэц зайгүй."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"\"Дуртай\" трей дээр өөр зай байхгүй байна"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"Апп"</string>
diff --git a/res/values-mr-rIN/strings.xml b/res/values-mr-rIN/strings.xml
index 657010d..dc29b54 100644
--- a/res/values-mr-rIN/strings.xml
+++ b/res/values-mr-rIN/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"अॅप्स शोधा"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"अॅप्स लोड करीत आहे..."</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"\"<xliff:g id="QUERY">%1$s</xliff:g>\" शी जुळणारे कोणतेही अॅप्स आढळले नाहीत"</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"<xliff:g id="QUERY">%1$s</xliff:g> वर जा"</string>
     <string name="out_of_space" msgid="4691004494942118364">"या मुख्य स्क्रीनवर आणखी जागा नाही."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"आवडीच्या ट्रे मध्ये आणखी जागा नाही"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"अॅप्स"</string>
diff --git a/res/values-ms-rMY/strings.xml b/res/values-ms-rMY/strings.xml
index 0c6a5f5..26f2748 100644
--- a/res/values-ms-rMY/strings.xml
+++ b/res/values-ms-rMY/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Cari Apl"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Memuatkan Apl…"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"Tiada Apl yang ditemui sepadan dengan \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"Pergi ke <xliff:g id="QUERY">%1$s</xliff:g>"</string>
     <string name="out_of_space" msgid="4691004494942118364">"Tiada lagi ruang pada skrin Laman Utama ini."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Tiada ruang dalam dulang Kegemaran lagi"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"Apl"</string>
diff --git a/res/values-my-rMM/strings.xml b/res/values-my-rMM/strings.xml
index 19ec3a6..c13ff7e 100644
--- a/res/values-my-rMM/strings.xml
+++ b/res/values-my-rMM/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"ရှာဖွေမှု Appများ"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"App များ ရယူနေစဉ်..."</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"\"<xliff:g id="QUERY">%1$s</xliff:g>\" နှင့်ကိုက်ညီသည့် အပ်ဖ်များမတွေ့ပါ"</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"<xliff:g id="QUERY">%1$s</xliff:g>ကို သွားပါ"</string>
     <string name="out_of_space" msgid="4691004494942118364">"ဤပင်မမျက်နှာစာတွင် နေရာလွတ် မကျန်တော့ပါ"</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"အနှစ်သက်ဆုံးများ ထားရာတွင် နေရာလွတ် မကျန်တော့ပါ"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"အပ်ပလီကေးရှင်းများ"</string>
diff --git a/res/values-nb/strings.xml b/res/values-nb/strings.xml
index 21b87c8..198d56c 100644
--- a/res/values-nb/strings.xml
+++ b/res/values-nb/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Søk i apper"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Laster inn apper …"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"Fant ingen apper som samsvarer med «<xliff:g id="QUERY">%1$s</xliff:g>»"</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"Gå til <xliff:g id="QUERY">%1$s</xliff:g>"</string>
     <string name="out_of_space" msgid="4691004494942118364">"Denne startsiden er full."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Favoritter-skuffen er full"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"Apper"</string>
diff --git a/res/values-ne-rNP/strings.xml b/res/values-ne-rNP/strings.xml
index dd3b3f3..472bd4f 100644
--- a/res/values-ne-rNP/strings.xml
+++ b/res/values-ne-rNP/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"अनुप्रयोगहरू खोज्नुहोस्"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"अनुप्रयोगहरू लोड गरिँदै..."</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"\"<xliff:g id="QUERY">%1$s</xliff:g>\" सँग मिल्दो कुनै अनुप्रयोगहरू फेला परेनन्"</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"<xliff:g id="QUERY">%1$s</xliff:g> मा जानुहोस्"</string>
     <string name="out_of_space" msgid="4691004494942118364">"यो गृह स्क्रिनमा कुनै थप ठाउँ छैन।"</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"मनपर्ने ट्रे अब कुनै ठाँउ छैन"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"अनुप्रयोगहरू"</string>
diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml
index fcbe375..6f49810 100644
--- a/res/values-nl/strings.xml
+++ b/res/values-nl/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Apps zoeken"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Apps laden…"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"Er zijn geen apps gevonden die overeenkomen met \'<xliff:g id="QUERY">%1$s</xliff:g>\'"</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"Ga naar <xliff:g id="QUERY">%1$s</xliff:g>"</string>
     <string name="out_of_space" msgid="4691004494942118364">"Er is geen ruimte meer op dit startscherm."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Geen ruimte meer in het vak \'Favorieten\'"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"Apps"</string>
diff --git a/res/values-pa-rIN/strings.xml b/res/values-pa-rIN/strings.xml
index d510dc7..30c4428 100644
--- a/res/values-pa-rIN/strings.xml
+++ b/res/values-pa-rIN/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"ਐਪਸ ਖੋਜੋ"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"ਐਪਸ ਲੋਡ ਕਰ ਰਿਹਾ ਹੈ..."</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"\"<xliff:g id="QUERY">%1$s</xliff:g>\" ਨਾਲ ਮਿਲਦੀ ਕੋਈ ਵੀ ਐਪਸ ਨਹੀਂ ਲੱਭੀਆਂ"</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"<xliff:g id="QUERY">%1$s</xliff:g> ਤੇ ਜਾਓ"</string>
     <string name="out_of_space" msgid="4691004494942118364">"ਇਸ ਹੋਮ ਸਕ੍ਰੀਨ ਲਈ ਹੋਰ ਖਾਲੀ ਸਥਾਨ ਨਹੀਂ ਹੈ।"</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"ਮਨਪਸੰਦ ਟ੍ਰੇ ਵਿੱਚ ਹੋਰ ਖਾਲੀ ਸਥਾਨ ਨਹੀਂ।"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"ਐਪਸ"</string>
diff --git a/res/values-pl/strings.xml b/res/values-pl/strings.xml
index b3b761d..a1fd29f 100644
--- a/res/values-pl/strings.xml
+++ b/res/values-pl/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Szukaj w aplikacjach"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Wczytuję aplikacje…"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"Nie znaleziono aplikacji pasujących do zapytania „<xliff:g id="QUERY">%1$s</xliff:g>”"</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"Otwórz <xliff:g id="QUERY">%1$s</xliff:g>"</string>
     <string name="out_of_space" msgid="4691004494942118364">"Brak miejsca na tym ekranie głównym."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Brak miejsca w Ulubionych"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"Aplikacje"</string>
diff --git a/res/values-pt-rPT/strings.xml b/res/values-pt-rPT/strings.xml
index 9140b62..0d8a431 100644
--- a/res/values-pt-rPT/strings.xml
+++ b/res/values-pt-rPT/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Pesquisar aplicações"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"A carregar aplicações..."</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"Não foram encontradas aplic. que correspondam a \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"Aceder a <xliff:g id="QUERY">%1$s</xliff:g>"</string>
     <string name="out_of_space" msgid="4691004494942118364">"Sem espaço suficiente neste Ecrã principal."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Não existe mais espaço no tabuleiro de Favoritos"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"Aplicações"</string>
diff --git a/res/values-pt/strings.xml b/res/values-pt/strings.xml
index c8e6458..41e5c02 100644
--- a/res/values-pt/strings.xml
+++ b/res/values-pt/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Pesquisar apps"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Carregando apps…"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"Nenhum app encontrado que corresponda a \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"Ir para <xliff:g id="QUERY">%1$s</xliff:g>"</string>
     <string name="out_of_space" msgid="4691004494942118364">"Não há mais espaço na tela inicial."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Sem espaço na bandeja de favoritos"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"Apps"</string>
diff --git a/res/values-ro/strings.xml b/res/values-ro/strings.xml
index 88cbc79..ffaca78 100644
--- a/res/values-ro/strings.xml
+++ b/res/values-ro/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Căutați aplicații"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Se încarcă aplicațiile..."</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"Nu s-a găsit nicio aplicație pentru „<xliff:g id="QUERY">%1$s</xliff:g>”"</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"Accesați <xliff:g id="QUERY">%1$s</xliff:g>"</string>
     <string name="out_of_space" msgid="4691004494942118364">"Nu mai este loc pe acest Ecran de pornire."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Spațiu epuizat în bara Preferate"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"Aplicații"</string>
diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml
index 5b492fe..6e69e4d 100644
--- a/res/values-ru/strings.xml
+++ b/res/values-ru/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Поиск приложений"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Загрузка…"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"По запросу \"<xliff:g id="QUERY">%1$s</xliff:g>\" ничего не найдено"</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"<xliff:g id="QUERY">%1$s</xliff:g>"</string>
     <string name="out_of_space" msgid="4691004494942118364">"На этом экране все занято"</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"В разделе \"Избранное\" больше нет места"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"Приложения"</string>
diff --git a/res/values-si-rLK/strings.xml b/res/values-si-rLK/strings.xml
index 68e3178..476fff8 100644
--- a/res/values-si-rLK/strings.xml
+++ b/res/values-si-rLK/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"යෙදුම් සෙවීම"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"යෙදුම් පූරණය වෙමින්…"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"\"<xliff:g id="QUERY">%1$s</xliff:g>\" සමග ගැළපෙන යෙදුම් හමු නොවිණි"</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"<xliff:g id="QUERY">%1$s</xliff:g> වෙත යන්න"</string>
     <string name="out_of_space" msgid="4691004494942118364">"මෙම මුල් පිටු තිරය මත තවත් අවසර නැත."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"ප්‍රියතම දෑ ඇති තැටියේ තවත් ඉඩ නොමැත"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"යෙදුම්"</string>
diff --git a/res/values-sk/strings.xml b/res/values-sk/strings.xml
index 1cb76e3..703b46c 100644
--- a/res/values-sk/strings.xml
+++ b/res/values-sk/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Vyhľadávanie v aplikáciách"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Načítavajú sa aplikácie..."</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"Nenašli sa žiadne aplikácie zodpovedajúce dopytu <xliff:g id="QUERY">%1$s</xliff:g>"</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"Prejsť na dopyt <xliff:g id="QUERY">%1$s</xliff:g>"</string>
     <string name="out_of_space" msgid="4691004494942118364">"Na tejto ploche už nie je miesto"</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Na paneli Obľúbené položky už nie je miesto"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"Aplikácie"</string>
diff --git a/res/values-sl/strings.xml b/res/values-sl/strings.xml
index 92c69a5..497378b 100644
--- a/res/values-sl/strings.xml
+++ b/res/values-sl/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Iskanje po aplikacijah"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Nalaganje aplikacij …"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"Ni aplikacij, ki bi ustrezale poizvedbi »<xliff:g id="QUERY">%1$s</xliff:g>«"</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"Odpri storitev <xliff:g id="QUERY">%1$s</xliff:g>"</string>
     <string name="out_of_space" msgid="4691004494942118364">"Na tem začetnem zaslonu ni več prostora."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"V vrstici za priljubljene ni več prostora"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"Aplikacije"</string>
diff --git a/res/values-sq-rAL/strings.xml b/res/values-sq-rAL/strings.xml
index 65aabf6..2b535a3 100644
--- a/res/values-sq-rAL/strings.xml
+++ b/res/values-sq-rAL/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Kërko për aplikacione"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Po ngarkon aplikacionet..."</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"Nuk u gjet asnjë aplikacion që përputhet me \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"Shko te <xliff:g id="QUERY">%1$s</xliff:g>"</string>
     <string name="out_of_space" msgid="4691004494942118364">"Nuk ka më hapësirë në këtë ekran bazë."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Nuk ka më hapësirë në tabakanë \"Të preferuarat\""</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"Aplikacionet"</string>
diff --git a/res/values-sr/strings.xml b/res/values-sr/strings.xml
index 0f93bf3..f21aae0 100644
--- a/res/values-sr/strings.xml
+++ b/res/values-sr/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Претражите апликације"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Апликације се учитавају..."</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"Није пронађена ниједна апликација за „<xliff:g id="QUERY">%1$s</xliff:g>“"</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"Иди на апликацију <xliff:g id="QUERY">%1$s</xliff:g>"</string>
     <string name="out_of_space" msgid="4691004494942118364">"Нема више простора на овом почетном екрану."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Нема више простора на траци Омиљено"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"Апликације"</string>
diff --git a/res/values-sv/strings.xml b/res/values-sv/strings.xml
index 93fce80..385d7a6 100644
--- a/res/values-sv/strings.xml
+++ b/res/values-sv/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Sök efter appar"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Läser in appar …"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"Det gick inte att hitta några appar som matchar <xliff:g id="QUERY">%1$s</xliff:g>"</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"Gå till <xliff:g id="QUERY">%1$s</xliff:g>"</string>
     <string name="out_of_space" msgid="4691004494942118364">"Det finns inte plats för mer på den här startskärmen."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Favoritfältet är fullt"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"Appar"</string>
diff --git a/res/values-sw/strings.xml b/res/values-sw/strings.xml
index 297b731..b86fb73 100644
--- a/res/values-sw/strings.xml
+++ b/res/values-sw/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Tafuta Programu"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Inapakia Programu..."</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"Haikupata programu zinazolingana na \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"Nenda kwenye <xliff:g id="QUERY">%1$s</xliff:g>"</string>
     <string name="out_of_space" msgid="4691004494942118364">"Hakuna nafasi katika skrini hii ya Mwanzo."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Hakuna nafasi zaidi katika treya ya Vipendeleo"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"Programu"</string>
diff --git a/res/values-sw600dp/dimens.xml b/res/values-sw600dp/dimens.xml
index 2651fbb..fb54f12 100644
--- a/res/values-sw600dp/dimens.xml
+++ b/res/values-sw600dp/dimens.xml
@@ -19,6 +19,8 @@
     <dimen name="all_apps_grid_view_start_margin">0dp</dimen>
     <dimen name="all_apps_grid_section_text_size">26sp</dimen>
     <dimen name="all_apps_icon_top_bottom_padding">12dp</dimen>
+    <dimen name="all_apps_background_canvas_width">850dp</dimen>
+    <dimen name="all_apps_background_canvas_height">525dp</dimen>
 
 <!-- Cling -->
     <dimen name="cling_migration_logo_height">400dp</dimen>
diff --git a/res/values-sw720dp/dimens.xml b/res/values-sw720dp/dimens.xml
index d48f9ee..807fab9 100644
--- a/res/values-sw720dp/dimens.xml
+++ b/res/values-sw720dp/dimens.xml
@@ -18,6 +18,8 @@
 <!-- All Apps -->
     <dimen name="all_apps_search_bar_height">54dp</dimen>
     <dimen name="all_apps_icon_top_bottom_padding">14dp</dimen>
+    <dimen name="all_apps_empty_search_message_top_offset">64dp</dimen>
+    <dimen name="all_apps_empty_search_bg_top_offset">180dp</dimen>
 
 <!-- QSB -->
     <dimen name="toolbar_button_vertical_padding">8dip</dimen>
diff --git a/res/values-ta-rIN/strings.xml b/res/values-ta-rIN/strings.xml
index 1f2a6f9..9dac6d0 100644
--- a/res/values-ta-rIN/strings.xml
+++ b/res/values-ta-rIN/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"பயன்பாடுகளில் தேடுக"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"பயன்பாடுகளை ஏற்றுகிறது..."</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"\"<xliff:g id="QUERY">%1$s</xliff:g>\" உடன் பொருந்தும் பயன்பாடுகள் இல்லை"</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"<xliff:g id="QUERY">%1$s</xliff:g>க்குச் செல்லவும்"</string>
     <string name="out_of_space" msgid="4691004494942118364">"முகப்புத் திரையில் இடமில்லை."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"பிடித்தவை ட்ரேயில் இடமில்லை"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"பயன்பாடுகள்"</string>
diff --git a/res/values-te-rIN/strings.xml b/res/values-te-rIN/strings.xml
index bd5fd70..32cb292 100644
--- a/res/values-te-rIN/strings.xml
+++ b/res/values-te-rIN/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"అనువర్తనాలను శోధించండి"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"అనువర్తనాలను లోడ్ చేస్తోంది…"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"\"<xliff:g id="QUERY">%1$s</xliff:g>\"కి సరిపోలే అనువర్తనాలేవీ కనుగొనబడలేదు"</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"<xliff:g id="QUERY">%1$s</xliff:g>కి వెళ్లు"</string>
     <string name="out_of_space" msgid="4691004494942118364">"ఈ హోమ్ స్క్రీన్‌లో ఖాళీ లేదు."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"ఇష్టమైనవి ట్రేలో ఖాళీ లేదు"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"అనువర్తనాలు"</string>
diff --git a/res/values-th/strings.xml b/res/values-th/strings.xml
index 85919af..53ae589 100644
--- a/res/values-th/strings.xml
+++ b/res/values-th/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"ค้นหาแอป"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"กำลังโหลดแอป…"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"ไม่พบแอปที่ตรงกับ \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"ไปที่ <xliff:g id="QUERY">%1$s</xliff:g>"</string>
     <string name="out_of_space" msgid="4691004494942118364">"ไม่มีที่ว่างในหน้าจอหลักนี้"</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"ไม่มีพื้นที่เหลือในถาดรายการโปรด"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"แอป"</string>
diff --git a/res/values-tl/strings.xml b/res/values-tl/strings.xml
index 69a5bb7..43afeab 100644
--- a/res/values-tl/strings.xml
+++ b/res/values-tl/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Mga App sa Paghahanap"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Nilo-load ang Mga App…"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"Walang nakitang Mga App na tumutugma sa \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"Pumunta sa <xliff:g id="QUERY">%1$s</xliff:g>"</string>
     <string name="out_of_space" msgid="4691004494942118364">"Wala nang lugar sa Home screen na ito."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Wala nang lugar sa tray ng Mga Paborito"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"Apps"</string>
diff --git a/res/values-tr/strings.xml b/res/values-tr/strings.xml
index d933c87..064d049 100644
--- a/res/values-tr/strings.xml
+++ b/res/values-tr/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Uygulamalarda Ara"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Uygulamalar Yükleniyor…"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"\"<xliff:g id="QUERY">%1$s</xliff:g>\" ile eşleşen uygulama bulunamadı"</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"<xliff:g id="QUERY">%1$s</xliff:g> uygulamasına git"</string>
     <string name="out_of_space" msgid="4691004494942118364">"Bu Ana ekranda yer kalmadı."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Favoriler tepsisinde başka yer kalmadı"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"Uygulamalar"</string>
diff --git a/res/values-uk/strings.xml b/res/values-uk/strings.xml
index b06177e..ff9364e 100644
--- a/res/values-uk/strings.xml
+++ b/res/values-uk/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Пошук додатків"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Завантаження додатків…"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"Немає додатків для запиту \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"Перейти в додаток <xliff:g id="QUERY">%1$s</xliff:g>"</string>
     <string name="out_of_space" msgid="4691004494942118364">"На цьому головному екрані більше немає місця."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"В області \"Вибране\" немає місця"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"Додатки"</string>
diff --git a/res/values-ur-rPK/strings.xml b/res/values-ur-rPK/strings.xml
index ba189c8..f3d6311 100644
--- a/res/values-ur-rPK/strings.xml
+++ b/res/values-ur-rPK/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"ایپس تلاش کریں"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"ایپس لوڈ ہو رہی ہیں…"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"\"<xliff:g id="QUERY">%1$s</xliff:g>\" سے مماثل کوئی ایپس نہیں ملیں"</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"<xliff:g id="QUERY">%1$s</xliff:g> پر جائیں"</string>
     <string name="out_of_space" msgid="4691004494942118364">"اس ہوم اسکرین پر مزید کوئی گنجائش نہیں ہے۔"</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"پسندیدہ ٹرے میں مزید کوئی گنجائش نہیں ہے"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"ایپس"</string>
diff --git a/res/values-uz-rUZ/strings.xml b/res/values-uz-rUZ/strings.xml
index c3bffeb..24e4c4d 100644
--- a/res/values-uz-rUZ/strings.xml
+++ b/res/values-uz-rUZ/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Ilovalarni qidirish"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Ilovalar yuklanmoqda…"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"“<xliff:g id="QUERY">%1$s</xliff:g>” bilan mos hech qanday ilova topilmadi"</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"O‘tish: <xliff:g id="QUERY">%1$s</xliff:g>"</string>
     <string name="out_of_space" msgid="4691004494942118364">"Uy ekranida bitta ham xona yo‘q."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Ajratilganlarda birorta ham xona yo‘q"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"Ilovalar"</string>
diff --git a/res/values-vi/strings.xml b/res/values-vi/strings.xml
index f57f491..6eff507 100644
--- a/res/values-vi/strings.xml
+++ b/res/values-vi/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Tìm kiếm ứng dụng"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Đang tải ứng dụng..."</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"Không tìm thấy ứng dụng nào phù hợp với \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"Chuyển tới <xliff:g id="QUERY">%1$s</xliff:g>"</string>
     <string name="out_of_space" msgid="4691004494942118364">"Không còn chỗ trên Màn hình chính này."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Không còn chỗ trong khay Mục yêu thích"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"Ứng dụng"</string>
diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml
index 82274ba..77f07c9 100644
--- a/res/values-zh-rCN/strings.xml
+++ b/res/values-zh-rCN/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"搜索应用"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"正在加载应用…"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"未找到与“<xliff:g id="QUERY">%1$s</xliff:g>”相符的应用"</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"转到 <xliff:g id="QUERY">%1$s</xliff:g>"</string>
     <string name="out_of_space" msgid="4691004494942118364">"此主屏幕上已没有空间。"</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"收藏栏已满"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"应用"</string>
diff --git a/res/values-zh-rHK/strings.xml b/res/values-zh-rHK/strings.xml
index f7d240e..62032ec 100644
--- a/res/values-zh-rHK/strings.xml
+++ b/res/values-zh-rHK/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"搜尋應用程式"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"正在載入應用程式…"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"無法找到與「<xliff:g id="QUERY">%1$s</xliff:g>」相符的應用程式"</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"前往 <xliff:g id="QUERY">%1$s</xliff:g>"</string>
     <string name="out_of_space" msgid="4691004494942118364">"主畫面已無空間。"</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"我的收藏寄存區沒有足夠空間"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"應用程式"</string>
diff --git a/res/values-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml
index 39a0ec7..0b3f4d6 100644
--- a/res/values-zh-rTW/strings.xml
+++ b/res/values-zh-rTW/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"搜尋應用程式"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"正在載入應用程式…"</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"找不到符合「<xliff:g id="QUERY">%1$s</xliff:g>」的應用程式"</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"前往 <xliff:g id="QUERY">%1$s</xliff:g>"</string>
     <string name="out_of_space" msgid="4691004494942118364">"這個主螢幕已無空間。"</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"「我的最愛」匣已無可用空間"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"應用程式"</string>
diff --git a/res/values-zu/strings.xml b/res/values-zu/strings.xml
index 83ba089..bb49f1f 100644
--- a/res/values-zu/strings.xml
+++ b/res/values-zu/strings.xml
@@ -32,6 +32,7 @@
     <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Sesha Izinhlelo Zokusebenza"</string>
     <string name="all_apps_loading_message" msgid="7557140873644765180">"Ilayisha izinhlelo zokusebenza..."</string>
     <string name="all_apps_no_search_results" msgid="6332185285860416787">"Azikho izinhlelo zokusebenza ezitholakele ezifana ne-\"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
+    <string name="all_apps_search_market_message" msgid="5470761048755751471">"Hamba ku-<xliff:g id="QUERY">%1$s</xliff:g>"</string>
     <string name="out_of_space" msgid="4691004494942118364">"Asisekho isikhala kulesi sikrini Sasekhaya."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Asisekho isikhala kwitreyi lezintandokazi"</string>
     <string name="all_apps_button_label" msgid="9110807029020582876">"Izinhlelo zokusebenza"</string>
diff --git a/res/values/colors.xml b/res/values/colors.xml
index 51e4d40..8a7f627 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -39,14 +39,14 @@
     <color name="outline_color">#FFFFFFFF</color>
 
     <!-- Containers -->
-    <color name="container_fastscroll_thumb_inactive_color">#42000000</color>
+    <color name="container_fastscroll_thumb_inactive_color">#009688</color>
     <color name="container_fastscroll_thumb_active_color">#009688</color>
 
     <!-- All Apps -->
     <color name="all_apps_grid_section_text_color">#009688</color>
+    <color name="all_apps_search_market_button_focused_bg_color">#DDDDDD</color>
 
     <!-- Widgets view -->
-    <color name="widgets_view_fastscroll_thumb_inactive_color">#42FFFFFF</color>
     <color name="widgets_view_section_text_color">#FFFFFF</color>
     <color name="widgets_view_item_text_color">#C4C4C4</color>
     <color name="widgets_cell_color">#263238</color>
diff --git a/res/values/config.xml b/res/values/config.xml
index 73de794..93c6d14 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -2,8 +2,6 @@
 <!-- Dynamic Grid -->
     <!-- Out of 100, the percent of space the overview bar should try and take vertically. -->
     <integer name="config_dynamic_grid_overview_icon_zone_percentage">20</integer>
-    <!-- Out of 100, the percent to shrink the workspace during overview mode. -->
-    <integer name="config_dynamic_grid_overview_scale_percentage">80</integer>
 
 <!-- Miscellaneous -->
     <bool name="config_largeHeap">false</bool>
@@ -30,6 +28,8 @@
 
     <!-- Out of 100, the percent to shrink the workspace during spring loaded mode. -->
     <integer name="config_workspaceSpringLoadShrinkPercentage">80</integer>
+    <!-- Out of 100, the percent to shrink the workspace during overview mode. -->
+    <integer name="config_workspaceOverviewShrinkPercentage">70</integer>
 
     <!-- Fade/zoom in/out duration & scale in a Launcher overlay transition.
          Note: This should be less than the config_overlayTransitionTime as they happen together. -->
@@ -78,6 +78,9 @@
          get build information. Can be empty. -->
     <string name="build_info_class" translatable="false"></string>
 
+    <!-- View ID to use for QSB widget -->
+    <item type="id" name="qsb_widget" />
+
 <!-- Accessibility actions -->
     <item type="id" name="action_remove" />
     <item type="id" name="action_uninstall" />
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 589d696..3672179 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -55,9 +55,9 @@
     <!-- Notes: container_bounds_inset - quantum_panel_outer_padding -->
     <dimen name="container_bounds_minus_quantum_panel_padding_inset">4dp</dimen>
 
-    <dimen name="container_fastscroll_thumb_min_width">4dp</dimen>
-    <dimen name="container_fastscroll_thumb_max_width">8dp</dimen>
-    <dimen name="container_fastscroll_thumb_height">64dp</dimen>
+    <dimen name="container_fastscroll_thumb_min_width">5dp</dimen>
+    <dimen name="container_fastscroll_thumb_max_width">9dp</dimen>
+    <dimen name="container_fastscroll_thumb_height">72dp</dimen>
     <dimen name="container_fastscroll_thumb_touch_inset">-24dp</dimen>
     <dimen name="container_fastscroll_popup_size">72dp</dimen>
     <dimen name="container_fastscroll_popup_text_size">48dp</dimen>
@@ -74,6 +74,10 @@
     <dimen name="all_apps_prediction_icon_top_padding">8dp</dimen>
     <dimen name="all_apps_prediction_icon_bottom_padding">18dp</dimen>
     <dimen name="all_apps_list_top_bottom_padding">8dp</dimen>
+    <dimen name="all_apps_empty_search_message_top_offset">40dp</dimen>
+    <dimen name="all_apps_empty_search_bg_top_offset">144dp</dimen>
+    <dimen name="all_apps_background_canvas_width">700dp</dimen>
+    <dimen name="all_apps_background_canvas_height">475dp</dimen>
 
 <!-- Widget tray -->
     <dimen name="widget_container_inset">8dp</dimen>
@@ -129,4 +133,8 @@
     <dimen name="blur_size_click_shadow">4dp</dimen>
     <dimen name="click_shadow_high_shift">2dp</dimen>
 
+<!-- Pending widget -->
+    <dimen name="pending_widget_min_padding">8dp</dimen>
+    <dimen name="pending_widget_elevation">2dp</dimen>
+
 </resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 88f149b..fefadef 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -24,10 +24,10 @@
     <!-- URI used to import old favorites. [DO NOT TRANSLATE] -->
     <string name="old_launcher_provider_uri" translatable="false">content://com.android.launcher2.settings/favorites?notify=true</string>
 
-    <!-- Permission to receive the com.android.launcher3.action.LAUNCH intent -->
+    <!-- Permission to receive the com.android.launcher3.action.LAUNCH intent. [DO NOT TRANSLATE] -->
     <string name="receive_launch_broadcasts_permission" translatable="false">com.android.launcher3.permission.RECEIVE_LAUNCH_BROADCASTS</string>
 
-    <!-- Permission to receive the com.android.launcher3.action.FIRST_LOAD_COMPLETE intent -->
+    <!-- Permission to receive the com.android.launcher3.action.FIRST_LOAD_COMPLETE intent. [DO NOT TRANSLATE] -->
     <string name="receive_first_load_broadcast_permission" translatable="false">com.android.launcher3.permission.RECEIVE_FIRST_LOAD_BROADCAST</string>
 
     <!-- Application name -->
@@ -61,6 +61,9 @@
     <string name="all_apps_loading_message">Loading Apps&#8230;</string>
     <!-- No-search-results text. [CHAR_LIMIT=50] -->
     <string name="all_apps_no_search_results">No Apps found matching \"<xliff:g id="query" example="Android">%1$s</xliff:g>\"</string>
+    <!-- Search market text.  This is a format string where the first argument is the name of the activity
+         handling the search.  The format string does not need to handle both of these arguments. [CHAR_LIMIT=50] -->
+    <string name="all_apps_search_market_message">Go to <xliff:g id="query" example="Play Store">%1$s</xliff:g></string>
 
     <!-- Drag and drop -->
     <skip />
diff --git a/res/xml/default_workspace_4x4.xml b/res/xml/default_workspace_4x4.xml
index 9bec86a..060a1f8 100644
--- a/res/xml/default_workspace_4x4.xml
+++ b/res/xml/default_workspace_4x4.xml
@@ -15,102 +15,33 @@
 -->
 
 <favorites xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3">
-    <!-- Far-left screen [0] -->
 
-    <!-- Left screen [1] -->
-    <appwidget
-        launcher:packageName="com.android.settings"
-        launcher:className="com.android.settings.widget.SettingsAppWidgetProvider"
-        launcher:screen="1"
-        launcher:x="0"
-        launcher:y="3"
-        launcher:spanX="4"
-        launcher:spanY="1" />
+    <!-- Hotseat -->
+    <include launcher:workspace="@xml/dw_phone_hotseat" />
 
-    <!-- Middle screen [2] -->
-    <appwidget
-        launcher:packageName="com.android.deskclock"
-        launcher:className="com.android.alarmclock.AnalogAppWidgetProvider"
-        launcher:screen="2"
-        launcher:x="1"
-        launcher:y="0"
-        launcher:spanX="2"
-        launcher:spanY="2" />
-    <favorite
-        launcher:packageName="com.android.camera"
-        launcher:className="com.android.camera.Camera"
-        launcher:screen="2"
-        launcher:x="0"
-        launcher:y="3" />
-
-    <!-- Right screen [3] -->
-    <favorite
-        launcher:packageName="com.android.gallery3d"
-        launcher:className="com.android.gallery3d.app.Gallery"
-        launcher:screen="3"
-        launcher:x="1"
-        launcher:y="3" />
-    <favorite
-        launcher:packageName="com.android.settings"
-        launcher:className="com.android.settings.Settings"
-        launcher:screen="3"
-        launcher:x="2"
-        launcher:y="3" />
-
-    <!-- Far-right screen [4] -->
-
-    <!-- Hotseat (We use the screen as the position of the item in the hotseat) -->
-    <!-- Dialer, Contacts, [All Apps], Messaging, Browser -->
+    <!-- Bottom row -->
     <resolve
-        launcher:container="-101"
         launcher:screen="0"
         launcher:x="0"
-        launcher:y="0" >
-        <favorite launcher:uri="#Intent;action=android.intent.action.DIAL;end" />
-        <favorite launcher:uri="tel:123" />
-        <favorite launcher:uri="#Intent;action=android.intent.action.CALL_BUTTON;end" />
-
-        <favorite
-            launcher:packageName="com.android.dialer"
-            launcher:className="com.android.dialer.DialtactsActivity" />
+        launcher:y="3" >
+        <favorite launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_EMAIL;end" />
+        <favorite launcher:uri="mailto:" />
     </resolve>
 
-    <favorite
-        launcher:packageName="com.android.contacts"
-        launcher:className="com.android.contacts.activities.PeopleActivity"
-        launcher:container="-101"
-        launcher:screen="1"
+    <resolve
+        launcher:screen="0"
         launcher:x="1"
-        launcher:y="0" />
-
-    <resolve
-        launcher:container="-101"
-        launcher:screen="3"
-        launcher:x="3"
-        launcher:y="0" >
-        <favorite
-            launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_MESSAGING;end" />
-        <favorite launcher:uri="sms:" />
-        <favorite launcher:uri="smsto:" />
-        <favorite launcher:uri="mms:" />
-        <favorite launcher:uri="mmsto:" />
-
-        <favorite
-            launcher:packageName="com.android.mms"
-            launcher:className="com.android.mms.ui.ConversationList" />
+        launcher:y="3" >
+        <favorite launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_GALLERY;end" />
+        <favorite launcher:uri="#Intent;type=images/*;end" />
     </resolve>
-    <resolve
-        launcher:container="-101"
-        launcher:screen="4"
-        launcher:x="4"
-        launcher:y="0" >
-        <favorite
-            launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_BROWSER;end" />
-        <favorite launcher:uri="http://www.example.com/" />
 
-        <favorite
-            launcher:packageName="com.android.browser"
-            launcher:className="com.android.browser.BrowserActivity" />
+    <resolve
+        launcher:screen="0"
+        launcher:x="3"
+        launcher:y="3" >
+        <favorite launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_MARKET;end" />
+        <favorite launcher:uri="market://details?id=com.android.launcher" />
     </resolve>
 
 </favorites>
diff --git a/res/xml/default_workspace_5x5.xml b/res/xml/default_workspace_5x5.xml
index 9bec86a..3226617 100644
--- a/res/xml/default_workspace_5x5.xml
+++ b/res/xml/default_workspace_5x5.xml
@@ -15,102 +15,34 @@
 -->
 
 <favorites xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3">
-    <!-- Far-left screen [0] -->
 
-    <!-- Left screen [1] -->
-    <appwidget
-        launcher:packageName="com.android.settings"
-        launcher:className="com.android.settings.widget.SettingsAppWidgetProvider"
-        launcher:screen="1"
-        launcher:x="0"
-        launcher:y="3"
-        launcher:spanX="4"
-        launcher:spanY="1" />
+    <!-- Hotseat -->
+    <include launcher:workspace="@xml/dw_phone_hotseat" />
 
-    <!-- Middle screen [2] -->
-    <appwidget
-        launcher:packageName="com.android.deskclock"
-        launcher:className="com.android.alarmclock.AnalogAppWidgetProvider"
-        launcher:screen="2"
-        launcher:x="1"
-        launcher:y="0"
-        launcher:spanX="2"
-        launcher:spanY="2" />
-    <favorite
-        launcher:packageName="com.android.camera"
-        launcher:className="com.android.camera.Camera"
-        launcher:screen="2"
-        launcher:x="0"
-        launcher:y="3" />
-
-    <!-- Right screen [3] -->
-    <favorite
-        launcher:packageName="com.android.gallery3d"
-        launcher:className="com.android.gallery3d.app.Gallery"
-        launcher:screen="3"
-        launcher:x="1"
-        launcher:y="3" />
-    <favorite
-        launcher:packageName="com.android.settings"
-        launcher:className="com.android.settings.Settings"
-        launcher:screen="3"
-        launcher:x="2"
-        launcher:y="3" />
-
-    <!-- Far-right screen [4] -->
-
-    <!-- Hotseat (We use the screen as the position of the item in the hotseat) -->
-    <!-- Dialer, Contacts, [All Apps], Messaging, Browser -->
+    <!-- Bottom row -->
     <resolve
-        launcher:container="-101"
         launcher:screen="0"
         launcher:x="0"
-        launcher:y="0" >
-        <favorite launcher:uri="#Intent;action=android.intent.action.DIAL;end" />
-        <favorite launcher:uri="tel:123" />
-        <favorite launcher:uri="#Intent;action=android.intent.action.CALL_BUTTON;end" />
+        launcher:y="4" >
+	    <favorite launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_EMAIL;end" />
+	    <favorite launcher:uri="mailto:" />
 
-        <favorite
-            launcher:packageName="com.android.dialer"
-            launcher:className="com.android.dialer.DialtactsActivity" />
     </resolve>
 
-    <favorite
-        launcher:packageName="com.android.contacts"
-        launcher:className="com.android.contacts.activities.PeopleActivity"
-        launcher:container="-101"
-        launcher:screen="1"
+    <resolve
+        launcher:screen="0"
         launcher:x="1"
-        launcher:y="0" />
+        launcher:y="4" >
+	    <favorite launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_GALLERY;end" />
+	    <favorite launcher:uri="#Intent;type=images/*;end" />
 
-    <resolve
-        launcher:container="-101"
-        launcher:screen="3"
-        launcher:x="3"
-        launcher:y="0" >
-        <favorite
-            launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_MESSAGING;end" />
-        <favorite launcher:uri="sms:" />
-        <favorite launcher:uri="smsto:" />
-        <favorite launcher:uri="mms:" />
-        <favorite launcher:uri="mmsto:" />
-
-        <favorite
-            launcher:packageName="com.android.mms"
-            launcher:className="com.android.mms.ui.ConversationList" />
     </resolve>
+
     <resolve
-        launcher:container="-101"
-        launcher:screen="4"
+        launcher:screen="0"
         launcher:x="4"
-        launcher:y="0" >
-        <favorite
-            launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_BROWSER;end" />
-        <favorite launcher:uri="http://www.example.com/" />
-
-        <favorite
-            launcher:packageName="com.android.browser"
-            launcher:className="com.android.browser.BrowserActivity" />
+        launcher:y="4" >
+        <favorite launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_MARKET;end" />
+        <favorite launcher:uri="market://details?id=com.android.launcher" />
     </resolve>
-
 </favorites>
diff --git a/res/xml/default_workspace_5x6.xml b/res/xml/default_workspace_5x6.xml
index d42a93a..bc236fb 100644
--- a/res/xml/default_workspace_5x6.xml
+++ b/res/xml/default_workspace_5x6.xml
@@ -15,101 +15,23 @@
 -->
 
 <favorites xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3">
-    <!-- Far-left screen [0] -->
 
-    <!-- Left screen [1] -->
-    <appwidget
-        launcher:packageName="com.android.settings"
-        launcher:className="com.android.settings.widget.SettingsAppWidgetProvider"
-        launcher:screen="1"
+    <!-- Hotseat -->
+    <include launcher:workspace="@xml/dw_tablet_hotseat" />
+
+    <!-- Bottom row -->
+    <favorite
+        launcher:screen="0"
         launcher:x="0"
-        launcher:y="3"
-        launcher:spanX="4"
-        launcher:spanY="1" />
-
-    <!-- Middle screen [2] -->
-    <appwidget
-        launcher:packageName="com.android.deskclock"
-        launcher:className="com.android.alarmclock.AnalogAppWidgetProvider"
-        launcher:screen="2"
-        launcher:x="1"
-        launcher:y="0"
-        launcher:spanX="2"
-        launcher:spanY="2" />
-    <favorite
-        launcher:packageName="com.android.camera"
-        launcher:className="com.android.camera.Camera"
-        launcher:screen="2"
-        launcher:x="0"
-        launcher:y="3" />
-
-    <!-- Right screen [3] -->
-    <favorite
-        launcher:packageName="com.android.gallery3d"
-        launcher:className="com.android.gallery3d.app.Gallery"
-        launcher:screen="3"
-        launcher:x="1"
-        launcher:y="3" />
-    <favorite
-        launcher:packageName="com.android.settings"
-        launcher:className="com.android.settings.Settings"
-        launcher:screen="3"
-        launcher:x="2"
-        launcher:y="3" />
-
-    <!-- Far-right screen [4] -->
-
-    <!-- Hotseat (We use the screen as the position of the item in the hotseat) -->
-    <!-- Dialer, Contacts, [All Apps], Messaging, Browser -->
-    <resolve
-        launcher:container="-101"
-        launcher:screen="1"
-        launcher:x="1"
-        launcher:y="0" >
-        <favorite launcher:uri="#Intent;action=android.intent.action.DIAL;end" />
-        <favorite launcher:uri="tel:123" />
-        <favorite launcher:uri="#Intent;action=android.intent.action.CALL_BUTTON;end" />
-
-        <favorite
-            launcher:packageName="com.android.dialer"
-            launcher:className="com.android.dialer.DialtactsActivity" />
-    </resolve>
-
-    <favorite
-        launcher:packageName="com.android.contacts"
-        launcher:className="com.android.contacts.activities.PeopleActivity"
-        launcher:container="-101"
-        launcher:screen="2"
-        launcher:x="2"
-        launcher:y="0" />
+        launcher:y="4"
+        launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_CONTACTS;end" />
 
     <resolve
-        launcher:container="-101"
-        launcher:screen="4"
-        launcher:x="4"
-        launcher:y="0" >
-        <favorite
-            launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_MESSAGING;end" />
-        <favorite launcher:uri="sms:" />
-        <favorite launcher:uri="smsto:" />
-        <favorite launcher:uri="mms:" />
-        <favorite launcher:uri="mmsto:" />
-
-        <favorite
-            launcher:packageName="com.android.mms"
-            launcher:className="com.android.mms.ui.ConversationList" />
-    </resolve>
-    <resolve
-        launcher:container="-101"
-        launcher:screen="5"
+        launcher:screen="0"
         launcher:x="5"
-        launcher:y="0" >
-        <favorite
-            launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_BROWSER;end" />
-        <favorite launcher:uri="http://www.example.com/" />
-
-        <favorite
-            launcher:packageName="com.android.browser"
-            launcher:className="com.android.browser.BrowserActivity" />
+        launcher:y="4" >
+        <favorite launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_MARKET;end" />
+        <favorite launcher:uri="market://details?id=com.android.launcher" />
     </resolve>
+
 </favorites>
diff --git a/res/xml/dw_phone_hotseat.xml b/res/xml/dw_phone_hotseat.xml
new file mode 100644
index 0000000..b58994d
--- /dev/null
+++ b/res/xml/dw_phone_hotseat.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<favorites xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3">
+    <!-- Hotseat (We use the screen as the position of the item in the hotseat) -->
+    <!-- Dialer, Messaging, [All Apps], Browser, Camera -->
+    <resolve
+        launcher:container="-101"
+        launcher:screen="0"
+        launcher:x="0"
+        launcher:y="0" >
+        <favorite launcher:uri="#Intent;action=android.intent.action.DIAL;end" />
+        <favorite launcher:uri="tel:123" />
+        <favorite launcher:uri="#Intent;action=android.intent.action.CALL_BUTTON;end" />
+    </resolve>
+
+    <resolve
+        launcher:container="-101"
+        launcher:screen="1"
+        launcher:x="1"
+        launcher:y="0" >
+        <favorite launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_MESSAGING;end" />
+        <favorite launcher:uri="sms:" />
+        <favorite launcher:uri="smsto:" />
+        <favorite launcher:uri="mms:" />
+        <favorite launcher:uri="mmsto:" />
+    </resolve>
+
+    <!-- All Apps -->
+
+    <resolve
+        launcher:container="-101"
+        launcher:screen="3"
+        launcher:x="3"
+        launcher:y="0" >
+        <favorite
+            launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_BROWSER;end" />
+        <favorite launcher:uri="http://www.example.com/" />
+    </resolve>
+
+    <resolve
+        launcher:container="-101"
+        launcher:screen="4"
+        launcher:x="4"
+        launcher:y="0" >
+        <favorite launcher:uri="#Intent;action=android.media.action.STILL_IMAGE_CAMERA;end" />
+        <favorite launcher:uri="#Intent;action=android.intent.action.CAMERA_BUTTON;end" />
+    </resolve>
+
+</favorites>
diff --git a/res/xml/dw_tablet_hotseat.xml b/res/xml/dw_tablet_hotseat.xml
new file mode 100644
index 0000000..671ccba
--- /dev/null
+++ b/res/xml/dw_tablet_hotseat.xml
@@ -0,0 +1,78 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<favorites xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3">
+    <!-- Hotseat (We use the screen as the position of the item in the hotseat) -->
+    <!-- Messaging, Email, Browser, [All Apps], Music, Gallery, Camera -->
+    <resolve
+        launcher:container="-101"
+        launcher:screen="0"
+        launcher:x="0"
+        launcher:y="0" >
+        <favorite launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_MESSAGING;end" />
+        <favorite launcher:uri="sms:" />
+        <favorite launcher:uri="smsto:" />
+        <favorite launcher:uri="mms:" />
+        <favorite launcher:uri="mmsto:" />
+    </resolve>
+
+    <resolve
+        launcher:container="-101"
+        launcher:screen="1"
+        launcher:x="1"
+        launcher:y="0" >
+        <favorite launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_EMAIL;end" />
+        <favorite launcher:uri="mailto:" />
+    </resolve>
+
+    <resolve
+        launcher:container="-101"
+        launcher:screen="2"
+        launcher:x="2"
+        launcher:y="0" >
+        <favorite
+            launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_BROWSER;end" />
+        <favorite launcher:uri="http://www.example.com/" />
+    </resolve>
+
+    <!-- All Apps -->
+
+    <favorite
+        launcher:container="-101"
+        launcher:screen="4"
+        launcher:x="4"
+        launcher:y="0"
+        launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_MUSIC;end" />
+
+    <resolve
+        launcher:container="-101"
+        launcher:screen="5"
+        launcher:x="5"
+        launcher:y="0" >
+        <favorite launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_GALLERY;end" />
+        <favorite launcher:uri="#Intent;type=images/*;end" />
+    </resolve>
+
+    <resolve
+        launcher:container="-101"
+        launcher:screen="6"
+        launcher:x="6"
+        launcher:y="0" >
+        <favorite launcher:uri="#Intent;action=android.media.action.STILL_IMAGE_CAMERA;end" />
+        <favorite launcher:uri="#Intent;action=android.intent.action.CAMERA_BUTTON;end" />
+    </resolve>
+
+</favorites>
diff --git a/src/com/android/launcher3/AppWidgetResizeFrame.java b/src/com/android/launcher3/AppWidgetResizeFrame.java
index ea7c221..e6bf525 100644
--- a/src/com/android/launcher3/AppWidgetResizeFrame.java
+++ b/src/com/android/launcher3/AppWidgetResizeFrame.java
@@ -75,8 +75,8 @@
         mResizeMode = info.resizeMode;
         mDragLayer = dragLayer;
 
-        mMinHSpan = info.getMinSpanX(mLauncher);
-        mMinVSpan = info.getMinSpanY(mLauncher);
+        mMinHSpan = info.minSpanX;
+        mMinVSpan = info.minSpanY;
 
         setBackgroundResource(R.drawable.widget_resize_shadow);
         setForeground(getResources().getDrawable(R.drawable.widget_resize_frame));
diff --git a/src/com/android/launcher3/AppWidgetsRestoredReceiver.java b/src/com/android/launcher3/AppWidgetsRestoredReceiver.java
index 5e7a012..b1d51ec 100644
--- a/src/com/android/launcher3/AppWidgetsRestoredReceiver.java
+++ b/src/com/android/launcher3/AppWidgetsRestoredReceiver.java
@@ -13,6 +13,7 @@
 import android.util.Log;
 
 import com.android.launcher3.LauncherSettings.Favorites;
+import com.android.launcher3.compat.AppWidgetManagerCompat;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -48,7 +49,8 @@
             final AppWidgetProviderInfo provider = widgets.getAppWidgetInfo(newWidgetIds[i]);
             final int state;
             if (LauncherModel.isValidProvider(provider)) {
-                state = LauncherAppWidgetInfo.RESTORE_COMPLETED;
+                // This will ensure that we show 'Click to setup' UI if required.
+                state = LauncherAppWidgetInfo.FLAG_UI_NOT_READY;
             } else {
                 state = LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY;
             }
diff --git a/src/com/android/launcher3/AutoInstallsLayout.java b/src/com/android/launcher3/AutoInstallsLayout.java
index 99a98dd..440e4e7 100644
--- a/src/com/android/launcher3/AutoInstallsLayout.java
+++ b/src/com/android/launcher3/AutoInstallsLayout.java
@@ -355,7 +355,7 @@
                     return addShortcut(info.loadLabel(mPackageManager).toString(),
                             intent, Favorites.ITEM_TYPE_APPLICATION);
                 } catch (PackageManager.NameNotFoundException e) {
-                    if (LOGD) Log.w(TAG, "Unable to add favorite: " + packageName + "/" + className, e);
+                    Log.e(TAG, "Unable to add favorite: " + packageName + "/" + className, e);
                 }
                 return -1;
             } else {
@@ -367,7 +367,7 @@
          * Helper method to allow extending the parser capabilities
          */
         protected long invalidPackageOrClass(XmlResourceParser parser) {
-            if (LOGD) Log.d(TAG, "Skipping invalid <favorite> with no component");
+            Log.w(TAG, "Skipping invalid <favorite> with no component");
             return -1;
         }
     }
diff --git a/src/com/android/launcher3/BaseContainerView.java b/src/com/android/launcher3/BaseContainerView.java
index c8de9df..c118240 100644
--- a/src/com/android/launcher3/BaseContainerView.java
+++ b/src/com/android/launcher3/BaseContainerView.java
@@ -34,10 +34,12 @@
     // The bounds of the search bar.  Only the left, top, right are used to inset the
     // search bar and the height is determined by the measurement of the layout
     private Rect mFixedSearchBarBounds = new Rect();
-    // The bounds of the container
+    // The computed bounds of the search bar
+    private Rect mSearchBarBounds = new Rect();
+    // The computed bounds of the container
     protected Rect mContentBounds = new Rect();
-    // The padding to apply to the container to achieve the bounds
-    protected Rect mContentPadding = new Rect();
+    // The computed padding to apply to the container to achieve the container bounds
+    private Rect mContentPadding = new Rect();
     // The inset to apply to the edges and between the search bar and the container
     private int mContainerBoundsInset;
     private boolean mHasSearchBar;
@@ -90,7 +92,7 @@
      */
     protected void updateBackgroundAndPaddings() {
         Rect padding;
-        Rect searchBarBounds = new Rect(mFixedSearchBarBounds);
+        Rect searchBarBounds = new Rect();
         if (!isValidSearchBarBounds(mFixedSearchBarBounds)) {
             // Use the default bounds
             padding = new Rect(mInsets.left + mContainerBoundsInset,
@@ -110,14 +112,20 @@
                     (mHasSearchBar ? 0 : (mInsets.top + mContainerBoundsInset)),
                     getMeasuredWidth() - mFixedSearchBarBounds.right,
                     mInsets.bottom + mContainerBoundsInset);
+
+            // Use the search bounds
+            searchBarBounds.set(mFixedSearchBarBounds);
         }
-        if (!padding.equals(mContentPadding) || !searchBarBounds.equals(mFixedSearchBarBounds)) {
+
+        // If either the computed container padding has changed, or the computed search bar bounds
+        // has changed, then notify the container
+        if (!padding.equals(mContentPadding) || !searchBarBounds.equals(mSearchBarBounds)) {
             mContentPadding.set(padding);
             mContentBounds.set(padding.left, padding.top,
                     getMeasuredWidth() - padding.right,
                     getMeasuredHeight() - padding.bottom);
-            mFixedSearchBarBounds.set(searchBarBounds);
-            onUpdateBackgroundAndPaddings(mFixedSearchBarBounds, padding);
+            mSearchBarBounds.set(searchBarBounds);
+            onUpdateBackgroundAndPaddings(mSearchBarBounds, padding);
         }
     }
 
diff --git a/src/com/android/launcher3/BaseRecyclerView.java b/src/com/android/launcher3/BaseRecyclerView.java
index 0fae427..f0d8b3b 100644
--- a/src/com/android/launcher3/BaseRecyclerView.java
+++ b/src/com/android/launcher3/BaseRecyclerView.java
@@ -92,9 +92,15 @@
             // TODO(winsonc): If we want to animate the section heads while scrolling, we can
             //                initiate that here if the recycler view scroll state is not
             //                RecyclerView.SCROLL_STATE_IDLE.
+
+            onUpdateScrollbar(dy);
         }
     }
 
+    public void reset() {
+        mScrollbar.reattachThumbToScroll();
+    }
+
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
@@ -143,7 +149,7 @@
                 mScrollbar.handleTouchEvent(ev, mDownX, mDownY, mLastY);
                 break;
         }
-        return mScrollbar.isDragging();
+        return mScrollbar.isDraggingThumb();
     }
 
     public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
@@ -185,12 +191,10 @@
      *   AvailableScrollHeight = Total height of the all items - last page height
      *
      * This assumes that all rows are the same height.
-     *
-     * @param yOffset the offset from the top of the recycler view to start tracking.
      */
-    protected int getAvailableScrollHeight(int rowCount, int rowHeight, int yOffset) {
+    protected int getAvailableScrollHeight(int rowCount, int rowHeight) {
         int visibleHeight = getHeight() - mBackgroundPadding.top - mBackgroundPadding.bottom;
-        int scrollHeight = getPaddingTop() + yOffset + rowCount * rowHeight + getPaddingBottom();
+        int scrollHeight = getPaddingTop() + rowCount * rowHeight + getPaddingBottom();
         int availableScrollHeight = scrollHeight - visibleHeight;
         return availableScrollHeight;
     }
@@ -222,7 +226,7 @@
     @Override
     protected void dispatchDraw(Canvas canvas) {
         super.dispatchDraw(canvas);
-        onUpdateScrollbar();
+        onUpdateScrollbar(0);
         mScrollbar.draw(canvas);
     }
 
@@ -234,24 +238,21 @@
      * @param scrollPosState the current scroll position
      * @param rowCount the number of rows, used to calculate the total scroll height (assumes that
      *                 all rows are the same height)
-     * @param yOffset the offset to start tracking in the recycler view (only used for all apps)
      */
     protected void synchronizeScrollBarThumbOffsetToViewScroll(ScrollPositionState scrollPosState,
-            int rowCount, int yOffset) {
-        int availableScrollHeight = getAvailableScrollHeight(rowCount, scrollPosState.rowHeight,
-                yOffset);
-        int availableScrollBarHeight = getAvailableScrollBarHeight();
-
+            int rowCount) {
         // Only show the scrollbar if there is height to be scrolled
+        int availableScrollBarHeight = getAvailableScrollBarHeight();
+        int availableScrollHeight = getAvailableScrollHeight(rowCount, scrollPosState.rowHeight);
         if (availableScrollHeight <= 0) {
-            mScrollbar.setScrollbarThumbOffset(-1, -1);
+            mScrollbar.setThumbOffset(-1, -1);
             return;
         }
 
         // Calculate the current scroll position, the scrollY of the recycler view accounts for the
         // view padding, while the scrollBarY is drawn right up to the background padding (ignoring
         // padding)
-        int scrollY = getPaddingTop() + yOffset +
+        int scrollY = getPaddingTop() +
                 (scrollPosState.rowIndex * scrollPosState.rowHeight) - scrollPosState.rowTopOffset;
         int scrollBarY = mBackgroundPadding.top +
                 (int) (((float) scrollY / availableScrollHeight) * availableScrollBarHeight);
@@ -261,9 +262,9 @@
         if (Utilities.isRtl(getResources())) {
             scrollBarX = mBackgroundPadding.left;
         } else {
-            scrollBarX = getWidth() - mBackgroundPadding.right - mScrollbar.getWidth();
+            scrollBarX = getWidth() - mBackgroundPadding.right - mScrollbar.getThumbWidth();
         }
-        mScrollbar.setScrollbarThumbOffset(scrollBarX, scrollBarY);
+        mScrollbar.setThumbOffset(scrollBarX, scrollBarY);
     }
 
     /**
@@ -276,10 +277,15 @@
      * Updates the bounds for the scrollbar.
      * <p>Override in each subclass of this base class.
      */
-    public abstract void onUpdateScrollbar();
+    public abstract void onUpdateScrollbar(int dy);
 
     /**
      * <p>Override in each subclass of this base class.
      */
     public void onFastScrollCompleted() {}
+
+    /**
+     * Returns information about the item that the recycler view is currently scrolled to.
+     */
+    protected abstract void getCurScrollState(ScrollPositionState stateOut);
 }
\ No newline at end of file
diff --git a/src/com/android/launcher3/BaseRecyclerViewFastScrollBar.java b/src/com/android/launcher3/BaseRecyclerViewFastScrollBar.java
index 2c4184d..fcee7e8 100644
--- a/src/com/android/launcher3/BaseRecyclerViewFastScrollBar.java
+++ b/src/com/android/launcher3/BaseRecyclerViewFastScrollBar.java
@@ -23,6 +23,7 @@
 import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.Paint;
+import android.graphics.Path;
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.view.MotionEvent;
@@ -51,14 +52,21 @@
     private int mThumbActiveColor;
     @Thunk Point mThumbOffset = new Point(-1, -1);
     @Thunk Paint mThumbPaint;
-    private Paint mTrackPaint;
     private int mThumbMinWidth;
     private int mThumbMaxWidth;
     @Thunk int mThumbWidth;
     @Thunk int mThumbHeight;
+    private int mThumbCurvature;
+    private Path mThumbPath = new Path();
+    private Paint mTrackPaint;
+    private int mTrackWidth;
+    private float mLastTouchY;
     // The inset is the buffer around which a point will still register as a click on the scrollbar
     private int mTouchInset;
     private boolean mIsDragging;
+    private boolean mIsThumbDetached;
+    private boolean mCanThumbDetach;
+    private boolean mIgnoreDragGesture;
 
     // This is the offset from the top of the scrollbar when the user first starts touching.  To
     // prevent jumping, this offset is applied as the user scrolls.
@@ -72,51 +80,74 @@
         mPopup = new BaseRecyclerViewFastScrollPopup(rv, res);
         mTrackPaint = new Paint();
         mTrackPaint.setColor(rv.getFastScrollerTrackColor(Color.BLACK));
-        mTrackPaint.setAlpha(0);
+        mTrackPaint.setAlpha(MAX_TRACK_ALPHA);
         mThumbInactiveColor = rv.getFastScrollerThumbInactiveColor(
                 res.getColor(R.color.container_fastscroll_thumb_inactive_color));
         mThumbActiveColor = res.getColor(R.color.container_fastscroll_thumb_active_color);
         mThumbPaint = new Paint();
+        mThumbPaint.setAntiAlias(true);
         mThumbPaint.setColor(mThumbInactiveColor);
+        mThumbPaint.setStyle(Paint.Style.FILL);
         mThumbWidth = mThumbMinWidth = res.getDimensionPixelSize(R.dimen.container_fastscroll_thumb_min_width);
         mThumbMaxWidth = res.getDimensionPixelSize(R.dimen.container_fastscroll_thumb_max_width);
         mThumbHeight = res.getDimensionPixelSize(R.dimen.container_fastscroll_thumb_height);
+        mThumbCurvature = mThumbMaxWidth - mThumbMinWidth;
         mTouchInset = res.getDimensionPixelSize(R.dimen.container_fastscroll_thumb_touch_inset);
     }
 
-    public void setScrollbarThumbOffset(int x, int y) {
+    public void setDetachThumbOnFastScroll() {
+        mCanThumbDetach = true;
+    }
+
+    public void reattachThumbToScroll() {
+        mIsThumbDetached = false;
+    }
+
+    public void setThumbOffset(int x, int y) {
         if (mThumbOffset.x == x && mThumbOffset.y == y) {
             return;
         }
-        mInvalidateRect.set(mThumbOffset.x, 0, mThumbOffset.x + mThumbWidth, mRv.getHeight());
+        mInvalidateRect.set(mThumbOffset.x - mThumbCurvature, mThumbOffset.y,
+                mThumbOffset.x + mThumbWidth, mThumbOffset.y + mThumbHeight);
         mThumbOffset.set(x, y);
-        mInvalidateRect.union(new Rect(mThumbOffset.x, 0, mThumbOffset.x + mThumbWidth,
-                mRv.getHeight()));
+        updateThumbPath();
+        mInvalidateRect.union(mThumbOffset.x - mThumbCurvature, mThumbOffset.y,
+                mThumbOffset.x + mThumbWidth, mThumbOffset.y + mThumbHeight);
         mRv.invalidate(mInvalidateRect);
     }
 
-    // Setter/getter for the search bar width for animations
-    public void setWidth(int width) {
-        mInvalidateRect.set(mThumbOffset.x, 0, mThumbOffset.x + mThumbWidth, mRv.getHeight());
+    public Point getThumbOffset() {
+        return mThumbOffset;
+    }
+
+    // Setter/getter for the thumb bar width for animations
+    public void setThumbWidth(int width) {
+        mInvalidateRect.set(mThumbOffset.x - mThumbCurvature, mThumbOffset.y,
+                mThumbOffset.x + mThumbWidth, mThumbOffset.y + mThumbHeight);
         mThumbWidth = width;
-        mInvalidateRect.union(new Rect(mThumbOffset.x, 0, mThumbOffset.x + mThumbWidth,
-                mRv.getHeight()));
+        updateThumbPath();
+        mInvalidateRect.union(mThumbOffset.x - mThumbCurvature, mThumbOffset.y,
+                mThumbOffset.x + mThumbWidth, mThumbOffset.y + mThumbHeight);
         mRv.invalidate(mInvalidateRect);
     }
 
-    public int getWidth() {
+    public int getThumbWidth() {
         return mThumbWidth;
     }
 
-    // Setter/getter for the track background alpha for animations
-    public void setTrackAlpha(int alpha) {
-        mTrackPaint.setAlpha(alpha);
-        mInvalidateRect.set(mThumbOffset.x, 0, mThumbOffset.x + mThumbWidth, mRv.getHeight());
+    // Setter/getter for the track bar width for animations
+    public void setTrackWidth(int width) {
+        mInvalidateRect.set(mThumbOffset.x - mThumbCurvature, 0, mThumbOffset.x + mThumbWidth,
+                mRv.getHeight());
+        mTrackWidth = width;
+        updateThumbPath();
+        mInvalidateRect.union(mThumbOffset.x - mThumbCurvature, 0, mThumbOffset.x + mThumbWidth,
+                mRv.getHeight());
         mRv.invalidate(mInvalidateRect);
     }
 
-    public int getTrackAlpha() {
-        return mTrackPaint.getAlpha();
+    public int getTrackWidth() {
+        return mTrackWidth;
     }
 
     public int getThumbHeight() {
@@ -127,10 +158,18 @@
         return mThumbMaxWidth;
     }
 
-    public boolean isDragging() {
+    public float getLastTouchY() {
+        return mLastTouchY;
+    }
+
+    public boolean isDraggingThumb() {
         return mIsDragging;
     }
 
+    public boolean isThumbDetached() {
+        return mIsThumbDetached;
+    }
+
     /**
      * Handles the touch event and determines whether to show the fast scroller (or updates it if
      * it is already showing).
@@ -142,16 +181,21 @@
         int y = (int) ev.getY();
         switch (action) {
             case MotionEvent.ACTION_DOWN:
-                if (isNearPoint(downX, downY)) {
+                if (isNearThumb(downX, downY)) {
                     mTouchOffset = downY - mThumbOffset.y;
                 }
                 break;
             case MotionEvent.ACTION_MOVE:
-                // Check if we should start scrolling
-                if (!mIsDragging && isNearPoint(downX, downY) &&
+                // Check if we should start scrolling, but ignore this fastscroll gesture if we have
+                // exceeded some fixed movement
+                mIgnoreDragGesture |= Math.abs(y - downY) > config.getScaledPagingTouchSlop();
+                if (!mIsDragging && !mIgnoreDragGesture && isNearThumb(downX, lastY) &&
                         Math.abs(y - downY) > config.getScaledTouchSlop()) {
                     mRv.getParent().requestDisallowInterceptTouchEvent(true);
                     mIsDragging = true;
+                    if (mCanThumbDetach) {
+                        mIsThumbDetached = true;
+                    }
                     mTouchOffset += (lastY - downY);
                     mPopup.animateVisibility(true);
                     animateScrollbar(true);
@@ -166,11 +210,14 @@
                     mPopup.setSectionName(sectionName);
                     mPopup.animateVisibility(!sectionName.isEmpty());
                     mRv.invalidate(mPopup.updateFastScrollerBounds(mRv, lastY));
+                    mLastTouchY = boundedY;
                 }
                 break;
             case MotionEvent.ACTION_UP:
             case MotionEvent.ACTION_CANCEL:
                 mTouchOffset = 0;
+                mLastTouchY = 0;
+                mIgnoreDragGesture = false;
                 if (mIsDragging) {
                     mIsDragging = false;
                     mPopup.animateVisibility(false);
@@ -189,8 +236,7 @@
         if (mTrackPaint.getAlpha() > 0) {
             canvas.drawRect(mThumbOffset.x, 0, mThumbOffset.x + mThumbWidth, mRv.getHeight(), mTrackPaint);
         }
-        canvas.drawRect(mThumbOffset.x, mThumbOffset.y, mThumbOffset.x + mThumbWidth,
-                mThumbOffset.y + mThumbHeight, mThumbPaint);
+        canvas.drawPath(mThumbPath, mThumbPaint);
 
         // Draw the popup
         mPopup.draw(canvas);
@@ -203,30 +249,49 @@
         if (mScrollbarAnimator != null) {
             mScrollbarAnimator.cancel();
         }
-        ObjectAnimator trackAlphaAnim = ObjectAnimator.ofInt(this, "trackAlpha",
-                isScrolling ? MAX_TRACK_ALPHA : 0);
-        ObjectAnimator thumbWidthAnim = ObjectAnimator.ofInt(this, "width",
-                isScrolling ? mThumbMaxWidth : mThumbMinWidth);
-        ValueAnimator colorAnimation = ValueAnimator.ofObject(new ArgbEvaluator(),
-                mThumbPaint.getColor(), isScrolling ? mThumbActiveColor : mThumbInactiveColor);
-        colorAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
-            @Override
-            public void onAnimationUpdate(ValueAnimator animator) {
-                mThumbPaint.setColor((Integer) animator.getAnimatedValue());
-                mRv.invalidate(mThumbOffset.x, mThumbOffset.y, mThumbOffset.x + mThumbWidth,
-                        mThumbOffset.y + mThumbHeight);
-            }
-        });
+
         mScrollbarAnimator = new AnimatorSet();
-        mScrollbarAnimator.playTogether(trackAlphaAnim, thumbWidthAnim, colorAnimation);
+        ObjectAnimator trackWidthAnim = ObjectAnimator.ofInt(this, "trackWidth",
+                isScrolling ? mThumbMaxWidth : mThumbMinWidth);
+        ObjectAnimator thumbWidthAnim = ObjectAnimator.ofInt(this, "thumbWidth",
+                isScrolling ? mThumbMaxWidth : mThumbMinWidth);
+        mScrollbarAnimator.playTogether(trackWidthAnim, thumbWidthAnim);
+        if (mThumbActiveColor != mThumbInactiveColor) {
+            ValueAnimator colorAnimation = ValueAnimator.ofObject(new ArgbEvaluator(),
+                    mThumbPaint.getColor(), isScrolling ? mThumbActiveColor : mThumbInactiveColor);
+            colorAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+                @Override
+                public void onAnimationUpdate(ValueAnimator animator) {
+                    mThumbPaint.setColor((Integer) animator.getAnimatedValue());
+                    mRv.invalidate(mThumbOffset.x, mThumbOffset.y, mThumbOffset.x + mThumbWidth,
+                            mThumbOffset.y + mThumbHeight);
+                }
+            });
+            mScrollbarAnimator.play(colorAnimation);
+        }
         mScrollbarAnimator.setDuration(SCROLL_BAR_VIS_DURATION);
         mScrollbarAnimator.start();
     }
 
     /**
+     * Updates the path for the thumb drawable.
+     */
+    private void updateThumbPath() {
+        mThumbCurvature = mThumbMaxWidth - mThumbWidth;
+        mThumbPath.reset();
+        mThumbPath.moveTo(mThumbOffset.x + mThumbWidth, mThumbOffset.y);                    // tr
+        mThumbPath.lineTo(mThumbOffset.x + mThumbWidth, mThumbOffset.y + mThumbHeight);     // br
+        mThumbPath.lineTo(mThumbOffset.x, mThumbOffset.y + mThumbHeight);                   // bl
+        mThumbPath.cubicTo(mThumbOffset.x, mThumbOffset.y + mThumbHeight,
+                mThumbOffset.x - mThumbCurvature, mThumbOffset.y + mThumbHeight / 2,
+                mThumbOffset.x, mThumbOffset.y);                                            // bl2tl
+        mThumbPath.close();
+    }
+
+    /**
      * Returns whether the specified points are near the scroll bar bounds.
      */
-    private boolean isNearPoint(int x, int y) {
+    private boolean isNearThumb(int x, int y) {
         mTmpRect.set(mThumbOffset.x, mThumbOffset.y, mThumbOffset.x + mThumbWidth,
                 mThumbOffset.y + mThumbHeight);
         mTmpRect.inset(mTouchInset, mTouchInset);
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index a0be8ea..5070878 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -34,11 +34,13 @@
 import android.util.TypedValue;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
+import android.view.View;
 import android.view.ViewConfiguration;
 import android.view.ViewParent;
 import android.view.animation.AccelerateInterpolator;
 import android.view.animation.DecelerateInterpolator;
 import android.widget.TextView;
+
 import com.android.launcher3.IconCache.IconLoadRequest;
 import com.android.launcher3.model.PackageItemInfo;
 
@@ -508,7 +510,7 @@
             mIcon.setBounds(0, 0, iconSize, iconSize);
         }
         if (mLayoutHorizontal) {
-            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+            if (Utilities.ATLEAST_JB_MR1) {
                 setCompoundDrawablesRelative(mIcon, null, null, null);
             } else {
                 setCompoundDrawables(mIcon, null, null, null);
@@ -538,6 +540,13 @@
             } else if (info instanceof ShortcutInfo) {
                 applyFromShortcutInfo((ShortcutInfo) info,
                         LauncherAppState.getInstance().getIconCache());
+                if ((info.rank < FolderIcon.NUM_ITEMS_IN_PREVIEW) && (info.container >= 0)) {
+                    View folderIcon =
+                            mLauncher.getWorkspace().getHomescreenIconByItemId(info.container);
+                    if (folderIcon != null) {
+                        folderIcon.invalidate();
+                    }
+                }
             } else if (info instanceof PackageItemInfo) {
                 applyFromPackageItemInfo((PackageItemInfo) info);
             }
diff --git a/src/com/android/launcher3/ButtonDropTarget.java b/src/com/android/launcher3/ButtonDropTarget.java
index b7f89d0..2baa6d8 100644
--- a/src/com/android/launcher3/ButtonDropTarget.java
+++ b/src/com/android/launcher3/ButtonDropTarget.java
@@ -24,7 +24,6 @@
 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;
@@ -93,7 +92,7 @@
         // drawableLeft and drawableStart.
         mDrawable = getResources().getDrawable(resId);
 
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+        if (Utilities.ATLEAST_JB_MR1) {
             setCompoundDrawablesRelativeWithIntrinsicBounds(mDrawable, null, null, null);
         } else {
             setCompoundDrawablesWithIntrinsicBounds(mDrawable, null, null, null);
@@ -114,7 +113,7 @@
     @Override
     public final void onDragEnter(DragObject d) {
         d.dragView.setColor(mHoverColor);
-        if (Utilities.isLmpOrAbove()) {
+        if (Utilities.ATLEAST_LOLLIPOP) {
             animateTextColor(mHoverColor);
         } else {
             if (mCurrentFilter == null) {
@@ -131,8 +130,8 @@
         // Do nothing
     }
 
-	protected void resetHoverColor() {
-        if (Utilities.isLmpOrAbove()) {
+    protected void resetHoverColor() {
+        if (Utilities.ATLEAST_LOLLIPOP) {
             animateTextColor(mOriginalTextColor.getDefaultColor());
         } else {
             mDrawable.setColorFilter(null);
diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java
index 8096887..84e2d49 100644
--- a/src/com/android/launcher3/CellLayout.java
+++ b/src/com/android/launcher3/CellLayout.java
@@ -54,7 +54,6 @@
 import com.android.launcher3.accessibility.FolderAccessibilityHelper;
 import com.android.launcher3.accessibility.WorkspaceAccessibilityHelper;
 import com.android.launcher3.util.Thunk;
-import com.android.launcher3.widget.PendingAddWidgetInfo;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -211,6 +210,7 @@
 
         mBackground = (TransitionDrawable) res.getDrawable(R.drawable.bg_screenpanel);
         mBackground.setCallback(this);
+        mBackground.setAlpha((int) (mBackgroundAlpha * 255));
 
         mReorderPreviewAnimationMagnitude = (REORDER_PREVIEW_MAGNITUDE *
                 grid.iconSizePx);
@@ -414,7 +414,11 @@
             if (mIsDragOverlapping) {
                 mBackground.startTransition(BACKGROUND_ACTIVATE_DURATION);
             } else {
-                mBackground.reverseTransition(BACKGROUND_ACTIVATE_DURATION);
+                if (mBackgroundAlpha > 0f) {
+                    mBackground.reverseTransition(BACKGROUND_ACTIVATE_DURATION);
+                } else {
+                    mBackground.resetTransition();
+                }
             }
             invalidate();
         }
@@ -2681,65 +2685,6 @@
         resultRect.set(x, y, x + width, y + height);
     }
 
-    /**
-     * Computes the required horizontal and vertical cell spans to always
-     * fit the given rectangle.
-     *
-     * @param width Width in pixels
-     * @param height Height in pixels
-     * @param result An array of length 2 in which to store the result (may be null).
-     */
-    public static int[] rectToCell(Launcher launcher, int width, int height, int[] result) {
-        return rectToCell(launcher.getDeviceProfile(), launcher, width, height, result);
-    }
-
-    public static int[] rectToCell(DeviceProfile grid, Context context, int width, int height,
-            int[] result) {
-        Rect padding = grid.getWorkspacePadding(Utilities.isRtl(context.getResources()));
-
-        // Always assume we're working with the smallest span to make sure we
-        // reserve enough space in both orientations.
-        int parentWidth = DeviceProfile.calculateCellWidth(grid.widthPx
-                - padding.left - padding.right, (int) grid.inv.numColumns);
-        int parentHeight = DeviceProfile.calculateCellHeight(grid.heightPx
-                - padding.top - padding.bottom, (int) grid.inv.numRows);
-        int smallerSize = Math.min(parentWidth, parentHeight);
-
-        // Always round up to next largest cell
-        int spanX = (int) Math.ceil(width / (float) smallerSize);
-        int spanY = (int) Math.ceil(height / (float) smallerSize);
-
-        if (result == null) {
-            return new int[] { spanX, spanY };
-        }
-        result[0] = spanX;
-        result[1] = spanY;
-        return result;
-    }
-
-    /**
-     * Calculate the grid spans needed to fit given item
-     */
-    public void calculateSpans(ItemInfo info) {
-        final int minWidth;
-        final int minHeight;
-
-        if (info instanceof LauncherAppWidgetInfo) {
-            minWidth = ((LauncherAppWidgetInfo) info).minWidth;
-            minHeight = ((LauncherAppWidgetInfo) info).minHeight;
-        } else if (info instanceof PendingAddWidgetInfo) {
-            minWidth = ((PendingAddWidgetInfo) info).minWidth;
-            minHeight = ((PendingAddWidgetInfo) info).minHeight;
-        } else {
-            // It's not a widget, so it must be 1x1
-            info.spanX = info.spanY = 1;
-            return;
-        }
-        int[] spans = rectToCell(mLauncher, minWidth, minHeight, null);
-        info.spanX = spans[0];
-        info.spanY = spans[1];
-    }
-
     private void clearOccupiedCells() {
         for (int x = 0; x < mCountX; x++) {
             for (int y = 0; y < mCountY; y++) {
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 62b05b0..774594f 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -56,7 +56,6 @@
     private final int overviewModeBarItemWidthPx;
     private final int overviewModeBarSpacerWidthPx;
     private final float overviewModeIconZoneRatio;
-    private final float overviewModeScaleFactor;
 
     // Workspace
     private int desiredWorkspaceLeftRightMarginPx;
@@ -136,8 +135,6 @@
                 res.getDimensionPixelSize(R.dimen.dynamic_grid_overview_bar_spacer_width);
         overviewModeIconZoneRatio =
                 res.getInteger(R.integer.config_dynamic_grid_overview_icon_zone_percentage) / 100f;
-        overviewModeScaleFactor =
-                res.getInteger(R.integer.config_dynamic_grid_overview_scale_percentage) / 100f;
         iconDrawablePaddingOriginalPx =
                 res.getDimensionPixelSize(R.dimen.dynamic_grid_icon_drawable_padding);
 
@@ -334,18 +331,11 @@
         }
     }
 
-    Rect getOverviewModeButtonBarRect() {
+    int getOverviewModeButtonBarHeight() {
         int zoneHeight = (int) (overviewModeIconZoneRatio * availableHeightPx);
         zoneHeight = Math.min(overviewModeMaxIconZoneHeightPx,
                 Math.max(overviewModeMinIconZoneHeightPx, zoneHeight));
-        return new Rect(0, availableHeightPx - zoneHeight, 0, availableHeightPx);
-    }
-
-    public float getOverviewModeScale(boolean isLayoutRtl) {
-        Rect workspacePadding = getWorkspacePadding(isLayoutRtl);
-        Rect overviewBar = getOverviewModeButtonBarRect();
-        int pageSpace = availableHeightPx - workspacePadding.top - workspacePadding.bottom;
-        return (overviewModeScaleFactor * (pageSpace - overviewBar.height())) / pageSpace;
+        return zoneHeight;
     }
 
     // The rect returned will be extended to below the system ui that covers the workspace
@@ -394,7 +384,7 @@
         final boolean isLayoutRtl = Utilities.isRtl(launcher.getResources());
 
         // Layout the search bar space
-        View searchBar = launcher.getSearchBar();
+        View searchBar = launcher.getSearchDropTargetBar();
         lp = (FrameLayout.LayoutParams) searchBar.getLayoutParams();
         if (hasVerticalBarLayout) {
             // Vertical search bar space -- The search bar is fixed in the layout to be on the left
@@ -476,7 +466,7 @@
         // Layout the Overview Mode
         ViewGroup overviewMode = launcher.getOverviewPanel();
         if (overviewMode != null) {
-            Rect r = getOverviewModeButtonBarRect();
+            int overviewButtonBarHeight = getOverviewModeButtonBarHeight();
             lp = (FrameLayout.LayoutParams) overviewMode.getLayoutParams();
             lp.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
 
@@ -485,7 +475,7 @@
             int maxWidth = totalItemWidth + (visibleChildCount-1) * overviewModeBarSpacerWidthPx;
 
             lp.width = Math.min(availableWidthPx, maxWidth);
-            lp.height = r.height();
+            lp.height = overviewButtonBarHeight;
             overviewMode.setLayoutParams(lp);
 
             if (lp.width > totalItemWidth && visibleChildCount > 1) {
diff --git a/src/com/android/launcher3/DragLayer.java b/src/com/android/launcher3/DragLayer.java
index aaa14e6..1c18747 100644
--- a/src/com/android/launcher3/DragLayer.java
+++ b/src/com/android/launcher3/DragLayer.java
@@ -159,7 +159,7 @@
     }
 
     private boolean isEventOverDropTargetBar(MotionEvent ev) {
-        getDescendantRectRelativeToSelf(mLauncher.getSearchBar(), mHitRect);
+        getDescendantRectRelativeToSelf(mLauncher.getSearchDropTargetBar(), mHitRect);
         if (mHitRect.contains((int) ev.getX(), (int) ev.getY())) {
             return true;
         }
@@ -321,7 +321,7 @@
             childrenForAccessibility.add(currentFolder);
 
             if (isInAccessibleDrag()) {
-                childrenForAccessibility.add(mLauncher.getSearchBar());
+                childrenForAccessibility.add(mLauncher.getSearchDropTargetBar());
             }
         } else {
             super.addChildrenForAccessibility(childrenForAccessibility);
diff --git a/src/com/android/launcher3/DragView.java b/src/com/android/launcher3/DragView.java
index dfa8202..2acfc61 100644
--- a/src/com/android/launcher3/DragView.java
+++ b/src/com/android/launcher3/DragView.java
@@ -131,7 +131,7 @@
         measure(ms, ms);
         mPaint = new Paint(Paint.FILTER_BITMAP_FLAG);
 
-        if (Utilities.isLmpOrAbove()) {
+        if (Utilities.ATLEAST_LOLLIPOP) {
             setElevation(getResources().getDimension(R.dimen.drag_elevation));
         }
     }
@@ -252,14 +252,14 @@
             setColorScale(color, m2);
             m1.postConcat(m2);
 
-            if (Utilities.isLmpOrAbove()) {
+            if (Utilities.ATLEAST_LOLLIPOP) {
                 animateFilterTo(m1.getArray());
             } else {
                 mPaint.setColorFilter(new ColorMatrixColorFilter(m1));
                 invalidate();
             }
         } else {
-            if (!Utilities.isLmpOrAbove() || mCurrentFilter == null) {
+            if (!Utilities.ATLEAST_LOLLIPOP || mCurrentFilter == null) {
                 mPaint.setColorFilter(null);
                 invalidate();
             } else {
diff --git a/src/com/android/launcher3/allapps/AllAppsSearchEditView.java b/src/com/android/launcher3/ExtendedEditText.java
similarity index 74%
rename from src/com/android/launcher3/allapps/AllAppsSearchEditView.java
rename to src/com/android/launcher3/ExtendedEditText.java
index b7dcd66..c7b64ec 100644
--- a/src/com/android/launcher3/allapps/AllAppsSearchEditView.java
+++ b/src/com/android/launcher3/ExtendedEditText.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.launcher3.allapps;
+package com.android.launcher3;
 
 import android.content.Context;
 import android.util.AttributeSet;
@@ -22,28 +22,28 @@
 
 
 /**
- * The edit text for the search container
+ * The edit text that reports back when the back key has been pressed.
  */
-public class AllAppsSearchEditView extends EditText {
+public class ExtendedEditText extends EditText {
 
     /**
      * Implemented by listeners of the back key.
      */
     public interface OnBackKeyListener {
-        public void onBackKey();
+        public boolean onBackKey();
     }
 
     private OnBackKeyListener mBackKeyListener;
 
-    public AllAppsSearchEditView(Context context) {
-        this(context, null);
+    public ExtendedEditText(Context context) {
+        super(context);
     }
 
-    public AllAppsSearchEditView(Context context, AttributeSet attrs) {
-        this(context, attrs, 0);
+    public ExtendedEditText(Context context, AttributeSet attrs) {
+        super(context, attrs);
     }
 
-    public AllAppsSearchEditView(Context context, AttributeSet attrs, int defStyleAttr) {
+    public ExtendedEditText(Context context, AttributeSet attrs, int defStyleAttr) {
         super(context, attrs, defStyleAttr);
     }
 
@@ -56,7 +56,7 @@
         // If this is a back key, propagate the key back to the listener
         if (keyCode == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_UP) {
             if (mBackKeyListener != null) {
-                mBackKeyListener.onBackKey();
+                return mBackKeyListener.onBackKey();
             }
             return false;
         }
diff --git a/src/com/android/launcher3/Folder.java b/src/com/android/launcher3/Folder.java
index 2e19f6e..c1aa356 100644
--- a/src/com/android/launcher3/Folder.java
+++ b/src/com/android/launcher3/Folder.java
@@ -124,7 +124,7 @@
 
     @Thunk FolderPagedView mContent;
     @Thunk View mContentWrapper;
-    FolderEditText mFolderName;
+    ExtendedEditText mFolderName;
 
     private View mFooter;
     private int mFooterHeight;
@@ -196,8 +196,15 @@
         mContent = (FolderPagedView) findViewById(R.id.folder_content);
         mContent.setFolder(this);
 
-        mFolderName = (FolderEditText) findViewById(R.id.folder_name);
-        mFolderName.setFolder(this);
+        mFolderName = (ExtendedEditText) findViewById(R.id.folder_name);
+        mFolderName.setOnBackKeyListener(new ExtendedEditText.OnBackKeyListener() {
+            @Override
+            public boolean onBackKey() {
+                // Close the activity on back key press
+                doneEditingFolderName(true);
+                return false;
+            }
+        });
         mFolderName.setOnFocusChangeListener(this);
 
         // We disable action mode for now since it messes up the view on phones
@@ -275,7 +282,7 @@
 
     @Override
     public void enableAccessibleDrag(boolean enable) {
-        mLauncher.getSearchBar().enableAccessibleDrag(enable);
+        mLauncher.getSearchDropTargetBar().enableAccessibleDrag(enable);
         for (int i = 0; i < mContent.getChildCount(); i++) {
             mContent.getPageAt(i).enableAccessibleDrag(enable, CellLayout.FOLDER_ACCESSIBILITY_DRAG);
         }
@@ -446,7 +453,7 @@
 
         Animator openFolderAnim = null;
         final Runnable onCompleteRunnable;
-        if (!Utilities.isLmpOrAbove()) {
+        if (!Utilities.ATLEAST_LOLLIPOP) {
             positionAndSizeAsIcon();
             centerAboutIcon();
 
@@ -561,7 +568,7 @@
                 public void onAnimationEnd(Animator animation) {
                     mFolderName.animate().setDuration(FOLDER_NAME_ANIMATION_DURATION)
                         .translationX(0)
-                        .setInterpolator(Utilities.isLmpOrAbove() ?
+                        .setInterpolator(Utilities.ATLEAST_LOLLIPOP ?
                                 AnimationUtils.loadInterpolator(mLauncher,
                                         android.R.interpolator.fast_out_slow_in)
                                 : new LogDecelerateInterpolator(100, 0));
@@ -1389,7 +1396,7 @@
     }
 
     // Compares item position based on rank and position giving priority to the rank.
-    private static final Comparator<ItemInfo> ITEM_POS_COMPARATOR = new Comparator<ItemInfo>() {
+    public static final Comparator<ItemInfo> ITEM_POS_COMPARATOR = new Comparator<ItemInfo>() {
 
         @Override
         public int compare(ItemInfo lhs, ItemInfo rhs) {
diff --git a/src/com/android/launcher3/FolderEditText.java b/src/com/android/launcher3/FolderEditText.java
deleted file mode 100644
index c311008..0000000
--- a/src/com/android/launcher3/FolderEditText.java
+++ /dev/null
@@ -1,36 +0,0 @@
-package com.android.launcher3;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.view.KeyEvent;
-import android.widget.EditText;
-
-public class FolderEditText extends EditText {
-
-    private Folder mFolder;
-
-    public FolderEditText(Context context) {
-        super(context);
-    }
-
-    public FolderEditText(Context context, AttributeSet attrs) {
-        super(context, attrs);
-    }
-
-    public FolderEditText(Context context, AttributeSet attrs, int defStyle) {
-        super(context, attrs, defStyle);
-    }
-
-    public void setFolder(Folder folder) {
-        mFolder = folder;
-    }
-
-    @Override
-    public boolean onKeyPreIme(int keyCode, KeyEvent event) {
-        // Catch the back button on the soft keyboard so that we can just close the activity
-        if (event.getKeyCode() == android.view.KeyEvent.KEYCODE_BACK) {
-            mFolder.doneEditingFolderName(true);
-        }
-        return super.onKeyPreIme(keyCode, event);
-    }
-}
diff --git a/src/com/android/launcher3/FolderPagedView.java b/src/com/android/launcher3/FolderPagedView.java
index f2ec1b6..cc9c573 100644
--- a/src/com/android/launcher3/FolderPagedView.java
+++ b/src/com/android/launcher3/FolderPagedView.java
@@ -329,6 +329,10 @@
                 lp.cellY = info.cellY;
                 currentPage.addViewToCellLayout(
                         v, -1, mFolder.mLauncher.getViewIdForItem(info), lp, true);
+
+                if (rank < FolderIcon.NUM_ITEMS_IN_PREVIEW && v instanceof BubbleTextView) {
+                    ((BubbleTextView) v).verifyHighRes();
+                }
             }
 
             rank ++;
diff --git a/src/com/android/launcher3/IconCache.java b/src/com/android/launcher3/IconCache.java
index 916418f..59ab839 100644
--- a/src/com/android/launcher3/IconCache.java
+++ b/src/com/android/launcher3/IconCache.java
@@ -540,7 +540,7 @@
             mCache.put(cacheKey, entry);
 
             // Check the DB first.
-            if (!getEntryFromDB(componentName, user, entry, useLowResIcon)) {
+            if (!getEntryFromDB(cacheKey, entry, useLowResIcon)) {
                 if (info != null) {
                     entry.icon = Utilities.createIconBitmap(info.getBadgedIcon(mIconDpi), mContext);
                 } else {
@@ -579,7 +579,14 @@
             Bitmap icon, CharSequence title) {
         removeFromMemCacheLocked(packageName, user);
 
-        CacheEntry entry = getEntryForPackageLocked(packageName, user, false);
+        ComponentKey cacheKey = getPackageKey(packageName, user);
+        CacheEntry entry = mCache.get(cacheKey);
+
+        // For icon caching, do not go through DB. Just update the in-memory entry.
+        if (entry == null) {
+            entry = new CacheEntry();
+            mCache.put(cacheKey, entry);
+        }
         if (!TextUtils.isEmpty(title)) {
             entry.title = title;
         }
@@ -588,15 +595,18 @@
         }
     }
 
+    private static ComponentKey getPackageKey(String packageName, UserHandleCompat user) {
+        ComponentName cn = new ComponentName(packageName, packageName + EMPTY_CLASS_NAME);
+        return new ComponentKey(cn, user);
+    }
+
     /**
      * Gets an entry for the package, which can be used as a fallback entry for various components.
      * This method is not thread safe, it must be called from a synchronized method.
-     *
      */
     private CacheEntry getEntryForPackageLocked(String packageName, UserHandleCompat user,
             boolean useLowResIcon) {
-        ComponentName cn = new ComponentName(packageName, packageName + EMPTY_CLASS_NAME);
-        ComponentKey cacheKey = new ComponentKey(cn, user);
+        ComponentKey cacheKey = getPackageKey(packageName, user);
         CacheEntry entry = mCache.get(cacheKey);
 
         if (entry == null || (entry.isLowResIcon && !useLowResIcon)) {
@@ -604,9 +614,11 @@
             boolean entryUpdated = true;
 
             // Check the DB first.
-            if (!getEntryFromDB(cn, user, entry, useLowResIcon)) {
+            if (!getEntryFromDB(cacheKey, entry, useLowResIcon)) {
                 try {
-                    PackageInfo info = mPackageManager.getPackageInfo(packageName, 0);
+                    int flags = UserHandleCompat.myUserHandle().equals(user) ? 0 :
+                        PackageManager.GET_UNINSTALLED_PACKAGES;
+                    PackageInfo info = mPackageManager.getPackageInfo(packageName, flags);
                     ApplicationInfo appInfo = info.applicationInfo;
                     if (appInfo == null) {
                         throw new NameNotFoundException("ApplicationInfo is null");
@@ -622,7 +634,8 @@
                     // package updates.
                     ContentValues values =
                             newContentValues(entry.icon, entry.title.toString(), mPackageBgColor);
-                    addIconToDB(values, cn, info, mUserManager.getSerialNumberForUser(user));
+                    addIconToDB(values, cacheKey.componentName, info,
+                            mUserManager.getSerialNumberForUser(user));
 
                 } catch (NameNotFoundException e) {
                     if (DEBUG) Log.d(TAG, "Application not installed " + packageName);
@@ -649,7 +662,7 @@
      * @param dpi the native density of the icon
      */
     public void preloadIcon(ComponentName componentName, Bitmap icon, int dpi, String label,
-            long userSerial) {
+            long userSerial, InvariantDeviceProfile idp) {
         // TODO rescale to the correct native DPI
         try {
             PackageManager packageManager = mContext.getPackageManager();
@@ -660,21 +673,22 @@
             // pass
         }
 
-        ContentValues values = newContentValues(icon, label, Color.TRANSPARENT);
+        ContentValues values = newContentValues(
+                Bitmap.createScaledBitmap(icon, idp.iconBitmapSize, idp.iconBitmapSize, true),
+                label, Color.TRANSPARENT);
         values.put(IconDB.COLUMN_COMPONENT, componentName.flattenToString());
         values.put(IconDB.COLUMN_USER, userSerial);
         mIconDb.getWritableDatabase().insertWithOnConflict(IconDB.TABLE_NAME, null, values,
                 SQLiteDatabase.CONFLICT_REPLACE);
     }
 
-    private boolean getEntryFromDB(ComponentName component, UserHandleCompat user,
-            CacheEntry entry, boolean lowRes) {
+    private boolean getEntryFromDB(ComponentKey cacheKey, CacheEntry entry, boolean lowRes) {
         Cursor c = mIconDb.getReadableDatabase().query(IconDB.TABLE_NAME,
                 new String[] {lowRes ? IconDB.COLUMN_ICON_LOW_RES : IconDB.COLUMN_ICON,
                         IconDB.COLUMN_LABEL},
                 IconDB.COLUMN_COMPONENT + " = ? AND " + IconDB.COLUMN_USER + " = ?",
-                new String[] {component.flattenToString(),
-                    Long.toString(mUserManager.getSerialNumberForUser(user))},
+                new String[] {cacheKey.componentName.flattenToString(),
+                    Long.toString(mUserManager.getSerialNumberForUser(cacheKey.user))},
                 null, null, null);
         try {
             if (c.moveToNext()) {
@@ -685,7 +699,8 @@
                     entry.title = "";
                     entry.contentDescription = "";
                 } else {
-                    entry.contentDescription = mUserManager.getBadgedLabelForUser(entry.title, user);
+                    entry.contentDescription = mUserManager.getBadgedLabelForUser(
+                            entry.title, cacheKey.user);
                 }
                 return true;
             }
@@ -774,7 +789,7 @@
     }
 
     private static final class IconDB extends SQLiteOpenHelper {
-        private final static int DB_VERSION = 6;
+        private final static int DB_VERSION = 7;
 
         private final static String TABLE_NAME = "icons";
         private final static String COLUMN_ROWID = "rowid";
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 6648b6e..1f843cb 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -116,8 +116,6 @@
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.PrintWriter;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
 import java.text.DateFormat;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -132,8 +130,7 @@
  */
 public class Launcher extends Activity
         implements View.OnClickListener, OnLongClickListener, LauncherModel.Callbacks,
-                   View.OnTouchListener, PageSwitchListener, LauncherProviderChangeListener,
-                   LauncherStateTransitionAnimation.Callbacks {
+                   View.OnTouchListener, PageSwitchListener, LauncherProviderChangeListener {
     static final String TAG = "Launcher";
     static final boolean LOGD = false;
 
@@ -304,6 +301,8 @@
     private boolean mHasFocus = false;
     private boolean mAttached = false;
 
+    private LauncherClings mClings;
+
     private static LongArrayMap<FolderInfo> sFolders = new LongArrayMap<>();
 
     private View.OnTouchListener mHapticFeedbackTouchListener;
@@ -361,18 +360,6 @@
         }
     }
 
-    // TODO: remove this field and call method directly when Launcher3 can depend on M APIs
-    private static Method sClipRevealMethod = null;
-    static {
-        Class<?> activityOptionsClass = ActivityOptions.class;
-        try {
-            sClipRevealMethod = activityOptionsClass.getDeclaredMethod("makeClipRevealAnimation",
-                    View.class, int.class, int.class, int.class, int.class);
-        } catch (Exception e) {
-            // Earlier version
-        }
-    }
-
     @Thunk Runnable mBuildLayersRunnable = new Runnable() {
         public void run() {
             if (mWorkspace != null) {
@@ -452,7 +439,7 @@
 
         mDragController = new DragController(this);
         mInflater = getLayoutInflater();
-        mStateTransitionAnimation = new LauncherStateTransitionAnimation(this, this);
+        mStateTransitionAnimation = new LauncherStateTransitionAnimation(this);
 
         mStats = new Stats(this);
 
@@ -663,12 +650,12 @@
     public boolean isDraggingEnabled() {
         // We prevent dragging when we are loading the workspace as it is possible to pick up a view
         // that is subsequently removed from the workspace in startBinding().
-        return !mModel.isLoadingWorkspace();
+        return !isWorkspaceLoading();
     }
 
     @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
     public static int generateViewId() {
-        if (Build.VERSION.SDK_INT >= 17) {
+        if (Utilities.ATLEAST_JB_MR1) {
             return View.generateViewId();
         } else {
             // View.generateViewId() is not available. The following fallback logic is a copy
@@ -743,6 +730,7 @@
         };
 
         if (requestCode == REQUEST_BIND_APPWIDGET) {
+            // This is called only if the user did not previously have permissions to bind widgets
             final int appWidgetId = data != null ?
                     data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1) : -1;
             if (resultCode == RESULT_CANCELED) {
@@ -752,6 +740,10 @@
             } else if (resultCode == RESULT_OK) {
                 addAppWidgetImpl(appWidgetId, mPendingAddInfo, null,
                         mPendingAddWidgetInfo, ON_ACTIVITY_RESULT_ANIMATION_DELAY);
+
+                // When the user has granted permission to bind widgets, we should check to see if
+                // we can inflate the default search bar widget.
+                getOrCreateQsbBar();
             }
             return;
         } else if (requestCode == REQUEST_PICK_WALLPAPER) {
@@ -1004,6 +996,12 @@
         mPaused = false;
         if (mRestoring || mOnResumeNeedsLoad) {
             setWorkspaceLoading(true);
+
+            // If we're starting binding all over again, clear any bind calls we'd postponed in
+            // the past (see waitUntilResume) -- we don't need them since we're starting binding
+            // from scratch again
+            mBindOnResumeCallbacks.clear();
+
             mModel.startLoader(PagedView.INVALID_RESTORE_PAGE);
             mRestoring = false;
             mOnResumeNeedsLoad = false;
@@ -1553,23 +1551,6 @@
         }
     }
 
-    private int[] getSpanForWidget(ComponentName component, int minWidth, int minHeight) {
-        Rect padding = AppWidgetHostView.getDefaultPaddingForWidget(this, component, null);
-        // We want to account for the extra amount of padding that we are adding to the widget
-        // to ensure that it gets the full amount of space that it has requested
-        int requiredWidth = minWidth + padding.left + padding.right;
-        int requiredHeight = minHeight + padding.top + padding.bottom;
-        return CellLayout.rectToCell(this, requiredWidth, requiredHeight, null);
-    }
-
-    public int[] getSpanForWidget(AppWidgetProviderInfo info) {
-        return getSpanForWidget(info.provider, info.minWidth, info.minHeight);
-    }
-
-    public int[] getMinSpanForWidget(AppWidgetProviderInfo info) {
-        return getSpanForWidget(info.provider, info.minResizeWidth, info.minResizeHeight);
-    }
-
     /**
      * Add a widget to the workspace.
      *
@@ -1666,18 +1647,18 @@
         }
         registerReceiver(mReceiver, filter);
         FirstFrameAnimatorHelper.initializeDrawListener(getWindow().getDecorView());
-        setupTransparentSystemBarsForLmp();
+        setupTransparentSystemBarsForLollipop();
         mAttached = true;
         mVisible = true;
     }
 
     /**
-     * Sets up transparent navigation and status bars in LMP.
+     * Sets up transparent navigation and status bars in Lollipop.
      * This method is a no-op for other platform versions.
      */
     @TargetApi(Build.VERSION_CODES.LOLLIPOP)
-    private void setupTransparentSystemBarsForLmp() {
-        if (Utilities.isLmpOrAbove()) {
+    private void setupTransparentSystemBarsForLollipop() {
+        if (Utilities.ATLEAST_LOLLIPOP) {
             Window window = getWindow();
             window.getAttributes().systemUiVisibility |=
                     (View.SYSTEM_UI_FLAG_LAYOUT_STABLE
@@ -1844,7 +1825,7 @@
         return mOverviewPanel;
     }
 
-    public SearchDropTargetBar getSearchBar() {
+    public SearchDropTargetBar getSearchDropTargetBar() {
         return mSearchDropTargetBar;
     }
 
@@ -1880,29 +1861,22 @@
         super.onNewIntent(intent);
 
         // Close the menu
-        if (Intent.ACTION_MAIN.equals(intent.getAction())) {
+        Folder openFolder = mWorkspace.getOpenFolder();
+        boolean alreadyOnHome = mHasFocus && ((intent.getFlags() &
+                Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT)
+                != Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT);
+        boolean isActionMain = Intent.ACTION_MAIN.equals(intent.getAction());
+        if (isActionMain) {
             // also will cancel mWaitingForResult.
             closeSystemDialogs();
 
-            final boolean alreadyOnHome = mHasFocus && ((intent.getFlags() &
-                    Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT)
-                    != Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT);
-
             if (mWorkspace == null) {
                 // Can be cases where mWorkspace is null, this prevents a NPE
                 return;
             }
-            Folder openFolder = mWorkspace.getOpenFolder();
             // In all these cases, only animate if we're already on home
             mWorkspace.exitWidgetResizeMode();
 
-            boolean moveToDefaultScreen = mLauncherCallbacks != null ?
-                    mLauncherCallbacks.shouldMoveToDefaultScreenOnHomeIntent() : true;
-            if (alreadyOnHome && mState == State.WORKSPACE && !mWorkspace.isTouchActive() &&
-                    openFolder == null && moveToDefaultScreen) {
-                mWorkspace.moveToDefaultScreen(true);
-            }
-
             closeFolder();
             exitSpringLoadedDragMode();
 
@@ -1936,13 +1910,30 @@
             }
         }
 
-        if (DEBUG_RESUME_TIME) {
-            Log.d(TAG, "Time spent in onNewIntent: " + (System.currentTimeMillis() - startTime));
-        }
-
         if (mLauncherCallbacks != null) {
             mLauncherCallbacks.onNewIntent(intent);
         }
+
+        // Defer moving to the default screen until after we callback to the LauncherCallbacks
+        // as slow logic in the callbacks eat into the time the scroller expects for the snapToPage
+        // animation.
+        if (isActionMain) {
+            boolean moveToDefaultScreen = mLauncherCallbacks != null ?
+                    mLauncherCallbacks.shouldMoveToDefaultScreenOnHomeIntent() : true;
+            if (alreadyOnHome && mState == State.WORKSPACE && !mWorkspace.isTouchActive() &&
+                    openFolder == null && moveToDefaultScreen) {
+                mWorkspace.post(new Runnable() {
+                    @Override
+                    public void run() {
+                        mWorkspace.moveToDefaultScreen(true);
+                    }
+                });
+            }
+        }
+
+        if (DEBUG_RESUME_TIME) {
+            Log.d(TAG, "Time spent in onNewIntent: " + (System.currentTimeMillis() - startTime));
+        }
     }
 
     @Override
@@ -2150,6 +2141,15 @@
         }
     }
 
+    public void startSearchFromAllApps(View v, Intent searchIntent, String searchQuery) {
+        if (mLauncherCallbacks != null && mLauncherCallbacks.startSearchFromAllApps(searchQuery)) {
+            return;
+        }
+
+        // If not handled, then just start the provided search intent
+        startActivitySafely(v, searchIntent, null);
+    }
+
     public boolean isOnCustomContent() {
         return mWorkspace.isOnOrMovingToCustomContent();
     }
@@ -2218,7 +2218,7 @@
         mPendingAddInfo.screenId = -1;
         mPendingAddInfo.cellX = mPendingAddInfo.cellY = -1;
         mPendingAddInfo.spanX = mPendingAddInfo.spanY = -1;
-        mPendingAddInfo.minSpanX = mPendingAddInfo.minSpanY = -1;
+        mPendingAddInfo.minSpanX = mPendingAddInfo.minSpanY = 1;
         mPendingAddInfo.dropPos = null;
     }
 
@@ -2551,6 +2551,10 @@
         if (!isAppsViewVisible()) {
             showAppsView(true /* animated */, false /* resetListToTop */,
                     true /* updatePredictedApps */, false /* focusSearchBar */);
+
+            if (mLauncherCallbacks != null) {
+                mLauncherCallbacks.onClickAllAppsButton(v);
+            }
         }
     }
 
@@ -2679,6 +2683,7 @@
             throw new IllegalArgumentException("Input must be a FolderIcon");
         }
 
+        // TODO(sunnygoyal): Re-evaluate this code.
         FolderIcon folderIcon = (FolderIcon) v;
         final FolderInfo info = folderIcon.getFolderInfo();
         Folder openFolder = mWorkspace.getFolderForTag(info);
@@ -2882,8 +2887,7 @@
             Bundle optsBundle = null;
             if (useLaunchAnimation) {
                 ActivityOptions opts = null;
-                if (sClipRevealMethod != null) {
-                    // TODO: call method directly when Launcher3 can depend on M APIs
+                if (Utilities.ATLEAST_MARSHMALLOW) {
                     int left = 0, top = 0;
                     int width = v.getMeasuredWidth(), height = v.getMeasuredHeight();
                     if (v instanceof TextView) {
@@ -2897,22 +2901,12 @@
                             height = bounds.height();
                         }
                     }
-                    try {
-                        opts = (ActivityOptions) sClipRevealMethod.invoke(null, v,
-                                left, top, width, height);
-                    } catch (IllegalAccessException e) {
-                        Log.d(TAG, "Could not call makeClipRevealAnimation: " + e);
-                        sClipRevealMethod = null;
-                    } catch (InvocationTargetException e) {
-                        Log.d(TAG, "Could not call makeClipRevealAnimation: " + e);
-                        sClipRevealMethod = null;
-                    }
-                }
-                if (opts == null && !Utilities.isLmpOrAbove()) {
+                    opts = ActivityOptions.makeClipRevealAnimation(v, left, top, width, height);
+                } else if (!Utilities.ATLEAST_LOLLIPOP) {
                     // Below L, we use a scale up animation
                     opts = ActivityOptions.makeScaleUpAnimation(v, 0, 0,
                                     v.getMeasuredWidth(), v.getMeasuredHeight());
-                } else if (opts == null && Utilities.isLmpMR1()) {
+                } else if (Utilities.ATLEAST_LOLLIPOP_MR1) {
                     // On L devices, we use the device default slide-up transition.
                     // On L MR1 devices, we a custom version of the slide-up transition which
                     // doesn't have the delay present in the device default.
@@ -2941,7 +2935,7 @@
         return false;
     }
 
-    @Thunk boolean startActivitySafely(View v, Intent intent, Object tag) {
+    public boolean startActivitySafely(View v, Intent intent, Object tag) {
         boolean success = false;
         if (mIsSafeModeEnabled && !Utilities.isSystemApp(this, intent)) {
             Toast.makeText(this, R.string.safemode_shortcut_error, Toast.LENGTH_SHORT).show();
@@ -3027,7 +3021,7 @@
 
         ObjectAnimator oa = LauncherAnimUtils.ofPropertyValuesHolder(mFolderIconImageView, alpha,
                 scaleX, scaleY);
-        if (Utilities.isLmpOrAbove()) {
+        if (Utilities.ATLEAST_LOLLIPOP) {
             oa.setInterpolator(new LogDecelerateInterpolator(100, 0));
         }
         oa.setDuration(getResources().getInteger(R.integer.config_folderExpandDuration));
@@ -3265,14 +3259,6 @@
         }
     }
 
-    @Override
-    public void onStateTransitionHideSearchBar() {
-        // Hide the search bar
-        if (mSearchDropTargetBar != null) {
-            mSearchDropTargetBar.hideSearchBar(false /* animated */);
-        }
-    }
-
     public void showWorkspace(boolean animated) {
         showWorkspace(WorkspaceStateTransitionAnimation.SCROLL_TO_CURRENT_PAGE, animated, null);
     }
@@ -3290,16 +3276,9 @@
         boolean changed = mState != State.WORKSPACE ||
                 mWorkspace.getState() != Workspace.State.NORMAL;
         if (changed) {
-            boolean wasInSpringLoadedMode = (mState != State.WORKSPACE);
             mWorkspace.setVisibility(View.VISIBLE);
-            mStateTransitionAnimation.startAnimationToWorkspace(mState, Workspace.State.NORMAL,
-                    snapToPage, animated, onCompleteRunnable);
-
-            // Show the search bar (only animate if we were showing the drop target bar in spring
-            // loaded mode)
-            if (mSearchDropTargetBar != null) {
-                mSearchDropTargetBar.showSearchBar(animated && wasInSpringLoadedMode);
-            }
+            mStateTransitionAnimation.startAnimationToWorkspace(mState, mWorkspace.getState(),
+                    Workspace.State.NORMAL, snapToPage, animated, onCompleteRunnable);
 
             // Set focus to the AppsCustomize button
             if (mAllAppsButton != null) {
@@ -3323,7 +3302,8 @@
 
     void showOverviewMode(boolean animated) {
         mWorkspace.setVisibility(View.VISIBLE);
-        mStateTransitionAnimation.startAnimationToWorkspace(mState, Workspace.State.OVERVIEW,
+        mStateTransitionAnimation.startAnimationToWorkspace(mState, mWorkspace.getState(),
+                Workspace.State.OVERVIEW,
                 WorkspaceStateTransitionAnimation.SCROLL_TO_CURRENT_PAGE, animated,
                 null /* onCompleteRunnable */);
         mState = State.WORKSPACE;
@@ -3378,9 +3358,10 @@
         }
 
         if (toState == State.APPS) {
-            mStateTransitionAnimation.startAnimationToAllApps(animated, focusSearchBar);
+            mStateTransitionAnimation.startAnimationToAllApps(mWorkspace.getState(), animated,
+                    focusSearchBar);
         } else {
-            mStateTransitionAnimation.startAnimationToWidgets(animated);
+            mStateTransitionAnimation.startAnimationToWidgets(mWorkspace.getState(), animated);
         }
 
         // Change the state *after* we've called all the transition code
@@ -3402,10 +3383,9 @@
      * new state.
      */
     public Animator startWorkspaceStateChangeAnimation(Workspace.State toState, int toPage,
-            boolean animated, boolean hasOverlaySearchBar, HashMap<View, Integer> layerViews) {
+            boolean animated, HashMap<View, Integer> layerViews) {
         Workspace.State fromState = mWorkspace.getState();
-        Animator anim = mWorkspace.setStateWithAnimation(toState, toPage, animated,
-                hasOverlaySearchBar, layerViews);
+        Animator anim = mWorkspace.setStateWithAnimation(toState, toPage, animated, layerViews);
         updateInteraction(fromState, toState);
         return anim;
     }
@@ -3417,7 +3397,8 @@
             return;
         }
 
-        mStateTransitionAnimation.startAnimationToWorkspace(mState, Workspace.State.SPRING_LOADED,
+        mStateTransitionAnimation.startAnimationToWorkspace(mState, mWorkspace.getState(),
+                Workspace.State.SPRING_LOADED,
                 WorkspaceStateTransitionAnimation.SCROLL_TO_CURRENT_PAGE, true /* animated */,
                 null /* onCompleteRunnable */);
         mState = isAppsViewVisible() ? State.APPS_SPRING_LOADED : State.WIDGETS_SPRING_LOADED;
@@ -3526,6 +3507,7 @@
             mAppWidgetHost.setQsbWidgetId(widgetId);
             if (widgetId != -1) {
                 mQsb = mAppWidgetHost.createView(this, widgetId, searchProvider);
+                mQsb.setId(R.id.qsb_widget);
                 mQsb.updateAppWidgetOptions(opts);
                 mQsb.setPadding(0, 0, 0, 0);
                 mSearchDropTargetBar.addView(mQsb);
@@ -3782,11 +3764,12 @@
                 continue;
             }
 
+            final View view;
             switch (item.itemType) {
                 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
                 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
                     ShortcutInfo info = (ShortcutInfo) item;
-                    View shortcut = createShortcut(info);
+                    view = createShortcut(info);
 
                     /*
                      * TODO: FIX collision case
@@ -3805,28 +3788,26 @@
                             }
                         }
                     }
-
-                    workspace.addInScreenFromBind(shortcut, item.container, item.screenId, item.cellX,
-                            item.cellY, 1, 1);
-                    if (animateIcons) {
-                        // Animate all the applications up now
-                        shortcut.setAlpha(0f);
-                        shortcut.setScaleX(0f);
-                        shortcut.setScaleY(0f);
-                        bounceAnims.add(createNewAppBounceAnimation(shortcut, i));
-                        newShortcutsScreenId = item.screenId;
-                    }
                     break;
                 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
-                    FolderIcon newFolder = FolderIcon.fromXml(R.layout.folder_icon, this,
+                    view = FolderIcon.fromXml(R.layout.folder_icon, this,
                             (ViewGroup) workspace.getChildAt(workspace.getCurrentPage()),
                             (FolderInfo) item, mIconCache);
-                    workspace.addInScreenFromBind(newFolder, item.container, item.screenId, item.cellX,
-                            item.cellY, 1, 1);
                     break;
                 default:
                     throw new RuntimeException("Invalid Item Type");
             }
+
+            workspace.addInScreenFromBind(view, item.container, item.screenId, item.cellX,
+                    item.cellY, 1, 1);
+            if (animateIcons) {
+                // Animate all the applications up now
+                view.setAlpha(0f);
+                view.setScaleX(0f);
+                view.setScaleY(0f);
+                bounceAnims.add(createNewAppBounceAnimation(view, i));
+                newShortcutsScreenId = item.screenId;
+            }
         }
 
         if (animateIcons) {
@@ -3901,7 +3882,8 @@
 
         if (!mIsSafeModeEnabled
                 && ((item.restoreStatus & LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY) == 0)
-                && ((item.restoreStatus & LauncherAppWidgetInfo.FLAG_ID_NOT_VALID) != 0)) {
+                && (item.restoreStatus != LauncherAppWidgetInfo.RESTORE_COMPLETED)) {
+
             if (appWidgetInfo == null) {
                 if (DEBUG_WIDGETS) {
                     Log.d(TAG, "Removing restored widget: id=" + item.appWidgetId
@@ -3911,42 +3893,51 @@
                 LauncherModel.deleteItemFromDatabase(this, item);
                 return;
             }
-            // Note: This assumes that the id remap broadcast is received before this step.
-            // If that is not the case, the id remap will be ignored and user may see the
-            // click to setup view.
-            PendingAddWidgetInfo pendingInfo = new PendingAddWidgetInfo(this, appWidgetInfo, null);
-            pendingInfo.spanX = item.spanX;
-            pendingInfo.spanY = item.spanY;
-            pendingInfo.minSpanX = item.minSpanX;
-            pendingInfo.minSpanY = item.minSpanY;
-            Bundle options = null;
-                    WidgetHostViewLoader.getDefaultOptionsForWidget(this, pendingInfo);
 
-            int newWidgetId = mAppWidgetHost.allocateAppWidgetId();
-            boolean success = mAppWidgetManager.bindAppWidgetIdIfAllowed(
-                    newWidgetId, appWidgetInfo, options);
+            // If we do not have a valid id, try to bind an id.
+            if ((item.restoreStatus & LauncherAppWidgetInfo.FLAG_ID_NOT_VALID) != 0) {
+                // Note: This assumes that the id remap broadcast is received before this step.
+                // If that is not the case, the id remap will be ignored and user may see the
+                // click to setup view.
+                PendingAddWidgetInfo pendingInfo = new PendingAddWidgetInfo(this, appWidgetInfo, null);
+                pendingInfo.spanX = item.spanX;
+                pendingInfo.spanY = item.spanY;
+                pendingInfo.minSpanX = item.minSpanX;
+                pendingInfo.minSpanY = item.minSpanY;
+                Bundle options = null;
+                        WidgetHostViewLoader.getDefaultOptionsForWidget(this, pendingInfo);
 
-            // TODO consider showing a permission dialog when the widget is clicked.
-            if (!success) {
-                mAppWidgetHost.deleteAppWidgetId(newWidgetId);
-                if (DEBUG_WIDGETS) {
-                    Log.d(TAG, "Removing restored widget: id=" + item.appWidgetId
-                            + " belongs to component " + item.providerName
-                            + ", as the launcher is unable to bing a new widget id");
+                int newWidgetId = mAppWidgetHost.allocateAppWidgetId();
+                boolean success = mAppWidgetManager.bindAppWidgetIdIfAllowed(
+                        newWidgetId, appWidgetInfo, options);
+
+                // TODO consider showing a permission dialog when the widget is clicked.
+                if (!success) {
+                    mAppWidgetHost.deleteAppWidgetId(newWidgetId);
+                    if (DEBUG_WIDGETS) {
+                        Log.d(TAG, "Removing restored widget: id=" + item.appWidgetId
+                                + " belongs to component " + item.providerName
+                                + ", as the launcher is unable to bing a new widget id");
+                    }
+                    LauncherModel.deleteItemFromDatabase(this, item);
+                    return;
                 }
-                LauncherModel.deleteItemFromDatabase(this, item);
-                return;
+
+                item.appWidgetId = newWidgetId;
+
+                // If the widget has a configure activity, it is still needs to set it up, otherwise
+                // the widget is ready to go.
+                item.restoreStatus = (appWidgetInfo.configure == null)
+                        ? LauncherAppWidgetInfo.RESTORE_COMPLETED
+                        : LauncherAppWidgetInfo.FLAG_UI_NOT_READY;
+
+                LauncherModel.updateItemInDatabase(this, item);
+            } else if (((item.restoreStatus & LauncherAppWidgetInfo.FLAG_UI_NOT_READY) != 0)
+                    && (appWidgetInfo.configure == null)) {
+                // If the ID is already valid, verify if we need to configure or not.
+                item.restoreStatus = LauncherAppWidgetInfo.RESTORE_COMPLETED;
+                LauncherModel.updateItemInDatabase(this, item);
             }
-
-            item.appWidgetId = newWidgetId;
-
-            // If the widget has a configure activity, it is still needs to set it up, otherwise
-            // the widget is ready to go.
-            item.restoreStatus = (appWidgetInfo.configure == null)
-                    ? LauncherAppWidgetInfo.RESTORE_COMPLETED
-                    : LauncherAppWidgetInfo.FLAG_UI_NOT_READY;
-
-            LauncherModel.updateItemInDatabase(this, item);
         }
 
         if (!mIsSafeModeEnabled && item.restoreStatus == LauncherAppWidgetInfo.RESTORE_COMPLETED) {
@@ -3957,6 +3948,8 @@
             }
 
             item.hostView = mAppWidgetHost.createView(this, appWidgetId, appWidgetInfo);
+            item.minSpanX = appWidgetInfo.minSpanX;
+            item.minSpanY = appWidgetInfo.minSpanY;
         } else {
             appWidgetInfo = null;
             PendingAppWidgetHostView view = new PendingAppWidgetHostView(this, item,
@@ -4079,7 +4072,8 @@
 
     private boolean canRunNewAppsAnimation() {
         long diff = System.currentTimeMillis() - mDragController.getLastGestureUpTime();
-        return diff > (NEW_APPS_ANIMATION_INACTIVE_TIMEOUT_SECONDS * 1000);
+        return diff > (NEW_APPS_ANIMATION_INACTIVE_TIMEOUT_SECONDS * 1000)
+                && (mClings == null || !mClings.isVisible());
     }
 
     private ValueAnimator createNewAppBounceAnimation(View v, int i) {
@@ -4101,7 +4095,7 @@
         return mDeviceProfile.getSearchBarBounds(Utilities.isRtl(getResources()));
     }
 
-    public void bindSearchablesChanged() {
+    public void bindSearchProviderChanged() {
         if (mSearchDropTargetBar == null) {
             return;
         }
@@ -4324,13 +4318,14 @@
         return oriMap[(d.getRotation() + indexOffset) % 4];
     }
 
+    @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
     public void lockScreenOrientation() {
         if (mRotationEnabled) {
-            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) {
+            if (Utilities.ATLEAST_JB_MR2) {
+                setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LOCKED);
+            } else {
                 setRequestedOrientation(mapConfigurationOriActivityInfoOri(getResources()
                         .getConfiguration().orientation));
-            } else {
-                setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LOCKED);
             }
         }
     }
@@ -4505,6 +4500,7 @@
         // launcher2). Otherwise, we prompt the user upon started for migration
         LauncherClings launcherClings = new LauncherClings(this);
         if (launcherClings.shouldShowFirstRunOrMigrationClings()) {
+            mClings = launcherClings;
             if (mModel.canMigrateFromOldLauncherDb(this)) {
                 launcherClings.showMigrationCling();
             } else {
@@ -4517,14 +4513,16 @@
         if (mWorkspace != null) mWorkspace.setAlpha(1f);
         if (mHotseat != null) mHotseat.setAlpha(1f);
         if (mPageIndicators != null) mPageIndicators.setAlpha(1f);
-        if (mSearchDropTargetBar != null) mSearchDropTargetBar.showSearchBar(false);
+        if (mSearchDropTargetBar != null) mSearchDropTargetBar.animateToState(
+                SearchDropTargetBar.State.SEARCH_BAR, 0);
     }
 
     void hideWorkspaceSearchAndHotseat() {
         if (mWorkspace != null) mWorkspace.setAlpha(0f);
         if (mHotseat != null) mHotseat.setAlpha(0f);
         if (mPageIndicators != null) mPageIndicators.setAlpha(0f);
-        if (mSearchDropTargetBar != null) mSearchDropTargetBar.hideSearchBar(false);
+        if (mSearchDropTargetBar != null) mSearchDropTargetBar.animateToState(
+                SearchDropTargetBar.State.INVISIBLE, 0);
     }
 
     // TODO: These method should be a part of LauncherSearchCallback
@@ -4532,7 +4530,7 @@
     public ItemInfo createAppDragInfo(Intent appLaunchIntent) {
         // Called from search suggestion
         UserHandleCompat user = null;
-        if (Utilities.isLmpOrAbove()) {
+        if (Utilities.ATLEAST_LOLLIPOP) {
             UserHandle userHandle = appLaunchIntent.getParcelableExtra(Intent.EXTRA_USER);
             if (userHandle != null) {
                 user = UserHandleCompat.fromUser(userHandle);
diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java
index 0b7b1fd..d87ad67 100644
--- a/src/com/android/launcher3/LauncherAppState.java
+++ b/src/com/android/launcher3/LauncherAppState.java
@@ -17,15 +17,16 @@
 package com.android.launcher3;
 
 import android.app.SearchManager;
-import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.os.UserManager;
 import android.util.Log;
 
 import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
 import com.android.launcher3.compat.LauncherAppsCompat;
 import com.android.launcher3.compat.PackageInstallerCompat;
+import com.android.launcher3.compat.UserManagerCompat;
 import com.android.launcher3.util.Thunk;
 
 import java.lang.ref.WeakReference;
@@ -96,12 +97,12 @@
         IntentFilter filter = new IntentFilter();
         filter.addAction(Intent.ACTION_LOCALE_CHANGED);
         filter.addAction(SearchManager.INTENT_GLOBAL_SEARCH_ACTIVITY_CHANGED);
-        filter.addAction(SearchManager.INTENT_ACTION_SEARCHABLES_CHANGED);
         // For handling managed profiles
         filter.addAction(LauncherAppsCompat.ACTION_MANAGED_PROFILE_ADDED);
         filter.addAction(LauncherAppsCompat.ACTION_MANAGED_PROFILE_REMOVED);
 
         sContext.registerReceiver(mModel, filter);
+        UserManagerCompat.getInstance(sContext).enableAndResetCache();
     }
 
     /**
@@ -126,7 +127,7 @@
     LauncherModel setLauncher(Launcher launcher) {
         getLauncherProvider().setLauncherProviderChangeListener(launcher);
         mModel.initialize(launcher);
-        mAccessibilityDelegate = ((launcher != null) && Utilities.isLmpOrAbove()) ?
+        mAccessibilityDelegate = ((launcher != null) && Utilities.ATLEAST_LOLLIPOP) ?
             new LauncherAccessibilityDelegate(launcher) : null;
         return mModel;
     }
@@ -147,7 +148,7 @@
         sLauncherProvider = new WeakReference<LauncherProvider>(provider);
     }
 
-    static LauncherProvider getLauncherProvider() {
+    public static LauncherProvider getLauncherProvider() {
         return sLauncherProvider.get();
     }
 
diff --git a/src/com/android/launcher3/LauncherAppWidgetHost.java b/src/com/android/launcher3/LauncherAppWidgetHost.java
index de7c610..6c3a1e8 100644
--- a/src/com/android/launcher3/LauncherAppWidgetHost.java
+++ b/src/com/android/launcher3/LauncherAppWidgetHost.java
@@ -95,8 +95,6 @@
     }
 
     protected void onProvidersChanged() {
-        mLauncher.getModel().loadAndBindWidgetsAndShortcuts(mLauncher, mLauncher,
-                true /* refresh */);
         if (!mProviderChangeListeners.isEmpty()) {
             for (Runnable callback : new ArrayList<>(mProviderChangeListeners)) {
                 callback.run();
diff --git a/src/com/android/launcher3/LauncherAppWidgetInfo.java b/src/com/android/launcher3/LauncherAppWidgetInfo.java
index aad18b5..882f7e2 100644
--- a/src/com/android/launcher3/LauncherAppWidgetInfo.java
+++ b/src/com/android/launcher3/LauncherAppWidgetInfo.java
@@ -68,10 +68,6 @@
 
     ComponentName providerName;
 
-    // TODO: Are these necessary here?
-    int minWidth = -1;
-    int minHeight = -1;
-
     /**
      * Indicates the restore status of the widget.
      */
diff --git a/src/com/android/launcher3/LauncherAppWidgetProviderInfo.java b/src/com/android/launcher3/LauncherAppWidgetProviderInfo.java
index 85af92f..71a08a8 100644
--- a/src/com/android/launcher3/LauncherAppWidgetProviderInfo.java
+++ b/src/com/android/launcher3/LauncherAppWidgetProviderInfo.java
@@ -1,10 +1,13 @@
 package com.android.launcher3;
 
 import android.annotation.TargetApi;
+import android.appwidget.AppWidgetHostView;
 import android.appwidget.AppWidgetProviderInfo;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.PackageManager;
+import android.graphics.Point;
+import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.os.Build;
 import android.os.Parcel;
@@ -19,10 +22,10 @@
 
     public boolean isCustomWidget = false;
 
-    private int mSpanX = -1;
-    private int mSpanY = -1;
-    private int mMinSpanX = -1;
-    private int mMinSpanY = -1;
+    public int spanX;
+    public int spanY;
+    public int minSpanX;
+    public int minSpanY;
 
     public static LauncherAppWidgetProviderInfo fromProviderInfo(Context context,
             AppWidgetProviderInfo info) {
@@ -41,6 +44,7 @@
 
     public LauncherAppWidgetProviderInfo(Parcel in) {
         super(in);
+        initSpans();
     }
 
     public LauncherAppWidgetProviderInfo(Context context, CustomAppWidget widget) {
@@ -52,6 +56,41 @@
         previewImage = widget.getPreviewImage();
         initialLayout = widget.getWidgetLayout();
         resizeMode = widget.getResizeMode();
+        initSpans();
+    }
+
+    private void initSpans() {
+        LauncherAppState app = LauncherAppState.getInstance();
+        InvariantDeviceProfile idp = app.getInvariantDeviceProfile();
+
+        // We only care out the cell size, which is independent of the the layout direction.
+        Rect paddingLand = idp.landscapeProfile.getWorkspacePadding(false /* isLayoutRtl */);
+        Rect paddingPort = idp.portraitProfile.getWorkspacePadding(false /* isLayoutRtl */);
+
+        // Always assume we're working with the smallest span to make sure we
+        // reserve enough space in both orientations.
+        float smallestCellWidth = DeviceProfile.calculateCellWidth(Math.min(
+                idp.landscapeProfile.widthPx - paddingLand.left - paddingLand.right,
+                idp.portraitProfile.widthPx - paddingPort.left - paddingPort.right),
+                idp.numColumns);
+        float smallestCellHeight = DeviceProfile.calculateCellWidth(Math.min(
+                idp.landscapeProfile.heightPx - paddingLand.top - paddingLand.bottom,
+                idp.portraitProfile.heightPx - paddingPort.top - paddingPort.bottom),
+                idp.numRows);
+
+        // We want to account for the extra amount of padding that we are adding to the widget
+        // to ensure that it gets the full amount of space that it has requested.
+        Rect widgetPadding = AppWidgetHostView.getDefaultPaddingForWidget(
+                app.getContext(), provider, null);
+        spanX = Math.max(1, (int) Math.ceil(
+                        (minWidth + widgetPadding.left + widgetPadding.right) / smallestCellWidth));
+        spanY = Math.max(1, (int) Math.ceil(
+                (minHeight + widgetPadding.top + widgetPadding.bottom) / smallestCellHeight));
+
+        minSpanX = Math.max(1, (int) Math.ceil(
+                (minResizeWidth + widgetPadding.left + widgetPadding.right) / smallestCellWidth));
+        minSpanY = Math.max(1, (int) Math.ceil(
+                (minResizeHeight + widgetPadding.top + widgetPadding.bottom) / smallestCellHeight));
     }
 
     @TargetApi(Build.VERSION_CODES.LOLLIPOP)
@@ -79,35 +118,9 @@
                 provider.toString(), provider.getPackageName(), provider.getShortClassName(), getLabel(pm));
     }
 
-    public int getSpanX(Launcher launcher) {
-        lazyLoadSpans(launcher);
-        return mSpanX;
-    }
-
-    public int getSpanY(Launcher launcher) {
-        lazyLoadSpans(launcher);
-        return mSpanY;
-    }
-
-    public int getMinSpanX(Launcher launcher) {
-        lazyLoadSpans(launcher);
-        return mMinSpanX;
-    }
-
-    public int getMinSpanY(Launcher launcher) {
-        lazyLoadSpans(launcher);
-        return mMinSpanY;
-    }
-
-    private void lazyLoadSpans(Launcher launcher) {
-        if (mSpanX < 0 || mSpanY < 0 || mMinSpanX < 0 || mMinSpanY < 0) {
-            int[] minResizeSpan = launcher.getMinSpanForWidget(this);
-            int[] span = launcher.getSpanForWidget(this);
-
-            mSpanX = span[0];
-            mSpanY = span[1];
-            mMinSpanX = minResizeSpan[0];
-            mMinSpanY = minResizeSpan[1];
-        }
+    public Point getMinSpans(InvariantDeviceProfile idp, Context context) {
+        return new Point(
+                (resizeMode & RESIZE_HORIZONTAL) != 0 ? minSpanX : -1,
+                        (resizeMode & RESIZE_VERTICAL) != 0 ? minSpanY : -1);
     }
  }
diff --git a/src/com/android/launcher3/LauncherBackupAgentHelper.java b/src/com/android/launcher3/LauncherBackupAgentHelper.java
index a92a889..8eb4e63 100644
--- a/src/com/android/launcher3/LauncherBackupAgentHelper.java
+++ b/src/com/android/launcher3/LauncherBackupAgentHelper.java
@@ -24,6 +24,8 @@
 import android.os.ParcelFileDescriptor;
 import android.util.Log;
 
+import com.android.launcher3.model.MigrateFromRestoreTask;
+
 import java.io.IOException;
 
 public class LauncherBackupAgentHelper extends BackupAgentHelper {
@@ -63,7 +65,7 @@
     @Override
     public void onRestore(BackupDataInput data, int appVersionCode, ParcelFileDescriptor newState)
             throws IOException {
-        if (!Utilities.isLmpOrAbove()) {
+        if (!Utilities.ATLEAST_LOLLIPOP) {
             // No restore for old devices.
             Log.i(TAG, "You shall not pass!!!");
             Log.d(TAG, "Restore is only supported on devices running Lollipop and above.");
@@ -91,11 +93,19 @@
             LauncherAppState.getLauncherProvider().clearFlagEmptyDbCreated();
             LauncherClings.synchonouslyMarkFirstRunClingDismissed(this);
 
-            // TODO: Update the backup set to include rank.
+            // Rank was added in v4.
             if (mHelper.restoredBackupVersion <= 3) {
                 LauncherAppState.getLauncherProvider().updateFolderItemsRank();
-                LauncherAppState.getLauncherProvider().convertShortcutsToLauncherActivities();
             }
+
+            if (MigrateFromRestoreTask.ENABLED && mHelper.shouldAttemptWorkspaceMigration()) {
+                MigrateFromRestoreTask.markForMigration(getApplicationContext(),
+                        (int) mHelper.migrationCompatibleProfileData.desktopCols,
+                        (int) mHelper.migrationCompatibleProfileData.desktopRows,
+                        mHelper.widgetSizes);
+            }
+
+            LauncherAppState.getLauncherProvider().convertShortcutsToLauncherActivities();
         } else {
             if (VERBOSE) Log.v(TAG, "Nothing was restored, clearing DB");
             LauncherAppState.getLauncherProvider().createEmptyDB();
diff --git a/src/com/android/launcher3/LauncherBackupHelper.java b/src/com/android/launcher3/LauncherBackupHelper.java
index 8c6fedb..2d11d3a 100644
--- a/src/com/android/launcher3/LauncherBackupHelper.java
+++ b/src/com/android/launcher3/LauncherBackupHelper.java
@@ -32,6 +32,7 @@
 import android.database.Cursor;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
+import android.graphics.Point;
 import android.graphics.drawable.Drawable;
 import android.os.ParcelFileDescriptor;
 import android.text.TextUtils;
@@ -51,6 +52,7 @@
 import com.android.launcher3.backup.BackupProtos.Widget;
 import com.android.launcher3.compat.UserHandleCompat;
 import com.android.launcher3.compat.UserManagerCompat;
+import com.android.launcher3.model.MigrateFromRestoreTask;
 import com.android.launcher3.util.Thunk;
 import com.google.protobuf.nano.InvalidProtocolBufferNanoException;
 import com.google.protobuf.nano.MessageNano;
@@ -75,7 +77,7 @@
     private static final boolean VERBOSE = LauncherBackupAgentHelper.VERBOSE;
     private static final boolean DEBUG = LauncherBackupAgentHelper.DEBUG;
 
-    private static final int BACKUP_VERSION = 3;
+    private static final int BACKUP_VERSION = 4;
     private static final int MAX_JOURNAL_SIZE = 1000000;
 
     // Journal key is such that it is always smaller than any dynamically generated
@@ -107,6 +109,7 @@
         Favorites.SPANY,                   // 15
         Favorites.TITLE,                   // 16
         Favorites.PROFILE_ID,              // 17
+        Favorites.RANK,                    // 18
     };
 
     private static final int ID_INDEX = 0;
@@ -126,6 +129,7 @@
     private static final int SPANX_INDEX = 14;
     private static final int SPANY_INDEX = 15;
     private static final int TITLE_INDEX = 16;
+    private static final int RANK_INDEX = 18;
 
     private static final String[] SCREEN_PROJECTION = {
         WorkspaceScreens._ID,              // 0
@@ -150,9 +154,16 @@
     private DeviceProfieData mDeviceProfileData;
     private InvariantDeviceProfile mIdp;
 
+    DeviceProfieData migrationCompatibleProfileData;
+    HashSet<String> widgetSizes = new HashSet<>();
+
     boolean restoreSuccessful;
     int restoredBackupVersion = 1;
 
+    // When migrating from a device which different hotseat configuration, the icons are shifted
+    // to center along the new all-apps icon.
+    private int mHotseatShift = 0;
+
     public LauncherBackupHelper(Context context) {
         mContext = context;
         mExistingKeys = new HashSet<String>();
@@ -282,17 +293,39 @@
             return true;
         }
 
-        boolean isHotsetCompatible = false;
+        boolean isHotseatCompatible = false;
         if (currentProfile.allappsRank >= oldProfile.hotseatCount) {
-            isHotsetCompatible = true;
-        }
-        if ((currentProfile.hotseatCount >= oldProfile.hotseatCount) &&
-                (currentProfile.allappsRank == oldProfile.allappsRank)) {
-            isHotsetCompatible = true;
+            isHotseatCompatible = true;
+            mHotseatShift = 0;
         }
 
-        return isHotsetCompatible && (currentProfile.desktopCols >= oldProfile.desktopCols)
-                && (currentProfile.desktopRows >= oldProfile.desktopRows);
+        if ((currentProfile.allappsRank >= oldProfile.allappsRank)
+                && ((currentProfile.hotseatCount - currentProfile.allappsRank) >=
+                        (oldProfile.hotseatCount - oldProfile.allappsRank))) {
+            // There is enough space on both sides of the hotseat.
+            isHotseatCompatible = true;
+            mHotseatShift = currentProfile.allappsRank - oldProfile.allappsRank;
+        }
+
+        if (!isHotseatCompatible) {
+            return false;
+        }
+        if ((currentProfile.desktopCols >= oldProfile.desktopCols)
+                && (currentProfile.desktopRows >= oldProfile.desktopRows)) {
+            return true;
+        }
+
+        if (MigrateFromRestoreTask.ENABLED &&
+                (oldProfile.desktopCols - currentProfile.desktopCols <= 1) &&
+                (oldProfile.desktopRows - currentProfile.desktopRows <= 1)) {
+            // Allow desktop migration when row and/or column count contracts by 1.
+
+            migrationCompatibleProfileData = initDeviceProfileData(mIdp);
+            migrationCompatibleProfileData.desktopCols = oldProfile.desktopCols;
+            migrationCompatibleProfileData.desktopRows = oldProfile.desktopRows;
+            return true;
+        }
+        return false;
     }
 
     /**
@@ -432,7 +465,10 @@
                 Key key = getKey(Key.FAVORITE, id);
                 mKeys.add(key);
                 final String backupKey = keyToBackupKey(key);
-                if (!mExistingKeys.contains(backupKey) || updateTime >= mLastBackupRestoreTime) {
+
+                // Favorite proto changed in v4. Backup again if the version is old.
+                if (!mExistingKeys.contains(backupKey) || updateTime >= mLastBackupRestoreTime
+                        || restoredBackupVersion < 4) {
                     writeRowToBackup(key, packFavorite(cursor), data);
                 } else {
                     if (DEBUG) Log.d(TAG, "favorite already backup up: " + id);
@@ -601,10 +637,11 @@
         Bitmap icon = BitmapFactory.decodeByteArray(res.data, 0, res.data.length);
         if (icon == null) {
             Log.w(TAG, "failed to unpack icon for " + key.name);
+        } else {
+            if (VERBOSE) Log.v(TAG, "saving restored icon as: " + key.name);
+            mIconCache.preloadIcon(ComponentName.unflattenFromString(key.name), icon, res.dpi,
+                    "" /* label */, mUserSerial, mIdp);
         }
-        if (VERBOSE) Log.v(TAG, "saving restored icon as: " + key.name);
-        mIconCache.preloadIcon(ComponentName.unflattenFromString(key.name), icon, res.dpi,
-                "" /* label */, mUserSerial);
     }
 
     /**
@@ -638,7 +675,9 @@
                 } else {
                     Log.w(TAG, "empty intent on appwidget: " + id);
                 }
-                if (mExistingKeys.contains(backupKey) && restoredBackupVersion >= BACKUP_VERSION) {
+
+                // Widget backup proto changed in v3. So add it again if the original backup is old.
+                if (mExistingKeys.contains(backupKey) && restoredBackupVersion >= 3) {
                     if (DEBUG) Log.d(TAG, "already saved widget " + backupKey);
 
                     // remember that we already backed this up previously
@@ -685,11 +724,12 @@
                 Log.w(TAG, "failed to unpack widget icon for " + key.name);
             } else {
                 mIconCache.preloadIcon(ComponentName.unflattenFromString(widget.provider),
-                        icon, widget.icon.dpi, widget.label, mUserSerial);
+                        icon, widget.icon.dpi, widget.label, mUserSerial, mIdp);
             }
         }
 
-        // future site of widget table mutation
+        // Cache widget min sizes incase migration is required.
+        widgetSizes.add(widget.provider + "#" + widget.minSpanX + "," + widget.minSpanY);
     }
 
     /** create a new key, with an integer ID.
@@ -773,6 +813,7 @@
         favorite.spanX = c.getInt(SPANX_INDEX);
         favorite.spanY = c.getInt(SPANY_INDEX);
         favorite.iconType = c.getInt(ICON_TYPE_INDEX);
+        favorite.rank = c.getInt(RANK_INDEX);
 
         String title = c.getString(TITLE_INDEX);
         if (!TextUtils.isEmpty(title)) {
@@ -847,6 +888,11 @@
             throws IOException {
         Favorite favorite = unpackProto(new Favorite(), buffer, dataSize);
 
+        // If it is a hotseat item, move it accordingly.
+        if (favorite.container == Favorites.CONTAINER_HOTSEAT) {
+            favorite.screen += mHotseatShift;
+        }
+
         ContentValues values = new ContentValues();
         values.put(Favorites._ID, favorite.id);
         values.put(Favorites.SCREEN, favorite.screen);
@@ -855,6 +901,7 @@
         values.put(Favorites.CELLY, favorite.cellY);
         values.put(Favorites.SPANX, favorite.spanX);
         values.put(Favorites.SPANY, favorite.spanY);
+        values.put(Favorites.RANK, favorite.rank);
 
         if (favorite.itemType == Favorites.ITEM_TYPE_SHORTCUT) {
             values.put(Favorites.ICON_TYPE, favorite.iconType);
@@ -880,7 +927,11 @@
                 UserManagerCompat.getInstance(mContext).getSerialNumberForUser(myUserHandle);
         values.put(LauncherSettings.Favorites.PROFILE_ID, userSerialNumber);
 
-        DeviceProfieData currentProfile = mDeviceProfileData;
+        // If we will attempt grid resize, use the original profile to validate grid size, as
+        // anything which fits in the original grid should fit in the current grid after
+        // grid migration.
+        DeviceProfieData currentProfile = migrationCompatibleProfileData == null
+                ? mDeviceProfileData : migrationCompatibleProfileData;
 
         if (favorite.itemType == Favorites.ITEM_TYPE_APPWIDGET) {
             if (!TextUtils.isEmpty(favorite.appWidgetProvider)) {
@@ -972,14 +1023,9 @@
             widget.icon.dpi = dpi;
         }
 
-        // Calculate the spans corresponding to any one of the orientations as it should not change
-        // based on orientation.
-        int[] minSpans = CellLayout.rectToCell(
-                mIdp.portraitProfile, mContext, info.minResizeWidth, info.minResizeHeight, null);
-        widget.minSpanX = (info.resizeMode & LauncherAppWidgetProviderInfo.RESIZE_HORIZONTAL) != 0
-                ? minSpans[0] : -1;
-        widget.minSpanY = (info.resizeMode & LauncherAppWidgetProviderInfo.RESIZE_VERTICAL) != 0
-                ? minSpans[1] : -1;
+        Point spans = info.getMinSpans(mIdp, mContext);
+        widget.minSpanX = spans.x;
+        widget.minSpanY = spans.y;
 
         return widget;
     }
@@ -1164,6 +1210,10 @@
         }
     }
 
+    public boolean shouldAttemptWorkspaceMigration() {
+        return migrationCompatibleProfileData != null;
+    }
+
     /**
      * A class to check if an activity can handle one of the intents from a list of
      * predefined intents.
diff --git a/src/com/android/launcher3/LauncherCallbacks.java b/src/com/android/launcher3/LauncherCallbacks.java
index 6618cca..e34bd57 100644
--- a/src/com/android/launcher3/LauncherCallbacks.java
+++ b/src/com/android/launcher3/LauncherCallbacks.java
@@ -77,6 +77,7 @@
     public boolean providesSearch();
     public boolean startSearch(String initialQuery, boolean selectInitialQuery,
             Bundle appSearchData, Rect sourceBounds);
+    public boolean startSearchFromAllApps(String query);
     @Deprecated
     public void startVoice();
     public boolean hasCustomContentToLeft();
diff --git a/src/com/android/launcher3/LauncherClings.java b/src/com/android/launcher3/LauncherClings.java
index c13752c..18fe8ef 100644
--- a/src/com/android/launcher3/LauncherClings.java
+++ b/src/com/android/launcher3/LauncherClings.java
@@ -16,8 +16,6 @@
 
 package com.android.launcher3;
 
-import android.accounts.Account;
-import android.accounts.AccountManager;
 import android.animation.ObjectAnimator;
 import android.animation.PropertyValuesHolder;
 import android.annotation.TargetApi;
@@ -36,6 +34,7 @@
 import android.view.ViewGroup;
 import android.view.ViewTreeObserver.OnGlobalLayoutListener;
 import android.view.accessibility.AccessibilityManager;
+
 import com.android.launcher3.util.Thunk;
 
 class LauncherClings implements OnClickListener {
@@ -44,8 +43,6 @@
 
     private static final String TAG_CROP_TOP_AND_SIDES = "crop_bg_top_and_sides";
 
-    private static final boolean DISABLE_CLINGS = false;
-
     private static final int SHOW_CLING_DURATION = 250;
     private static final int DISMISS_CLING_DURATION = 200;
 
@@ -54,6 +51,7 @@
 
     @Thunk Launcher mLauncher;
     private LayoutInflater mInflater;
+    @Thunk boolean mIsVisible;
 
     /** Ctor */
     public LauncherClings(Launcher launcher) {
@@ -94,6 +92,7 @@
      * package was not preinstalled and there exists a db to migrate from.
      */
     public void showMigrationCling() {
+        mIsVisible = true;
         mLauncher.hideWorkspaceSearchAndHotseat();
 
         ViewGroup root = (ViewGroup) mLauncher.findViewById(R.id.launcher);
@@ -120,6 +119,7 @@
     }
 
     public void showLongPressCling(boolean showWelcome) {
+        mIsVisible = true;
         ViewGroup root = (ViewGroup) mLauncher.findViewById(R.id.launcher);
         View cling = mInflater.inflate(R.layout.longpress_cling, root, false);
 
@@ -199,6 +199,7 @@
                     mLauncher.getSharedPrefs().edit()
                         .putBoolean(flag, true)
                         .apply();
+                    mIsVisible = false;
                     if (postAnimationCb != null) {
                         postAnimationCb.run();
                     }
@@ -212,13 +213,13 @@
         }
     }
 
+    public boolean isVisible() {
+        return mIsVisible;
+    }
+
     /** Returns whether the clings are enabled or should be shown */
     @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
     private boolean areClingsEnabled() {
-        if (DISABLE_CLINGS) {
-            return false;
-        }
-
         // disable clings when running in a test harness
         if(ActivityManager.isRunningInTestHarness()) return false;
 
@@ -231,10 +232,7 @@
 
         // Restricted secondary users (child mode) will potentially have very few apps
         // seeded when they start up for the first time. Clings won't work well with that
-        boolean supportsLimitedUsers =
-                android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR2;
-        Account[] accounts = AccountManager.get(mLauncher).getAccounts();
-        if (supportsLimitedUsers && accounts.length == 0) {
+        if (Utilities.ATLEAST_JB_MR2) {
             UserManager um = (UserManager) mLauncher.getSystemService(Context.USER_SERVICE);
             Bundle restrictions = um.getUserRestrictions();
             if (restrictions.getBoolean(UserManager.DISALLOW_MODIFY_ACCOUNTS, false)) {
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index b60477f..b5922c6 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -17,6 +17,7 @@
 package com.android.launcher3;
 
 import android.app.SearchManager;
+import android.appwidget.AppWidgetManager;
 import android.appwidget.AppWidgetProviderInfo;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
@@ -33,7 +34,6 @@
 import android.database.Cursor;
 import android.graphics.Bitmap;
 import android.net.Uri;
-import android.os.Build;
 import android.os.Environment;
 import android.os.Handler;
 import android.os.HandlerThread;
@@ -55,6 +55,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.MigrateFromRestoreTask;
 import com.android.launcher3.model.WidgetsModel;
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.CursorIconInfo;
@@ -203,7 +204,7 @@
         public void bindComponentsRemoved(ArrayList<String> packageNames,
                         ArrayList<AppInfo> appInfos, UserHandleCompat user, int reason);
         public void bindAllPackages(WidgetsModel model);
-        public void bindSearchablesChanged();
+        public void bindSearchProviderChanged();
         public boolean isAllAppsButtonRank(int rank);
         public void onPageBoundSynchronously(int page);
         public void dumpLogsToLocalData();
@@ -257,7 +258,7 @@
 
     /** Runs the specified runnable immediately if called from the worker thread, otherwise it is
      * posted on the worker thread handler. */
-    private static void runOnWorkerThread(Runnable r) {
+    @Thunk static void runOnWorkerThread(Runnable r) {
         if (sWorkerThread.getThreadId() == Process.myTid()) {
             r.run();
         } else {
@@ -266,19 +267,6 @@
         }
     }
 
-    /**
-     * Runs the specified runnable after the loader is complete
-     */
-    @Thunk void runAfterBindCompletes(Runnable r) {
-        if (isLoadingWorkspace() || !mHasLoaderCompletedOnce) {
-            synchronized (mBindCompleteRunnables) {
-                mBindCompleteRunnables.add(r);
-            }
-        } else {
-            runOnWorkerThread(r);
-        }
-    }
-
     boolean canMigrateFromOldLauncherDb(Launcher launcher) {
         return mOldContentProviderExists && !launcher.isLauncherPreinstalled() ;
     }
@@ -892,8 +880,13 @@
     }
 
     private void assertWorkspaceLoaded() {
-        if (LauncherAppState.isDogfoodBuild() && (isLoadingWorkspace() || !mHasLoaderCompletedOnce)) {
-            throw new RuntimeException("Trying to add shortcut while loader is running");
+        if (LauncherAppState.isDogfoodBuild()) {
+            synchronized (mLock) {
+                if (!mHasLoaderCompletedOnce ||
+                        (mLoaderTask != null && mLoaderTask.mIsLoadingAndBindingWorkspace)) {
+                    throw new RuntimeException("Trying to add shortcut while loader is running");
+                }
+            }
         }
     }
 
@@ -1133,7 +1126,7 @@
      * Update the order of the workspace screens in the database. The array list contains
      * a list of screen ids in the order that they should appear.
      */
-    void updateWorkspaceScreenOrder(Context context, final ArrayList<Long> screens) {
+    public void updateWorkspaceScreenOrder(Context context, final ArrayList<Long> screens) {
         final ArrayList<Long> screensCopy = new ArrayList<Long>(screens);
         final ContentResolver cr = context.getContentResolver();
         final Uri uri = LauncherSettings.WorkspaceScreens.CONTENT_URI;
@@ -1280,14 +1273,14 @@
         if (Intent.ACTION_LOCALE_CHANGED.equals(action)) {
             // If we have changed locale we need to clear out the labels in all apps/workspace.
             forceReload();
-        } else if (SearchManager.INTENT_GLOBAL_SEARCH_ACTIVITY_CHANGED.equals(action) ||
-                   SearchManager.INTENT_ACTION_SEARCHABLES_CHANGED.equals(action)) {
+        } else if (SearchManager.INTENT_GLOBAL_SEARCH_ACTIVITY_CHANGED.equals(action)) {
             Callbacks callbacks = getCallback();
             if (callbacks != null) {
-                callbacks.bindSearchablesChanged();
+                callbacks.bindSearchProviderChanged();
             }
         } else if (LauncherAppsCompat.ACTION_MANAGED_PROFILE_ADDED.equals(action)
                 || LauncherAppsCompat.ACTION_MANAGED_PROFILE_REMOVED.equals(action)) {
+            UserManagerCompat.getInstance(context).enableAndResetCache();
             forceReload();
         }
     }
@@ -1388,16 +1381,6 @@
                 mHandler.post(r);
             }
         }
-
-        // Run all the bind complete runnables after workspace is bound.
-        if (!mBindCompleteRunnables.isEmpty()) {
-            synchronized (mBindCompleteRunnables) {
-                for (final Runnable r : mBindCompleteRunnables) {
-                    runOnWorkerThread(r);
-                }
-                mBindCompleteRunnables.clear();
-            }
-        }
     }
 
     public void stopLoader() {
@@ -1411,7 +1394,7 @@
     /**
      * Loads the workspace screen ids in an ordered list.
      */
-    @Thunk static ArrayList<Long> loadWorkspaceScreensDb(Context context) {
+    public static ArrayList<Long> loadWorkspaceScreensDb(Context context) {
         final ContentResolver contentResolver = context.getContentResolver();
         final Uri screensUri = LauncherSettings.WorkspaceScreens.CONTENT_URI;
 
@@ -1439,15 +1422,6 @@
         return mAllAppsLoaded;
     }
 
-    boolean isLoadingWorkspace() {
-        synchronized (mLock) {
-            if (mLoaderTask != null) {
-                return mLoaderTask.isLoadingWorkspace();
-            }
-        }
-        return false;
-    }
-
     /**
      * Runnable for the thread that loads the contents of the launcher:
      *   - workspace icons
@@ -1466,10 +1440,6 @@
             mFlags = flags;
         }
 
-        boolean isLoadingWorkspace() {
-            return mIsLoadingAndBindingWorkspace;
-        }
-
         private void loadAndBindWorkspace() {
             mIsLoadingAndBindingWorkspace = true;
 
@@ -1641,11 +1611,12 @@
         }
 
         // check & update map of what's occupied; used to discard overlapping/invalid items
-        private boolean checkItemPlacement(LongArrayMap<ItemInfo[][]> occupied, ItemInfo item) {
+        private boolean checkItemPlacement(LongArrayMap<ItemInfo[][]> occupied, ItemInfo item,
+                   ArrayList<Long> workspaceScreens) {
             LauncherAppState app = LauncherAppState.getInstance();
             InvariantDeviceProfile profile = app.getInvariantDeviceProfile();
-            final int countX = (int) profile.numColumns;
-            final int countY = (int) profile.numRows;
+            final int countX = profile.numColumns;
+            final int countY = profile.numRows;
 
             long containerIndex = item.screenId;
             if (item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
@@ -1687,7 +1658,12 @@
                     occupied.put((long) LauncherSettings.Favorites.CONTAINER_HOTSEAT, items);
                     return true;
                 }
-            } else if (item.container != LauncherSettings.Favorites.CONTAINER_DESKTOP) {
+            } else if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
+                if (!workspaceScreens.contains((Long) item.screenId)) {
+                    // The item has an invalid screen id.
+                    return false;
+                }
+            } else {
                 // Skip further checking if it is not the hotseat or workspace container
                 return true;
             }
@@ -1754,8 +1730,27 @@
 
             LauncherAppState app = LauncherAppState.getInstance();
             InvariantDeviceProfile profile = app.getInvariantDeviceProfile();
-            int countX = (int) profile.numColumns;
-            int countY = (int) profile.numRows;
+            int countX = profile.numColumns;
+            int countY = profile.numRows;
+
+            if (MigrateFromRestoreTask.ENABLED && MigrateFromRestoreTask.shouldRunTask(mContext)) {
+                long migrationStartTime = System.currentTimeMillis();
+                Log.v(TAG, "Starting workspace migration after restore");
+                try {
+                    MigrateFromRestoreTask task = new MigrateFromRestoreTask(mContext);
+                    // Clear the flags before starting the task, so that we do not run the task
+                    // again, in case there was an uncaught error.
+                    MigrateFromRestoreTask.clearFlags(mContext);
+                    task.execute();
+                } catch (Exception e) {
+                    Log.e(TAG, "Error during grid migration", e);
+
+                    // Clear workspace.
+                    mFlags = mFlags | LOADER_FLAG_CLEAR_WORKSPACE;
+                }
+                Log.v(TAG, "Workspace migration completed in "
+                        + (System.currentTimeMillis() - migrationStartTime));
+            }
 
             if ((mFlags & LOADER_FLAG_CLEAR_WORKSPACE) != 0) {
                 Launcher.addDumpLog(TAG, "loadWorkspace: resetting launcher database", true);
@@ -1776,6 +1771,7 @@
                 clearSBgDataStructures();
                 final HashMap<String, Integer> installingPkgs = PackageInstallerCompat
                         .getInstance(mContext).updateAndGetActiveSessionCache();
+                sBgWorkspaceScreens.addAll(loadWorkspaceScreensDb(mContext));
 
                 final ArrayList<Long> itemsToRemove = new ArrayList<Long>();
                 final ArrayList<Long> restoredRows = new ArrayList<Long>();
@@ -1977,6 +1973,7 @@
                                 } catch (URISyntaxException e) {
                                     Launcher.addDumpLog(TAG,
                                             "Invalid uri: " + intentDescription, true);
+                                    itemsToRemove.add(id);
                                     continue;
                                 }
 
@@ -2047,7 +2044,7 @@
                                     }
 
                                     // check & update map of what's occupied
-                                    if (!checkItemPlacement(occupied, info)) {
+                                    if (!checkItemPlacement(occupied, info, sBgWorkspaceScreens)) {
                                         itemsToRemove.add(id);
                                         break;
                                     }
@@ -2098,7 +2095,7 @@
                                 folderInfo.options = c.getInt(optionsIndex);
 
                                 // check & update map of what's occupied
-                                if (!checkItemPlacement(occupied, folderInfo)) {
+                                if (!checkItemPlacement(occupied, folderInfo, sBgWorkspaceScreens)) {
                                     itemsToRemove.add(id);
                                     break;
                                 }
@@ -2163,14 +2160,18 @@
                                         appWidgetInfo = new LauncherAppWidgetInfo(appWidgetId,
                                                 provider.provider);
 
-                                        int status = restoreStatus;
+                                        // The provider is available. So the widget is either
+                                        // available or not available. We do not need to track
+                                        // any future restore updates.
+                                        int status = restoreStatus &
+                                                ~LauncherAppWidgetInfo.FLAG_RESTORE_STARTED;
                                         if (!wasProviderReady) {
                                             // If provider was not previously ready, update the
                                             // status and UI flag.
 
                                             // Id would be valid only if the widget restore broadcast was received.
                                             if (isIdValid) {
-                                                status = LauncherAppWidgetInfo.RESTORE_COMPLETED;
+                                                status = LauncherAppWidgetInfo.FLAG_UI_NOT_READY;
                                             } else {
                                                 status &= ~LauncherAppWidgetInfo
                                                         .FLAG_PROVIDER_NOT_READY;
@@ -2214,13 +2215,14 @@
                                     if (container != LauncherSettings.Favorites.CONTAINER_DESKTOP &&
                                         container != LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
                                         Log.e(TAG, "Widget found where container != " +
-                                            "CONTAINER_DESKTOP nor CONTAINER_HOTSEAT - ignoring!");
+                                                "CONTAINER_DESKTOP nor CONTAINER_HOTSEAT - ignoring!");
+                                        itemsToRemove.add(id);
                                         continue;
                                     }
 
                                     appWidgetInfo.container = container;
                                     // check & update map of what's occupied
-                                    if (!checkItemPlacement(occupied, appWidgetInfo)) {
+                                    if (!checkItemPlacement(occupied, appWidgetInfo, sBgWorkspaceScreens)) {
                                         itemsToRemove.add(id);
                                         break;
                                     }
@@ -2279,6 +2281,21 @@
                     }
                 }
 
+                // Sort all the folder items and make sure the first 3 items are high resolution.
+                for (FolderInfo folder : sBgFolders) {
+                    Collections.sort(folder.contents, Folder.ITEM_POS_COMPARATOR);
+                    int pos = 0;
+                    for (ShortcutInfo info : folder.contents) {
+                        if (info.usingLowResIcon) {
+                            info.updateIcon(mIconCache, false);
+                        }
+                        pos ++;
+                        if (pos >= FolderIcon.NUM_ITEMS_IN_PREVIEW) {
+                            break;
+                        }
+                    }
+                }
+
                 if (restoredRows.size() > 0) {
                     // Update restored items that no longer require special handling
                     ContentValues values = new ContentValues();
@@ -2294,8 +2311,6 @@
                             null, sWorker);
                 }
 
-                sBgWorkspaceScreens.addAll(loadWorkspaceScreensDb(mContext));
-
                 // Remove any empty screens
                 ArrayList<Long> unusedScreens = new ArrayList<Long>(sBgWorkspaceScreens);
                 for (ItemInfo item: sBgItemsIdMap) {
@@ -2657,13 +2672,24 @@
                         callbacks.finishBindingItems();
                     }
 
+                    mIsLoadingAndBindingWorkspace = false;
+
+                    // Run all the bind complete runnables after workspace is bound.
+                    if (!mBindCompleteRunnables.isEmpty()) {
+                        synchronized (mBindCompleteRunnables) {
+                            for (final Runnable r : mBindCompleteRunnables) {
+                                runOnWorkerThread(r);
+                            }
+                            mBindCompleteRunnables.clear();
+                        }
+                    }
+
                     // If we're profiling, ensure this is the last thing in the queue.
                     if (DEBUG_LOADERS) {
                         Log.d(TAG, "bound workspace in "
                             + (SystemClock.uptimeMillis()-t) + "ms");
                     }
 
-                    mIsLoadingAndBindingWorkspace = false;
                 }
             };
             if (isLoadingSynchronously) {
@@ -2792,12 +2818,27 @@
 
                 final ManagedProfileHeuristic heuristic = ManagedProfileHeuristic.get(mContext, user);
                 if (heuristic != null) {
-                    runAfterBindCompletes(new Runnable() {
+                    final Runnable r = new Runnable() {
 
                         @Override
                         public void run() {
                             heuristic.processUserApps(apps);
                         }
+                    };
+                    runOnMainThread(new Runnable() {
+
+                        @Override
+                        public void run() {
+                            // Check isLoadingWorkspace on the UI thread, as it is updated on
+                            // the UI thread.
+                            if (mIsLoadingAndBindingWorkspace) {
+                                synchronized (mBindCompleteRunnables) {
+                                    mBindCompleteRunnables.add(r);
+                                }
+                            } else {
+                                runOnWorkerThread(r);
+                            }
+                        }
                     });
                 }
             }
@@ -2825,8 +2866,7 @@
             // Cleanup any data stored for a deleted user.
             ManagedProfileHeuristic.processAllUsers(profiles, mContext);
 
-            loadAndBindWidgetsAndShortcuts(mApp.getContext(), tryGetCallbacks(oldCallbacks),
-                    true /* refresh */);
+            loadAndBindWidgetsAndShortcuts(tryGetCallbacks(oldCallbacks), true /* refresh */);
             if (DEBUG_LOADERS) {
                 Log.d(TAG, "Icons processed in "
                         + (SystemClock.uptimeMillis() - loadTime) + "ms");
@@ -2895,7 +2935,7 @@
         }
 
         // Reload widget list. No need to refresh, as we only want to update the icons and labels.
-        loadAndBindWidgetsAndShortcuts(mApp.getContext(), callbacks, false);
+        loadAndBindWidgetsAndShortcuts(callbacks, false);
     }
 
     void enqueuePackageUpdated(PackageUpdatedTask task) {
@@ -3155,7 +3195,15 @@
                             if (mUser.equals(widgetInfo.user)
                                     && widgetInfo.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY)
                                     && packageSet.contains(widgetInfo.providerName.getPackageName())) {
-                                widgetInfo.restoreStatus &= ~LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY;
+                                widgetInfo.restoreStatus &=
+                                        ~LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY &
+                                        ~LauncherAppWidgetInfo.FLAG_RESTORE_STARTED;
+
+                                // adding this flag ensures that launcher shows 'click to setup'
+                                // if the widget has a config activity. In case there is no config
+                                // activity, it will be marked as 'restored' during bind.
+                                widgetInfo.restoreStatus |= LauncherAppWidgetInfo.FLAG_UI_NOT_READY;
+
                                 widgets.add(widgetInfo);
                                 updateItemInDatabase(context, widgetInfo);
                             }
@@ -3235,8 +3283,36 @@
                 });
             }
 
-            // onProvidersChanged method (API >= 17) already refreshed the widget list
-            loadAndBindWidgetsAndShortcuts(context, callbacks, Build.VERSION.SDK_INT < 17);
+            // Update widgets
+            if (mOp == OP_ADD || mOp == OP_REMOVE || mOp == OP_UPDATE) {
+                // Always refresh for a package event on secondary user
+                boolean needToRefresh = !mUser.equals(UserHandleCompat.myUserHandle());
+
+                // Refresh widget list, if the package already had a widget.
+                synchronized (sBgLock) {
+                    if (sBgWidgetProviders != null) {
+                        HashSet<String> pkgSet = new HashSet<>();
+                        Collections.addAll(pkgSet, mPackages);
+
+                        for (ComponentKey key : sBgWidgetProviders.keySet()) {
+                            needToRefresh |= key.user.equals(mUser) &&
+                                    pkgSet.contains(key.componentName.getPackageName());
+                        }
+                    }
+                }
+
+                if (!needToRefresh && mOp != OP_REMOVE) {
+                    // Refresh widget list, if there is any newly added widget
+                    PackageManager pm = context.getPackageManager();
+                    for (String pkg : mPackages) {
+                        needToRefresh |= !pm.queryBroadcastReceivers(
+                                new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE)
+                                    .setPackage(pkg), 0).isEmpty();
+                    }
+                }
+
+                loadAndBindWidgetsAndShortcuts(callbacks, needToRefresh);
+            }
 
             // Write all the logs to disk
             mHandler.post(new Runnable() {
@@ -3310,13 +3386,12 @@
         }
     }
 
-    public void loadAndBindWidgetsAndShortcuts(final Context context, final Callbacks callbacks,
-            final boolean refresh) {
+    public void loadAndBindWidgetsAndShortcuts(final Callbacks callbacks, final boolean refresh) {
 
         runOnWorkerThread(new Runnable() {
             @Override
             public void run() {
-                updateWidgetsModel(context, refresh);
+                updateWidgetsModel(refresh);
                 final WidgetsModel model = mBgWidgetsModel.clone();
 
                 mHandler.post(new Runnable() {
@@ -3340,10 +3415,10 @@
      *
      * @see #loadAndBindWidgetsAndShortcuts
      */
-    @Thunk void updateWidgetsModel(Context context, boolean refresh) {
-        PackageManager packageManager = context.getPackageManager();
+    @Thunk void updateWidgetsModel(boolean refresh) {
+        PackageManager packageManager = mApp.getContext().getPackageManager();
         final ArrayList<Object> widgetsAndShortcuts = new ArrayList<Object>();
-        widgetsAndShortcuts.addAll(getWidgetProviders(context, refresh));
+        widgetsAndShortcuts.addAll(getWidgetProviders(mApp.getContext(), refresh));
         Intent shortcutsIntent = new Intent(Intent.ACTION_CREATE_SHORTCUT);
         widgetsAndShortcuts.addAll(packageManager.queryIntentActivities(shortcutsIntent, 0));
         mBgWidgetsModel.setWidgetsAndShortcuts(widgetsAndShortcuts);
diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java
index cc5e18b..8791e9e 100644
--- a/src/com/android/launcher3/LauncherProvider.java
+++ b/src/com/android/launcher3/LauncherProvider.java
@@ -65,20 +65,17 @@
 import java.util.List;
 
 public class LauncherProvider extends ContentProvider {
-    private static final String TAG = "Launcher.LauncherProvider";
+    private static final String TAG = "LauncherProvider";
     private static final boolean LOGD = false;
 
     private static final int DATABASE_VERSION = 26;
 
-    static final String OLD_AUTHORITY = "com.android.launcher2.settings";
-    static final String AUTHORITY = ProviderConfig.AUTHORITY;
+    public static final String AUTHORITY = ProviderConfig.AUTHORITY;
 
     static final String TABLE_FAVORITES = LauncherSettings.Favorites.TABLE_NAME;
     static final String TABLE_WORKSPACE_SCREENS = LauncherSettings.WorkspaceScreens.TABLE_NAME;
     static final String EMPTY_DATABASE_CREATED = "EMPTY_DATABASE_CREATED";
 
-    private static final String URI_PARAM_IS_EXTERNAL_ADD = "isExternalAdd";
-
     private static final String RESTRICTION_PACKAGE_NAME = "workspace.configuration.package.name";
 
     @Thunk LauncherProviderChangeListener mListener;
@@ -140,14 +137,21 @@
         return db.insert(table, nullColumnHack, values);
     }
 
+    private void reloadLauncherIfExternal() {
+        if (Utilities.ATLEAST_MARSHMALLOW && Binder.getCallingPid() != Process.myPid()) {
+            LauncherAppState app = LauncherAppState.getInstanceNoCreate();
+            if (app != null) {
+                app.reloadWorkspace();
+            }
+        }
+    }
+
     @Override
     public Uri insert(Uri uri, ContentValues initialValues) {
         SqlArguments args = new SqlArguments(uri);
 
-        // In very limited cases, we support system|signature permission apps to add to the db
-        String externalAdd = uri.getQueryParameter(URI_PARAM_IS_EXTERNAL_ADD);
-        final boolean isExternalAll = externalAdd != null && "true".equals(externalAdd);
-        if (isExternalAll) {
+        // In very limited cases, we support system|signature permission apps to modify the db.
+        if (Binder.getCallingPid() != Process.myPid()) {
             if (!mOpenHelper.initializeExternalAdd(initialValues)) {
                 return null;
             }
@@ -161,13 +165,20 @@
         uri = ContentUris.withAppendedId(uri, rowId);
         notifyListeners();
 
-        if (isExternalAll) {
+        if (Utilities.ATLEAST_MARSHMALLOW) {
+            reloadLauncherIfExternal();
+        } else {
+            // Deprecated behavior to support legacy devices which rely on provider callbacks.
             LauncherAppState app = LauncherAppState.getInstanceNoCreate();
-            if (app != null) {
+            if (app != null && "true".equals(uri.getQueryParameter("isExternalAdd"))) {
                 app.reloadWorkspace();
             }
-        }
 
+            String notify = uri.getQueryParameter("notify");
+            if (notify == null || "true".equals(notify)) {
+                getContext().getContentResolver().notifyChange(uri, null);
+            }
+        }
         return uri;
     }
 
@@ -192,6 +203,7 @@
         }
 
         notifyListeners();
+        reloadLauncherIfExternal();
         return values.length;
     }
 
@@ -203,6 +215,7 @@
         try {
             ContentProviderResult[] result =  super.applyBatch(operations);
             db.setTransactionSuccessful();
+            reloadLauncherIfExternal();
             return result;
         } finally {
             db.endTransaction();
@@ -217,6 +230,7 @@
         int count = db.delete(args.table, args.where, args.args);
         if (count > 0) notifyListeners();
 
+        reloadLauncherIfExternal();
         return count;
     }
 
@@ -229,6 +243,7 @@
         int count = db.update(args.table, values, args.where, args.args);
         if (count > 0) notifyListeners();
 
+        reloadLauncherIfExternal();
         return count;
     }
 
@@ -399,7 +414,7 @@
     @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
     private AutoInstallsLayout createWorkspaceLoaderFromAppRestriction() {
         // UserManager.getApplicationRestrictions() requires minSdkVersion >= 18
-        if (android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) {
+        if (!Utilities.ATLEAST_JB_MR2) {
             return null;
         }
 
diff --git a/src/com/android/launcher3/LauncherSettings.java b/src/com/android/launcher3/LauncherSettings.java
index f2c85a1..8a5804f 100644
--- a/src/com/android/launcher3/LauncherSettings.java
+++ b/src/com/android/launcher3/LauncherSettings.java
@@ -143,7 +143,7 @@
          *
          * @return The unique content URL for the specified row.
          */
-        static Uri getContentUri(long id) {
+        public static Uri getContentUri(long id) {
             return Uri.parse("content://" + ProviderConfig.AUTHORITY +
                     "/" + TABLE_NAME + "/" + id);
         }
diff --git a/src/com/android/launcher3/LauncherStateTransitionAnimation.java b/src/com/android/launcher3/LauncherStateTransitionAnimation.java
index d69b743..cdde8c1 100644
--- a/src/com/android/launcher3/LauncherStateTransitionAnimation.java
+++ b/src/com/android/launcher3/LauncherStateTransitionAnimation.java
@@ -80,13 +80,6 @@
 public class LauncherStateTransitionAnimation {
 
     /**
-     * Callbacks made during the state transition
-     */
-    interface Callbacks {
-        public void onStateTransitionHideSearchBar();
-    }
-
-    /**
      * Private callbacks made during transition setup.
      */
     static abstract class PrivateTransitionCallbacks {
@@ -111,12 +104,10 @@
     public static final int SINGLE_FRAME_DELAY = 16;
 
     @Thunk Launcher mLauncher;
-    @Thunk Callbacks mCb;
-    @Thunk AnimatorSet mStateAnimation;
+    @Thunk AnimatorSet mCurrentAnimation;
 
-    public LauncherStateTransitionAnimation(Launcher l, Callbacks cb) {
+    public LauncherStateTransitionAnimation(Launcher l) {
         mLauncher = l;
-        mCb = cb;
     }
 
     /**
@@ -125,8 +116,8 @@
      * @param startSearchAfterTransition Immediately starts app search after the transition to
      *                                   All Apps is completed.
      */
-    public void startAnimationToAllApps(final boolean animated,
-            final boolean startSearchAfterTransition) {
+    public void startAnimationToAllApps(final Workspace.State fromWorkspaceState,
+            final boolean animated, final boolean startSearchAfterTransition) {
         final AllAppsContainerView toView = mLauncher.getAppsView();
         final View buttonView = mLauncher.getAllAppsButton();
         PrivateTransitionCallbacks cb = new PrivateTransitionCallbacks() {
@@ -159,15 +150,16 @@
             }
         };
         // Only animate the search bar if animating from spring loaded mode back to all apps
-        startAnimationToOverlay(Workspace.State.NORMAL_HIDDEN, buttonView, toView,
-                toView.getContentView(), toView.getRevealView(), toView.getSearchBarView(),
-                animated, true /* hideSearchBar */, cb);
+        mCurrentAnimation = startAnimationToOverlay(fromWorkspaceState,
+                Workspace.State.NORMAL_HIDDEN, buttonView, toView, toView.getContentView(),
+                toView.getRevealView(), toView.getSearchBarView(), animated, cb);
     }
 
     /**
      * Starts an animation to the widgets view.
      */
-    public void startAnimationToWidgets(final boolean animated) {
+    public void startAnimationToWidgets(final Workspace.State fromWorkspaceState,
+            final boolean animated) {
         final WidgetsContainerView toView = mLauncher.getWidgetsView();
         final View buttonView = mLauncher.getWidgetsButton();
 
@@ -177,17 +169,17 @@
                 return 0.3f;
             }
         };
-        startAnimationToOverlay(Workspace.State.OVERVIEW_HIDDEN, buttonView, toView,
-                toView.getContentView(), toView.getRevealView(), null, animated,
-                true /* hideSearchBar */, cb);
+        mCurrentAnimation = startAnimationToOverlay(fromWorkspaceState,
+                Workspace.State.OVERVIEW_HIDDEN, buttonView, toView, toView.getContentView(),
+                toView.getRevealView(), null, animated, cb);
     }
 
     /**
      * Starts and animation to the workspace from the current overlay view.
      */
     public void startAnimationToWorkspace(final Launcher.State fromState,
-              final Workspace.State toWorkspaceState, final int toWorkspacePage,
-              final boolean animated, final Runnable onCompleteRunnable) {
+            final Workspace.State fromWorkspaceState, final Workspace.State toWorkspaceState,
+            final int toWorkspacePage, final boolean animated, final Runnable onCompleteRunnable) {
         if (toWorkspaceState != Workspace.State.NORMAL &&
                 toWorkspaceState != Workspace.State.SPRING_LOADED &&
                 toWorkspaceState != Workspace.State.OVERVIEW) {
@@ -195,10 +187,10 @@
         }
 
         if (fromState == Launcher.State.APPS || fromState == Launcher.State.APPS_SPRING_LOADED) {
-            startAnimationToWorkspaceFromAllApps(toWorkspaceState, toWorkspacePage,
+            startAnimationToWorkspaceFromAllApps(fromWorkspaceState, toWorkspaceState, toWorkspacePage,
                     animated, onCompleteRunnable);
         } else {
-            startAnimationToWorkspaceFromWidgets(toWorkspaceState, toWorkspacePage,
+            startAnimationToWorkspaceFromWidgets(fromWorkspaceState, toWorkspaceState, toWorkspacePage,
                     animated, onCompleteRunnable);
         }
     }
@@ -207,12 +199,13 @@
      * Creates and starts a new animation to a particular overlay view.
      */
     @SuppressLint("NewApi")
-    private void startAnimationToOverlay(final Workspace.State toWorkspaceState,
-            final View buttonView, final View toView, final View contentView, final View revealView,
-            final View overlaySearchBarView, final boolean animated, final boolean hideSearchBar,
-            final PrivateTransitionCallbacks pCb) {
+    private AnimatorSet startAnimationToOverlay(final Workspace.State fromWorkspaceState,
+            final Workspace.State toWorkspaceState, final View buttonView, final View toView,
+            final View contentView, final View revealView, final View overlaySearchBarView,
+            final boolean animated, final PrivateTransitionCallbacks pCb) {
+        final AnimatorSet animation = LauncherAnimUtils.createAnimatorSet();
         final Resources res = mLauncher.getResources();
-        final boolean material = Utilities.isLmpOrAbove();
+        final boolean material = Utilities.ATLEAST_LOLLIPOP;
         final int revealDuration = res.getInteger(R.integer.config_overlayRevealTime);
         final int itemsAlphaStagger =
                 res.getInteger(R.integer.config_overlayItemsAlphaStagger);
@@ -230,11 +223,13 @@
         // Create the workspace animation.
         // NOTE: this call apparently also sets the state for the workspace if !animated
         Animator workspaceAnim = mLauncher.startWorkspaceStateChangeAnimation(toWorkspaceState, -1,
-                animated, overlaySearchBarView != null /* hasOverlaySearchBar */, layerViews);
+                animated, layerViews);
+
+        // Animate the search bar
+        startWorkspaceSearchBarAnimation(animation, fromWorkspaceState, toWorkspaceState,
+                animated ? revealDuration : 0, overlaySearchBarView);
 
         if (animated && initialized) {
-            mStateAnimation = LauncherAnimUtils.createAnimatorSet();
-
             // Setup the reveal view animation
             int width = revealView.getMeasuredWidth();
             int height = revealView.getMeasuredHeight();
@@ -274,7 +269,7 @@
 
             // Play the animation
             layerViews.put(revealView, BUILD_AND_SET_LAYER);
-            mStateAnimation.play(panelAlphaAndDrift);
+            animation.play(panelAlphaAndDrift);
 
             if (overlaySearchBarView != null) {
                 overlaySearchBarView.setAlpha(0f);
@@ -282,7 +277,7 @@
                 searchBarAlpha.setDuration(100);
                 searchBarAlpha.setInterpolator(new AccelerateInterpolator(1.5f));
                 layerViews.put(overlaySearchBarView, BUILD_AND_SET_LAYER);
-                mStateAnimation.play(searchBarAlpha);
+                animation.play(searchBarAlpha);
             }
 
             // Setup the animation for the content view
@@ -297,13 +292,13 @@
             pageDrift.setDuration(revealDuration);
             pageDrift.setInterpolator(new LogDecelerateInterpolator(100, 0));
             pageDrift.setStartDelay(itemsAlphaStagger);
-            mStateAnimation.play(pageDrift);
+            animation.play(pageDrift);
 
             ObjectAnimator itemsAlpha = ObjectAnimator.ofFloat(contentView, "alpha", 0f, 1f);
             itemsAlpha.setDuration(revealDuration);
             itemsAlpha.setInterpolator(new AccelerateInterpolator(1.5f));
             itemsAlpha.setStartDelay(itemsAlphaStagger);
-            mStateAnimation.play(itemsAlpha);
+            animation.play(itemsAlpha);
 
             if (material) {
                 float startRadius = pCb.getMaterialRevealViewStartFinalRadius();
@@ -316,10 +311,10 @@
                 if (listener != null) {
                     reveal.addListener(listener);
                 }
-                mStateAnimation.play(reveal);
+                animation.play(reveal);
             }
 
-            mStateAnimation.addListener(new AnimatorListenerAdapter() {
+            animation.addListener(new AnimatorListenerAdapter() {
                 @Override
                 public void onAnimationEnd(Animator animation) {
                     dispatchOnLauncherTransitionEnd(fromView, animated, false);
@@ -335,12 +330,8 @@
                         }
                     }
 
-                    if (hideSearchBar) {
-                        mCb.onStateTransitionHideSearchBar();
-                    }
-
                     // This can hold unnecessary references to views.
-                    mStateAnimation = null;
+                    cleanupAnimation();
                     pCb.onTransitionComplete();
                 }
 
@@ -348,7 +339,7 @@
 
             // Play the workspace animation
             if (workspaceAnim != null) {
-                mStateAnimation.play(workspaceAnim);
+                animation.play(workspaceAnim);
             }
 
             // Dispatch the prepare transition signal
@@ -356,23 +347,22 @@
             dispatchOnLauncherTransitionPrepare(toView, animated, false);
 
 
-            final AnimatorSet stateAnimation = mStateAnimation;
+            final AnimatorSet stateAnimation = animation;
             final Runnable startAnimRunnable = new Runnable() {
                 public void run() {
-                    // Check that mStateAnimation hasn't changed while
+                    // Check that mCurrentAnimation hasn't changed while
                     // we waited for a layout/draw pass
-                    if (mStateAnimation != stateAnimation)
+                    if (mCurrentAnimation != stateAnimation)
                         return;
                     dispatchOnLauncherTransitionStart(fromView, animated, false);
                     dispatchOnLauncherTransitionStart(toView, animated, false);
 
                     // Enable all necessary layers
-                    boolean isLmpOrAbove = Utilities.isLmpOrAbove();
                     for (View v : layerViews.keySet()) {
                         if (layerViews.get(v) == BUILD_AND_SET_LAYER) {
                             v.setLayerType(View.LAYER_TYPE_HARDWARE, null);
                         }
-                        if (isLmpOrAbove && Utilities.isViewAttachedToWindow(v)) {
+                        if (Utilities.ATLEAST_LOLLIPOP && Utilities.isViewAttachedToWindow(v)) {
                             v.buildLayer();
                         }
                     }
@@ -380,12 +370,14 @@
                     // Focus the new view
                     toView.requestFocus();
 
-                    mStateAnimation.start();
+                    stateAnimation.start();
                 }
             };
             toView.bringToFront();
             toView.setVisibility(View.VISIBLE);
             toView.post(startAnimRunnable);
+
+            return animation;
         } else {
             toView.setTranslationX(0.0f);
             toView.setTranslationY(0.0f);
@@ -397,10 +389,6 @@
             // Show the content view
             contentView.setVisibility(View.VISIBLE);
 
-            if (hideSearchBar) {
-                mCb.onStateTransitionHideSearchBar();
-            }
-
             dispatchOnLauncherTransitionPrepare(fromView, animated, false);
             dispatchOnLauncherTransitionStart(fromView, animated, false);
             dispatchOnLauncherTransitionEnd(fromView, animated, false);
@@ -408,18 +396,19 @@
             dispatchOnLauncherTransitionStart(toView, animated, false);
             dispatchOnLauncherTransitionEnd(toView, animated, false);
             pCb.onTransitionComplete();
+
+            return null;
         }
     }
 
     /**
      * Starts and animation to the workspace from the apps view.
      */
-    private void startAnimationToWorkspaceFromAllApps(final Workspace.State toWorkspaceState,
-            final int toWorkspacePage, final boolean animated, final Runnable onCompleteRunnable) {
+    private void startAnimationToWorkspaceFromAllApps(final Workspace.State fromWorkspaceState,
+            final Workspace.State toWorkspaceState, final int toWorkspacePage,
+            final boolean animated, final Runnable onCompleteRunnable) {
         AllAppsContainerView appsView = mLauncher.getAppsView();
         PrivateTransitionCallbacks cb = new PrivateTransitionCallbacks() {
-            int[] mAllAppsToPanelDelta;
-
             @Override
             float getMaterialRevealViewFinalAlpha(View revealView) {
                 // No alpha anim from all apps
@@ -451,8 +440,8 @@
             }
         };
         // Only animate the search bar if animating to spring loaded mode from all apps
-        startAnimationToWorkspaceFromOverlay(toWorkspaceState, toWorkspacePage,
-                mLauncher.getAllAppsButton(), appsView, appsView.getContentView(),
+        mCurrentAnimation = startAnimationToWorkspaceFromOverlay(fromWorkspaceState, toWorkspaceState,
+                toWorkspacePage, mLauncher.getAllAppsButton(), appsView, appsView.getContentView(),
                 appsView.getRevealView(), appsView.getSearchBarView(), animated,
                 onCompleteRunnable, cb);
     }
@@ -460,8 +449,9 @@
     /**
      * Starts and animation to the workspace from the widgets view.
      */
-    private void startAnimationToWorkspaceFromWidgets(final Workspace.State toWorkspaceState,
-              final int toWorkspacePage, final boolean animated, final Runnable onCompleteRunnable) {
+    private void startAnimationToWorkspaceFromWidgets(final Workspace.State fromWorkspaceState,
+            final Workspace.State toWorkspaceState, final int toWorkspacePage,
+            final boolean animated, final Runnable onCompleteRunnable) {
         final WidgetsContainerView widgetsView = mLauncher.getWidgetsView();
         PrivateTransitionCallbacks cb = new PrivateTransitionCallbacks() {
             @Override
@@ -479,21 +469,23 @@
                 };
             }
         };
-        startAnimationToWorkspaceFromOverlay(toWorkspaceState, toWorkspacePage,
-                mLauncher.getWidgetsButton(), widgetsView, widgetsView.getContentView(),
-                widgetsView.getRevealView(), null, animated, onCompleteRunnable, cb);
+        mCurrentAnimation = startAnimationToWorkspaceFromOverlay(fromWorkspaceState,
+                toWorkspaceState, toWorkspacePage, mLauncher.getWidgetsButton(), widgetsView,
+                widgetsView.getContentView(), widgetsView.getRevealView(), null, animated,
+                onCompleteRunnable, cb);
     }
 
     /**
      * Creates and starts a new animation to the workspace.
      */
-    private void startAnimationToWorkspaceFromOverlay(final Workspace.State toWorkspaceState,
-              final int toWorkspacePage, final View buttonView, final View fromView,
-              final View contentView, final View revealView, final View overlaySearchBarView,
-              final boolean animated, final Runnable onCompleteRunnable,
-              final PrivateTransitionCallbacks pCb) {
+    private AnimatorSet startAnimationToWorkspaceFromOverlay(final Workspace.State fromWorkspaceState,
+            final Workspace.State toWorkspaceState, final int toWorkspacePage, final View buttonView,
+            final View fromView, final View contentView, final View revealView,
+            final View overlaySearchBarView, final boolean animated, final Runnable onCompleteRunnable,
+            final PrivateTransitionCallbacks pCb) {
+        final AnimatorSet animation = LauncherAnimUtils.createAnimatorSet();
         final Resources res = mLauncher.getResources();
-        final boolean material = Utilities.isLmpOrAbove();
+        final boolean material = Utilities.ATLEAST_LOLLIPOP;
         final int revealDuration = res.getInteger(R.integer.config_overlayRevealTime);
         final int itemsAlphaStagger =
                 res.getInteger(R.integer.config_overlayItemsAlphaStagger);
@@ -511,15 +503,16 @@
         // Create the workspace animation.
         // NOTE: this call apparently also sets the state for the workspace if !animated
         Animator workspaceAnim = mLauncher.startWorkspaceStateChangeAnimation(toWorkspaceState,
-                toWorkspacePage, animated, overlaySearchBarView != null /* hasOverlaySearchBar */,
-                layerViews);
+                toWorkspacePage, animated, layerViews);
+
+        // Animate the search bar
+        startWorkspaceSearchBarAnimation(animation, fromWorkspaceState, toWorkspaceState,
+                animated ? revealDuration : 0, overlaySearchBarView);
 
         if (animated && initialized) {
-            mStateAnimation = LauncherAnimUtils.createAnimatorSet();
-
             // Play the workspace animation
             if (workspaceAnim != null) {
-                mStateAnimation.play(workspaceAnim);
+                animation.play(workspaceAnim);
             }
 
             // hideAppsCustomizeHelper is called in some cases when it is already hidden
@@ -558,14 +551,14 @@
                 panelDriftY.setDuration(revealDuration - SINGLE_FRAME_DELAY);
                 panelDriftY.setStartDelay(itemsAlphaStagger + SINGLE_FRAME_DELAY);
                 panelDriftY.setInterpolator(decelerateInterpolator);
-                mStateAnimation.play(panelDriftY);
+                animation.play(panelDriftY);
 
                 ObjectAnimator panelDriftX = ObjectAnimator.ofFloat(revealView, "translationX",
                         0, revealViewToXDrift);
                 panelDriftX.setDuration(revealDuration - SINGLE_FRAME_DELAY);
                 panelDriftX.setStartDelay(itemsAlphaStagger + SINGLE_FRAME_DELAY);
                 panelDriftX.setInterpolator(decelerateInterpolator);
-                mStateAnimation.play(panelDriftX);
+                animation.play(panelDriftX);
 
                 // Setup animation for the reveal panel alpha
                 final float revealViewToAlpha = !material ? 0f :
@@ -576,7 +569,7 @@
                     panelAlpha.setDuration(material ? revealDuration : 150);
                     panelAlpha.setStartDelay(material ? 0 : itemsAlphaStagger + SINGLE_FRAME_DELAY);
                     panelAlpha.setInterpolator(decelerateInterpolator);
-                    mStateAnimation.play(panelAlpha);
+                    animation.play(panelAlpha);
                 }
 
                 // Setup the animation for the content view
@@ -589,13 +582,13 @@
                 pageDrift.setDuration(revealDuration - SINGLE_FRAME_DELAY);
                 pageDrift.setInterpolator(decelerateInterpolator);
                 pageDrift.setStartDelay(itemsAlphaStagger + SINGLE_FRAME_DELAY);
-                mStateAnimation.play(pageDrift);
+                animation.play(pageDrift);
 
                 contentView.setAlpha(1f);
                 ObjectAnimator itemsAlpha = ObjectAnimator.ofFloat(contentView, "alpha", 1f, 0f);
                 itemsAlpha.setDuration(100);
                 itemsAlpha.setInterpolator(decelerateInterpolator);
-                mStateAnimation.play(itemsAlpha);
+                animation.play(itemsAlpha);
 
                 if (overlaySearchBarView != null) {
                     overlaySearchBarView.setAlpha(1f);
@@ -604,7 +597,7 @@
                     searchAlpha.setInterpolator(decelerateInterpolator);
                     searchAlpha.setStartDelay(material ? 0 : itemsAlphaStagger + SINGLE_FRAME_DELAY);
                     layerViews.put(overlaySearchBarView, BUILD_AND_SET_LAYER);
-                    mStateAnimation.play(searchAlpha);
+                    animation.play(searchAlpha);
                 }
 
                 if (material) {
@@ -620,14 +613,14 @@
                     if (listener != null) {
                         reveal.addListener(listener);
                     }
-                    mStateAnimation.play(reveal);
+                    animation.play(reveal);
                 }
 
                 dispatchOnLauncherTransitionPrepare(fromView, animated, true);
                 dispatchOnLauncherTransitionPrepare(toView, animated, true);
             }
 
-            mStateAnimation.addListener(new AnimatorListenerAdapter() {
+            animation.addListener(new AnimatorListenerAdapter() {
                 @Override
                 public void onAnimationEnd(Animator animation) {
                     fromView.setVisibility(View.GONE);
@@ -657,35 +650,37 @@
                     }
 
                     // This can hold unnecessary references to views.
-                    mStateAnimation = null;
+                    cleanupAnimation();
                     pCb.onTransitionComplete();
                 }
             });
 
-            final AnimatorSet stateAnimation = mStateAnimation;
+            final AnimatorSet stateAnimation = animation;
             final Runnable startAnimRunnable = new Runnable() {
                 public void run() {
-                    // Check that mStateAnimation hasn't changed while
+                    // Check that mCurrentAnimation hasn't changed while
                     // we waited for a layout/draw pass
-                    if (mStateAnimation != stateAnimation)
+                    if (mCurrentAnimation != stateAnimation)
                         return;
+
                     dispatchOnLauncherTransitionStart(fromView, animated, false);
                     dispatchOnLauncherTransitionStart(toView, animated, false);
 
                     // Enable all necessary layers
-                    boolean isLmpOrAbove = Utilities.isLmpOrAbove();
                     for (View v : layerViews.keySet()) {
                         if (layerViews.get(v) == BUILD_AND_SET_LAYER) {
                             v.setLayerType(View.LAYER_TYPE_HARDWARE, null);
                         }
-                        if (isLmpOrAbove && Utilities.isViewAttachedToWindow(v)) {
+                        if (Utilities.ATLEAST_LOLLIPOP && Utilities.isViewAttachedToWindow(v)) {
                             v.buildLayer();
                         }
                     }
-                    mStateAnimation.start();
+                    stateAnimation.start();
                 }
             };
             fromView.post(startAnimRunnable);
+
+            return animation;
         } else {
             fromView.setVisibility(View.GONE);
             dispatchOnLauncherTransitionPrepare(fromView, animated, true);
@@ -700,9 +695,44 @@
             if (onCompleteRunnable != null) {
                 onCompleteRunnable.run();
             }
+
+            return null;
         }
     }
 
+    /**
+     * Coordinates the workspace search bar animation along with the launcher state animation.
+     */
+    private void startWorkspaceSearchBarAnimation(AnimatorSet animation,
+            final Workspace.State fromWorkspaceState, final Workspace.State toWorkspaceState, int duration,
+            View overlaySearchBar) {
+        final SearchDropTargetBar.State toSearchBarState =
+                toWorkspaceState.getSearchDropTargetBarState();
+
+        if (overlaySearchBar != null) {
+            if ((toWorkspaceState == Workspace.State.NORMAL) &&
+                    (fromWorkspaceState == Workspace.State.NORMAL_HIDDEN)) {
+                // If we are transitioning from the overlay to the workspace, then show the
+                // workspace search bar immediately and let the overlay search bar fade out on top
+                mLauncher.getSearchDropTargetBar().animateToState(toSearchBarState, 0);
+            } else if (fromWorkspaceState == Workspace.State.NORMAL) {
+                // If we are transitioning from the workspace to the overlay, then keep the
+                // workspace search bar visible until the overlay search bar fades in on top
+                animation.addListener(new AnimatorListenerAdapter() {
+                    @Override
+                    public void onAnimationEnd(Animator animation) {
+                        mLauncher.getSearchDropTargetBar().animateToState(toSearchBarState, 0);
+                    }
+                });
+            } else {
+                // Otherwise, then just animate the workspace search bar normally
+                mLauncher.getSearchDropTargetBar().animateToState(toSearchBarState, duration);
+            }
+        } else {
+            // If there is no overlay search bar, then just animate the workspace search bar
+            mLauncher.getSearchDropTargetBar().animateToState(toSearchBarState, duration);
+        }
+    }
 
     /**
      * Dispatches the prepare-transition event to suitable views.
@@ -753,10 +783,14 @@
      * Cancels the current animation.
      */
     private void cancelAnimation() {
-        if (mStateAnimation != null) {
-            mStateAnimation.setDuration(0);
-            mStateAnimation.cancel();
-            mStateAnimation = null;
+        if (mCurrentAnimation != null) {
+            mCurrentAnimation.setDuration(0);
+            mCurrentAnimation.cancel();
+            mCurrentAnimation = null;
         }
     }
+
+    @Thunk void cleanupAnimation() {
+        mCurrentAnimation = null;
+    }
 }
diff --git a/src/com/android/launcher3/LauncherViewPropertyAnimator.java b/src/com/android/launcher3/LauncherViewPropertyAnimator.java
index 4cafbbf..4406a2c 100644
--- a/src/com/android/launcher3/LauncherViewPropertyAnimator.java
+++ b/src/com/android/launcher3/LauncherViewPropertyAnimator.java
@@ -26,6 +26,7 @@
 import java.util.EnumSet;
 
 public class LauncherViewPropertyAnimator extends Animator implements AnimatorListener {
+
     enum Properties {
             TRANSLATION_X,
             TRANSLATION_Y,
@@ -51,13 +52,12 @@
     long mStartDelay;
     long mDuration;
     TimeInterpolator mInterpolator;
-    ArrayList<Animator.AnimatorListener> mListeners;
+    ArrayList<Animator.AnimatorListener> mListeners = new ArrayList<>();
     boolean mRunning = false;
     FirstFrameAnimatorHelper mFirstFrameHelper;
 
     public LauncherViewPropertyAnimator(View target) {
         mTarget = target;
-        mListeners = new ArrayList<Animator.AnimatorListener>();
     }
 
     @Override
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index 218c1a3..05f0a05 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -2293,7 +2293,7 @@
         // Besides disabling the accessibility long-click, this also prevents this view from getting
         // accessibility focus.
         info.setLongClickable(false);
-        if (Utilities.isLmpOrAbove()) {
+        if (Utilities.ATLEAST_LOLLIPOP) {
             info.removeAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_LONG_CLICK);
         }
     }
diff --git a/src/com/android/launcher3/PendingAppWidgetHostView.java b/src/com/android/launcher3/PendingAppWidgetHostView.java
index 08f8e56..40eadab 100644
--- a/src/com/android/launcher3/PendingAppWidgetHostView.java
+++ b/src/com/android/launcher3/PendingAppWidgetHostView.java
@@ -16,13 +16,17 @@
 
 package com.android.launcher3;
 
+import android.annotation.TargetApi;
 import android.content.Context;
 import android.content.Intent;
 import android.content.res.Resources.Theme;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.PorterDuff;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
+import android.os.Build;
 import android.os.Bundle;
 import android.text.Layout;
 import android.text.StaticLayout;
@@ -32,6 +36,8 @@
 import android.view.View.OnClickListener;
 
 public class PendingAppWidgetHostView extends LauncherAppWidgetHostView implements OnClickListener {
+    private static final float SETUP_ICON_SIZE_FACTOR = 2f / 5;
+    private static final float MIN_SATUNATION = 0.7f;
 
     private static Theme sPreloaderTheme;
 
@@ -47,13 +53,14 @@
     private Bitmap mIcon;
 
     private Drawable mCenterDrawable;
-    private Drawable mTopCornerDrawable;
+    private Drawable mSettingIconDrawable;
 
     private boolean mDrawableSizeChanged;
 
     private final TextPaint mPaint;
     private Layout mSetupTextLayout;
 
+    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
     public PendingAppWidgetHostView(Context context, LauncherAppWidgetInfo info,
             boolean disabledForSafeMode) {
         super(context);
@@ -70,6 +77,10 @@
                 mLauncher.getDeviceProfile().iconTextSizePx, getResources().getDisplayMetrics()));
         setBackgroundResource(R.drawable.quantum_panel_dark);
         setWillNotDraw(false);
+
+        if (Utilities.ATLEAST_LOLLIPOP) {
+            setElevation(getResources().getDimension(R.dimen.pending_widget_elevation));
+        }
     }
 
     @Override
@@ -124,10 +135,12 @@
                 FastBitmapDrawable disabledIcon = mLauncher.createIconDrawable(mIcon);
                 disabledIcon.setGhostModeEnabled(true);
                 mCenterDrawable = disabledIcon;
-                mTopCornerDrawable = null;
+                mSettingIconDrawable = null;
             } else if (isReadyForClickSetup()) {
-                mCenterDrawable = getResources().getDrawable(R.drawable.ic_setting);
-                mTopCornerDrawable = new FastBitmapDrawable(mIcon);
+                mCenterDrawable = new FastBitmapDrawable(mIcon);
+                mSettingIconDrawable = getResources().getDrawable(R.drawable.ic_setting).mutate();
+
+                updateSettingColor();
             } else {
                 if (sPreloaderTheme == null) {
                     sPreloaderTheme = getResources().newTheme();
@@ -137,13 +150,25 @@
                 FastBitmapDrawable drawable = mLauncher.createIconDrawable(mIcon);
                 mCenterDrawable = new PreloadIconDrawable(drawable, sPreloaderTheme);
                 mCenterDrawable.setCallback(this);
-                mTopCornerDrawable = null;
+                mSettingIconDrawable = null;
                 applyState();
             }
             mDrawableSizeChanged = true;
         }
     }
 
+    private void updateSettingColor() {
+        int color = Utilities.findDominantColorByHue(mIcon, 20);
+        // Make the dominant color bright.
+        float[] hsv = new float[3];
+        Color.colorToHSV(color, hsv);
+        hsv[1] = Math.min(hsv[1], MIN_SATUNATION);
+        hsv[2] = 1;
+        color = Color.HSVToColor(hsv);
+
+        mSettingIconDrawable.setColorFilter(color,  PorterDuff.Mode.SRC_IN);
+    }
+
     @Override
     protected boolean verifyDrawable(Drawable who) {
         return (who == mCenterDrawable) || super.verifyDrawable(who);
@@ -169,6 +194,83 @@
                 && (mInfo.restoreStatus & LauncherAppWidgetInfo.FLAG_UI_NOT_READY) != 0;
     }
 
+    private void updateDrawableBounds() {
+        DeviceProfile grid = mLauncher.getDeviceProfile();
+        int paddingTop = getPaddingTop();
+        int paddingBottom = getPaddingBottom();
+        int paddingLeft = getPaddingLeft();
+        int paddingRight = getPaddingRight();
+
+        int minPadding = getResources()
+                .getDimensionPixelSize(R.dimen.pending_widget_min_padding);
+
+        int availableWidth = getWidth() - paddingLeft - paddingRight - 2 * minPadding;
+        int availableHeight = getHeight() - paddingTop - paddingBottom - 2 * minPadding;
+
+        if (mSettingIconDrawable == null) {
+            int outset = (mCenterDrawable instanceof PreloadIconDrawable) ?
+                    ((PreloadIconDrawable) mCenterDrawable).getOutset() : 0;
+            int maxSize = grid.iconSizePx + 2 * outset;
+            int size = Math.min(maxSize, Math.min(availableWidth, availableHeight));
+
+            mRect.set(0, 0, size, size);
+            mRect.inset(outset, outset);
+            mRect.offsetTo((getWidth() - mRect.width()) / 2, (getHeight() - mRect.height()) / 2);
+            mCenterDrawable.setBounds(mRect);
+        } else  {
+            float iconSize = Math.min(availableWidth, availableHeight);
+
+            // Use twice the setting size factor, as the setting is drawn at a corner and the
+            // icon is drawn in the center.
+            float settingIconScaleFactor = 1 + SETUP_ICON_SIZE_FACTOR * 2;
+            int maxSize = Math.max(availableWidth, availableHeight);
+            if (iconSize * settingIconScaleFactor > maxSize) {
+                // There is an overlap
+                iconSize = maxSize / settingIconScaleFactor;
+            }
+
+            int actualIconSize = (int) Math.min(iconSize, grid.iconSizePx);
+
+            // Recreate the setup text.
+            mSetupTextLayout = new StaticLayout(
+                    getResources().getText(R.string.gadget_setup_text), mPaint, availableWidth,
+                    Layout.Alignment.ALIGN_CENTER, 1, 0, true);
+            int textHeight = mSetupTextLayout.getHeight();
+
+            // Extra icon size due to the setting icon
+            float minHeightWithText = textHeight + actualIconSize * settingIconScaleFactor
+                    + grid.iconDrawablePaddingPx;
+
+            int iconTop;
+            if (minHeightWithText < availableHeight) {
+                // We can draw the text as well
+                iconTop =  (getHeight() - textHeight -
+                        grid.iconDrawablePaddingPx - actualIconSize) / 2;
+
+            } else {
+                // The text will not fit. Only draw the icons.
+                iconTop = (getHeight() - actualIconSize) / 2;
+                mSetupTextLayout = null;
+            }
+
+            mRect.set(0, 0, actualIconSize, actualIconSize);
+            mRect.offset((getWidth() - actualIconSize) / 2, iconTop);
+            mCenterDrawable.setBounds(mRect);
+
+            mRect.left = paddingLeft + minPadding;
+            mRect.right = mRect.left + (int) (SETUP_ICON_SIZE_FACTOR * actualIconSize);
+            mRect.top = paddingTop + minPadding;
+            mRect.bottom = mRect.top + (int) (SETUP_ICON_SIZE_FACTOR * actualIconSize);
+            mSettingIconDrawable.setBounds(mRect);
+
+            if (mSetupTextLayout != null) {
+                // Set up position for dragging the text
+                mRect.left = paddingLeft + minPadding;
+                mRect.top = mCenterDrawable.getBounds().bottom + grid.iconDrawablePaddingPx;
+            }
+        }
+    }
+
     @Override
     protected void onDraw(Canvas canvas) {
         if (mCenterDrawable == null) {
@@ -176,81 +278,21 @@
             return;
         }
 
-        DeviceProfile grid = mLauncher.getDeviceProfile();
-        if (mTopCornerDrawable == null) {
-            if (mDrawableSizeChanged) {
-                int outset = (mCenterDrawable instanceof PreloadIconDrawable) ?
-                        ((PreloadIconDrawable) mCenterDrawable).getOutset() : 0;
-                int maxSize = grid.iconSizePx + 2 * outset;
-                int size = Math.min(maxSize, Math.min(
-                        getWidth() - getPaddingLeft() - getPaddingRight(),
-                        getHeight() - getPaddingTop() - getPaddingBottom()));
-
-                mRect.set(0, 0, size, size);
-                mRect.inset(outset, outset);
-                mRect.offsetTo((getWidth() - mRect.width()) / 2, (getHeight() - mRect.height()) / 2);
-                mCenterDrawable.setBounds(mRect);
-                mDrawableSizeChanged = false;
-            }
-            mCenterDrawable.draw(canvas);
-        } else  {
-            // Draw the top corner icon and "Setup" text is possible
-            if (mDrawableSizeChanged) {
-                int iconSize = grid.iconSizePx;
-                int paddingTop = getPaddingTop();
-                int paddingBottom = getPaddingBottom();
-                int paddingLeft = getPaddingLeft();
-                int paddingRight = getPaddingRight();
-
-                int availableWidth = getWidth() - paddingLeft - paddingRight;
-                int availableHeight = getHeight() - paddingTop - paddingBottom;
-
-                // Recreate the setup text.
-                mSetupTextLayout = new StaticLayout(
-                        getResources().getText(R.string.gadget_setup_text), mPaint, availableWidth,
-                        Layout.Alignment.ALIGN_CENTER, 1, 0, true);
-                if (mSetupTextLayout.getLineCount() == 1) {
-                    // The text fits in a single line. No need to draw the setup icon.
-                    int size = Math.min(iconSize, Math.min(availableWidth,
-                            availableHeight - mSetupTextLayout.getHeight()));
-                    mRect.set(0, 0, size, size);
-                    mRect.offsetTo((getWidth() - mRect.width()) / 2,
-                            (getHeight() - mRect.height() - mSetupTextLayout.getHeight()
-                                    - grid.iconDrawablePaddingPx) / 2);
-
-                    mTopCornerDrawable.setBounds(mRect);
-
-                    // Update left and top to indicate the position where the text will be drawn.
-                    mRect.left = paddingLeft;
-                    mRect.top = mRect.bottom + grid.iconDrawablePaddingPx;
-                } else {
-                    // The text can't be drawn in a single line. Draw a setup icon instead.
-                    mSetupTextLayout = null;
-                    int size = Math.min(iconSize, Math.min(
-                            getWidth() - paddingLeft - paddingRight,
-                            getHeight() - paddingTop - paddingBottom));
-                    mRect.set(0, 0, size, size);
-                    mRect.offsetTo((getWidth() - mRect.width()) / 2, (getHeight() - mRect.height()) / 2);
-                    mCenterDrawable.setBounds(mRect);
-
-                    size = Math.min(size / 2,
-                            Math.max(mRect.top - paddingTop, mRect.left - paddingLeft));
-                    mTopCornerDrawable.setBounds(paddingLeft, paddingTop,
-                            paddingLeft + size, paddingTop + size);
-                }
-                mDrawableSizeChanged = false;
-            }
-
-            if (mSetupTextLayout == null) {
-                mCenterDrawable.draw(canvas);
-                mTopCornerDrawable.draw(canvas);
-            } else {
-                canvas.save();
-                canvas.translate(mRect.left, mRect.top);
-                mSetupTextLayout.draw(canvas);
-                canvas.restore();
-                mTopCornerDrawable.draw(canvas);
-            }
+        if (mDrawableSizeChanged) {
+            updateDrawableBounds();
+            mDrawableSizeChanged = false;
         }
+
+        mCenterDrawable.draw(canvas);
+        if (mSettingIconDrawable != null) {
+            mSettingIconDrawable.draw(canvas);
+        }
+        if (mSetupTextLayout != null) {
+            canvas.save();
+            canvas.translate(mRect.left, mRect.top);
+            mSetupTextLayout.draw(canvas);
+            canvas.restore();
+        }
+
     }
 }
diff --git a/src/com/android/launcher3/PreloadIconDrawable.java b/src/com/android/launcher3/PreloadIconDrawable.java
index bcb59c4..45e4b2c 100644
--- a/src/com/android/launcher3/PreloadIconDrawable.java
+++ b/src/com/android/launcher3/PreloadIconDrawable.java
@@ -213,6 +213,10 @@
         return mAnimationProgress;
     }
 
+    public boolean hasNotCompleted() {
+        return mAnimationProgress < ANIMATION_PROGRESS_COMPLETED;
+    }
+
     @Override
     public int getIntrinsicHeight() {
         return mIcon.getIntrinsicHeight();
diff --git a/src/com/android/launcher3/SearchDropTargetBar.java b/src/com/android/launcher3/SearchDropTargetBar.java
index 4cdf1ca..772a334 100644
--- a/src/com/android/launcher3/SearchDropTargetBar.java
+++ b/src/com/android/launcher3/SearchDropTargetBar.java
@@ -18,32 +18,57 @@
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
-import android.animation.ObjectAnimator;
-import android.animation.ValueAnimator;
 import android.content.Context;
 import android.graphics.Rect;
 import android.util.AttributeSet;
 import android.view.View;
+import android.view.accessibility.AccessibilityManager;
 import android.view.animation.AccelerateInterpolator;
 import android.widget.FrameLayout;
 
+import com.android.launcher3.util.Thunk;
+
 /*
  * Ths bar will manage the transition between the QSB search bar and the delete drop
  * targets so that each of the individual IconDropTargets don't have to.
  */
 public class SearchDropTargetBar extends FrameLayout implements DragController.DragListener {
 
-    private static final int TRANSITION_DURATION = 200;
+    /** The different states that the search bar space can be in. */
+    public enum State {
+        INVISIBLE   (0f, 0f),
+        SEARCH_BAR  (1f, 0f),
+        DROP_TARGET (0f, 1f);
 
-    private ObjectAnimator mShowDropTargetBarAnim;
-    private ValueAnimator mHideSearchBarAnim;
+        private final float mSearchBarAlpha;
+        private final float mDropTargetBarAlpha;
+
+        State(float sbAlpha, float dtbAlpha) {
+            mSearchBarAlpha = sbAlpha;
+            mDropTargetBarAlpha = dtbAlpha;
+        }
+
+        float getSearchBarAlpha() {
+            return mSearchBarAlpha;
+        }
+
+        float getDropTargetBarAlpha() {
+            return mDropTargetBarAlpha;
+        }
+    }
+
+    private static int DEFAULT_DRAG_FADE_DURATION = 175;
+
+    private LauncherViewPropertyAnimator mDropTargetBarAnimator;
+    private LauncherViewPropertyAnimator mQSBSearchBarAnimator;
     private static final AccelerateInterpolator sAccelerateInterpolator =
             new AccelerateInterpolator();
 
-    private boolean mIsSearchBarHidden;
-    private View mQSBSearchBar;
-    private View mDropTargetBar;
+    private State mState = State.SEARCH_BAR;
+    @Thunk View mQSB;
+    @Thunk View mDropTargetBar;
     private boolean mDeferOnDragEnd = false;
+    @Thunk boolean mAccessibilityEnabled = false;
 
     // Drop targets
     private ButtonDropTarget mInfoDropTarget;
@@ -75,39 +100,6 @@
         mUninstallDropTarget.setLauncher(launcher);
     }
 
-    public void setQsbSearchBar(View qsb) {
-        mQSBSearchBar = qsb;
-        if (mQSBSearchBar != null) {
-            mHideSearchBarAnim = LauncherAnimUtils.ofFloat(mQSBSearchBar, "alpha", 1f, 0f);
-            setupAnimation(mHideSearchBarAnim, mQSBSearchBar);
-        } else {
-            // Create a no-op animation of the search bar is null
-            mHideSearchBarAnim = ValueAnimator.ofFloat(0, 0);
-            mHideSearchBarAnim.setDuration(TRANSITION_DURATION);
-        }
-    }
-
-    private void prepareStartAnimation(View v) {
-        // Enable the hw layers before the animation starts (will be disabled in the onAnimationEnd
-        // callback below)
-        if (v != null) {
-            v.setLayerType(View.LAYER_TYPE_HARDWARE, null);
-        }
-    }
-
-    private void setupAnimation(ValueAnimator anim, final View v) {
-        anim.setInterpolator(sAccelerateInterpolator);
-        anim.setDuration(TRANSITION_DURATION);
-        anim.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                if (v != null) {
-                    v.setLayerType(View.LAYER_TYPE_NONE, null);
-                }
-            }
-        });
-    }
-
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
@@ -124,72 +116,89 @@
 
         // Create the various fade animations
         mDropTargetBar.setAlpha(0f);
-        mShowDropTargetBarAnim = LauncherAnimUtils.ofFloat(mDropTargetBar, "alpha", 0f, 1f);
-        setupAnimation(mShowDropTargetBarAnim, mDropTargetBar);
+        mDropTargetBarAnimator = new LauncherViewPropertyAnimator(mDropTargetBar);
+        mDropTargetBarAnimator.setInterpolator(sAccelerateInterpolator);
+        mDropTargetBarAnimator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationStart(Animator animation) {
+                // Ensure that the view is visible for the animation
+                mDropTargetBar.setVisibility(View.VISIBLE);
+            }
+
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                if (mDropTargetBar != null) {
+                    AlphaUpdateListener.updateVisibility(mDropTargetBar, mAccessibilityEnabled);
+                }
+            }
+        });
     }
 
-    /**
-     * Finishes all the animations on the search and drop target bars.
-     */
-    public void finishAnimations() {
-        prepareStartAnimation(mDropTargetBar);
-        mShowDropTargetBarAnim.reverse();
-        prepareStartAnimation(mQSBSearchBar);
-        mHideSearchBarAnim.reverse();
-    }
+    public void setQsbSearchBar(View qsb) {
+        mQSB = qsb;
+        if (mQSB != null) {
+            // Update the search ber animation
+            mQSBSearchBarAnimator = new LauncherViewPropertyAnimator(mQSB);
+            mQSBSearchBarAnimator.setInterpolator(sAccelerateInterpolator);
+            mQSBSearchBarAnimator.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationStart(Animator animation) {
+                    // Ensure that the view is visible for the animation
+                    if (mQSB != null) {
+                        mQSB.setVisibility(View.VISIBLE);
+                    }
+                }
 
-    /**
-     * Shows the search bar.
-     */
-    public void showSearchBar(boolean animated) {
-        if (!mIsSearchBarHidden) return;
-        if (animated) {
-            prepareStartAnimation(mQSBSearchBar);
-            mHideSearchBarAnim.reverse();
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    if (mQSB != null) {
+                        AlphaUpdateListener.updateVisibility(mQSB, mAccessibilityEnabled);
+                    }
+                }
+            });
         } else {
-            mHideSearchBarAnim.cancel();
-            if (mQSBSearchBar != null) {
-                mQSBSearchBar.setAlpha(1f);
+            mQSBSearchBarAnimator = null;
+        }
+    }
+
+    /**
+     * Animates the current search bar state to a new state.  If the {@param duration} is 0, then
+     * the state is applied immediately.
+     */
+    public void animateToState(State newState, int duration) {
+        if (mState != newState) {
+            mState = newState;
+
+            // Update the accessibility state
+            AccessibilityManager am = (AccessibilityManager)
+                    getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
+            mAccessibilityEnabled = am.isEnabled();
+
+            animateViewAlpha(mQSBSearchBarAnimator, mQSB, newState.getSearchBarAlpha(),
+                    duration);
+            animateViewAlpha(mDropTargetBarAnimator, mDropTargetBar, newState.getDropTargetBarAlpha(),
+                    duration);
+        }
+    }
+
+    /**
+     * Convenience method to animate the alpha of a view using hardware layers.
+     */
+    private void animateViewAlpha(LauncherViewPropertyAnimator animator, View v, float alpha,
+            int duration) {
+        if (v == null) {
+            return;
+        }
+
+        animator.cancel();
+        if (Float.compare(v.getAlpha(), alpha) != 0) {
+            if (duration > 0) {
+                animator.alpha(alpha).withLayer().setDuration(duration).start();
+            } else {
+                v.setAlpha(alpha);
+                AlphaUpdateListener.updateVisibility(v, mAccessibilityEnabled);
             }
         }
-        mIsSearchBarHidden = false;
-    }
-
-    /**
-     * Hides the search bar.  We only use this for clings.
-     */
-    public void hideSearchBar(boolean animated) {
-        if (mIsSearchBarHidden) return;
-        if (animated) {
-            prepareStartAnimation(mQSBSearchBar);
-            mHideSearchBarAnim.start();
-        } else {
-            mHideSearchBarAnim.cancel();
-            if (mQSBSearchBar != null) {
-                mQSBSearchBar.setAlpha(0f);
-            }
-        }
-        mIsSearchBarHidden = true;
-    }
-
-    /**
-     * Shows the drop target bar.
-     */
-    public void showDeleteTarget() {
-        // Animate out the QSB search bar, and animate in the drop target bar
-        prepareStartAnimation(mDropTargetBar);
-        mShowDropTargetBarAnim.start();
-        hideSearchBar(true);
-    }
-
-    /**
-     * Hides the drop target bar.
-     */
-    public void hideDeleteTarget() {
-        // Restore the QSB search bar, and animate out the drop target bar
-        prepareStartAnimation(mDropTargetBar);
-        mShowDropTargetBarAnim.reverse();
-        showSearchBar(true);
     }
 
     /*
@@ -197,9 +206,13 @@
      */
     @Override
     public void onDragStart(DragSource source, Object info, int dragAction) {
-        showDeleteTarget();
+        animateToState(State.DROP_TARGET, DEFAULT_DRAG_FADE_DURATION);
     }
 
+    /**
+     * This is called to defer hiding the delete drop target until the drop animation has completed,
+     * instead of hiding immediately when the drag has ended.
+     */
     public void deferOnDragEnd() {
         mDeferOnDragEnd = true;
     }
@@ -207,22 +220,25 @@
     @Override
     public void onDragEnd() {
         if (!mDeferOnDragEnd) {
-            hideDeleteTarget();
+            animateToState(State.SEARCH_BAR, DEFAULT_DRAG_FADE_DURATION);
         } else {
             mDeferOnDragEnd = false;
         }
     }
 
+    /**
+     * @return the bounds of the QSB search bar.
+     */
     public Rect getSearchBarBounds() {
-        if (mQSBSearchBar != null) {
+        if (mQSB != null) {
             final int[] pos = new int[2];
-            mQSBSearchBar.getLocationOnScreen(pos);
+            mQSB.getLocationOnScreen(pos);
 
             final Rect rect = new Rect();
             rect.left = pos[0];
             rect.top = pos[1];
-            rect.right = pos[0] + mQSBSearchBar.getWidth();
-            rect.bottom = pos[1] + mQSBSearchBar.getHeight();
+            rect.right = pos[0] + mQSB.getWidth();
+            rect.bottom = pos[1] + mQSB.getHeight();
             return rect;
         } else {
             return null;
@@ -230,8 +246,8 @@
     }
 
     public void enableAccessibleDrag(boolean enable) {
-        if (mQSBSearchBar != null) {
-            mQSBSearchBar.setVisibility(enable ? View.GONE : View.VISIBLE);
+        if (mQSB != null) {
+            mQSB.setVisibility(enable ? View.GONE : View.VISIBLE);
         }
         mInfoDropTarget.enableAccessibleDrag(enable);
         mDeleteDropTarget.enableAccessibleDrag(enable);
diff --git a/src/com/android/launcher3/ShortcutInfo.java b/src/com/android/launcher3/ShortcutInfo.java
index 56c0b9d..5766cf2 100644
--- a/src/com/android/launcher3/ShortcutInfo.java
+++ b/src/com/android/launcher3/ShortcutInfo.java
@@ -198,13 +198,17 @@
         return mIcon;
     }
 
-    public void updateIcon(IconCache iconCache) {
+    public void updateIcon(IconCache iconCache, boolean useLowRes) {
         if (itemType == Favorites.ITEM_TYPE_APPLICATION) {
             iconCache.getTitleAndIcon(this, promisedIntent != null ? promisedIntent : intent, user,
-                    shouldUseLowResIcon());
+                    useLowRes);
         }
     }
 
+    public void updateIcon(IconCache iconCache) {
+        updateIcon(iconCache, shouldUseLowResIcon());
+    }
+
     @Override
     void onAddToDatabase(Context context, ContentValues values) {
         super.onAddToDatabase(context, values);
diff --git a/src/com/android/launcher3/StylusEventHelper.java b/src/com/android/launcher3/StylusEventHelper.java
index da46e6a..e03273a 100644
--- a/src/com/android/launcher3/StylusEventHelper.java
+++ b/src/com/android/launcher3/StylusEventHelper.java
@@ -1,8 +1,5 @@
 package com.android.launcher3;
 
-import com.android.launcher3.Utilities;
-
-import android.util.Log;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewConfiguration;
@@ -77,8 +74,9 @@
      * @param event The event to check.
      * @return Whether a stylus button press occurred.
      */
-    public static boolean isStylusButtonPressed(MotionEvent event) {
+    private static boolean isStylusButtonPressed(MotionEvent event) {
         return event.getToolType(0) == MotionEvent.TOOL_TYPE_STYLUS
-                && event.isButtonPressed(MotionEvent.BUTTON_SECONDARY);
+                && ((event.getButtonState() & MotionEvent.BUTTON_SECONDARY)
+                        == MotionEvent.BUTTON_SECONDARY);
     }
 }
\ No newline at end of file
diff --git a/src/com/android/launcher3/UninstallDropTarget.java b/src/com/android/launcher3/UninstallDropTarget.java
index 0819f8c..955d401 100644
--- a/src/com/android/launcher3/UninstallDropTarget.java
+++ b/src/com/android/launcher3/UninstallDropTarget.java
@@ -38,7 +38,7 @@
 
     @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
     public static boolean supportsDrop(Context context, Object info) {
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
+        if (Utilities.ATLEAST_JB_MR2) {
             UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
             Bundle restrictions = userManager.getUserRestrictions();
             if (restrictions.getBoolean(UserManager.DISALLOW_APPS_CONTROL, false)
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index 8fd298d..adedd33 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -87,6 +87,24 @@
     private static final int[] sLoc0 = new int[2];
     private static final int[] sLoc1 = new int[2];
 
+    // TODO: use Build.VERSION_CODES when available
+    public static final boolean ATLEAST_MARSHMALLOW = Build.VERSION.SDK_INT >= 23;
+
+    public static final boolean ATLEAST_LOLLIPOP_MR1 =
+            Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1;
+
+    public static final boolean ATLEAST_LOLLIPOP =
+            Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP;
+
+    public static final boolean ATLEAST_KITKAT =
+            Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
+
+    public static final boolean ATLEAST_JB_MR1 =
+            Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1;
+
+    public static final boolean ATLEAST_JB_MR2 =
+            Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2;
+
     // To turn on these properties, type
     // adb shell setprop log.tag.PROPERTY_NAME [VERBOSE | SUPPRESS]
     private static final String FORCE_ENABLE_ROTATION_PROPERTY = "launcher_force_rotate";
@@ -110,23 +128,6 @@
         return sForceEnableRotation || context.getResources().getBoolean(R.bool.allow_rotation);
     }
 
-    /**
-     * Indicates if the device is running LMP or higher.
-     */
-    public static boolean isLmpOrAbove() {
-        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP;
-    }
-
-    public static boolean isLmpMR1OrAbove() {
-        // TODO(adamcohen): update to Build.VERSION_CODES.LOLLIPOP_MR1 once building against 22;
-        return Build.VERSION.SDK_INT >= 22;
-    }
-
-    public static boolean isLmpMR1() {
-        // TODO(adamcohen): update to Build.VERSION_CODES.LOLLIPOP_MR1 once building against 22;
-        return Build.VERSION.SDK_INT == 22;
-    }
-
     public static Bitmap createIconBitmap(Cursor c, int iconIndex, Context context) {
         byte[] data = c.getBlob(iconIndex);
         try {
@@ -517,7 +518,7 @@
 
     @TargetApi(Build.VERSION_CODES.KITKAT)
     public static boolean isViewAttachedToWindow(View v) {
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
+        if (ATLEAST_KITKAT) {
             return v.isAttachedToWindow();
         } else {
             // A proxy call which returns null, if the view is not attached to the window.
@@ -544,7 +545,7 @@
         AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
         for (AppWidgetProviderInfo info : appWidgetManager.getInstalledProviders()) {
             if (info.provider.getPackageName().equals(providerPkg)) {
-                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+                if (ATLEAST_JB_MR1) {
                     if ((info.widgetCategory & AppWidgetProviderInfo.WIDGET_CATEGORY_SEARCHBOX) != 0) {
                         return info;
                     } else if (defaultWidgetForSearchPackage == null) {
@@ -655,7 +656,7 @@
 
     @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
     public static boolean isRtl(Resources res) {
-        return (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) &&
+        return ATLEAST_JB_MR1 &&
                 (res.getConfiguration().getLayoutDirection() == View.LAYOUT_DIRECTION_RTL);
     }
 
diff --git a/src/com/android/launcher3/WidgetPreviewLoader.java b/src/com/android/launcher3/WidgetPreviewLoader.java
index 629387e..3460555 100644
--- a/src/com/android/launcher3/WidgetPreviewLoader.java
+++ b/src/com/android/launcher3/WidgetPreviewLoader.java
@@ -105,7 +105,7 @@
      * sizes (landscape vs portrait).
      */
     private static class CacheDb extends SQLiteOpenHelper {
-        private static final int DB_VERSION = 3;
+        private static final int DB_VERSION = 4;
 
         private static final String TABLE_NAME = "shortcut_and_widget_previews";
         private static final String COLUMN_COMPONENT = "componentName";
@@ -212,7 +212,6 @@
     public void removeObsoletePreviews(ArrayList<Object> list) {
         Utilities.assertWorkerThread();
 
-        LongSparseArray<UserHandleCompat> userIdCache = new LongSparseArray<>();
         LongSparseArray<HashSet<String>> validPackages = new LongSparseArray<>();
 
         for (Object obj : list) {
@@ -227,15 +226,7 @@
                 pkg = info.provider.getPackageName();
             }
 
-            int userIdIndex = userIdCache.indexOfValue(user);
-            final long userId;
-            if (userIdIndex < 0) {
-                userId = mUserManager.getSerialNumberForUser(user);
-                userIdCache.put(userId, user);
-            } else {
-                userId = userIdCache.keyAt(userIdIndex);
-            }
-
+            final long userId = mUserManager.getSerialNumberForUser(user);
             HashSet<String> packages = validPackages.get(userId);
             if (packages == null) {
                 packages = new HashSet<>();
@@ -361,8 +352,8 @@
         }
 
         final boolean widgetPreviewExists = (drawable != null);
-        final int spanX = info.getSpanX(launcher) < 1 ? 1 : info.getSpanX(launcher);
-        final int spanY = info.getSpanY(launcher) < 1 ? 1 : info.getSpanY(launcher);
+        final int spanX = info.spanX;
+        final int spanY = info.spanY;
 
         int previewWidth;
         int previewHeight;
@@ -386,7 +377,7 @@
             preScaledWidthOut[0] = previewWidth;
         }
         if (previewWidth > maxPreviewWidth) {
-            scale = maxPreviewWidth / (float) previewWidth;
+            scale = (maxPreviewWidth - 2 * mProfileBadgeMargin) / (float) (previewWidth);
         }
         if (scale != 1f) {
             previewWidth = (int) (scale * previewWidth);
@@ -405,7 +396,7 @@
         }
 
         // Draw the scaled preview into the final bitmap
-        int x = (preview.getWidth() - previewWidth - mProfileBadgeMargin) / 2;
+        int x = (preview.getWidth() - previewWidth) / 2;
         if (widgetPreviewExists) {
             drawable.setBounds(x, 0, x + previewWidth, previewHeight);
             drawable.draw(c);
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 4a6b90a..856e3b8 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -178,7 +178,24 @@
     // State variable that indicates whether the pages are small (ie when you're
     // in all apps or customize mode)
 
-    enum State { NORMAL, NORMAL_HIDDEN, SPRING_LOADED, OVERVIEW, OVERVIEW_HIDDEN};
+    enum State {
+        NORMAL          (SearchDropTargetBar.State.SEARCH_BAR),
+        NORMAL_HIDDEN   (SearchDropTargetBar.State.INVISIBLE),
+        SPRING_LOADED   (SearchDropTargetBar.State.DROP_TARGET),
+        OVERVIEW        (SearchDropTargetBar.State.INVISIBLE),
+        OVERVIEW_HIDDEN (SearchDropTargetBar.State.INVISIBLE);
+
+        private final SearchDropTargetBar.State mBarState;
+
+        State(SearchDropTargetBar.State searchBarState) {
+            mBarState = searchBarState;
+        }
+
+        public SearchDropTargetBar.State getSearchDropTargetBarState() {
+            return mBarState;
+        }
+    };
+
     private State mState = State.NORMAL;
     private boolean mIsSwitchingState = false;
 
@@ -306,8 +323,9 @@
         TypedArray a = context.obtainStyledAttributes(attrs,
                 R.styleable.Workspace, defStyle, 0);
         mSpringLoadedShrinkFactor =
-            res.getInteger(R.integer.config_workspaceSpringLoadShrinkPercentage) / 100.0f;
-        mOverviewModeShrinkFactor = grid.getOverviewModeScale(mIsRtl);
+                res.getInteger(R.integer.config_workspaceSpringLoadShrinkPercentage) / 100.0f;
+        mOverviewModeShrinkFactor =
+                res.getInteger(R.integer.config_workspaceOverviewShrinkPercentage) / 100f;
         mOriginalDefaultPage = mDefaultPage = a.getInt(R.styleable.Workspace_defaultScreen, 1);
         a.recycle();
 
@@ -869,6 +887,9 @@
             }
         }
 
+        LauncherAccessibilityDelegate delegate =
+                LauncherAppState.getInstance().getAccessibilityDelegate();
+
         // We enforce at least one page to add new items to. In the case that we remove the last
         // such screen, we convert the last screen to the empty screen
         int minScreens = 1 + numCustomPages();
@@ -883,6 +904,11 @@
                 if (indexOfChild(cl) < currentPage) {
                     pageShift++;
                 }
+
+                if (delegate != null && delegate.isInAccessibleDrag()) {
+                    cl.enableAccessibleDrag(false, CellLayout.WORKSPACE_ACCESSIBILITY_DRAG);
+                }
+
                 removeView(cl);
             } else {
                 // if this is the last non-custom content screen, convert it to the empty screen
@@ -1566,7 +1592,7 @@
             // Reset our click listener
             setOnClickListener(mLauncher);
         }
-        mLauncher.getSearchBar().enableAccessibleDrag(enable);
+        mLauncher.getSearchDropTargetBar().enableAccessibleDrag(enable);
         mLauncher.getHotseat().getLayout()
             .enableAccessibleDrag(enable, CellLayout.WORKSPACE_ACCESSIBILITY_DRAG);
     }
@@ -1952,15 +1978,17 @@
 
     int getOverviewModeTranslationY() {
         DeviceProfile grid = mLauncher.getDeviceProfile();
-        Rect overviewBar = grid.getOverviewModeButtonBarRect();
+        Rect workspacePadding = grid.getWorkspacePadding(Utilities.isRtl(getResources()));
+        int overviewButtonBarHeight = grid.getOverviewModeButtonBarHeight();
 
-        int availableHeight = getViewportHeight();
         int scaledHeight = (int) (mOverviewModeShrinkFactor * getNormalChildHeight());
-        int offsetFromTopEdge = (availableHeight - scaledHeight) / 2;
-        int offsetToCenterInOverview = (availableHeight - mInsets.top - overviewBar.height()
-                - scaledHeight) / 2;
-
-        return -offsetFromTopEdge + mInsets.top + offsetToCenterInOverview;
+        int workspaceTop = mInsets.top + workspacePadding.top;
+        int workspaceBottom = getViewportHeight() - mInsets.bottom - workspacePadding.bottom;
+        int overviewTop = mInsets.top;
+        int overviewBottom = getViewportHeight() - mInsets.bottom - overviewButtonBarHeight;
+        int workspaceOffsetTopEdge = workspaceTop + ((workspaceBottom - workspaceTop) - scaledHeight) / 2;
+        int overviewOffsetTopEdge = overviewTop + (overviewBottom - overviewTop - scaledHeight) / 2;
+        return -workspaceOffsetTopEdge + overviewOffsetTopEdge;
     }
 
     /**
@@ -1968,10 +1996,10 @@
      * to that new state.
      */
     public Animator setStateWithAnimation(State toState, int toPage, boolean animated,
-            boolean hasOverlaySearchBar, HashMap<View, Integer> layerViews) {
+            HashMap<View, Integer> layerViews) {
         // Create the animation to the new state
         Animator workspaceAnim =  mStateTransitionAnimation.getAnimationToState(mState,
-                toState, toPage, animated, hasOverlaySearchBar, layerViews);
+                toState, toPage, animated, layerViews);
 
         // Update the current state
         mState = toState;
@@ -1985,7 +2013,7 @@
     }
 
     public void updateAccessibilityFlags() {
-        if (Utilities.isLmpOrAbove()) {
+        if (Utilities.ATLEAST_LOLLIPOP) {
             int total = getPageCount();
             for (int i = numCustomPages(); i < total; i++) {
                 updateAccessibilityFlags((CellLayout) getPageAt(i), i);
@@ -2273,6 +2301,8 @@
             dragRect = new Rect(left, top, right, bottom);
         } else if (child instanceof FolderIcon) {
             int previewSize = grid.folderIconSizePx;
+            dragVisualizeOffset = new Point(-padding.get() / 2,
+                    padding.get() / 2 - child.getPaddingTop());
             dragRect = new Rect(0, child.getPaddingTop(), child.getWidth(), previewSize);
         }
 
@@ -2289,14 +2319,14 @@
             throw new IllegalStateException(msg);
         }
 
-        DragView dv = mDragController.startDrag(b, dragLayerX, dragLayerY, source, child.getTag(),
-                DragController.DRAG_ACTION_MOVE, dragVisualizeOffset, dragRect, scale, accessible);
-        dv.setIntrinsicIconScaleFactor(source.getIntrinsicIconScaleFactor());
-
         if (child.getParent() instanceof ShortcutAndWidgetContainer) {
             mDragSourceInternal = (ShortcutAndWidgetContainer) child.getParent();
         }
 
+        DragView dv = mDragController.startDrag(b, dragLayerX, dragLayerY, source, child.getTag(),
+                DragController.DRAG_ACTION_MOVE, dragVisualizeOffset, dragRect, scale, accessible);
+        dv.setIntrinsicIconScaleFactor(source.getIntrinsicIconScaleFactor());
+
         b.recycle();
     }
 
@@ -3763,7 +3793,11 @@
         if (parentCell != null) {
             parentCell.removeView(v);
         } else if (LauncherAppState.isDogfoodBuild()) {
-            throw new NullPointerException("mDragInfo.cell has null parent");
+            // When an app is uninstalled using the drop target, we wait until resume to remove
+            // the icon. We also remove all the corresponding items from the workspace at
+            // {@link Launcher#bindComponentsRemoved}. That call can come before or after
+            // {@link Launcher#mOnResumeCallbacks} depending on how busy the worker thread is.
+            Log.e(TAG, "mDragInfo.cell has null parent");
         }
         if (v instanceof DropTarget) {
             mDragController.removeDropTarget((DropTarget) v);
@@ -4050,6 +4084,16 @@
         });
     }
 
+    public View getHomescreenIconByItemId(final long id) {
+        return getFirstMatch(new ItemOperator() {
+
+            @Override
+            public boolean evaluate(ItemInfo info, View v, View parent) {
+                return info != null && info.id == id;
+            }
+        });
+    }
+
     public View getViewForTag(final Object tag) {
         return getFirstMatch(new ItemOperator() {
 
@@ -4298,8 +4342,9 @@
                         updates.contains(info)) {
                     ShortcutInfo si = (ShortcutInfo) info;
                     BubbleTextView shortcut = (BubbleTextView) v;
-                    boolean oldPromiseState = getTextViewIcon(shortcut)
-                            instanceof PreloadIconDrawable;
+                    Drawable oldIcon = getTextViewIcon(shortcut);
+                    boolean oldPromiseState = (oldIcon instanceof PreloadIconDrawable)
+                            && ((PreloadIconDrawable) oldIcon).hasNotCompleted();
                     shortcut.applyFromShortcutInfo(si, mIconCache,
                             si.isPromise() != oldPromiseState);
 
diff --git a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
index b8916a7..54f63bb 100644
--- a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
+++ b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
@@ -211,22 +211,20 @@
         mOverlayTransitionTime = res.getInteger(R.integer.config_overlayTransitionTime);
         mSpringLoadedShrinkFactor =
                 res.getInteger(R.integer.config_workspaceSpringLoadShrinkPercentage) / 100f;
+        mOverviewModeShrinkFactor =
+                res.getInteger(R.integer.config_workspaceOverviewShrinkPercentage) / 100f;
         mWorkspaceScrimAlpha = res.getInteger(R.integer.config_workspaceScrimAlpha) / 100f;
-        mOverviewModeShrinkFactor = grid.getOverviewModeScale(Utilities.isRtl(res));
         mWorkspaceFadeInAdjacentScreens = grid.shouldFadeAdjacentWorkspaceScreens();
     }
 
     public AnimatorSet getAnimationToState(Workspace.State fromState, Workspace.State toState,
-            int toPage, boolean animated, boolean hasOverlaySearchBar,
-            HashMap<View, Integer> layerViews) {
+            int toPage, boolean animated, HashMap<View, Integer> layerViews) {
         AccessibilityManager am = (AccessibilityManager)
                 mLauncher.getSystemService(Context.ACCESSIBILITY_SERVICE);
         final boolean accessibilityEnabled = am.isEnabled();
         TransitionStates states = new TransitionStates(fromState, toState);
-        int duration = getAnimationDuration(states);
-        animateWorkspace(states, toPage, animated, duration, layerViews,
-                accessibilityEnabled);
-        animateSearchBar(states, animated, duration, hasOverlaySearchBar, layerViews,
+        int workspaceDuration = getAnimationDuration(states);
+        animateWorkspace(states, toPage, animated, workspaceDuration, layerViews,
                 accessibilityEnabled);
         animateBackgroundGradient(states, animated, BACKGROUND_FADE_OUT_DURATION);
         return mStateAnimator;
@@ -473,75 +471,9 @@
     }
 
     /**
-     * Coordinates with the workspace animation to animate the search bar.
-     *
-     * TODO: This should really be coordinated with the SearchDropTargetBar, otherwise the
-     *       bar has no idea that it is hidden, and this has no idea what state the bar is
-     *       actually in.
-     */
-    private void animateSearchBar(TransitionStates states, boolean animated, int duration,
-            boolean hasOverlaySearchBar, final HashMap<View, Integer> layerViews,
-            final boolean accessibilityEnabled) {
-
-        // The search bar is only visible in the workspace
-        final View searchBar = mLauncher.getOrCreateQsbBar();
-        if (searchBar != null) {
-            final boolean searchBarWillBeShown = states.stateIsNormal;
-            final float finalSearchBarAlpha = searchBarWillBeShown ? 1f : 0f;
-            if (animated) {
-                if (hasOverlaySearchBar) {
-                    // If there is an overlay search bar, then we will coordinate with it.
-                    mStateAnimator.addListener(new AnimatorListenerAdapter() {
-                        @Override
-                        public void onAnimationStart(Animator animation) {
-                            // If we are transitioning to a visible search bar, show it immediately
-                            // and let the overlay search bar has faded out
-                            if (searchBarWillBeShown) {
-                                searchBar.setAlpha(finalSearchBarAlpha);
-                                AlphaUpdateListener.updateVisibility(searchBar, accessibilityEnabled);
-                            }
-                        }
-
-                        @Override
-                        public void onAnimationEnd(Animator animation) {
-                            // If we are transitioning to a hidden search bar, hide it only after
-                            // the overlay search bar has faded in
-                            if (!searchBarWillBeShown) {
-                                searchBar.setAlpha(finalSearchBarAlpha);
-                                AlphaUpdateListener.updateVisibility(searchBar, accessibilityEnabled);
-                            }
-                        }
-                    });
-                } else {
-                    // Otherwise, we can just do the normal animation
-                    LauncherViewPropertyAnimator searchBarAlpha =
-                            new LauncherViewPropertyAnimator(searchBar).alpha(finalSearchBarAlpha);
-                    searchBarAlpha.addListener(new AlphaUpdateListener(searchBar,
-                            accessibilityEnabled));
-                    searchBar.setLayerType(View.LAYER_TYPE_HARDWARE, null);
-                    if (layerViews != null) {
-                        // If layerViews is not null, we add these views, and indicate that
-                        // the caller can manage layer state.
-                        layerViews.put(searchBar, LauncherStateTransitionAnimation.BUILD_AND_SET_LAYER);
-                    } else {
-                        // Otherwise let the animator handle layer management.
-                        searchBarAlpha.withLayer();
-                    }
-                    searchBarAlpha.setDuration(duration);
-                    mStateAnimator.play(searchBarAlpha);
-                }
-            } else {
-                // Set the search bar state immediately
-                searchBar.setAlpha(finalSearchBarAlpha);
-                AlphaUpdateListener.updateVisibility(searchBar, accessibilityEnabled);
-            }
-        }
-    }
-
-    /**
      * Animates the background scrim. Add to the state animator to prevent jankiness.
      *
-     * @param finalAlpha the final alpha for the background scrim
+     * @param states the current and final workspace states
      * @param animated whether or not to set the background alpha immediately
      * @duration duration of the animation
      */
diff --git a/src/com/android/launcher3/allapps/AllAppsBackgroundDrawable.java b/src/com/android/launcher3/allapps/AllAppsBackgroundDrawable.java
new file mode 100644
index 0000000..117aca9
--- /dev/null
+++ b/src/com/android/launcher3/allapps/AllAppsBackgroundDrawable.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.allapps;
+
+import android.animation.ObjectAnimator;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.graphics.ColorFilter;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+
+import android.view.Gravity;
+import com.android.launcher3.R;
+
+/**
+ * A helper class to positon and orient a drawable to be drawn.
+ */
+class TransformedImageDrawable {
+    private Drawable mImage;
+    private float mXPercent;
+    private float mYPercent;
+    private int mGravity;
+
+    /**
+     * @param gravity If one of the Gravity center values, the x and y offset will take the width
+     *                and height of the image into account to center the image to the offset.
+     */
+    public TransformedImageDrawable(Resources res, int resourceId, float xPct, float yPct,
+            int gravity) {
+        mImage = res.getDrawable(resourceId);
+        mXPercent = xPct;
+        mYPercent = yPct;
+        mGravity = gravity;
+    }
+
+    public void setAlpha(int alpha) {
+        mImage.setAlpha(alpha);
+    }
+
+    public int getAlpha() {
+        return mImage.getAlpha();
+    }
+
+    public void updateBounds(Rect bounds) {
+        int width = mImage.getIntrinsicWidth();
+        int height = mImage.getIntrinsicHeight();
+        int left = bounds.left + (int) (mXPercent * bounds.width());
+        int top = bounds.top + (int) (mYPercent * bounds.height());
+        if ((mGravity & Gravity.CENTER_HORIZONTAL) == Gravity.CENTER_HORIZONTAL) {
+            left -= (width / 2);
+        }
+        if ((mGravity & Gravity.CENTER_VERTICAL) == Gravity.CENTER_VERTICAL) {
+            top -= (height / 2);
+        }
+        mImage.setBounds(left, top, left + width, top + height);
+    }
+
+    public void draw(Canvas canvas) {
+        int c = canvas.save(Canvas.MATRIX_SAVE_FLAG);
+        mImage.draw(canvas);
+        canvas.restoreToCount(c);
+    }
+}
+
+/**
+ * This is a custom composite drawable that has a fixed virtual size and dynamically lays out its
+ * children images relatively within its bounds.  This way, we can reduce the memory usage of a
+ * single, large sparsely populated image.
+ */
+public class AllAppsBackgroundDrawable extends Drawable {
+
+    private final TransformedImageDrawable mHand;
+    private final TransformedImageDrawable[] mIcons;
+    private final int mWidth;
+    private final int mHeight;
+
+    private ObjectAnimator mBackgroundAnim;
+
+    public AllAppsBackgroundDrawable(Context context) {
+        Resources res = context.getResources();
+        mHand = new TransformedImageDrawable(res, R.drawable.ic_all_apps_bg_hand,
+                0.575f, 0.1f, Gravity.CENTER_HORIZONTAL);
+        mIcons = new TransformedImageDrawable[4];
+        mIcons[0] = new TransformedImageDrawable(res, R.drawable.ic_all_apps_bg_icon_1,
+                0.375f, 0, Gravity.CENTER_HORIZONTAL);
+        mIcons[1] = new TransformedImageDrawable(res, R.drawable.ic_all_apps_bg_icon_2,
+                0.3125f, 0.25f, Gravity.CENTER_HORIZONTAL);
+        mIcons[2] = new TransformedImageDrawable(res, R.drawable.ic_all_apps_bg_icon_3,
+                0.475f, 0.4f, Gravity.CENTER_HORIZONTAL);
+        mIcons[3] = new TransformedImageDrawable(res, R.drawable.ic_all_apps_bg_icon_4,
+                0.7f, 0.125f, Gravity.CENTER_HORIZONTAL);
+        mWidth = res.getDimensionPixelSize(R.dimen.all_apps_background_canvas_width);
+        mHeight = res.getDimensionPixelSize(R.dimen.all_apps_background_canvas_height);
+    }
+
+    /**
+     * Animates the background alpha.
+     */
+    public void animateBgAlpha(float finalAlpha, int duration) {
+        int finalAlphaI = (int) (finalAlpha * 255f);
+        if (getAlpha() != finalAlphaI) {
+            mBackgroundAnim = cancelAnimator(mBackgroundAnim);
+            mBackgroundAnim = ObjectAnimator.ofInt(this, "alpha", finalAlphaI);
+            mBackgroundAnim.setDuration(duration);
+            mBackgroundAnim.start();
+        }
+    }
+
+    /**
+     * Sets the background alpha immediately.
+     */
+    public void setBgAlpha(float finalAlpha) {
+        int finalAlphaI = (int) (finalAlpha * 255f);
+        if (getAlpha() != finalAlphaI) {
+            mBackgroundAnim = cancelAnimator(mBackgroundAnim);
+            setAlpha(finalAlphaI);
+        }
+    }
+
+    @Override
+    public int getIntrinsicWidth() {
+        return mWidth;
+    }
+
+    @Override
+    public int getIntrinsicHeight() {
+        return mHeight;
+    }
+
+    @Override
+    public void draw(Canvas canvas) {
+        mHand.draw(canvas);
+        for (int i = 0; i < mIcons.length; i++) {
+            mIcons[i].draw(canvas);
+        }
+    }
+
+    @Override
+    protected void onBoundsChange(Rect bounds) {
+        super.onBoundsChange(bounds);
+        mHand.updateBounds(bounds);
+        for (int i = 0; i < mIcons.length; i++) {
+            mIcons[i].updateBounds(bounds);
+        }
+        invalidateSelf();
+    }
+
+    @Override
+    public void setAlpha(int alpha) {
+        mHand.setAlpha(alpha);
+        for (int i = 0; i < mIcons.length; i++) {
+            mIcons[i].setAlpha(alpha);
+        }
+        invalidateSelf();
+    }
+
+    @Override
+    public int getAlpha() {
+        return mHand.getAlpha();
+    }
+
+    @Override
+    public void setColorFilter(ColorFilter colorFilter) {
+        // Do nothing
+    }
+
+    @Override
+    public int getOpacity() {
+        return PixelFormat.TRANSLUCENT;
+    }
+
+    private ObjectAnimator cancelAnimator(ObjectAnimator animator) {
+        if (animator != null) {
+            animator.removeAllListeners();
+            animator.cancel();
+        }
+        return null;
+    }
+}
diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java
index 67d5728..88c6aca 100644
--- a/src/com/android/launcher3/allapps/AllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java
@@ -16,34 +16,26 @@
 package com.android.launcher3.allapps;
 
 import android.annotation.SuppressLint;
-import android.annotation.TargetApi;
 import android.content.Context;
+import android.content.Intent;
 import android.content.res.Resources;
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.graphics.drawable.InsetDrawable;
-import android.os.Build;
-import android.os.Bundle;
 import android.support.v7.widget.RecyclerView;
 import android.text.Selection;
 import android.text.SpannableStringBuilder;
 import android.text.method.TextKeyListener;
 import android.util.AttributeSet;
 import android.view.KeyEvent;
-import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewConfiguration;
 import android.view.ViewGroup;
-import android.view.ViewTreeObserver;
-import android.widget.FrameLayout;
 import android.widget.LinearLayout;
-
 import com.android.launcher3.AppInfo;
 import com.android.launcher3.BaseContainerView;
-import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.CellLayout;
-import com.android.launcher3.CheckLongPressHelper;
 import com.android.launcher3.DeleteDropTarget;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.DragSource;
@@ -53,7 +45,6 @@
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherTransitionable;
 import com.android.launcher3.R;
-import com.android.launcher3.Stats;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.Workspace;
 import com.android.launcher3.util.ComponentKey;
@@ -155,6 +146,7 @@
     @Thunk AllAppsSearchBarController mSearchBarController;
     private ViewGroup mSearchBarContainerView;
     private View mSearchBarView;
+    private SpannableStringBuilder mSearchQueryBuilder = null;
 
     private int mSectionNamesMargin;
     private int mNumAppsPerRow;
@@ -165,7 +157,13 @@
     // This coordinate is relative to its parent
     private final Point mIconLastTouchPos = new Point();
 
-    private SpannableStringBuilder mSearchQueryBuilder = null;
+    private View.OnClickListener mSearchClickListener = new View.OnClickListener() {
+        @Override
+        public void onClick(View v) {
+            Intent searchIntent = (Intent) v.getTag();
+            mLauncher.startActivitySafely(v, searchIntent, null);
+        }
+    };
 
     public AllAppsContainerView(Context context) {
         this(context, null);
@@ -182,8 +180,7 @@
         mLauncher = (Launcher) context;
         mSectionNamesMargin = res.getDimensionPixelSize(R.dimen.all_apps_grid_view_start_margin);
         mApps = new AlphabeticalAppsList(context);
-        mAdapter = new AllAppsGridAdapter(context, mApps, this, mLauncher, this);
-        mAdapter.setEmptySearchText(res.getString(R.string.all_apps_loading_message));
+        mAdapter = new AllAppsGridAdapter(mLauncher, mApps, this, mLauncher, this);
         mApps.setAdapter(mAdapter);
         mLayoutManager = mAdapter.getLayoutManager();
         mItemDecoration = mAdapter.getItemDecoration();
@@ -528,7 +525,6 @@
                 CellLayout layout = (CellLayout) workspace.getChildAt(currentScreen);
                 ItemInfo itemInfo = (ItemInfo) d.dragInfo;
                 if (layout != null) {
-                    layout.calculateSpans(itemInfo);
                     showOutOfSpaceMessage =
                             !layout.findCellForSpan(null, itemInfo.spanX, itemInfo.spanY);
                 }
@@ -559,8 +555,9 @@
     @Override
     public void onLauncherTransitionEnd(Launcher l, boolean animated, boolean toWorkspace) {
         if (toWorkspace) {
-            // Reset the search bar after transitioning home
+            // Reset the search bar and base recycler view after transitioning home
             mSearchBarController.reset();
+            mAppsRecyclerView.reset();
         }
     }
 
@@ -616,19 +613,16 @@
     @Override
     public void onSearchResult(String query, ArrayList<ComponentKey> apps) {
         if (apps != null) {
-            if (apps.isEmpty()) {
-                String formatStr = getResources().getString(R.string.all_apps_no_search_results);
-                mAdapter.setEmptySearchText(String.format(formatStr, query));
-            } else {
-                mAppsRecyclerView.scrollToTop();
-            }
             mApps.setOrderedFilter(apps);
+            mAdapter.setLastSearchQuery(query);
+            mAppsRecyclerView.onSearchResultsChanged();
         }
     }
 
     @Override
     public void clearSearchResult() {
         mApps.setOrderedFilter(null);
+        mAppsRecyclerView.onSearchResultsChanged();
 
         // Clear the search query
         mSearchQueryBuilder.clear();
diff --git a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
index 057883c..1f95133 100644
--- a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
+++ b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
@@ -16,21 +16,30 @@
 package com.android.launcher3.allapps;
 
 import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
 import android.content.res.Resources;
 import android.graphics.Canvas;
 import android.graphics.Paint;
 import android.graphics.PointF;
 import android.graphics.Rect;
-import android.os.Handler;
+import android.graphics.drawable.Drawable;
+import android.support.v4.view.accessibility.AccessibilityRecordCompat;
+import android.support.v4.view.accessibility.AccessibilityEventCompat;
+import android.net.Uri;
 import android.support.v7.widget.GridLayoutManager;
 import android.support.v7.widget.RecyclerView;
+import android.view.Gravity;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewConfiguration;
 import android.view.ViewGroup;
+import android.view.accessibility.AccessibilityEvent;
 import android.widget.TextView;
 import com.android.launcher3.AppInfo;
 import com.android.launcher3.BubbleTextView;
+import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.util.Thunk;
@@ -42,7 +51,7 @@
 /**
  * The grid view adapter of all the apps.
  */
-class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.ViewHolder> {
+public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.ViewHolder> {
 
     public static final String TAG = "AppsGridAdapter";
     private static final boolean DEBUG = false;
@@ -55,6 +64,10 @@
     public static final int PREDICTION_ICON_VIEW_TYPE = 2;
     // The message shown when there are no filtered results
     public static final int EMPTY_SEARCH_VIEW_TYPE = 3;
+    // A divider that separates the apps list and the search market button
+    public static final int SEARCH_MARKET_DIVIDER_VIEW_TYPE = 4;
+    // The message to continue to a market search when there are no filtered results
+    public static final int SEARCH_MARKET_VIEW_TYPE = 5;
 
     /**
      * ViewHolder for each icon.
@@ -69,6 +82,38 @@
     }
 
     /**
+     * A subclass of GridLayoutManager that overrides accessibility values during app search.
+     */
+    public class AppsGridLayoutManager extends GridLayoutManager {
+
+        public AppsGridLayoutManager(Context context) {
+            super(context, 1, GridLayoutManager.VERTICAL, false);
+        }
+
+        @Override
+        public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
+            super.onInitializeAccessibilityEvent(event);
+
+            // Ensure that we only report the number apps for accessibility not including other
+            // adapter views
+            final AccessibilityRecordCompat record = AccessibilityEventCompat
+                    .asRecord(event);
+            record.setItemCount(mApps.getNumFilteredApps());
+        }
+
+        @Override
+        public int getRowCountForAccessibility(RecyclerView.Recycler recycler,
+                RecyclerView.State state) {
+            if (mApps.hasNoFilteredResults()) {
+                // Disregard the no-search-results text as a list item for accessibility
+                return 0;
+            } else {
+                return super.getRowCountForAccessibility(recycler, state);
+            }
+        }
+    }
+
+    /**
      * Helper class to size the grid items.
      */
     public class GridSpanSizer extends GridLayoutManager.SpanSizeLookup {
@@ -80,11 +125,6 @@
 
         @Override
         public int getSpanSize(int position) {
-            if (mApps.hasNoFilteredResults()) {
-                // Empty view spans full width
-                return mAppsPerRow;
-            }
-
             switch (mApps.getAdapterItems().get(position).viewType) {
                 case AllAppsGridAdapter.ICON_VIEW_TYPE:
                 case AllAppsGridAdapter.PREDICTION_ICON_VIEW_TYPE:
@@ -279,6 +319,7 @@
         }
     }
 
+    private Launcher mLauncher;
     private LayoutInflater mLayoutInflater;
     @Thunk AlphabeticalAppsList mApps;
     private GridLayoutManager mGridLayoutMgr;
@@ -291,7 +332,19 @@
     @Thunk int mPredictionBarDividerOffset;
     @Thunk int mAppsPerRow;
     @Thunk boolean mIsRtl;
-    private String mEmptySearchText;
+
+    // The text to show when there are no search results and no market search handler.
+    private String mEmptySearchMessage;
+    // The name of the market app which handles searches, to be used in the format str
+    // below when updating the search-market view.  Only needs to be loaded once.
+    private String mMarketAppName;
+    // The text to show when there is a market app which can handle a specific query, updated
+    // each time the search query changes.
+    private String mMarketSearchMessage;
+    // The intent to send off to the market app, updated each time the search query changes.
+    private Intent mMarketSearchIntent;
+    // The last query that the user entered into the search field
+    private String mLastSearchQuery;
 
     // Section drawing
     @Thunk int mSectionNamesMargin;
@@ -299,16 +352,18 @@
     @Thunk Paint mSectionTextPaint;
     @Thunk Paint mPredictedAppsDividerPaint;
 
-    public AllAppsGridAdapter(Context context, AlphabeticalAppsList apps,
+    public AllAppsGridAdapter(Launcher launcher, AlphabeticalAppsList apps,
             View.OnTouchListener touchListener, View.OnClickListener iconClickListener,
             View.OnLongClickListener iconLongClickListener) {
-        Resources res = context.getResources();
+        Resources res = launcher.getResources();
+        mLauncher = launcher;
         mApps = apps;
+        mEmptySearchMessage = res.getString(R.string.all_apps_loading_message);
         mGridSizer = new GridSpanSizer();
-        mGridLayoutMgr = new GridLayoutManager(context, 1, GridLayoutManager.VERTICAL, false);
+        mGridLayoutMgr = new AppsGridLayoutManager(launcher);
         mGridLayoutMgr.setSpanSizeLookup(mGridSizer);
         mItemDecoration = new GridItemDecoration();
-        mLayoutInflater = LayoutInflater.from(context);
+        mLayoutInflater = LayoutInflater.from(launcher);
         mTouchListener = touchListener;
         mIconClickListener = iconClickListener;
         mIconLongClickListener = iconLongClickListener;
@@ -328,6 +383,14 @@
         mPredictionBarDividerOffset =
                 (-res.getDimensionPixelSize(R.dimen.all_apps_prediction_icon_bottom_padding) +
                         res.getDimensionPixelSize(R.dimen.all_apps_icon_top_bottom_padding)) / 2;
+
+        // Resolve the market app handling additional searches
+        PackageManager pm = launcher.getPackageManager();
+        ResolveInfo marketInfo = pm.resolveActivity(createMarketSearchIntent(""),
+                PackageManager.MATCH_DEFAULT_ONLY);
+        if (marketInfo != null) {
+            mMarketAppName = marketInfo.loadLabel(pm).toString();
+        }
     }
 
     /**
@@ -346,10 +409,19 @@
     }
 
     /**
-     * Sets the text to show when there are no apps.
+     * Sets the last search query that was made, used to show when there are no results and to also
+     * seed the intent for searching the market.
      */
-    public void setEmptySearchText(String query) {
-        mEmptySearchText = query;
+    public void setLastSearchQuery(String query) {
+        Resources res = mLauncher.getResources();
+        String formatStr = res.getString(R.string.all_apps_no_search_results);
+        mLastSearchQuery = query;
+        mEmptySearchMessage = String.format(formatStr, query);
+        if (mMarketAppName != null) {
+            mMarketSearchMessage = String.format(res.getString(R.string.all_apps_search_market_message),
+                    mMarketAppName);
+            mMarketSearchIntent = createMarketSearchIntent(query);
+        }
     }
 
     /**
@@ -378,9 +450,6 @@
     @Override
     public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
         switch (viewType) {
-            case EMPTY_SEARCH_VIEW_TYPE:
-                return new ViewHolder(mLayoutInflater.inflate(R.layout.all_apps_empty_search, parent,
-                        false));
             case SECTION_BREAK_VIEW_TYPE:
                 return new ViewHolder(new View(parent.getContext()));
             case ICON_VIEW_TYPE: {
@@ -405,6 +474,22 @@
                 icon.setFocusable(true);
                 return new ViewHolder(icon);
             }
+            case EMPTY_SEARCH_VIEW_TYPE:
+                return new ViewHolder(mLayoutInflater.inflate(R.layout.all_apps_empty_search,
+                        parent, false));
+            case SEARCH_MARKET_DIVIDER_VIEW_TYPE:
+                return new ViewHolder(mLayoutInflater.inflate(R.layout.all_apps_search_market_divider,
+                        parent, false));
+            case SEARCH_MARKET_VIEW_TYPE:
+                View searchMarketView = mLayoutInflater.inflate(R.layout.all_apps_search_market,
+                        parent, false);
+                searchMarketView.setOnClickListener(new View.OnClickListener() {
+                    @Override
+                    public void onClick(View v) {
+                        mLauncher.startSearchFromAllApps(v, mMarketSearchIntent, mLastSearchQuery);
+                    }
+                });
+                return new ViewHolder(searchMarketView);
             default:
                 throw new RuntimeException("Unexpected view type");
         }
@@ -426,28 +511,47 @@
                 break;
             }
             case EMPTY_SEARCH_VIEW_TYPE:
-                TextView emptyViewText = (TextView) holder.mContent.findViewById(R.id.empty_text);
-                emptyViewText.setText(mEmptySearchText);
+                TextView emptyViewText = (TextView) holder.mContent;
+                emptyViewText.setText(mEmptySearchMessage);
+                emptyViewText.setGravity(mApps.hasNoFilteredResults() ? Gravity.CENTER :
+                        Gravity.START | Gravity.CENTER_VERTICAL);
+                break;
+            case SEARCH_MARKET_VIEW_TYPE:
+                TextView searchView = (TextView) holder.mContent;
+                if (mMarketSearchIntent != null) {
+                    searchView.setVisibility(View.VISIBLE);
+                    searchView.setContentDescription(mMarketSearchMessage);
+                    searchView.setGravity(mApps.hasNoFilteredResults() ? Gravity.CENTER :
+                            Gravity.START | Gravity.CENTER_VERTICAL);
+                    searchView.setText(mMarketSearchMessage);
+                } else {
+                    searchView.setVisibility(View.GONE);
+                }
                 break;
         }
     }
 
     @Override
     public int getItemCount() {
-        if (mApps.hasNoFilteredResults()) {
-            // For the empty view
-            return 1;
-        }
         return mApps.getAdapterItems().size();
     }
 
     @Override
     public int getItemViewType(int position) {
-        if (mApps.hasNoFilteredResults()) {
-            return EMPTY_SEARCH_VIEW_TYPE;
-        }
-
         AlphabeticalAppsList.AdapterItem item = mApps.getAdapterItems().get(position);
         return item.viewType;
     }
+
+    /**
+     * Creates a new market search intent.
+     */
+    private Intent createMarketSearchIntent(String query) {
+        Uri marketSearchUri = Uri.parse("market://search")
+                .buildUpon()
+                .appendQueryParameter("q", query)
+                .build();
+        Intent marketSearchIntent = new Intent(Intent.ACTION_VIEW);
+        marketSearchIntent.setData(marketSearchUri);
+        return marketSearchIntent;
+    }
 }
diff --git a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
index 730c8d1..2f66e2c 100644
--- a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
@@ -15,8 +15,11 @@
  */
 package com.android.launcher3.allapps;
 
+import android.animation.ObjectAnimator;
 import android.content.Context;
+import android.content.res.Resources;
 import android.graphics.Canvas;
+import android.graphics.drawable.Drawable;
 import android.os.Bundle;
 import android.support.v7.widget.LinearLayoutManager;
 import android.support.v7.widget.RecyclerView;
@@ -26,6 +29,7 @@
 import com.android.launcher3.BaseRecyclerView;
 import com.android.launcher3.BaseRecyclerViewFastScrollBar;
 import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.R;
 import com.android.launcher3.Stats;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.util.Thunk;
@@ -57,6 +61,9 @@
 
     private ScrollPositionState mScrollPosState = new ScrollPositionState();
 
+    private AllAppsBackgroundDrawable mEmptySearchBackground;
+    private int mEmptySearchBackgroundTopOffset;
+
     public AllAppsRecyclerView(Context context) {
         this(context, null);
     }
@@ -72,6 +79,11 @@
     public AllAppsRecyclerView(Context context, AttributeSet attrs, int defStyleAttr,
             int defStyleRes) {
         super(context, attrs, defStyleAttr);
+
+        Resources res = getResources();
+        mScrollbar.setDetachThumbOnFastScroll();
+        mEmptySearchBackgroundTopOffset = res.getDimensionPixelSize(
+                R.dimen.all_apps_empty_search_bg_top_offset);
     }
 
     /**
@@ -90,6 +102,8 @@
         RecyclerView.RecycledViewPool pool = getRecycledViewPool();
         int approxRows = (int) Math.ceil(grid.availableHeightPx / grid.allAppsIconSizePx);
         pool.setMaxRecycledViews(AllAppsGridAdapter.EMPTY_SEARCH_VIEW_TYPE, 1);
+        pool.setMaxRecycledViews(AllAppsGridAdapter.SEARCH_MARKET_DIVIDER_VIEW_TYPE, 1);
+        pool.setMaxRecycledViews(AllAppsGridAdapter.SEARCH_MARKET_VIEW_TYPE, 1);
         pool.setMaxRecycledViews(AllAppsGridAdapter.ICON_VIEW_TYPE, approxRows * mNumAppsPerRow);
         pool.setMaxRecycledViews(AllAppsGridAdapter.PREDICTION_ICON_VIEW_TYPE, mNumAppsPerRow);
         pool.setMaxRecycledViews(AllAppsGridAdapter.SECTION_BREAK_VIEW_TYPE, approxRows);
@@ -99,6 +113,10 @@
      * Scrolls this recycler view to the top.
      */
     public void scrollToTop() {
+        // Ensure we reattach the scrollbar if it was previously detached while fast-scrolling
+        if (mScrollbar.isThumbDetached()) {
+            mScrollbar.reattachThumbToScroll();
+        }
         scrollToPosition(0);
     }
 
@@ -115,6 +133,30 @@
     }
 
     @Override
+    public void onDraw(Canvas c) {
+        // Draw the background
+        if (mEmptySearchBackground != null && mEmptySearchBackground.getAlpha() > 0) {
+            c.clipRect(mBackgroundPadding.left, mBackgroundPadding.top,
+                    getWidth() - mBackgroundPadding.right,
+                    getHeight() - mBackgroundPadding.bottom);
+
+            mEmptySearchBackground.draw(c);
+        }
+
+        super.onDraw(c);
+    }
+
+    @Override
+    protected boolean verifyDrawable(Drawable who) {
+        return who == mEmptySearchBackground || super.verifyDrawable(who);
+    }
+
+    @Override
+    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+        updateEmptySearchBackgroundBounds();
+    }
+
+    @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
 
@@ -134,6 +176,25 @@
         }
     }
 
+    public void onSearchResultsChanged() {
+        // Always scroll the view to the top so the user can see the changed results
+        scrollToTop();
+
+        if (mApps.hasNoFilteredResults()) {
+            if (mEmptySearchBackground == null) {
+                mEmptySearchBackground = new AllAppsBackgroundDrawable(getContext());
+                mEmptySearchBackground.setAlpha(0);
+                mEmptySearchBackground.setCallback(this);
+                updateEmptySearchBackgroundBounds();
+            }
+            mEmptySearchBackground.animateBgAlpha(1f, 150);
+        } else if (mEmptySearchBackground != null) {
+            // For the time being, we just immediately hide the background to ensure that it does
+            // not overlap with the results
+            mEmptySearchBackground.setBgAlpha(0f);
+        }
+    }
+
     /**
      * Maps the touch (from 0..1) to the adapter position that should be visible.
      */
@@ -166,8 +227,8 @@
         }
 
         // Map the touch position back to the scroll of the recycler view
-        getCurScrollState(mScrollPosState, mApps.getAdapterItems());
-        int availableScrollHeight = getAvailableScrollHeight(rowCount, mScrollPosState.rowHeight, 0);
+        getCurScrollState(mScrollPosState);
+        int availableScrollHeight = getAvailableScrollHeight(rowCount, mScrollPosState.rowHeight);
         LinearLayoutManager layoutManager = (LinearLayoutManager) getLayoutManager();
         if (mFastScrollMode == FAST_SCROLL_MODE_FREE_SCROLL) {
             layoutManager.scrollToPositionWithOffset(0, (int) -(availableScrollHeight * touchFraction));
@@ -214,24 +275,83 @@
      * Updates the bounds for the scrollbar.
      */
     @Override
-    public void onUpdateScrollbar() {
+    public void onUpdateScrollbar(int dy) {
         List<AlphabeticalAppsList.AdapterItem> items = mApps.getAdapterItems();
 
         // Skip early if there are no items or we haven't been measured
         if (items.isEmpty() || mNumAppsPerRow == 0) {
-            mScrollbar.setScrollbarThumbOffset(-1, -1);
+            mScrollbar.setThumbOffset(-1, -1);
             return;
         }
 
         // Find the index and height of the first visible row (all rows have the same height)
         int rowCount = mApps.getNumAppRows();
-        getCurScrollState(mScrollPosState, items);
+        getCurScrollState(mScrollPosState);
         if (mScrollPosState.rowIndex < 0) {
-            mScrollbar.setScrollbarThumbOffset(-1, -1);
+            mScrollbar.setThumbOffset(-1, -1);
             return;
         }
 
-        synchronizeScrollBarThumbOffsetToViewScroll(mScrollPosState, rowCount, 0);
+        // Only show the scrollbar if there is height to be scrolled
+        int availableScrollBarHeight = getAvailableScrollBarHeight();
+        int availableScrollHeight = getAvailableScrollHeight(mApps.getNumAppRows(), mScrollPosState.rowHeight);
+        if (availableScrollHeight <= 0) {
+            mScrollbar.setThumbOffset(-1, -1);
+            return;
+        }
+
+        // Calculate the current scroll position, the scrollY of the recycler view accounts for the
+        // view padding, while the scrollBarY is drawn right up to the background padding (ignoring
+        // padding)
+        int scrollY = getPaddingTop() +
+                (mScrollPosState.rowIndex * mScrollPosState.rowHeight) - mScrollPosState.rowTopOffset;
+        int scrollBarY = mBackgroundPadding.top +
+                (int) (((float) scrollY / availableScrollHeight) * availableScrollBarHeight);
+
+        if (mScrollbar.isThumbDetached()) {
+            int scrollBarX;
+            if (Utilities.isRtl(getResources())) {
+                scrollBarX = mBackgroundPadding.left;
+            } else {
+                scrollBarX = getWidth() - mBackgroundPadding.right - mScrollbar.getThumbWidth();
+            }
+
+            if (mScrollbar.isDraggingThumb()) {
+                // If the thumb is detached, then just update the thumb to the current
+                // touch position
+                mScrollbar.setThumbOffset(scrollBarX, (int) mScrollbar.getLastTouchY());
+            } else {
+                int thumbScrollY = mScrollbar.getThumbOffset().y;
+                int diffScrollY = scrollBarY - thumbScrollY;
+                if (diffScrollY * dy > 0f) {
+                    // User is scrolling in the same direction the thumb needs to catch up to the
+                    // current scroll position.  We do this by mapping the difference in movement
+                    // from the original scroll bar position to the difference in movement necessary
+                    // in the detached thumb position to ensure that both speed towards the same
+                    // position at either end of the list.
+                    if (dy < 0) {
+                        int offset = (int) ((dy * thumbScrollY) / (float) scrollBarY);
+                        thumbScrollY += Math.max(offset, diffScrollY);
+                    } else {
+                        int offset = (int) ((dy * (availableScrollBarHeight - thumbScrollY)) /
+                                (float) (availableScrollBarHeight - scrollBarY));
+                        thumbScrollY += Math.min(offset, diffScrollY);
+                    }
+                    thumbScrollY = Math.max(0, Math.min(availableScrollBarHeight, thumbScrollY));
+                    mScrollbar.setThumbOffset(scrollBarX, thumbScrollY);
+                    if (scrollBarY == thumbScrollY) {
+                        mScrollbar.reattachThumbToScroll();
+                    }
+                } else {
+                    // User is scrolling in an opposite direction to the direction that the thumb
+                    // needs to catch up to the scroll position.  Do nothing except for updating
+                    // the scroll bar x to match the thumb width.
+                    mScrollbar.setThumbOffset(scrollBarX, thumbScrollY);
+                }
+            }
+        } else {
+            synchronizeScrollBarThumbOffsetToViewScroll(mScrollPosState, rowCount);
+        }
     }
 
     /**
@@ -283,13 +403,13 @@
     /**
      * Returns the current scroll state of the apps rows.
      */
-    private void getCurScrollState(ScrollPositionState stateOut,
-            List<AlphabeticalAppsList.AdapterItem> items) {
+    protected void getCurScrollState(ScrollPositionState stateOut) {
         stateOut.rowIndex = -1;
         stateOut.rowTopOffset = -1;
         stateOut.rowHeight = -1;
 
         // Return early if there are no items or we haven't been measured
+        List<AlphabeticalAppsList.AdapterItem> items = mApps.getAdapterItems();
         if (items.isEmpty() || mNumAppsPerRow == 0) {
             return;
         }
@@ -324,4 +444,20 @@
             return 0;
         }
     }
+
+    /**
+     * Updates the bounds of the empty search background.
+     */
+    private void updateEmptySearchBackgroundBounds() {
+        if (mEmptySearchBackground == null) {
+            return;
+        }
+
+        // Center the empty search background on this new view bounds
+        int x = (getMeasuredWidth() - mEmptySearchBackground.getIntrinsicWidth()) / 2;
+        int y = mEmptySearchBackgroundTopOffset;
+        mEmptySearchBackground.setBounds(x, y,
+                x + mEmptySearchBackground.getIntrinsicWidth(),
+                y + mEmptySearchBackground.getIntrinsicHeight());
+    }
 }
diff --git a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
index 47241ce..dac0df1 100644
--- a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
+++ b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
@@ -43,6 +43,11 @@
     private static final boolean DEBUG = false;
     private static final boolean DEBUG_PREDICTIONS = false;
 
+    private static final int FAST_SCROLL_FRACTION_DISTRIBUTE_BY_ROWS_FRACTION = 0;
+    private static final int FAST_SCROLL_FRACTION_DISTRIBUTE_BY_NUM_SECTIONS = 1;
+
+    private final int mFastScrollDistributionMode = FAST_SCROLL_FRACTION_DISTRIBUTE_BY_NUM_SECTIONS;
+
     /**
      * Info about a section in the alphabetic list
      */
@@ -81,8 +86,6 @@
         public int position;
         // The type of this item
         public int viewType;
-        // The row that this item shows up on
-        public int rowIndex;
 
         /** Section & App properties */
         // The section for this item
@@ -94,6 +97,8 @@
         public String sectionName = null;
         // The index of this app in the section
         public int sectionAppIndex = -1;
+        // The row that this item shows up on
+        public int rowIndex;
         // The index of this app in the row
         public int rowAppIndex;
         // The associated AppInfo for the app
@@ -111,14 +116,14 @@
         }
 
         public static AdapterItem asPredictedApp(int pos, SectionInfo section, String sectionName,
-                                        int sectionAppIndex, AppInfo appInfo, int appIndex) {
+                int sectionAppIndex, AppInfo appInfo, int appIndex) {
             AdapterItem item = asApp(pos, section, sectionName, sectionAppIndex, appInfo, appIndex);
             item.viewType = AllAppsGridAdapter.PREDICTION_ICON_VIEW_TYPE;
             return item;
         }
 
         public static AdapterItem asApp(int pos, SectionInfo section, String sectionName,
-                                        int sectionAppIndex, AppInfo appInfo, int appIndex) {
+                int sectionAppIndex, AppInfo appInfo, int appIndex) {
             AdapterItem item = new AdapterItem();
             item.viewType = AllAppsGridAdapter.ICON_VIEW_TYPE;
             item.position = pos;
@@ -129,6 +134,27 @@
             item.appIndex = appIndex;
             return item;
         }
+
+        public static AdapterItem asEmptySearch(int pos) {
+            AdapterItem item = new AdapterItem();
+            item.viewType = AllAppsGridAdapter.EMPTY_SEARCH_VIEW_TYPE;
+            item.position = pos;
+            return item;
+        }
+
+        public static AdapterItem asDivider(int pos) {
+            AdapterItem item = new AdapterItem();
+            item.viewType = AllAppsGridAdapter.SEARCH_MARKET_DIVIDER_VIEW_TYPE;
+            item.position = pos;
+            return item;
+        }
+
+        public static AdapterItem asMarketSearch(int pos) {
+            AdapterItem item = new AdapterItem();
+            item.viewType = AllAppsGridAdapter.SEARCH_MARKET_VIEW_TYPE;
+            item.position = pos;
+            return item;
+        }
     }
 
     /**
@@ -222,13 +248,6 @@
     }
 
     /**
-     * Returns the number of applications in this list.
-     */
-    public int getSize() {
-        return mFilteredApps.size();
-    }
-
-    /**
      * Returns the number of rows of applications (not including predictions)
      */
     public int getNumAppRows() {
@@ -236,6 +255,13 @@
     }
 
     /**
+     * Returns the number of applications in this list.
+     */
+    public int getNumFilteredApps() {
+        return mFilteredApps.size();
+    }
+
+    /**
      * Returns whether there are is a filter set.
      */
     public boolean hasFilter() {
@@ -457,6 +483,16 @@
             mFilteredApps.add(info);
         }
 
+        // Append the search market item if we are currently searching
+        if (hasFilter()) {
+            if (hasNoFilteredResults()) {
+                mAdapterItems.add(AdapterItem.asEmptySearch(position++));
+            } else {
+                mAdapterItems.add(AdapterItem.asDivider(position++));
+            }
+            mAdapterItems.add(AdapterItem.asMarketSearch(position++));
+        }
+
         // Merge multiple sections together as requested by the merge strategy for this device
         mergeSections();
 
@@ -484,18 +520,36 @@
             }
             mNumAppRowsInAdapter = rowIndex + 1;
 
-            // Pre-calculate all the fast scroller fractions based on the number of rows
-            float rowFraction = 1f / mNumAppRowsInAdapter;
-            for (FastScrollSectionInfo info : mFastScrollerSections) {
-                AdapterItem item = info.fastScrollToItem;
-                if (item.viewType != AllAppsGridAdapter.ICON_VIEW_TYPE &&
-                        item.viewType != AllAppsGridAdapter.PREDICTION_ICON_VIEW_TYPE) {
-                    info.touchFraction = 0f;
-                    continue;
-                }
+            // Pre-calculate all the fast scroller fractions
+            switch (mFastScrollDistributionMode) {
+                case FAST_SCROLL_FRACTION_DISTRIBUTE_BY_ROWS_FRACTION:
+                    float rowFraction = 1f / mNumAppRowsInAdapter;
+                    for (FastScrollSectionInfo info : mFastScrollerSections) {
+                        AdapterItem item = info.fastScrollToItem;
+                        if (item.viewType != AllAppsGridAdapter.ICON_VIEW_TYPE &&
+                                item.viewType != AllAppsGridAdapter.PREDICTION_ICON_VIEW_TYPE) {
+                            info.touchFraction = 0f;
+                            continue;
+                        }
 
-                float subRowFraction = item.rowAppIndex * (rowFraction / mNumAppsPerRow);
-                info.touchFraction = item.rowIndex * rowFraction + subRowFraction;
+                        float subRowFraction = item.rowAppIndex * (rowFraction / mNumAppsPerRow);
+                        info.touchFraction = item.rowIndex * rowFraction + subRowFraction;
+                    }
+                    break;
+                case FAST_SCROLL_FRACTION_DISTRIBUTE_BY_NUM_SECTIONS:
+                    float perSectionTouchFraction = 1f / mFastScrollerSections.size();
+                    float cumulativeTouchFraction = 0f;
+                    for (FastScrollSectionInfo info : mFastScrollerSections) {
+                        AdapterItem item = info.fastScrollToItem;
+                        if (item.viewType != AllAppsGridAdapter.ICON_VIEW_TYPE &&
+                                item.viewType != AllAppsGridAdapter.PREDICTION_ICON_VIEW_TYPE) {
+                            info.touchFraction = 0f;
+                            continue;
+                        }
+                        info.touchFraction = cumulativeTouchFraction;
+                        cumulativeTouchFraction += perSectionTouchFraction;
+                    }
+                    break;
             }
         }
 
diff --git a/src/com/android/launcher3/allapps/DefaultAppSearchController.java b/src/com/android/launcher3/allapps/DefaultAppSearchController.java
index 83b9205..3169f84 100644
--- a/src/com/android/launcher3/allapps/DefaultAppSearchController.java
+++ b/src/com/android/launcher3/allapps/DefaultAppSearchController.java
@@ -25,6 +25,7 @@
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputMethodManager;
 import android.widget.TextView;
+import com.android.launcher3.ExtendedEditText;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.util.Thunk;
@@ -54,7 +55,8 @@
     @Thunk View mSearchBarContainerView;
     private View mSearchButtonView;
     private View mDismissSearchButtonView;
-    @Thunk AllAppsSearchEditView mSearchBarEditView;
+    @Thunk
+    ExtendedEditText mSearchBarEditView;
     @Thunk AllAppsRecyclerView mAppsRecyclerView;
     @Thunk Runnable mFocusRecyclerViewRunnable = new Runnable() {
         @Override
@@ -82,21 +84,23 @@
         mSearchBarContainerView = mSearchView.findViewById(R.id.search_container);
         mDismissSearchButtonView = mSearchBarContainerView.findViewById(R.id.dismiss_search_button);
         mDismissSearchButtonView.setOnClickListener(this);
-        mSearchBarEditView = (AllAppsSearchEditView)
+        mSearchBarEditView = (ExtendedEditText)
                 mSearchBarContainerView.findViewById(R.id.search_box_input);
         mSearchBarEditView.addTextChangedListener(this);
         mSearchBarEditView.setOnEditorActionListener(this);
         mSearchBarEditView.setOnBackKeyListener(
-                new AllAppsSearchEditView.OnBackKeyListener() {
+                new ExtendedEditText.OnBackKeyListener() {
                     @Override
-                    public void onBackKey() {
+                    public boolean onBackKey() {
                         // Only hide the search field if there is no query, or if there
                         // are no filtered results
                         String query = Utilities.trim(
                                 mSearchBarEditView.getEditableText().toString());
                         if (query.isEmpty() || mApps.hasNoFilteredResults()) {
                             hideSearchField(true, mFocusRecyclerViewRunnable);
+                            return true;
                         }
+                        return false;
                     }
                 });
         return mSearchView;
@@ -166,22 +170,24 @@
             return false;
         }
         // Skip if it's not the right action
-        if (actionId != EditorInfo.IME_ACTION_DONE) {
+        if (actionId != EditorInfo.IME_ACTION_SEARCH) {
             return false;
         }
-        // Skip if there isn't exactly one item
-        if (mApps.getSize() != 1) {
+        // Skip if there are more than one icon
+        if (mApps.getNumFilteredApps() > 1) {
             return false;
         }
-        // If there is exactly one icon, then quick-launch it
+        // Otherwise, find the first icon, or fallback to the search-market-view and launch it
         List<AlphabeticalAppsList.AdapterItem> items = mApps.getAdapterItems();
         for (int i = 0; i < items.size(); i++) {
             AlphabeticalAppsList.AdapterItem item = items.get(i);
-            if (item.viewType == AllAppsGridAdapter.ICON_VIEW_TYPE) {
-                mAppsRecyclerView.getChildAt(i).performClick();
-                mInputMethodManager.hideSoftInputFromWindow(
-                        mContainerView.getWindowToken(), 0);
-                return true;
+            switch (item.viewType) {
+                case AllAppsGridAdapter.ICON_VIEW_TYPE:
+                case AllAppsGridAdapter.SEARCH_MARKET_VIEW_TYPE:
+                    mAppsRecyclerView.getChildAt(i).performClick();
+                    mInputMethodManager.hideSoftInputFromWindow(
+                            mContainerView.getWindowToken(), 0);
+                    return true;
             }
         }
         return false;
diff --git a/src/com/android/launcher3/compat/AppWidgetManagerCompat.java b/src/com/android/launcher3/compat/AppWidgetManagerCompat.java
index 7aa36d4..434f13d 100644
--- a/src/com/android/launcher3/compat/AppWidgetManagerCompat.java
+++ b/src/com/android/launcher3/compat/AppWidgetManagerCompat.java
@@ -40,7 +40,7 @@
     public static AppWidgetManagerCompat getInstance(Context context) {
         synchronized (sInstanceLock) {
             if (sInstance == null) {
-                if (Utilities.isLmpOrAbove()) {
+                if (Utilities.ATLEAST_LOLLIPOP) {
                     sInstance = new AppWidgetManagerCompatVL(context.getApplicationContext());
                 } else {
                     sInstance = new AppWidgetManagerCompatV16(context.getApplicationContext());
diff --git a/src/com/android/launcher3/compat/AppWidgetManagerCompatV16.java b/src/com/android/launcher3/compat/AppWidgetManagerCompatV16.java
index f7f4b7e..463cf90 100644
--- a/src/com/android/launcher3/compat/AppWidgetManagerCompatV16.java
+++ b/src/com/android/launcher3/compat/AppWidgetManagerCompatV16.java
@@ -54,10 +54,10 @@
     @Override
     public boolean bindAppWidgetIdIfAllowed(int appWidgetId, AppWidgetProviderInfo info,
             Bundle options) {
-        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) {
-            return mAppWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId, info.provider);
-        } else {
+        if (Utilities.ATLEAST_JB_MR1) {
             return mAppWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId, info.provider, options);
+        } else {
+            return mAppWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId, info.provider);
         }
     }
 
diff --git a/src/com/android/launcher3/compat/LauncherAppsCompat.java b/src/com/android/launcher3/compat/LauncherAppsCompat.java
index 5858bc8..95e3ba9 100644
--- a/src/com/android/launcher3/compat/LauncherAppsCompat.java
+++ b/src/com/android/launcher3/compat/LauncherAppsCompat.java
@@ -53,7 +53,7 @@
     public static LauncherAppsCompat getInstance(Context context) {
         synchronized (sInstanceLock) {
             if (sInstance == null) {
-                if (Utilities.isLmpOrAbove()) {
+                if (Utilities.ATLEAST_LOLLIPOP) {
                     sInstance = new LauncherAppsCompatVL(context.getApplicationContext());
                 } else {
                     sInstance = new LauncherAppsCompatV16(context.getApplicationContext());
diff --git a/src/com/android/launcher3/compat/LauncherAppsCompatV16.java b/src/com/android/launcher3/compat/LauncherAppsCompatV16.java
index ac3d252..339c457 100644
--- a/src/com/android/launcher3/compat/LauncherAppsCompatV16.java
+++ b/src/com/android/launcher3/compat/LauncherAppsCompatV16.java
@@ -31,6 +31,7 @@
 import android.os.Bundle;
 import android.provider.Settings;
 
+import com.android.launcher3.Utilities;
 import com.android.launcher3.util.Thunk;
 
 import java.util.ArrayList;
@@ -188,7 +189,7 @@
                 // when moving a package or mounting/un-mounting external storage. Assume that
                 // it is a replacing operation.
                 final boolean replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING,
-                        Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT);
+                        !Utilities.ATLEAST_KITKAT);
                 String[] packages = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
                 for (OnAppsChangedCallbackCompat callback : getCallbacks()) {
                     callback.onPackagesAvailable(packages, user, replacing);
diff --git a/src/com/android/launcher3/compat/PackageInstallerCompat.java b/src/com/android/launcher3/compat/PackageInstallerCompat.java
index c499083..ec5014d 100644
--- a/src/com/android/launcher3/compat/PackageInstallerCompat.java
+++ b/src/com/android/launcher3/compat/PackageInstallerCompat.java
@@ -34,7 +34,7 @@
     public static PackageInstallerCompat getInstance(Context context) {
         synchronized (sInstanceLock) {
             if (sInstance == null) {
-                if (Utilities.isLmpOrAbove()) {
+                if (Utilities.ATLEAST_LOLLIPOP) {
                     sInstance = new PackageInstallerCompatVL(context);
                 } else {
                     sInstance = new PackageInstallerCompatV16();
diff --git a/src/com/android/launcher3/compat/UserHandleCompat.java b/src/com/android/launcher3/compat/UserHandleCompat.java
index ab4b721..567022b 100644
--- a/src/com/android/launcher3/compat/UserHandleCompat.java
+++ b/src/com/android/launcher3/compat/UserHandleCompat.java
@@ -34,7 +34,7 @@
 
     @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
     public static UserHandleCompat myUserHandle() {
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+        if (Utilities.ATLEAST_JB_MR1) {
             return new UserHandleCompat(android.os.Process.myUserHandle());
         } else {
             return new UserHandleCompat();
@@ -55,7 +55,7 @@
 
     @Override
     public String toString() {
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+        if (Utilities.ATLEAST_JB_MR1) {
             return mUser.toString();
         } else {
             return "";
@@ -67,7 +67,7 @@
         if (!(other instanceof UserHandleCompat)) {
             return false;
         }
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+        if (Utilities.ATLEAST_JB_MR1) {
             return mUser.equals(((UserHandleCompat) other).mUser);
         } else {
             return true;
@@ -76,7 +76,7 @@
 
     @Override
     public int hashCode() {
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+        if (Utilities.ATLEAST_JB_MR1) {
             return mUser.hashCode();
         } else {
             return 0;
@@ -89,7 +89,7 @@
      * profiles so this is a no-op.
      */
     public void addToIntent(Intent intent, String name) {
-        if (Utilities.isLmpOrAbove() && mUser != null) {
+        if (Utilities.ATLEAST_LOLLIPOP && mUser != null) {
             intent.putExtra(name, mUser);
         }
     }
diff --git a/src/com/android/launcher3/compat/UserManagerCompat.java b/src/com/android/launcher3/compat/UserManagerCompat.java
index a79d946..f708004 100644
--- a/src/com/android/launcher3/compat/UserManagerCompat.java
+++ b/src/com/android/launcher3/compat/UserManagerCompat.java
@@ -28,16 +28,29 @@
     protected UserManagerCompat() {
     }
 
+    private static final Object sInstanceLock = new Object();
+    private static UserManagerCompat sInstance;
+
     public static UserManagerCompat getInstance(Context context) {
-        if (Utilities.isLmpOrAbove()) {
-            return new UserManagerCompatVL(context);
-        } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
-            return new UserManagerCompatV17(context);
-        } else {
-            return new UserManagerCompatV16();
+        synchronized (sInstanceLock) {
+            if (sInstance == null) {
+                if (Utilities.ATLEAST_LOLLIPOP) {
+                    sInstance = new UserManagerCompatVL(context.getApplicationContext());
+                } else if (Utilities.ATLEAST_JB_MR1) {
+                    sInstance = new UserManagerCompatV17(context.getApplicationContext());
+                } else {
+                    sInstance = new UserManagerCompatV16();
+                }
+            }
+            return sInstance;
         }
     }
 
+    /**
+     * Creates a cache for users.
+     */
+    public abstract void enableAndResetCache();
+
     public abstract List<UserHandleCompat> getUserProfiles();
     public abstract long getSerialNumberForUser(UserHandleCompat user);
     public abstract UserHandleCompat getUserForSerialNumber(long serialNumber);
diff --git a/src/com/android/launcher3/compat/UserManagerCompatV16.java b/src/com/android/launcher3/compat/UserManagerCompatV16.java
index ffe698c..85aee57 100644
--- a/src/com/android/launcher3/compat/UserManagerCompatV16.java
+++ b/src/com/android/launcher3/compat/UserManagerCompatV16.java
@@ -53,4 +53,8 @@
     public long getUserCreationTime(UserHandleCompat user) {
         return 0;
     }
+
+    @Override
+    public void enableAndResetCache() {
+    }
 }
diff --git a/src/com/android/launcher3/compat/UserManagerCompatV17.java b/src/com/android/launcher3/compat/UserManagerCompatV17.java
index c42c00c..75203b7 100644
--- a/src/com/android/launcher3/compat/UserManagerCompatV17.java
+++ b/src/com/android/launcher3/compat/UserManagerCompatV17.java
@@ -21,8 +21,18 @@
 import android.os.Build;
 import android.os.UserManager;
 
+import com.android.launcher3.util.LongArrayMap;
+
+import java.util.HashMap;
+
 @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
 public class UserManagerCompatV17 extends UserManagerCompatV16 {
+
+    protected LongArrayMap<UserHandleCompat> mUsers;
+    // Create a separate reverse map as LongArrayMap.indexOfValue checks if objects are same
+    // and not {@link Object#equals}
+    protected HashMap<UserHandleCompat, Long> mUserToSerialMap;
+
     protected UserManager mUserManager;
 
     UserManagerCompatV17(Context context) {
@@ -30,11 +40,34 @@
     }
 
     public long getSerialNumberForUser(UserHandleCompat user) {
+        synchronized (this) {
+            if (mUserToSerialMap != null) {
+                Long serial = mUserToSerialMap.get(user);
+                return serial == null ? 0 : serial;
+            }
+        }
         return mUserManager.getSerialNumberForUser(user.getUser());
     }
 
     public UserHandleCompat getUserForSerialNumber(long serialNumber) {
+        synchronized (this) {
+            if (mUsers != null) {
+                return mUsers.get(serialNumber);
+            }
+        }
         return UserHandleCompat.fromUser(mUserManager.getUserForSerialNumber(serialNumber));
     }
+
+    @Override
+    public void enableAndResetCache() {
+        synchronized (this) {
+            mUsers = new LongArrayMap<>();
+            mUserToSerialMap = new HashMap<>();
+            UserHandleCompat myUser = UserHandleCompat.myUserHandle();
+            long serial = mUserManager.getSerialNumberForUser(myUser.getUser());
+            mUsers.put(serial, myUser);
+            mUserToSerialMap.put(myUser, serial);
+        }
+    }
 }
 
diff --git a/src/com/android/launcher3/compat/UserManagerCompatVL.java b/src/com/android/launcher3/compat/UserManagerCompatVL.java
index dd7a726..dc3ec3c 100644
--- a/src/com/android/launcher3/compat/UserManagerCompatVL.java
+++ b/src/com/android/launcher3/compat/UserManagerCompatVL.java
@@ -24,9 +24,14 @@
 import android.graphics.drawable.Drawable;
 import android.os.Build;
 import android.os.UserHandle;
+
 import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.util.LongArrayMap;
+
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.List;
 
 @TargetApi(Build.VERSION_CODES.LOLLIPOP)
@@ -43,7 +48,32 @@
     }
 
     @Override
+    public void enableAndResetCache() {
+        synchronized (this) {
+            mUsers = new LongArrayMap<>();
+            mUserToSerialMap = new HashMap<>();
+            List<UserHandle> users = mUserManager.getUserProfiles();
+            if (users != null) {
+                for (UserHandle user : users) {
+                    long serial = mUserManager.getSerialNumberForUser(user);
+                    UserHandleCompat userCompat = UserHandleCompat.fromUser(user);
+                    mUsers.put(serial, userCompat);
+                    mUserToSerialMap.put(userCompat, serial);
+                }
+            }
+        }
+    }
+
+    @Override
     public List<UserHandleCompat> getUserProfiles() {
+        synchronized (this) {
+            if (mUsers != null) {
+                List<UserHandleCompat> users = new ArrayList<>();
+                users.addAll(mUserToSerialMap.keySet());
+                return users;
+            }
+        }
+
         List<UserHandle> users = mUserManager.getUserProfiles();
         if (users == null) {
             return Collections.emptyList();
@@ -71,7 +101,9 @@
 
     @Override
     public long getUserCreationTime(UserHandleCompat user) {
-        // TODO: Use system API once available.
+        if (Utilities.ATLEAST_MARSHMALLOW) {
+            return mUserManager.getUserCreationTime(user.getUser());
+        }
         SharedPreferences prefs = mContext.getSharedPreferences(
                 LauncherAppState.getSharedPreferencesKey(), Context.MODE_PRIVATE);
         String key = USER_CREATION_TIME_KEY + getSerialNumberForUser(user);
diff --git a/src/com/android/launcher3/model/AbstractUserComparator.java b/src/com/android/launcher3/model/AbstractUserComparator.java
index cf47ce6..bd28560 100644
--- a/src/com/android/launcher3/model/AbstractUserComparator.java
+++ b/src/com/android/launcher3/model/AbstractUserComparator.java
@@ -22,14 +22,12 @@
 import com.android.launcher3.compat.UserManagerCompat;
 
 import java.util.Comparator;
-import java.util.HashMap;
 
 /**
  * A comparator to arrange items based on user profiles.
  */
 public abstract class AbstractUserComparator<T extends ItemInfo> implements Comparator<T> {
 
-    private HashMap<UserHandleCompat, Long> mUserSerialCache = new HashMap<>();
     private final UserManagerCompat mUserManager;
     private final UserHandleCompat mMyUser;
 
@@ -43,25 +41,9 @@
         if (mMyUser.equals(lhs.user)) {
             return -1;
         } else {
-            Long aUserSerial = getAndCacheUserSerial(lhs.user);
-            Long bUserSerial = getAndCacheUserSerial(rhs.user);
+            Long aUserSerial = mUserManager.getSerialNumberForUser(lhs.user);
+            Long bUserSerial = mUserManager.getSerialNumberForUser(rhs.user);
             return aUserSerial.compareTo(bUserSerial);
         }
     }
-
-    /**
-     * Returns the user serial for this user, using a cached serial if possible.
-     */
-    private Long getAndCacheUserSerial(UserHandleCompat user) {
-        Long userSerial = mUserSerialCache.get(user);
-        if (userSerial == null) {
-            userSerial = mUserManager.getSerialNumberForUser(user);
-            mUserSerialCache.put(user, userSerial);
-        }
-        return userSerial;
-    }
-
-    public void clearUserCache() {
-        mUserSerialCache.clear();
-    }
 }
diff --git a/src/com/android/launcher3/model/AppNameComparator.java b/src/com/android/launcher3/model/AppNameComparator.java
index c4b74d4..5f80037 100644
--- a/src/com/android/launcher3/model/AppNameComparator.java
+++ b/src/com/android/launcher3/model/AppNameComparator.java
@@ -68,8 +68,6 @@
      * Returns a locale-aware comparator that will alphabetically order a list of applications.
      */
     public Comparator<ItemInfo> getAppInfoComparator() {
-        // Clear the user serial cache so that we get serials as needed in the comparator
-        mAppInfoComparator.clearUserCache();
         return mAppInfoComparator;
     }
 
diff --git a/src/com/android/launcher3/model/MigrateFromRestoreTask.java b/src/com/android/launcher3/model/MigrateFromRestoreTask.java
new file mode 100644
index 0000000..6a529f6
--- /dev/null
+++ b/src/com/android/launcher3/model/MigrateFromRestoreTask.java
@@ -0,0 +1,767 @@
+package com.android.launcher3.model;
+
+import android.content.ComponentName;
+import android.content.ContentProviderOperation;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.pm.PackageInfo;
+import android.database.Cursor;
+import android.graphics.Point;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherAppWidgetProviderInfo;
+import com.android.launcher3.LauncherModel;
+import com.android.launcher3.LauncherProvider;
+import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.LauncherSettings.Favorites;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.compat.PackageInstallerCompat;
+import com.android.launcher3.compat.UserHandleCompat;
+import com.android.launcher3.util.LongArrayMap;
+import com.android.launcher3.util.Thunk;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+
+/**
+ * This class takes care of shrinking the workspace (by maximum of one row and one column), as a
+ * result of restoring from a larger device.
+ */
+public class MigrateFromRestoreTask {
+
+    public static boolean ENABLED = false;
+
+    private static final String TAG = "MigrateFromRestoreTask";
+    private static final boolean DEBUG = true;
+
+    private static final String KEY_MIGRATION_SOURCE_SIZE = "migration_restore_src_size";
+    private static final String KEY_MIGRATION_WIDGET_MINSIZE = "migration_widget_min_size";
+
+    // These are carefully selected weights for various item types (Math.random?), to allow for
+    // the lease absurd migration experience.
+    private static final float WT_SHORTCUT = 1;
+    private static final float WT_APPLICATION = 0.8f;
+    private static final float WT_WIDGET_MIN = 2;
+    private static final float WT_WIDGET_FACTOR = 0.6f;
+    private static final float WT_FOLDER_FACTOR = 0.5f;
+
+    private final Context mContext;
+    private final ContentValues mTempValues = new ContentValues();
+    private final HashMap<String, Point> mWidgetMinSize;
+    private final InvariantDeviceProfile mIdp;
+
+    private HashSet<String> mValidPackages;
+    public ArrayList<Long> mEntryToRemove;
+    private ArrayList<ContentProviderOperation> mUpdateOperations;
+
+    private ArrayList<DbEntry> mCarryOver;
+
+    private final int mSrcX, mSrcY;
+    @Thunk final int mTrgX, mTrgY;
+    private final boolean mShouldRemoveX, mShouldRemoveY;
+
+    public MigrateFromRestoreTask(Context context) {
+        mContext = context;
+
+        SharedPreferences prefs = prefs(context);
+        Point sourceSize = parsePoint(prefs.getString(KEY_MIGRATION_SOURCE_SIZE, ""));
+        mSrcX = sourceSize.x;
+        mSrcY = sourceSize.y;
+
+        mWidgetMinSize = new HashMap<String, Point>();
+        for (String s : prefs.getStringSet(KEY_MIGRATION_WIDGET_MINSIZE,
+                Collections.<String>emptySet())) {
+            String[] parts = s.split("#");
+            mWidgetMinSize.put(parts[0], parsePoint(parts[1]));
+        }
+
+        mIdp = LauncherAppState.getInstance().getInvariantDeviceProfile();
+        mTrgX = mIdp.numColumns;
+        mTrgY = mIdp.numRows;
+        mShouldRemoveX = mTrgX < mSrcX;
+        mShouldRemoveY = mTrgY < mSrcY;
+    }
+
+    public void execute() throws Exception {
+        mEntryToRemove = new ArrayList<>();
+        mCarryOver = new ArrayList<>();
+        mUpdateOperations = new ArrayList<>();
+
+        // Initialize list of valid packages. This contain all the packages which are already on
+        // the device and packages which are being installed. Any item which doesn't belong to
+        // this set is removed.
+        // Since the loader removes such items anyway, removing these items here doesn't cause any
+        // extra data loss and gives us more free space on the grid for better migration.
+        mValidPackages = new HashSet<>();
+        for (PackageInfo info : mContext.getPackageManager().getInstalledPackages(0)) {
+            mValidPackages.add(info.packageName);
+        }
+        mValidPackages.addAll(PackageInstallerCompat.getInstance(mContext)
+                .updateAndGetActiveSessionCache().keySet());
+
+        ArrayList<Long> allScreens = LauncherModel.loadWorkspaceScreensDb(mContext);
+        if (allScreens.isEmpty()) {
+            throw new Exception("Unable to get workspace screens");
+        }
+
+        for (long screenId : allScreens) {
+            if (DEBUG) {
+                Log.d(TAG, "Migrating " + screenId);
+            }
+            migrateScreen(screenId);
+        }
+
+        if (!mCarryOver.isEmpty()) {
+            LongArrayMap<DbEntry> itemMap = new LongArrayMap<>();
+            for (DbEntry e : mCarryOver) {
+                itemMap.put(e.id, e);
+            }
+
+            do {
+                // Some items are still remaining. Try adding a few new screens.
+
+                // At every iteration, make sure that at least one item is removed from
+                // {@link #mCarryOver}, to prevent an infinite loop. If no item could be removed,
+                // break the loop and abort migration by throwing an exception.
+                OptimalPlacementSolution placement = new OptimalPlacementSolution(
+                        new boolean[mTrgX][mTrgY], deepCopy(mCarryOver), true);
+                placement.find();
+                if (placement.finalPlacedItems.size() > 0) {
+                    long newScreenId = LauncherAppState.getLauncherProvider().generateNewScreenId();
+                    allScreens.add(newScreenId);
+                    for (DbEntry item : placement.finalPlacedItems) {
+                        if (!mCarryOver.remove(itemMap.get(item.id))) {
+                            throw new Exception("Unable to find matching items");
+                        }
+                        item.screenId = newScreenId;
+                        update(item);
+                    }
+                } else {
+                    throw new Exception("None of the items can be placed on an empty screen");
+                }
+
+            } while (!mCarryOver.isEmpty());
+
+
+            LauncherAppState.getInstance().getModel()
+                .updateWorkspaceScreenOrder(mContext, allScreens);
+        }
+
+        // Update items
+        mContext.getContentResolver().applyBatch(LauncherProvider.AUTHORITY, mUpdateOperations);
+
+        if (!mEntryToRemove.isEmpty()) {
+            if (DEBUG) {
+                Log.d(TAG, "Removing items: " + TextUtils.join(", ", mEntryToRemove));
+            }
+            mContext.getContentResolver().delete(LauncherSettings.Favorites.CONTENT_URI,
+                    Utilities.createDbSelectionQuery(
+                            LauncherSettings.Favorites._ID, mEntryToRemove), null);
+        }
+
+        // Make sure we haven't removed everything.
+        final Cursor c = mContext.getContentResolver().query(
+                LauncherSettings.Favorites.CONTENT_URI, null, null, null, null);
+        boolean hasData = c.moveToNext();
+        c.close();
+        if (!hasData) {
+            throw new Exception("Removed every thing during grid resize");
+        }
+    }
+
+    /**
+     * Migrate a particular screen id.
+     * Strategy:
+     *   1) For all possible combinations of row and column, pick the one which causes the least
+     *      data loss: {@link #tryRemove(int, int, ArrayList, float[])}
+     *   2) Maintain a list of all lost items before this screen, and add any new item lost from
+     *      this screen to that list as well.
+     *   3) If all those items from the above list can be placed on this screen, place them
+     *      (otherwise they are placed on a new screen).
+     */
+    private void migrateScreen(long screenId) {
+        ArrayList<DbEntry> items = loadEntries(screenId);
+
+        int removedCol = Integer.MAX_VALUE;
+        int removedRow = Integer.MAX_VALUE;
+
+        // removeWt represents the cost function for loss of items during migration, and moveWt
+        // represents the cost function for repositioning the items. moveWt is only considered if
+        // removeWt is same for two different configurations.
+        // Start with Float.MAX_VALUE (assuming full data) and pick the configuration with least
+        // cost.
+        float removeWt = Float.MAX_VALUE;
+        float moveWt = Float.MAX_VALUE;
+        float[] outLoss = new float[2];
+        ArrayList<DbEntry> finalItems = null;
+
+        // Try removing all possible combinations
+        for (int x = 0; x < mSrcX; x++) {
+            for (int y = 0; y < mSrcY; y++) {
+                // Use a deep copy when trying out a particular combination as it can change
+                // the underlying object.
+                ArrayList<DbEntry> itemsOnScreen = tryRemove(x, y, deepCopy(items), outLoss);
+
+                if ((outLoss[0] < removeWt) || ((outLoss[0] == removeWt) && (outLoss[1] < moveWt))) {
+                    removeWt = outLoss[0];
+                    moveWt = outLoss[1];
+                    removedCol = mShouldRemoveX ? x : removedCol;
+                    removedRow = mShouldRemoveY ? y : removedRow;
+                    finalItems = itemsOnScreen;
+                }
+
+                // No need to loop over all rows, if a row removal is not needed.
+                if (!mShouldRemoveY) {
+                    break;
+                }
+            }
+
+            if (!mShouldRemoveX) {
+                break;
+            }
+        }
+
+        if (DEBUG) {
+            Log.d(TAG, String.format("Removing row %d, column %d on screen %d",
+                    removedRow, removedCol, screenId));
+        }
+
+        LongArrayMap<DbEntry> itemMap = new LongArrayMap<>();
+        for (DbEntry e : deepCopy(items)) {
+            itemMap.put(e.id, e);
+        }
+
+        for (DbEntry item : finalItems) {
+            DbEntry org = itemMap.get(item.id);
+            itemMap.remove(item.id);
+
+            // Check if update is required
+            if (!item.columnsSame(org)) {
+                update(item);
+            }
+        }
+
+        // The remaining items in {@link #itemMap} are those which didn't get placed.
+        for (DbEntry item : itemMap) {
+            mCarryOver.add(item);
+        }
+
+        if (!mCarryOver.isEmpty() && removeWt == 0) {
+            // No new items were removed in this step. Try placing all the items on this screen.
+            boolean[][] occupied = new boolean[mTrgX][mTrgY];
+            for (DbEntry item : finalItems) {
+                markCells(occupied, item, true);
+            }
+
+            OptimalPlacementSolution placement = new OptimalPlacementSolution(occupied,
+                    deepCopy(mCarryOver), true);
+            placement.find();
+            if (placement.lowestWeightLoss == 0) {
+                // All items got placed
+
+                for (DbEntry item : placement.finalPlacedItems) {
+                    item.screenId = screenId;
+                    update(item);
+                }
+
+                mCarryOver.clear();
+            }
+        }
+    }
+
+    /**
+     * Updates an item in the DB.
+     */
+    private void update(DbEntry item) {
+        mTempValues.clear();
+        item.addToContentValues(mTempValues);
+        mUpdateOperations.add(ContentProviderOperation
+                .newUpdate(LauncherSettings.Favorites.getContentUri(item.id))
+                .withValues(mTempValues).build());
+    }
+
+    /**
+     * Tries the remove the provided row and column.
+     * @param items all the items on the screen under operation
+     * @param outLoss array of size 2. The first entry is filled with weight loss, and the second
+     * with the overall item movement.
+     */
+    private ArrayList<DbEntry> tryRemove(int col, int row, ArrayList<DbEntry> items,
+            float[] outLoss) {
+        boolean[][] occupied = new boolean[mTrgX][mTrgY];
+
+        col = mShouldRemoveX ? col : Integer.MAX_VALUE;
+        row = mShouldRemoveY ? row : Integer.MAX_VALUE;
+
+        ArrayList<DbEntry> finalItems = new ArrayList<>();
+        ArrayList<DbEntry> removedItems = new ArrayList<>();
+
+        for (DbEntry item : items) {
+            if ((item.cellX <= col && (item.spanX + item.cellX) > col)
+                || (item.cellY <= row && (item.spanY + item.cellY) > row)) {
+                removedItems.add(item);
+                if (item.cellX >= col) item.cellX --;
+                if (item.cellY >= row) item.cellY --;
+            } else {
+                if (item.cellX > col) item.cellX --;
+                if (item.cellY > row) item.cellY --;
+                finalItems.add(item);
+                markCells(occupied, item, true);
+            }
+        }
+
+        OptimalPlacementSolution placement = new OptimalPlacementSolution(occupied, removedItems);
+        placement.find();
+        finalItems.addAll(placement.finalPlacedItems);
+        outLoss[0] = placement.lowestWeightLoss;
+        outLoss[1] = placement.lowestMoveCost;
+        return finalItems;
+    }
+
+    @Thunk void markCells(boolean[][] occupied, DbEntry item, boolean val) {
+        for (int i = item.cellX; i < (item.cellX + item.spanX); i++) {
+            for (int j = item.cellY; j < (item.cellY + item.spanY); j++) {
+                occupied[i][j] = val;
+            }
+        }
+    }
+
+    @Thunk boolean isVacant(boolean[][] occupied, int x, int y, int w, int h) {
+        if (x + w > mTrgX) return false;
+        if (y + h > mTrgY) return false;
+
+        for (int i = 0; i < w; i++) {
+            for (int j = 0; j < h; j++) {
+                if (occupied[i + x][j + y]) {
+                    return false;
+                }
+            }
+        }
+        return true;
+    }
+
+    private class OptimalPlacementSolution {
+        private final ArrayList<DbEntry> itemsToPlace;
+        private final boolean[][] occupied;
+
+        // If set to true, item movement are not considered in move cost, leading to a more
+        // linear placement.
+        private final boolean ignoreMove;
+
+        float lowestWeightLoss = Float.MAX_VALUE;
+        float lowestMoveCost = Float.MAX_VALUE;
+        ArrayList<DbEntry> finalPlacedItems;
+
+        public OptimalPlacementSolution(boolean[][] occupied, ArrayList<DbEntry> itemsToPlace) {
+            this(occupied, itemsToPlace, false);
+        }
+
+        public OptimalPlacementSolution(boolean[][] occupied, ArrayList<DbEntry> itemsToPlace,
+                boolean ignoreMove) {
+            this.occupied = occupied;
+            this.itemsToPlace = itemsToPlace;
+            this.ignoreMove = ignoreMove;
+
+            // Sort the items such that larger widgets appear first followed by 1x1 items
+            Collections.sort(this.itemsToPlace);
+        }
+
+        public void find() {
+            find(0, 0, 0, new ArrayList<DbEntry>());
+        }
+
+        /**
+         * Recursively finds a placement for the provided items.
+         * @param index the position in {@link #itemsToPlace} to start looking at.
+         * @param weightLoss total weight loss upto this point
+         * @param moveCost total move cost upto this point
+         * @param itemsPlaced all the items already placed upto this point
+         */
+        public void find(int index, float weightLoss, float moveCost,
+                ArrayList<DbEntry> itemsPlaced) {
+            if ((weightLoss >= lowestWeightLoss) ||
+                    ((weightLoss == lowestWeightLoss) && (moveCost >= lowestMoveCost))) {
+                // Abort, as we already have a better solution.
+                return;
+
+            } else if (index >= itemsToPlace.size()) {
+                // End loop.
+                lowestWeightLoss = weightLoss;
+                lowestMoveCost = moveCost;
+
+                // Keep a deep copy of current configuration as it can change during recursion.
+                finalPlacedItems = deepCopy(itemsPlaced);
+                return;
+            }
+
+            DbEntry me = itemsToPlace.get(index);
+            int myX = me.cellX;
+            int myY = me.cellY;
+
+            // List of items to pass over if this item was placed.
+            ArrayList<DbEntry> itemsIncludingMe = new ArrayList<>(itemsPlaced.size() + 1);
+            itemsIncludingMe.addAll(itemsPlaced);
+            itemsIncludingMe.add(me);
+
+            if (me.spanX > 1 || me.spanY > 1) {
+                // If the current item is a widget (and it greater than 1x1), try to place it at
+                // all possible positions. This is because a widget placed at one position can
+                // affect the placement of a different widget.
+                int myW = me.spanX;
+                int myH = me.spanY;
+
+                for (int y = 0; y < mTrgY; y++) {
+                    for (int x = 0; x < mTrgX; x++) {
+                        float newMoveCost = moveCost;
+                        if (x != myX) {
+                            me.cellX = x;
+                            newMoveCost ++;
+                        }
+                        if (y != myY) {
+                            me.cellY = y;
+                            newMoveCost ++;
+                        }
+                        if (ignoreMove) {
+                            newMoveCost = moveCost;
+                        }
+
+                        if (isVacant(occupied, x, y, myW, myH)) {
+                            // place at this position and continue search.
+                            markCells(occupied, me, true);
+                            find(index + 1, weightLoss, newMoveCost, itemsIncludingMe);
+                            markCells(occupied, me, false);
+                        }
+
+                        // Try resizing horizontally
+                        if (myW > me.minSpanX && isVacant(occupied, x, y, myW - 1, myH)) {
+                            me.spanX --;
+                            markCells(occupied, me, true);
+                            // 1 extra move cost
+                            find(index + 1, weightLoss, newMoveCost + 1, itemsIncludingMe);
+                            markCells(occupied, me, false);
+                            me.spanX ++;
+                        }
+
+                        // Try resizing vertically
+                        if (myH > me.minSpanY && isVacant(occupied, x, y, myW, myH - 1)) {
+                            me.spanY --;
+                            markCells(occupied, me, true);
+                            // 1 extra move cost
+                            find(index + 1, weightLoss, newMoveCost + 1, itemsIncludingMe);
+                            markCells(occupied, me, false);
+                            me.spanY ++;
+                        }
+
+                        // Try resizing horizontally & vertically
+                        if (myH > me.minSpanY && myW > me.minSpanX &&
+                                isVacant(occupied, x, y, myW - 1, myH - 1)) {
+                            me.spanX --;
+                            me.spanY --;
+                            markCells(occupied, me, true);
+                            // 2 extra move cost
+                            find(index + 1, weightLoss, newMoveCost + 2, itemsIncludingMe);
+                            markCells(occupied, me, false);
+                            me.spanX ++;
+                            me.spanY ++;
+                        }
+                        me.cellX = myX;
+                        me.cellY = myY;
+                    }
+                }
+
+                // Finally also try a solution when this item is not included. Trying it in the end
+                // causes it to get skipped in most cases due to higher weight loss, and prevents
+                // unnecessary deep copies of various configurations.
+                find(index + 1, weightLoss + me.weight, moveCost, itemsPlaced);
+            } else {
+                // Since this is a 1x1 item and all the following items are also 1x1, just place
+                // it at 'the most appropriate position' and hope for the best.
+                // The most appropriate position: one with lease straight line distance
+                int newDistance = Integer.MAX_VALUE;
+                int newX = Integer.MAX_VALUE, newY = Integer.MAX_VALUE;
+
+                for (int y = 0; y < mTrgY; y++) {
+                    for (int x = 0; x < mTrgX; x++) {
+                        if (!occupied[x][y]) {
+                            int dist = ignoreMove ? 0 :
+                                ((me.cellX - x) * (me.cellX - x) + (me.cellY - y) * (me.cellY - y));
+                            if (dist < newDistance) {
+                                newX = x;
+                                newY = y;
+                                newDistance = dist;
+                            }
+                        }
+                    }
+                }
+
+                if (newX < mTrgX && newY < mTrgY) {
+                    float newMoveCost = moveCost;
+                    if (newX != myX) {
+                        me.cellX = newX;
+                        newMoveCost ++;
+                    }
+                    if (newY != myY) {
+                        me.cellY = newY;
+                        newMoveCost ++;
+                    }
+                    if (ignoreMove) {
+                        newMoveCost = moveCost;
+                    }
+                    markCells(occupied, me, true);
+                    find(index + 1, weightLoss, newMoveCost, itemsIncludingMe);
+                    markCells(occupied, me, false);
+                    me.cellX = myX;
+                    me.cellY = myY;
+
+                    // Try to find a solution without this item, only if
+                    //  1) there was at least one space, i.e., we were able to place this item
+                    //  2) if the next item has the same weight (all items are already sorted), as
+                    //     if it has lower weight, that solution will automatically get discarded.
+                    //  3) ignoreMove false otherwise, move cost is ignored and the weight will
+                    //      anyway be same.
+                    if (index + 1 < itemsToPlace.size()
+                            && itemsToPlace.get(index + 1).weight >= me.weight && !ignoreMove) {
+                        find(index + 1, weightLoss + me.weight, moveCost, itemsPlaced);
+                    }
+                } else {
+                    // No more space. Jump to the end.
+                    for (int i = index + 1; i < itemsToPlace.size(); i++) {
+                        weightLoss += itemsToPlace.get(i).weight;
+                    }
+                    find(itemsToPlace.size(), weightLoss + me.weight, moveCost, itemsPlaced);
+                }
+            }
+        }
+    }
+
+    /**
+     * Loads entries for a particular screen id.
+     */
+    public ArrayList<DbEntry> loadEntries(long screen) {
+       Cursor c =  mContext.getContentResolver().query(LauncherSettings.Favorites.CONTENT_URI,
+                new String[] {
+                    Favorites._ID,                  // 0
+                    Favorites.ITEM_TYPE,            // 1
+                    Favorites.CELLX,                // 2
+                    Favorites.CELLY,                // 3
+                    Favorites.SPANX,                // 4
+                    Favorites.SPANY,                // 5
+                    Favorites.INTENT,               // 6
+                    Favorites.APPWIDGET_PROVIDER},  // 7
+                Favorites.CONTAINER + " = " + Favorites.CONTAINER_DESKTOP
+                    + " AND " + Favorites.SCREEN + " = " + screen, null, null, null);
+
+       final int indexId = c.getColumnIndexOrThrow(Favorites._ID);
+       final int indexItemType = c.getColumnIndexOrThrow(Favorites.ITEM_TYPE);
+       final int indexCellX = c.getColumnIndexOrThrow(Favorites.CELLX);
+       final int indexCellY = c.getColumnIndexOrThrow(Favorites.CELLY);
+       final int indexSpanX = c.getColumnIndexOrThrow(Favorites.SPANX);
+       final int indexSpanY = c.getColumnIndexOrThrow(Favorites.SPANY);
+       final int indexIntent = c.getColumnIndexOrThrow(Favorites.INTENT);
+       final int indexAppWidgetProvider = c.getColumnIndexOrThrow(Favorites.APPWIDGET_PROVIDER);
+
+       ArrayList<DbEntry> entries = new ArrayList<>();
+       while (c.moveToNext()) {
+           DbEntry entry = new DbEntry();
+           entry.id = c.getLong(indexId);
+           entry.itemType = c.getInt(indexItemType);
+           entry.cellX = c.getInt(indexCellX);
+           entry.cellY = c.getInt(indexCellY);
+           entry.spanX = c.getInt(indexSpanX);
+           entry.spanY = c.getInt(indexSpanY);
+           entry.screenId = screen;
+
+           try {
+               // calculate weight
+               switch (entry.itemType) {
+                   case Favorites.ITEM_TYPE_SHORTCUT:
+                   case Favorites.ITEM_TYPE_APPLICATION: {
+                       verifyIntent(c.getString(indexIntent));
+                       entry.weight = entry.itemType == Favorites.ITEM_TYPE_SHORTCUT
+                           ? WT_SHORTCUT : WT_APPLICATION;
+                       break;
+                   }
+                   case Favorites.ITEM_TYPE_APPWIDGET: {
+                       String provider = c.getString(indexAppWidgetProvider);
+                       ComponentName cn = ComponentName.unflattenFromString(provider);
+                       verifyPackage(cn.getPackageName());
+                       entry.weight = Math.max(WT_WIDGET_MIN, WT_WIDGET_FACTOR
+                               * entry.spanX * entry.spanY);
+
+                       // Migration happens for current user only.
+                       LauncherAppWidgetProviderInfo pInfo = LauncherModel.getProviderInfo(
+                               mContext, cn, UserHandleCompat.myUserHandle());
+                       Point spans = pInfo == null ?
+                               mWidgetMinSize.get(provider) : pInfo.getMinSpans(mIdp, mContext);
+                       if (spans != null) {
+                           entry.minSpanX = spans.x > 0 ? spans.x : entry.spanX;
+                           entry.minSpanY = spans.y > 0 ? spans.y : entry.spanY;
+                       } else {
+                           // Assume that the widget be resized down to 2x2
+                           entry.minSpanX = entry.minSpanY = 2;
+                       }
+
+                       if (entry.minSpanX > mTrgX || entry.minSpanY > mTrgY) {
+                           throw new Exception("Widget can't be resized down to fit the grid");
+                       }
+                       break;
+                   }
+                   case Favorites.ITEM_TYPE_FOLDER: {
+                       int total = getFolderItemsCount(entry.id);
+                       if (total == 0) {
+                           throw new Exception("Folder is empty");
+                       }
+                       entry.weight = WT_FOLDER_FACTOR * total;
+                       break;
+                   }
+                   default:
+                       throw new Exception("Invalid item type");
+               }
+           } catch (Exception e) {
+               if (DEBUG) {
+                   Log.d(TAG, "Removing item " + entry.id, e);
+               }
+               mEntryToRemove.add(entry.id);
+               continue;
+           }
+
+           entries.add(entry);
+       }
+       return entries;
+    }
+
+    /**
+     * @return the number of valid items in the folder.
+     */
+    private int getFolderItemsCount(long folderId) {
+        Cursor c =  mContext.getContentResolver().query(LauncherSettings.Favorites.CONTENT_URI,
+                new String[] {Favorites._ID, Favorites.INTENT},
+                Favorites.CONTAINER + " = " + folderId, null, null, null);
+
+        int total = 0;
+        while (c.moveToNext()) {
+            try {
+                verifyIntent(c.getString(1));
+                total++;
+            } catch (Exception e) {
+                mEntryToRemove.add(c.getLong(0));
+            }
+        }
+
+        return total;
+    }
+
+    /**
+     * Verifies if the intent should be restored.
+     */
+    private void verifyIntent(String intentStr) throws Exception {
+        Intent intent = Intent.parseUri(intentStr, 0);
+        if (intent.getComponent() != null) {
+            verifyPackage(intent.getComponent().getPackageName());
+        } else if (intent.getPackage() != null) {
+            // Only verify package if the component was null.
+            verifyPackage(intent.getPackage());
+        }
+    }
+
+    /**
+     * Verifies if the package should be restored
+     */
+    private void verifyPackage(String packageName) throws Exception {
+        if (!mValidPackages.contains(packageName)) {
+            throw new Exception("Package not available");
+        }
+    }
+
+    private static class DbEntry extends ItemInfo implements Comparable<DbEntry> {
+
+        public float weight;
+
+        public DbEntry() { }
+
+        public DbEntry copy() {
+            DbEntry entry = new DbEntry();
+            entry.copyFrom(this);
+            entry.weight = weight;
+            entry.minSpanX = minSpanX;
+            entry.minSpanY = minSpanY;
+            return entry;
+        }
+
+        /**
+         * Comparator such that larger widgets come first,  followed by all 1x1 items
+         * based on their weights.
+         */
+        @Override
+        public int compareTo(DbEntry another) {
+            if (itemType == Favorites.ITEM_TYPE_APPWIDGET) {
+                if (another.itemType == Favorites.ITEM_TYPE_APPWIDGET) {
+                    return another.spanY * another.spanX - spanX * spanY;
+                } else {
+                    return -1;
+                }
+            } else if (another.itemType == Favorites.ITEM_TYPE_APPWIDGET) {
+                return 1;
+            } else {
+                // Place higher weight before lower weight.
+                return Float.compare(another.weight, weight);
+            }
+        }
+
+        public boolean columnsSame(DbEntry org) {
+            return org.cellX == cellX && org.cellY == cellY && org.spanX == spanX &&
+                    org.spanY == spanY && org.screenId == screenId;
+        }
+
+        public void addToContentValues(ContentValues values) {
+            values.put(LauncherSettings.Favorites.SCREEN, screenId);
+            values.put(LauncherSettings.Favorites.CELLX, cellX);
+            values.put(LauncherSettings.Favorites.CELLY, cellY);
+            values.put(LauncherSettings.Favorites.SPANX, spanX);
+            values.put(LauncherSettings.Favorites.SPANY, spanY);
+        }
+    }
+
+    @Thunk static ArrayList<DbEntry> deepCopy(ArrayList<DbEntry> src) {
+        ArrayList<DbEntry> dup = new ArrayList<DbEntry>(src.size());
+        for (DbEntry e : src) {
+            dup.add(e.copy());
+        }
+        return dup;
+    }
+
+    private static Point parsePoint(String point) {
+        String[] split = point.split(",");
+        return new Point(Integer.parseInt(split[0]), Integer.parseInt(split[1]));
+    }
+
+    public static void markForMigration(Context context, int srcX, int srcY,
+            HashSet<String> widgets) {
+        prefs(context).edit()
+                .putString(KEY_MIGRATION_SOURCE_SIZE, srcX + "," + srcY)
+                .putStringSet(KEY_MIGRATION_WIDGET_MINSIZE, widgets)
+                .apply();
+    }
+
+    public static boolean shouldRunTask(Context context) {
+        return !TextUtils.isEmpty(prefs(context).getString(KEY_MIGRATION_SOURCE_SIZE, ""));
+    }
+
+    public static void clearFlags(Context context) {
+        prefs(context).edit().remove(KEY_MIGRATION_SOURCE_SIZE)
+                .remove(KEY_MIGRATION_WIDGET_MINSIZE).commit();
+    }
+
+    private static SharedPreferences prefs(Context context) {
+        return context.getSharedPreferences(LauncherAppState.getSharedPreferencesKey(),
+                Context.MODE_PRIVATE);
+    }
+}
diff --git a/src/com/android/launcher3/model/WidgetsAndShortcutNameComparator.java b/src/com/android/launcher3/model/WidgetsAndShortcutNameComparator.java
index 61e8952..b990560 100644
--- a/src/com/android/launcher3/model/WidgetsAndShortcutNameComparator.java
+++ b/src/com/android/launcher3/model/WidgetsAndShortcutNameComparator.java
@@ -1,13 +1,14 @@
 package com.android.launcher3.model;
 
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
-
 import com.android.launcher3.LauncherAppWidgetProviderInfo;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.compat.AppWidgetManagerCompat;
 import com.android.launcher3.compat.UserHandleCompat;
+import com.android.launcher3.util.ComponentKey;
 
 import java.text.Collator;
 import java.util.Comparator;
@@ -16,53 +17,81 @@
 public class WidgetsAndShortcutNameComparator implements Comparator<Object> {
     private final AppWidgetManagerCompat mManager;
     private final PackageManager mPackageManager;
-    private final HashMap<Object, String> mLabelCache;
+    private final HashMap<ComponentKey, 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>();
+        mLabelCache = new HashMap<>();
         mCollator = Collator.getInstance();
         mMainHandle = UserHandleCompat.myUserHandle();
     }
 
+    /**
+     * Resets any stored state.
+     */
+    public void reset() {
+        mLabelCache.clear();
+    }
+
     @Override
-    public final int compare(Object a, Object b) {
-        String labelA, labelB;
-        if (mLabelCache.containsKey(a)) {
-            labelA = mLabelCache.get(a);
-        } else {
-            labelA = (a instanceof LauncherAppWidgetProviderInfo)
-                    ? Utilities.trim(mManager.loadLabel((LauncherAppWidgetProviderInfo) a))
-                    : Utilities.trim(((ResolveInfo) a).loadLabel(mPackageManager));
-            mLabelCache.put(a, labelA);
-        }
-        if (mLabelCache.containsKey(b)) {
-            labelB = mLabelCache.get(b);
-        } else {
-            labelB = (b instanceof LauncherAppWidgetProviderInfo)
-                    ? Utilities.trim(mManager.loadLabel((LauncherAppWidgetProviderInfo) b))
-                    : Utilities.trim(((ResolveInfo) b).loadLabel(mPackageManager));
-            mLabelCache.put(b, labelB);
-        }
-
-        // 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));
+    public final int compare(Object objA, Object objB) {
+        ComponentKey keyA = getComponentKey(objA);
+        ComponentKey keyB = getComponentKey(objB);
 
         // Independent of how the labels compare, if only one of the two widget info belongs to
         // work profile, put that one in the back.
+        boolean aWorkProfile = !mMainHandle.equals(keyA.user);
+        boolean bWorkProfile = !mMainHandle.equals(keyB.user);
         if (aWorkProfile && !bWorkProfile) {
             return 1;
         }
         if (!aWorkProfile && bWorkProfile) {
             return -1;
         }
+
+        // Get the labels for comparison
+        String labelA = mLabelCache.get(keyA);
+        String labelB = mLabelCache.get(keyB);
+        if (labelA == null) {
+            labelA = getLabel(objA);
+            mLabelCache.put(keyA, labelA);
+        }
+        if (labelB == null) {
+            labelB = getLabel(objB);
+            mLabelCache.put(keyB, labelB);
+        }
         return mCollator.compare(labelA, labelB);
     }
+
+    /**
+     * @return a component key for the given widget or shortcut info.
+     */
+    private ComponentKey getComponentKey(Object o) {
+        if (o instanceof LauncherAppWidgetProviderInfo) {
+            LauncherAppWidgetProviderInfo widgetInfo = (LauncherAppWidgetProviderInfo) o;
+            return new ComponentKey(widgetInfo.provider, mManager.getUser(widgetInfo));
+        } else {
+            ResolveInfo shortcutInfo = (ResolveInfo) o;
+            ComponentName cn = new ComponentName(shortcutInfo.activityInfo.packageName,
+                    shortcutInfo.activityInfo.name);
+            // Currently, there are no work profile shortcuts
+            return new ComponentKey(cn, UserHandleCompat.myUserHandle());
+        }
+    }
+
+    /**
+     * @return the label for the given widget or shortcut info.  This may be an expensive call.
+     */
+    private String getLabel(Object o) {
+        if (o instanceof LauncherAppWidgetProviderInfo) {
+            LauncherAppWidgetProviderInfo widgetInfo = (LauncherAppWidgetProviderInfo) o;
+            return Utilities.trim(mManager.loadLabel(widgetInfo));
+        } else {
+            ResolveInfo shortcutInfo = (ResolveInfo) o;
+            return Utilities.trim(shortcutInfo.loadLabel(mPackageManager));
+        }
+    }
 };
diff --git a/src/com/android/launcher3/model/WidgetsModel.java b/src/com/android/launcher3/model/WidgetsModel.java
index 185dfca..eef4f91 100644
--- a/src/com/android/launcher3/model/WidgetsModel.java
+++ b/src/com/android/launcher3/model/WidgetsModel.java
@@ -8,6 +8,9 @@
 
 import com.android.launcher3.AppFilter;
 import com.android.launcher3.IconCache;
+import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherAppWidgetProviderInfo;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.compat.AlphabeticIndexCompat;
@@ -39,8 +42,8 @@
     private ArrayList<Object> mRawList;
 
     private final AppWidgetManagerCompat mAppWidgetMgr;
-    private final Comparator mWidgetAndShortcutNameComparator;
-    private final Comparator mAppNameComparator;
+    private final WidgetsAndShortcutNameComparator mWidgetAndShortcutNameComparator;
+    private final Comparator<ItemInfo> mAppNameComparator;
     private final IconCache mIconCache;
     private final AppFilter mAppFilter;
     private AlphabeticIndexCompat mIndexer;
@@ -54,6 +57,7 @@
         mIndexer = new AlphabeticIndexCompat(context);
     }
 
+    @SuppressWarnings("unchecked")
     private WidgetsModel(WidgetsModel model) {
         mAppWidgetMgr = model.mAppWidgetMgr;
         mPackageItemInfos = (ArrayList<PackageItemInfo>) model.mPackageItemInfos.clone();
@@ -103,6 +107,9 @@
         // clear the lists.
         mWidgetsList.clear();
         mPackageItemInfos.clear();
+        mWidgetAndShortcutNameComparator.reset();
+
+        InvariantDeviceProfile idp = LauncherAppState.getInstance().getInvariantDeviceProfile();
 
         // add and update.
         for (Object o: rawWidgetsShortcuts) {
@@ -111,9 +118,23 @@
             ComponentName componentName = null;
             if (o instanceof LauncherAppWidgetProviderInfo) {
                 LauncherAppWidgetProviderInfo widgetInfo = (LauncherAppWidgetProviderInfo) o;
-                componentName = widgetInfo.provider;
-                packageName = widgetInfo.provider.getPackageName();
-                userHandle = mAppWidgetMgr.getUser(widgetInfo);
+
+                // Ensure that all widgets we show can be added on a workspace of this size
+                int minSpanX = Math.min(widgetInfo.spanX, widgetInfo.minSpanX);
+                int minSpanY = Math.min(widgetInfo.spanY, widgetInfo.minSpanY);
+                if (minSpanX <= (int) idp.numColumns &&
+                    minSpanY <= (int) idp.numRows) {
+                    componentName = widgetInfo.provider;
+                    packageName = widgetInfo.provider.getPackageName();
+                    userHandle = mAppWidgetMgr.getUser(widgetInfo);
+                } else {
+                    if (DEBUG) {
+                        Log.d(TAG, String.format(
+                                "Widget %s : (%d X %d) can't fit on this device",
+                                widgetInfo.provider, minSpanX, minSpanY));
+                    }
+                    continue;
+                }
             } else if (o instanceof ResolveInfo) {
                 ResolveInfo resolveInfo = (ResolveInfo) o;
                 componentName = new ComponentName(resolveInfo.activityInfo.packageName,
@@ -139,7 +160,7 @@
             if (widgetsShortcutsList != null) {
                 widgetsShortcutsList.add(o);
             } else {
-                widgetsShortcutsList = new ArrayList<Object>();
+                widgetsShortcutsList = new ArrayList<>();
                 widgetsShortcutsList.add(o);
                 pInfo = new PackageItemInfo(packageName);
                 mIconCache.getTitleAndIconForApp(packageName, userHandle,
diff --git a/src/com/android/launcher3/testing/LauncherExtension.java b/src/com/android/launcher3/testing/LauncherExtension.java
index 34492e4..8702877 100644
--- a/src/com/android/launcher3/testing/LauncherExtension.java
+++ b/src/com/android/launcher3/testing/LauncherExtension.java
@@ -202,6 +202,11 @@
         }
 
         @Override
+        public boolean startSearchFromAllApps(String query) {
+            return false;
+        }
+
+        @Override
         public void startVoice() {
         }
 
diff --git a/src/com/android/launcher3/util/ComponentKey.java b/src/com/android/launcher3/util/ComponentKey.java
index 6a7df43..b7aafae 100644
--- a/src/com/android/launcher3/util/ComponentKey.java
+++ b/src/com/android/launcher3/util/ComponentKey.java
@@ -64,8 +64,11 @@
      * Encodes a component key as a string of the form [flattenedComponentString#userId].
      */
     public String flattenToString(Context context) {
-        return componentName.flattenToString() + "#" +
-                UserManagerCompat.getInstance(context).getSerialNumberForUser(user);
+        String flattened = componentName.flattenToString();
+        if (user != null) {
+            flattened += "#" + UserManagerCompat.getInstance(context).getSerialNumberForUser(user);
+        }
+        return flattened;
     }
 
     @Override
diff --git a/src/com/android/launcher3/util/ManagedProfileHeuristic.java b/src/com/android/launcher3/util/ManagedProfileHeuristic.java
index b37f447..74fc92a 100644
--- a/src/com/android/launcher3/util/ManagedProfileHeuristic.java
+++ b/src/com/android/launcher3/util/ManagedProfileHeuristic.java
@@ -68,7 +68,7 @@
     private static final long AUTO_ADD_TO_FOLDER_DURATION = 8 * 60 * 60 * 1000;
 
     public static ManagedProfileHeuristic get(Context context, UserHandleCompat user) {
-        if (Utilities.isLmpOrAbove() && !UserHandleCompat.myUserHandle().equals(user)) {
+        if (Utilities.ATLEAST_LOLLIPOP && !UserHandleCompat.myUserHandle().equals(user)) {
             return new ManagedProfileHeuristic(context, user);
         }
         return null;
@@ -296,7 +296,7 @@
      * Verifies that entries corresponding to {@param users} exist and removes all invalid entries.
      */
     public static void processAllUsers(List<UserHandleCompat> users, Context context) {
-        if (!Utilities.isLmpOrAbove()) {
+        if (!Utilities.ATLEAST_LOLLIPOP) {
             return;
         }
         UserManagerCompat userManager = UserManagerCompat.getInstance(context);
diff --git a/src/com/android/launcher3/util/UiThreadCircularReveal.java b/src/com/android/launcher3/util/UiThreadCircularReveal.java
index 3ca3aee..f2b5e5e 100644
--- a/src/com/android/launcher3/util/UiThreadCircularReveal.java
+++ b/src/com/android/launcher3/util/UiThreadCircularReveal.java
@@ -47,7 +47,7 @@
                 float progress = arg0.getAnimatedFraction();
                 outlineProvider.setProgress(progress);
                 revealView.invalidateOutline();
-                if (!Utilities.isLmpMR1OrAbove()) {
+                if (!Utilities.ATLEAST_LOLLIPOP_MR1) {
                     revealView.invalidate();
                 }
             }
diff --git a/src/com/android/launcher3/util/WallpaperUtils.java b/src/com/android/launcher3/util/WallpaperUtils.java
index 53b2acd..b9fccbc 100644
--- a/src/com/android/launcher3/util/WallpaperUtils.java
+++ b/src/com/android/launcher3/util/WallpaperUtils.java
@@ -24,6 +24,8 @@
 import android.os.Build;
 import android.view.WindowManager;
 
+import com.android.launcher3.Utilities;
+
 /**
  * Utility methods for wallpaper management.
  */
@@ -99,7 +101,7 @@
             int maxDim = Math.max(maxDims.x, maxDims.y);
             int minDim = Math.max(minDims.x, minDims.y);
 
-            if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR1) {
+            if (Utilities.ATLEAST_JB_MR1) {
                 Point realSize = new Point();
                 windowManager.getDefaultDisplay().getRealSize(realSize);
                 maxDim = Math.max(realSize.x, realSize.y);
diff --git a/src/com/android/launcher3/widget/PendingAddWidgetInfo.java b/src/com/android/launcher3/widget/PendingAddWidgetInfo.java
index 758287a..fcb714f 100644
--- a/src/com/android/launcher3/widget/PendingAddWidgetInfo.java
+++ b/src/com/android/launcher3/widget/PendingAddWidgetInfo.java
@@ -31,10 +31,6 @@
  * @see {@link PendingAddItemInfo}
  */
 public class PendingAddWidgetInfo extends PendingAddItemInfo {
-    public int minWidth;
-    public int minHeight;
-    public int minResizeWidth;
-    public int minResizeHeight;
     public int previewImage;
     public int icon;
     public LauncherAppWidgetProviderInfo info;
@@ -50,17 +46,13 @@
         this.info = i;
         user = AppWidgetManagerCompat.getInstance(launcher).getUser(i);
         componentName = i.provider;
-        minWidth = i.minWidth;
-        minHeight = i.minHeight;
-        minResizeWidth = i.minResizeWidth;
-        minResizeHeight = i.minResizeHeight;
         previewImage = i.previewImage;
         icon = i.icon;
 
-        spanX = i.getSpanX(launcher);
-        spanY = i.getSpanY(launcher);
-        minSpanX = i.getMinSpanX(launcher);
-        minSpanY = i.getMinSpanY(launcher);
+        spanX = i.spanX;
+        spanY = i.spanY;
+        minSpanX = i.minSpanX;
+        minSpanY = i.minSpanY;
     }
 
     public boolean isCustomWidget() {
diff --git a/src/com/android/launcher3/widget/WidgetCell.java b/src/com/android/launcher3/widget/WidgetCell.java
index 7496ea2..94bbd92 100644
--- a/src/com/android/launcher3/widget/WidgetCell.java
+++ b/src/com/android/launcher3/widget/WidgetCell.java
@@ -146,8 +146,8 @@
         mInfo = info;
         // TODO(hyunyoungs): setup a cache for these labels.
         mWidgetName.setText(AppWidgetManagerCompat.getInstance(getContext()).loadLabel(info));
-        int hSpan = Math.min(info.getSpanX(mLauncher), profile.numColumns);
-        int vSpan = Math.min(info.getSpanY(mLauncher), profile.numRows);
+        int hSpan = Math.min(info.spanX, profile.numColumns);
+        int vSpan = Math.min(info.spanY, profile.numRows);
         mWidgetDims.setText(String.format(mDimensionsFormatString, hSpan, vSpan));
         mWidgetPreviewLoader = loader;
     }
diff --git a/src/com/android/launcher3/widget/WidgetHostViewLoader.java b/src/com/android/launcher3/widget/WidgetHostViewLoader.java
index 30b3d58..461aebb 100644
--- a/src/com/android/launcher3/widget/WidgetHostViewLoader.java
+++ b/src/com/android/launcher3/widget/WidgetHostViewLoader.java
@@ -15,6 +15,7 @@
 import com.android.launcher3.DragSource;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAppWidgetProviderInfo;
+import com.android.launcher3.Utilities;
 import com.android.launcher3.compat.AppWidgetManagerCompat;
 import com.android.launcher3.util.Thunk;
 
@@ -131,7 +132,7 @@
     public static Bundle getDefaultOptionsForWidget(Launcher launcher, PendingAddWidgetInfo info) {
         Bundle options = null;
         Rect rect = new Rect();
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+        if (Utilities.ATLEAST_JB_MR1) {
             AppWidgetResizeFrame.getWidgetSizeRanges(launcher, info.spanX, info.spanY, rect);
             Rect padding = AppWidgetHostView.getDefaultPaddingForWidget(launcher,
                     info.componentName, null);
diff --git a/src/com/android/launcher3/widget/WidgetsContainerView.java b/src/com/android/launcher3/widget/WidgetsContainerView.java
index 5afd7c4..0c6ea31 100644
--- a/src/com/android/launcher3/widget/WidgetsContainerView.java
+++ b/src/com/android/launcher3/widget/WidgetsContainerView.java
@@ -319,7 +319,6 @@
                 CellLayout layout = (CellLayout) workspace.getChildAt(currentScreen);
                 ItemInfo itemInfo = (ItemInfo) d.dragInfo;
                 if (layout != null) {
-                    layout.calculateSpans(itemInfo);
                     showOutOfSpaceMessage =
                             !layout.findCellForSpan(null, itemInfo.spanX, itemInfo.spanY);
                 }
diff --git a/src/com/android/launcher3/widget/WidgetsListAdapter.java b/src/com/android/launcher3/widget/WidgetsListAdapter.java
index d2ea252..f1cde29 100644
--- a/src/com/android/launcher3/widget/WidgetsListAdapter.java
+++ b/src/com/android/launcher3/widget/WidgetsListAdapter.java
@@ -87,6 +87,9 @@
 
     @Override
     public int getItemCount() {
+        if (mWidgetsModel == null) {
+            return 0;
+        }
         return mWidgetsModel.getPackageSize();
     }
 
@@ -166,7 +169,7 @@
 
         // if the end padding is 0, then container view (horizontal scroll view) doesn't respect
         // the end of the linear layout width + the start padding and doesn't allow scrolling.
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+        if (Utilities.ATLEAST_JB_MR1) {
             cellList.setPaddingRelative(mIndent, 0, 1, 0);
         } else {
             cellList.setPadding(mIndent, 0, 1, 0);
diff --git a/src/com/android/launcher3/widget/WidgetsRecyclerView.java b/src/com/android/launcher3/widget/WidgetsRecyclerView.java
index 61e63cd..884bdc4 100644
--- a/src/com/android/launcher3/widget/WidgetsRecyclerView.java
+++ b/src/com/android/launcher3/widget/WidgetsRecyclerView.java
@@ -64,10 +64,6 @@
         return Color.WHITE;
     }
 
-    public int getFastScrollerThumbInactiveColor(int defaultInactiveThumbColor) {
-        return getResources().getColor(R.color.widgets_view_fastscroll_thumb_inactive_color);
-    }
-
     /**
      * Sets the widget model in this view, used to determine the fast scroll position.
      */
@@ -92,6 +88,12 @@
      */
     @Override
     public String scrollToPositionAtProgress(float touchFraction) {
+        // Skip early if widgets are not bound.
+        if (mWidgets == null) {
+            return "";
+        }
+
+        // Skip early if there are no widgets.
         int rowCount = mWidgets.getPackageSize();
         if (rowCount == 0) {
             return "";
@@ -102,7 +104,7 @@
 
         getCurScrollState(mScrollPosState);
         float pos = rowCount * touchFraction;
-        int availableScrollHeight = getAvailableScrollHeight(rowCount, mScrollPosState.rowHeight, 0);
+        int availableScrollHeight = getAvailableScrollHeight(rowCount, mScrollPosState.rowHeight);
         LinearLayoutManager layoutManager = ((LinearLayoutManager) getLayoutManager());
         layoutManager.scrollToPositionWithOffset(0, (int) -(availableScrollHeight * touchFraction));
 
@@ -115,36 +117,44 @@
      * Updates the bounds for the scrollbar.
      */
     @Override
-    public void onUpdateScrollbar() {
-        int rowCount = mWidgets.getPackageSize();
+    public void onUpdateScrollbar(int dy) {
+        // Skip early if widgets are not bound.
+        if (mWidgets == null) {
+            return;
+        }
 
-        // Skip early if, there are no items.
+        // Skip early if there are no widgets.
+        int rowCount = mWidgets.getPackageSize();
         if (rowCount == 0) {
-            mScrollbar.setScrollbarThumbOffset(-1, -1);
+            mScrollbar.setThumbOffset(-1, -1);
             return;
         }
 
         // Skip early if, there no child laid out in the container.
         getCurScrollState(mScrollPosState);
         if (mScrollPosState.rowIndex < 0) {
-            mScrollbar.setScrollbarThumbOffset(-1, -1);
+            mScrollbar.setThumbOffset(-1, -1);
             return;
         }
 
-        synchronizeScrollBarThumbOffsetToViewScroll(mScrollPosState, rowCount, 0);
+        synchronizeScrollBarThumbOffsetToViewScroll(mScrollPosState, rowCount);
     }
 
     /**
      * Returns the current scroll state.
      */
-    private void getCurScrollState(ScrollPositionState stateOut) {
+    protected void getCurScrollState(ScrollPositionState stateOut) {
         stateOut.rowIndex = -1;
         stateOut.rowTopOffset = -1;
         stateOut.rowHeight = -1;
 
-        int rowCount = mWidgets.getPackageSize();
+        // Skip early if widgets are not bound.
+        if (mWidgets == null) {
+            return;
+        }
 
         // Return early if there are no items
+        int rowCount = mWidgets.getPackageSize();
         if (rowCount == 0) {
             return;
         }