Merge "Code to enable dragging to System UI shelf." into ub-launcher3-master
diff --git a/Android.mk b/Android.mk
index 3ff542e..ec675c6 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..ab430d1 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -51,11 +51,9 @@
     <uses-permission android:name="android.permission.CALL_PHONE" />
     <uses-permission android:name="android.permission.SET_WALLPAPER" />
     <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" />
     <uses-permission android:name="com.android.launcher.permission.READ_SETTINGS" />
     <uses-permission android:name="com.android.launcher.permission.WRITE_SETTINGS" />
@@ -204,12 +202,6 @@
             </intent-filter>
         </receiver>
 
-        <receiver android:name="com.android.launcher3.StartupReceiver" >
-            <intent-filter>
-                <action android:name="android.intent.action.BOOT_COMPLETED" />
-            </intent-filter>
-        </receiver>
-
         <!-- The settings provider contains Home's data, like the workspace favorites -->
         <provider
             android:name="com.android.launcher3.LauncherProvider"
diff --git a/proguard.flags b/proguard.flags
index 6a9d6f3..a8e2b60 100644
--- a/proguard.flags
+++ b/proguard.flags
@@ -1,3 +1,7 @@
+-keep,allowshrinking,allowoptimization class com.android.launcher3.** {
+  *;
+}
+
 -keep class com.android.launcher3.BaseRecyclerViewFastScrollBar {
   public void setWidth(int);
   public int getWidth();
@@ -63,4 +67,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/layout/user_folder.xml b/res/layout/user_folder.xml
index ecf7def..bf32789 100644
--- a/res/layout/user_folder.xml
+++ b/res/layout/user_folder.xml
@@ -20,7 +20,8 @@
     android:layout_height="wrap_content"
     android:background="@drawable/quantum_panel"
     android:elevation="5dp"
-    android:orientation="vertical" >
+    android:orientation="vertical"
+    android:descendantFocusability="afterDescendants" >
 
     <FrameLayout
         android:id="@+id/folder_content_wrapper"
diff --git a/res/values/config.xml b/res/values/config.xml
index 73de794..0f18dff 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. -->
@@ -74,10 +74,6 @@
          filter the activities shown in the launcher. Can be empty. -->
     <string name="app_filter_class" translatable="false"></string>
 
-    <!-- Name of a subclass of com.android.launcher3.BuildInfo used to
-         get build information. Can be empty. -->
-    <string name="build_info_class" translatable="false"></string>
-
 <!-- Accessibility actions -->
     <item type="id" name="action_remove" />
     <item type="id" name="action_uninstall" />
diff --git a/src/com/android/launcher3/BaseContainerView.java b/src/com/android/launcher3/BaseContainerView.java
index c8de9df..a049ec0 100644
--- a/src/com/android/launcher3/BaseContainerView.java
+++ b/src/com/android/launcher3/BaseContainerView.java
@@ -22,6 +22,8 @@
 import android.util.Log;
 import android.widget.LinearLayout;
 
+import com.android.launcher3.config.ProviderConfig;
+
 /**
  * A base container view, which supports resizing.
  */
@@ -69,7 +71,7 @@
      * Sets the search bar bounds for this container view to match.
      */
     final public void setSearchBarBounds(Rect bounds) {
-        if (LauncherAppState.isDogfoodBuild() && !isValidSearchBarBounds(bounds)) {
+        if (ProviderConfig.IS_DOGFOOD_BUILD && !isValidSearchBarBounds(bounds)) {
             Log.e(TAG, "Invalid search bar bounds: " + bounds);
         }
 
diff --git a/src/com/android/launcher3/BuildInfo.java b/src/com/android/launcher3/BuildInfo.java
index b49ee0d..1392d7a 100644
--- a/src/com/android/launcher3/BuildInfo.java
+++ b/src/com/android/launcher3/BuildInfo.java
@@ -1,32 +1,9 @@
 package com.android.launcher3;
 
-import android.text.TextUtils;
-import android.util.Log;
-
+// TODO: Remove this class once all its references are gone.
 public class BuildInfo {
-    private static final boolean DBG = false;
-    private static final String TAG = "BuildInfo";
 
     public boolean isDogfoodBuild() {
         return false;
     }
-
-    public static BuildInfo loadByName(String className) {
-        if (TextUtils.isEmpty(className)) return new BuildInfo();
-
-        if (DBG) Log.d(TAG, "Loading BuildInfo: " + className);
-        try {
-            Class<?> cls = Class.forName(className);
-            return (BuildInfo) cls.newInstance();
-        } catch (ClassNotFoundException e) {
-            Log.e(TAG, "Bad BuildInfo class", e);
-        } catch (InstantiationException e) {
-            Log.e(TAG, "Bad BuildInfo class", e);
-        } catch (IllegalAccessException e) {
-            Log.e(TAG, "Bad BuildInfo class", e);
-        } catch (ClassCastException e) {
-            Log.e(TAG, "Bad BuildInfo class", e);
-        }
-        return new BuildInfo();
-    }
 }
diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java
index e96768f..cad1a4f 100644
--- a/src/com/android/launcher3/CellLayout.java
+++ b/src/com/android/launcher3/CellLayout.java
@@ -54,6 +54,7 @@
 import com.android.launcher3.accessibility.DragAndDropAccessibilityDelegate;
 import com.android.launcher3.accessibility.FolderAccessibilityHelper;
 import com.android.launcher3.accessibility.WorkspaceAccessibilityHelper;
+import com.android.launcher3.config.ProviderConfig;
 import com.android.launcher3.util.Thunk;
 import com.android.launcher3.widget.PendingAddWidgetInfo;
 
@@ -212,6 +213,7 @@
 
         mBackground = (TransitionDrawable) res.getDrawable(R.drawable.bg_screenpanel);
         mBackground.setCallback(this);
+        mBackground.setAlpha((int) (mBackgroundAlpha * 255));
 
         mReorderPreviewAnimationMagnitude = (REORDER_PREVIEW_MAGNITUDE *
                 grid.iconSizePx);
@@ -415,7 +417,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();
         }
@@ -564,7 +570,7 @@
         try {
             dispatchRestoreInstanceState(states);
         } catch (IllegalArgumentException ex) {
-            if (LauncherAppState.isDogfoodBuild()) {
+            if (ProviderConfig.IS_DOGFOOD_BUILD) {
                 throw ex;
             }
             // Mismatched viewId / viewType preventing restore. Skip restore on production builds.
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 5966112..6cfdcf7 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -25,6 +25,7 @@
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.util.DisplayMetrics;
+import android.util.Size;
 import android.view.Gravity;
 import android.view.View;
 import android.view.ViewGroup;
@@ -56,7 +57,6 @@
     private final int overviewModeBarItemWidthPx;
     private final int overviewModeBarSpacerWidthPx;
     private final float overviewModeIconZoneRatio;
-    private final float overviewModeScaleFactor;
 
     // Workspace
     private int desiredWorkspaceLeftRightMarginPx;
@@ -136,8 +136,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 +332,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
@@ -476,7 +467,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 +476,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/FocusHelper.java b/src/com/android/launcher3/FocusHelper.java
index 57aec32..3751b88 100644
--- a/src/com/android/launcher3/FocusHelper.java
+++ b/src/com/android/launcher3/FocusHelper.java
@@ -22,6 +22,7 @@
 import android.view.View;
 import android.view.ViewGroup;
 
+import com.android.launcher3.config.ProviderConfig;
 import com.android.launcher3.util.FocusLogic;
 import com.android.launcher3.util.Thunk;
 
@@ -74,7 +75,7 @@
 
 
             if (!(v.getParent() instanceof ShortcutAndWidgetContainer)) {
-                if (LauncherAppState.isDogfoodBuild()) {
+                if (ProviderConfig.IS_DOGFOOD_BUILD) {
                     throw new IllegalStateException("Parent of the focused item is not supported.");
                 } else {
                     return false;
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 279a82b..e27e183 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -103,6 +103,7 @@
 import com.android.launcher3.compat.LauncherAppsCompat;
 import com.android.launcher3.compat.UserHandleCompat;
 import com.android.launcher3.compat.UserManagerCompat;
+import com.android.launcher3.config.ProviderConfig;
 import com.android.launcher3.model.WidgetsModel;
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.LongArrayMap;
@@ -3775,7 +3776,7 @@
                             Object tag = v.getTag();
                             String desc = "Collision while binding workspace item: " + item
                                     + ". Collides with " + tag;
-                            if (LauncherAppState.isDogfoodBuild()) {
+                            if (ProviderConfig.IS_DOGFOOD_BUILD) {
                                 throw (new RuntimeException(desc));
                             } else {
                                 Log.d(TAG, desc);
diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java
index 0b7b1fd..e97080c 100644
--- a/src/com/android/launcher3/LauncherAppState.java
+++ b/src/com/android/launcher3/LauncherAppState.java
@@ -17,7 +17,6 @@
 package com.android.launcher3;
 
 import android.app.SearchManager;
-import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
@@ -33,7 +32,6 @@
 public class LauncherAppState {
 
     private final AppFilter mAppFilter;
-    private final BuildInfo mBuildInfo;
     @Thunk final LauncherModel mModel;
     private final IconCache mIconCache;
     private final WidgetPreviewLoader mWidgetCache;
@@ -87,7 +85,6 @@
         mWidgetCache = new WidgetPreviewLoader(sContext, mIconCache);
 
         mAppFilter = AppFilter.loadByName(sContext.getString(R.string.app_filter_class));
-        mBuildInfo = BuildInfo.loadByName(sContext.getString(R.string.build_info_class));
         mModel = new LauncherModel(this, mIconCache, mAppFilter);
 
         LauncherAppsCompat.getInstance(sContext).addOnAppsChangedCallback(mModel);
@@ -147,7 +144,7 @@
         sLauncherProvider = new WeakReference<LauncherProvider>(provider);
     }
 
-    static LauncherProvider getLauncherProvider() {
+    public static LauncherProvider getLauncherProvider() {
         return sLauncherProvider.get();
     }
 
@@ -172,8 +169,4 @@
     public InvariantDeviceProfile getInvariantDeviceProfile() {
         return mInvariantDeviceProfile;
     }
-
-    public static boolean isDogfoodBuild() {
-        return getInstance().mBuildInfo.isDogfoodBuild();
-    }
 }
diff --git a/src/com/android/launcher3/LauncherAppWidgetProviderInfo.java b/src/com/android/launcher3/LauncherAppWidgetProviderInfo.java
index 85af92f..9ba7853 100644
--- a/src/com/android/launcher3/LauncherAppWidgetProviderInfo.java
+++ b/src/com/android/launcher3/LauncherAppWidgetProviderInfo.java
@@ -5,6 +5,7 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.PackageManager;
+import android.graphics.Point;
 import android.graphics.drawable.Drawable;
 import android.os.Build;
 import android.os.Parcel;
@@ -110,4 +111,15 @@
             mMinSpanY = minResizeSpan[1];
         }
     }
+
+    public Point getMinSpans(InvariantDeviceProfile idp, Context context) {
+        // Calculate the spans corresponding to any one of the orientations as it should not change
+        // based on orientation.
+        // TODO: Use the max of both profiles
+        int[] minSpans = CellLayout.rectToCell(
+                idp.portraitProfile, context, minResizeWidth, minResizeHeight, null);
+        return new Point(
+                (resizeMode & RESIZE_HORIZONTAL) != 0 ? minSpans[0] : -1,
+                        (resizeMode & RESIZE_VERTICAL) != 0 ? minSpans[1] : -1);
+    }
  }
diff --git a/src/com/android/launcher3/LauncherBackupAgentHelper.java b/src/com/android/launcher3/LauncherBackupAgentHelper.java
index a92a889..611ab2e 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 {
@@ -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 (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 38a4bde..136556b 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;
@@ -75,7 +76,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 +108,7 @@
         Favorites.SPANY,                   // 15
         Favorites.TITLE,                   // 16
         Favorites.PROFILE_ID,              // 17
+        Favorites.RANK,                    // 18
     };
 
     private static final int ID_INDEX = 0;
@@ -126,6 +128,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,6 +153,9 @@
     private DeviceProfieData mDeviceProfileData;
     private InvariantDeviceProfile mIdp;
 
+    DeviceProfieData migrationCompatibleProfileData;
+    HashSet<String> widgetSizes = new HashSet<>();
+
     boolean restoreSuccessful;
     int restoredBackupVersion = 1;
 
@@ -286,9 +292,9 @@
             return true;
         }
 
-        boolean isHotsetCompatible = false;
+        boolean isHotseatCompatible = false;
         if (currentProfile.allappsRank >= oldProfile.hotseatCount) {
-            isHotsetCompatible = true;
+            isHotseatCompatible = true;
             mHotseatShift = 0;
         }
 
@@ -296,12 +302,28 @@
                 && ((currentProfile.hotseatCount - currentProfile.allappsRank) >=
                         (oldProfile.hotseatCount - oldProfile.allappsRank))) {
             // There is enough space on both sides of the hotseat.
-            isHotsetCompatible = true;
+            isHotseatCompatible = true;
             mHotseatShift = currentProfile.allappsRank - oldProfile.allappsRank;
         }
 
-        return isHotsetCompatible && (currentProfile.desktopCols >= oldProfile.desktopCols)
-                && (currentProfile.desktopRows >= oldProfile.desktopRows);
+        if (!isHotseatCompatible) {
+            return false;
+        }
+        if ((currentProfile.desktopCols >= oldProfile.desktopCols)
+                && (currentProfile.desktopRows >= oldProfile.desktopRows)) {
+            return true;
+        }
+
+        if ((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;
     }
 
     /**
@@ -441,7 +463,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);
@@ -648,7 +673,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
@@ -699,7 +726,8 @@
             }
         }
 
-        // 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.
@@ -783,6 +811,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)) {
@@ -870,6 +899,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);
@@ -895,7 +925,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)) {
@@ -987,14 +1021,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;
     }
@@ -1179,6 +1208,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/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index 1fbb06e..12e1054 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -55,6 +55,8 @@
 import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo;
 import com.android.launcher3.compat.UserHandleCompat;
 import com.android.launcher3.compat.UserManagerCompat;
+import com.android.launcher3.config.ProviderConfig;
+import com.android.launcher3.model.MigrateFromRestoreTask;
 import com.android.launcher3.model.WidgetsModel;
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.CursorIconInfo;
@@ -887,7 +889,7 @@
     }
 
     private void assertWorkspaceLoaded() {
-        if (LauncherAppState.isDogfoodBuild() && (isLoadingWorkspace() || !mHasLoaderCompletedOnce)) {
+        if (ProviderConfig.IS_DOGFOOD_BUILD && (isLoadingWorkspace() || !mHasLoaderCompletedOnce)) {
             throw new RuntimeException("Trying to add shortcut while loader is running");
         }
     }
@@ -1128,7 +1130,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;
@@ -1406,7 +1408,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;
 
@@ -1744,14 +1746,33 @@
             final PackageManager manager = context.getPackageManager();
             final boolean isSafeMode = manager.isSafeMode();
             final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
-            final boolean isSdCardReady = context.registerReceiver(null,
-                    new IntentFilter(StartupReceiver.SYSTEM_READY)) != null;
+            final boolean isSdCardReady = Utilities.isBootCompleted();
 
             LauncherAppState app = LauncherAppState.getInstance();
             InvariantDeviceProfile profile = app.getInvariantDeviceProfile();
             int countX = (int) profile.numColumns;
             int countY = (int) profile.numRows;
 
+
+            if (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);
                 LauncherAppState.getLauncherProvider().deleteDatabase();
@@ -2289,7 +2310,7 @@
 
                 if (!isSdCardReady && !sPendingPackages.isEmpty()) {
                     context.registerReceiver(new AppsAvailabilityCheck(),
-                            new IntentFilter(StartupReceiver.SYSTEM_READY),
+                            new IntentFilter(Intent.ACTION_BOOT_COMPLETED),
                             null, sWorker);
                 }
 
@@ -2461,7 +2482,7 @@
                                 return (int) (lhs.screenId - rhs.screenId);
                             }
                             default:
-                                if (LauncherAppState.isDogfoodBuild()) {
+                                if (ProviderConfig.IS_DOGFOOD_BUILD) {
                                     throw new RuntimeException("Unexpected container type when " +
                                             "sorting workspace items.");
                                 }
diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java
index cc5e18b..6cbb267 100644
--- a/src/com/android/launcher3/LauncherProvider.java
+++ b/src/com/android/launcher3/LauncherProvider.java
@@ -71,7 +71,7 @@
     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;
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/StartupReceiver.java b/src/com/android/launcher3/StartupReceiver.java
deleted file mode 100644
index 65f913f..0000000
--- a/src/com/android/launcher3/StartupReceiver.java
+++ /dev/null
@@ -1,15 +0,0 @@
-package com.android.launcher3;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-
-public class StartupReceiver extends BroadcastReceiver {
-
-    static final String SYSTEM_READY = "com.android.launcher3.SYSTEM_READY";
-
-    @Override
-    public void onReceive(Context context, Intent intent) {
-        context.sendStickyBroadcast(new Intent(SYSTEM_READY));
-    }
-}
diff --git a/src/com/android/launcher3/Stats.java b/src/com/android/launcher3/Stats.java
index cb0e252..4aba150 100644
--- a/src/com/android/launcher3/Stats.java
+++ b/src/com/android/launcher3/Stats.java
@@ -25,6 +25,8 @@
 import android.view.View;
 import android.view.ViewParent;
 
+import com.android.launcher3.config.ProviderConfig;
+
 public class Stats {
 
     /**
@@ -71,7 +73,7 @@
 
             if (provider != null) {
                 provider.fillInLaunchSourceData(sourceData);
-            } else if (LauncherAppState.isDogfoodBuild()) {
+            } else if (ProviderConfig.IS_DOGFOOD_BUILD) {
                 throw new RuntimeException("Expected LaunchSourceProvider");
             }
         }
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index 8fd298d..9ae989f 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -56,8 +56,11 @@
 import android.view.View;
 import android.widget.Toast;
 
+import com.android.launcher3.config.ProviderConfig;
+
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
+import java.lang.reflect.Method;
 import java.util.ArrayList;
 import java.util.Locale;
 import java.util.Set;
@@ -660,7 +663,7 @@
     }
 
     public static void assertWorkerThread() {
-        if (LauncherAppState.isDogfoodBuild() &&
+        if (ProviderConfig.IS_DOGFOOD_BUILD &&
                 (LauncherModel.sWorkerThread.getThreadId() != Process.myTid())) {
             throw new IllegalStateException();
         }
@@ -709,4 +712,18 @@
     public static String createDbSelectionQuery(String columnName, Iterable<?> values) {
         return String.format(Locale.ENGLISH, "%s IN (%s)", columnName, TextUtils.join(", ", values));
     }
+
+    @SuppressWarnings({"unchecked", "rawtypes"})
+    public static boolean isBootCompleted() {
+        try {
+            Class clazz = Class.forName("android.os.SystemProperties");
+            Method getter = clazz.getDeclaredMethod("get", String.class);
+            String value = (String) getter.invoke(null, "sys.boot_completed");
+            return "1".equals(value);
+        } catch (Exception e) {
+            Log.d(TAG, "Unable to read system properties");
+            // Assume that boot has completed
+            return true;
+        }
+    }
 }
diff --git a/src/com/android/launcher3/WidgetPreviewLoader.java b/src/com/android/launcher3/WidgetPreviewLoader.java
index 629387e..2a8053d 100644
--- a/src/com/android/launcher3/WidgetPreviewLoader.java
+++ b/src/com/android/launcher3/WidgetPreviewLoader.java
@@ -386,7 +386,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 +405,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 edf8a32..3ab8d79 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -68,6 +68,7 @@
 import com.android.launcher3.accessibility.LauncherAccessibilityDelegate.AccessibilityDragSource;
 import com.android.launcher3.accessibility.OverviewScreenAccessibilityDelegate;
 import com.android.launcher3.compat.UserHandleCompat;
+import com.android.launcher3.config.ProviderConfig;
 import com.android.launcher3.util.LongArrayMap;
 import com.android.launcher3.util.Thunk;
 import com.android.launcher3.util.WallpaperUtils;
@@ -300,8 +301,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();
 
@@ -1956,15 +1958,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;
     }
 
     /**
@@ -2675,7 +2679,7 @@
                         CellLayout parentCell = getParentCellLayoutForView(cell);
                         if (parentCell != null) {
                             parentCell.removeView(cell);
-                        } else if (LauncherAppState.isDogfoodBuild()) {
+                        } else if (ProviderConfig.IS_DOGFOOD_BUILD) {
                             throw new NullPointerException("mDragInfo.cell has null parent");
                         }
                         addInScreen(cell, container, screenId, mTargetCell[0], mTargetCell[1],
@@ -3117,7 +3121,7 @@
         CellLayout layout = null;
         ItemInfo item = d.dragInfo;
         if (item == null) {
-            if (LauncherAppState.isDogfoodBuild()) {
+            if (ProviderConfig.IS_DOGFOOD_BUILD) {
                 throw new NullPointerException("DragObject has null info");
             }
             return;
@@ -3725,7 +3729,7 @@
                     mDragInfo.container, mDragInfo.screenId);
             if (cellLayout != null) {
                 cellLayout.onDropChild(mDragInfo.cell);
-            } else if (LauncherAppState.isDogfoodBuild()) {
+            } else if (ProviderConfig.IS_DOGFOOD_BUILD) {
                 throw new RuntimeException("Invalid state: cellLayout == null in "
                         + "Workspace#onDropCompleted. Please file a bug. ");
             };
@@ -3745,7 +3749,7 @@
         CellLayout parentCell = getParentCellLayoutForView(v);
         if (parentCell != null) {
             parentCell.removeView(v);
-        } else if (LauncherAppState.isDogfoodBuild()) {
+        } else if (ProviderConfig.IS_DOGFOOD_BUILD) {
             throw new NullPointerException("mDragInfo.cell has null parent");
         }
         if (v instanceof DropTarget) {
diff --git a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
index 5d16cd5..f5a1cc4 100644
--- a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
+++ b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
@@ -210,8 +210,9 @@
         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();
     }
 
diff --git a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
index 057883c..e96567c 100644
--- a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
+++ b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
@@ -22,12 +22,15 @@
 import android.graphics.PointF;
 import android.graphics.Rect;
 import android.os.Handler;
+import android.support.v4.view.accessibility.AccessibilityRecordCompat;
+import android.support.v4.view.accessibility.AccessibilityEventCompat;
 import android.support.v7.widget.GridLayoutManager;
 import android.support.v7.widget.RecyclerView;
 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;
@@ -69,6 +72,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);
+            if (mApps.hasNoFilteredResults()) {
+                // Disregard the no-search-results text as a list item for accessibility
+                final AccessibilityRecordCompat record = AccessibilityEventCompat
+                        .asRecord(event);
+                record.setItemCount(0);
+            }
+        }
+
+        @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 {
@@ -305,7 +340,7 @@
         Resources res = context.getResources();
         mApps = apps;
         mGridSizer = new GridSpanSizer();
-        mGridLayoutMgr = new GridLayoutManager(context, 1, GridLayoutManager.VERTICAL, false);
+        mGridLayoutMgr = new AppsGridLayoutManager(context);
         mGridLayoutMgr.setSpanSizeLookup(mGridSizer);
         mItemDecoration = new GridItemDecoration();
         mLayoutInflater = LayoutInflater.from(context);
diff --git a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
index 47241ce..01e0f0c 100644
--- a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
+++ b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
@@ -18,11 +18,12 @@
 import android.content.Context;
 import android.support.v7.widget.RecyclerView;
 import android.util.Log;
+
 import com.android.launcher3.AppInfo;
 import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.compat.AlphabeticIndexCompat;
 import com.android.launcher3.compat.UserHandleCompat;
+import com.android.launcher3.config.ProviderConfig;
 import com.android.launcher3.model.AppNameComparator;
 import com.android.launcher3.util.ComponentKey;
 
@@ -393,7 +394,7 @@
                 if (info != null) {
                     mPredictedApps.add(info);
                 } else {
-                    if (LauncherAppState.isDogfoodBuild()) {
+                    if (ProviderConfig.IS_DOGFOOD_BUILD) {
                         Log.e(TAG, "Predicted app not found: " + ck.flattenToString(mLauncher));
                     }
                 }
diff --git a/src/com/android/launcher3/compat/AlphabeticIndexCompat.java b/src/com/android/launcher3/compat/AlphabeticIndexCompat.java
index ec1fb66..463278a 100644
--- a/src/com/android/launcher3/compat/AlphabeticIndexCompat.java
+++ b/src/com/android/launcher3/compat/AlphabeticIndexCompat.java
@@ -1,6 +1,7 @@
 package com.android.launcher3.compat;
 
 import android.content.Context;
+
 import com.android.launcher3.Utilities;
 
 import java.lang.reflect.Constructor;
@@ -62,6 +63,7 @@
     private boolean mHasValidAlphabeticIndex;
     private String mDefaultMiscLabel;
 
+    @SuppressWarnings({"unchecked", "rawtypes"})
     public AlphabeticIndexCompat(Context context) {
         super();
         try {
diff --git a/src/com/android/launcher3/config/ProviderConfig.java b/src/com/android/launcher3/config/ProviderConfig.java
index e8930d0..825b434 100644
--- a/src/com/android/launcher3/config/ProviderConfig.java
+++ b/src/com/android/launcher3/config/ProviderConfig.java
@@ -19,4 +19,6 @@
 public class ProviderConfig {
 
     public static final String AUTHORITY = "com.android.launcher3.settings".intern();
+
+    public static boolean IS_DOGFOOD_BUILD = false;
 }
diff --git a/src/com/android/launcher3/model/MigrateFromRestoreTask.java b/src/com/android/launcher3/model/MigrateFromRestoreTask.java
new file mode 100644
index 0000000..8d4472f
--- /dev/null
+++ b/src/com/android/launcher3/model/MigrateFromRestoreTask.java
@@ -0,0 +1,765 @@
+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 {
+
+    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..cabff14 100644
--- a/src/com/android/launcher3/model/WidgetsModel.java
+++ b/src/com/android/launcher3/model/WidgetsModel.java
@@ -39,7 +39,7 @@
     private ArrayList<Object> mRawList;
 
     private final AppWidgetManagerCompat mAppWidgetMgr;
-    private final Comparator mWidgetAndShortcutNameComparator;
+    private final WidgetsAndShortcutNameComparator mWidgetAndShortcutNameComparator;
     private final Comparator mAppNameComparator;
     private final IconCache mIconCache;
     private final AppFilter mAppFilter;
@@ -103,6 +103,7 @@
         // clear the lists.
         mWidgetsList.clear();
         mPackageItemInfos.clear();
+        mWidgetAndShortcutNameComparator.reset();
 
         // add and update.
         for (Object o: rawWidgetsShortcuts) {
@@ -139,7 +140,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/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/widget/WidgetsListAdapter.java b/src/com/android/launcher3/widget/WidgetsListAdapter.java
index d2ea252..a54626a 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();
     }