Revert "Revert "Revert "Revert "Revert "Revert "Changing GridMigrationTask to use ModelDbController directly""""""

This reverts commit 09905cfa90a46b5fb4868d0ba4963f9c45e3f336.

Reason for revert: Fixed the crashing issue

Change-Id: Ibcfc28a89356d262e67e8842d6bae37e230a70f5
diff --git a/src/com/android/launcher3/AppWidgetsRestoredReceiver.java b/src/com/android/launcher3/AppWidgetsRestoredReceiver.java
index dc4ed26..3dd8627 100644
--- a/src/com/android/launcher3/AppWidgetsRestoredReceiver.java
+++ b/src/com/android/launcher3/AppWidgetsRestoredReceiver.java
@@ -15,8 +15,8 @@
 import androidx.annotation.WorkerThread;
 
 import com.android.launcher3.LauncherSettings.Favorites;
-import com.android.launcher3.model.DatabaseHelper;
 import com.android.launcher3.model.LoaderTask;
+import com.android.launcher3.model.ModelDbController;
 import com.android.launcher3.model.WidgetsModel;
 import com.android.launcher3.model.data.LauncherAppWidgetInfo;
 import com.android.launcher3.pm.UserCache;
@@ -53,7 +53,7 @@
      * Updates the app widgets whose id has changed during the restore process.
      */
     @WorkerThread
-    public static void restoreAppWidgetIds(Context context, DatabaseHelper helper,
+    public static void restoreAppWidgetIds(Context context, ModelDbController controller,
             int[] oldWidgetIds, int[] newWidgetIds, @NonNull AppWidgetHost host) {
         if (WidgetsModel.GO_DISABLE_WIDGETS) {
             Log.e(TAG, "Skipping widget ID remap as widgets not supported");
@@ -78,7 +78,7 @@
                 + ", newWidgetIds=" + IntArray.wrap(newWidgetIds).toConcatString());
 
         try {
-            IntArray result = LauncherDbUtils.queryIntArray(false, helper.getReadableDatabase(),
+            IntArray result = LauncherDbUtils.queryIntArray(false, controller.getDb(),
                     Favorites.TABLE_NAME, Favorites.APPWIDGET_ID,
                     Favorites.APPWIDGET_ID + "!=" + LauncherAppWidgetInfo.NO_ID, null, null);
             // TODO(b/234700507): Remove the logs after the bug is fixed
@@ -108,7 +108,7 @@
             final String where = "appWidgetId=? and (restored & 1) = 1 and profileId=?";
             final String[] args = new String[] { oldWidgetId, Long.toString(mainProfileId) };
             int result = new ContentWriter(context,
-                            new ContentWriter.CommitParams(helper, where, args))
+                            new ContentWriter.CommitParams(controller, where, args))
                     .put(LauncherSettings.Favorites.APPWIDGET_ID, newWidgetIds[i])
                     .put(LauncherSettings.Favorites.RESTORED, state)
                     .commit();
@@ -116,7 +116,7 @@
                 // TODO(b/234700507): Remove the logs after the bug is fixed
                 Log.e(TAG, "restoreAppWidgetIds: remapping failed since the widget is not in"
                         + " the database anymore");
-                try (Cursor cursor = helper.getWritableDatabase().query(
+                try (Cursor cursor = controller.getDb().query(
                         Favorites.TABLE_NAME,
                         new String[]{Favorites.APPWIDGET_ID},
                         "appWidgetId=?", new String[]{oldWidgetId}, null, null, null)) {
diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java
index 0df4bd4..9abec50 100644
--- a/src/com/android/launcher3/LauncherProvider.java
+++ b/src/com/android/launcher3/LauncherProvider.java
@@ -263,18 +263,6 @@
                 getModelDbController().refreshHotseatRestoreTable();
                 return null;
             }
-            case LauncherSettings.Settings.METHOD_UPDATE_CURRENT_OPEN_HELPER: {
-                Bundle result = new Bundle();
-                result.putBoolean(LauncherSettings.Settings.EXTRA_VALUE,
-                        getModelDbController().updateCurrentOpenHelper(arg /* dbFile */));
-                return result;
-            }
-            case LauncherSettings.Settings.METHOD_PREP_FOR_PREVIEW: {
-                Bundle result = new Bundle();
-                result.putBoolean(LauncherSettings.Settings.EXTRA_VALUE,
-                        getModelDbController().prepareForPreview(arg /* dbFile */));
-                return result;
-            }
         }
         return null;
     }
diff --git a/src/com/android/launcher3/LauncherSettings.java b/src/com/android/launcher3/LauncherSettings.java
index b65e96b..7fda326 100644
--- a/src/com/android/launcher3/LauncherSettings.java
+++ b/src/com/android/launcher3/LauncherSettings.java
@@ -148,11 +148,6 @@
         public static final String HYBRID_HOTSEAT_BACKUP_TABLE = "hotseat_restore_backup";
 
         /**
-         * Temporary table used specifically for grid migrations during wallpaper preview
-         */
-        public static final String PREVIEW_TABLE_NAME = "favorites_preview";
-
-        /**
          * Temporary table used specifically for multi-db grid migrations
          */
         public static final String TMP_TABLE = "favorites_tmp";
@@ -164,18 +159,6 @@
                 + LauncherProvider.AUTHORITY + "/" + TABLE_NAME);
 
         /**
-         * The content:// style URL for "favorites_preview" table
-         */
-        public static final Uri PREVIEW_CONTENT_URI = Uri.parse("content://"
-                + LauncherProvider.AUTHORITY + "/" + PREVIEW_TABLE_NAME);
-
-        /**
-         * The content:// style URL for "favorites_tmp" table
-         */
-        public static final Uri TMP_CONTENT_URI = Uri.parse("content://"
-                + LauncherProvider.AUTHORITY + "/" + TMP_TABLE);
-
-        /**
          * The content:// style URL for a given row, identified by its id.
          *
          * @param id The row id.
@@ -376,10 +359,6 @@
 
         public static final String METHOD_REFRESH_HOTSEAT_RESTORE_TABLE = "restore_hotseat_table";
 
-        public static final String METHOD_UPDATE_CURRENT_OPEN_HELPER = "update_current_open_helper";
-
-        public static final String METHOD_PREP_FOR_PREVIEW = "prep_for_preview";
-
         public static final String EXTRA_VALUE = "value";
 
         public static final String EXTRA_DB_NAME = "db_name";
@@ -393,11 +372,8 @@
         }
 
         public static Bundle call(ContentResolver cr, String method, String arg) {
-            return call(cr, method, arg, null /* extras */);
+            return cr.call(CONTENT_URI, method, arg, null);
         }
 
-        public static Bundle call(ContentResolver cr, String method, String arg, Bundle extras) {
-            return cr.call(CONTENT_URI, method, arg, extras);
-        }
     }
 }
diff --git a/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java b/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java
index 372e9bf..8f0b8ec 100644
--- a/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java
+++ b/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java
@@ -16,6 +16,7 @@
 
 package com.android.launcher3.graphics;
 
+import static com.android.launcher3.LauncherSettings.Favorites.TABLE_NAME;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
 
@@ -52,6 +53,8 @@
 import com.android.launcher3.model.BgDataModel;
 import com.android.launcher3.model.GridSizeMigrationUtil;
 import com.android.launcher3.model.LoaderTask;
+import com.android.launcher3.model.ModelDbController;
+import com.android.launcher3.provider.LauncherDbUtils;
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.RunnableList;
 import com.android.launcher3.util.Themes;
@@ -145,7 +148,9 @@
         final String query = LauncherSettings.Favorites.ITEM_TYPE + " = "
                 + LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET;
 
-        try (Cursor c = context.getContentResolver().query(LauncherSettings.Favorites.CONTENT_URI,
+        ModelDbController mainController =
+                LauncherAppState.getInstance(mContext).getModel().getModelDbController();
+        try (Cursor c = mainController.query(TABLE_NAME,
                 new String[] {
                         LauncherSettings.Favorites.APPWIDGET_ID,
                         LauncherSettings.Favorites.SPANX,
@@ -190,8 +195,6 @@
 
     @WorkerThread
     private void loadModelData() {
-        final boolean migrated = doGridMigrationIfNecessary();
-
         final Context inflationContext;
         if (mWallpaperColors != null) {
             // Create a themed context, without affecting the main application context
@@ -209,8 +212,20 @@
                     Themes.getActivityThemeRes(mContext));
         }
 
-        if (migrated) {
+        if (GridSizeMigrationUtil.needsToMigrate(inflationContext, mIdp)) {
+            // Start the migration
             PreviewContext previewContext = new PreviewContext(inflationContext, mIdp);
+            // Copy existing data to preview DB
+            LauncherDbUtils.copyTable(LauncherAppState.getInstance(mContext)
+                    .getModel().getModelDbController().getDb(),
+                    TABLE_NAME,
+                    LauncherAppState.getInstance(previewContext)
+                            .getModel().getModelDbController().getDb(),
+                    TABLE_NAME,
+                    mContext);
+            LauncherAppState.getInstance(previewContext)
+                    .getModel().getModelDbController().clearEmptyDbFlag();
+
             new LoaderTask(
                     LauncherAppState.getInstance(previewContext),
                     /* bgAllAppsList= */ null,
@@ -229,8 +244,7 @@
                         query += " or " + LauncherSettings.Favorites.SCREEN + " = "
                                 + Workspace.SECOND_SCREEN_ID;
                     }
-                    loadWorkspaceForPreviewSurfaceRenderer(new ArrayList<>(),
-                            LauncherSettings.Favorites.PREVIEW_CONTENT_URI, query);
+                    loadWorkspace(new ArrayList<>(), query, null);
 
                     final SparseArray<Size> spanInfo =
                             getLoadedLauncherWidgetInfo(previewContext.getBaseContext());
@@ -253,14 +267,6 @@
         }
     }
 
-    @WorkerThread
-    private boolean doGridMigrationIfNecessary() {
-        if (!GridSizeMigrationUtil.needsToMigrate(mContext, mIdp)) {
-            return false;
-        }
-        return GridSizeMigrationUtil.migrateGridIfNeeded(mContext, mIdp);
-    }
-
     @UiThread
     private void renderView(Context inflationContext, BgDataModel dataModel,
             Map<ComponentKey, AppWidgetProviderInfo> widgetProviderInfoMap,
diff --git a/src/com/android/launcher3/model/DatabaseHelper.java b/src/com/android/launcher3/model/DatabaseHelper.java
index dc5fcf7..ecf5f67 100644
--- a/src/com/android/launcher3/model/DatabaseHelper.java
+++ b/src/com/android/launcher3/model/DatabaseHelper.java
@@ -15,8 +15,8 @@
  */
 package com.android.launcher3.model;
 
+import static com.android.launcher3.LauncherSettings.Favorites.addTableToDb;
 import static com.android.launcher3.provider.LauncherDbUtils.dropTable;
-import static com.android.launcher3.provider.LauncherDbUtils.tableExists;
 
 import android.content.ContentValues;
 import android.content.Context;
@@ -36,9 +36,6 @@
 
 import com.android.launcher3.AutoInstallsLayout;
 import com.android.launcher3.AutoInstallsLayout.LayoutParserCallback;
-import com.android.launcher3.InvariantDeviceProfile;
-import com.android.launcher3.LauncherFiles;
-import com.android.launcher3.LauncherPrefs;
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.LauncherSettings.Favorites;
 import com.android.launcher3.Utilities;
@@ -58,6 +55,7 @@
 import java.net.URISyntaxException;
 import java.util.Arrays;
 import java.util.Locale;
+import java.util.function.ToLongFunction;
 import java.util.stream.Collectors;
 
 /**
@@ -76,45 +74,23 @@
     private static final boolean LOGD = false;
 
     private static final String DOWNGRADE_SCHEMA_FILE = "downgrade_schema.json";
-    public static final String EMPTY_DATABASE_CREATED = "EMPTY_DATABASE_CREATED";
 
     private final Context mContext;
-    private final boolean mForMigration;
+    private final ToLongFunction<UserHandle> mUserSerialProvider;
+    private final Runnable mOnEmptyDbCreateCallback;
+
     private int mMaxItemId = -1;
     public boolean mHotseatRestoreTableExists;
 
-    public static DatabaseHelper createDatabaseHelper(Context context, boolean forMigration) {
-        return createDatabaseHelper(context, null, forMigration);
-    }
-
-    public static DatabaseHelper createDatabaseHelper(Context context, String dbName,
-            boolean forMigration) {
-        if (dbName == null) {
-            dbName = InvariantDeviceProfile.INSTANCE.get(context).dbFile;
-        }
-        DatabaseHelper databaseHelper = new DatabaseHelper(context, dbName, forMigration);
-        // Table creation sometimes fails silently, which leads to a crash loop.
-        // This way, we will try to create a table every time after crash, so the device
-        // would eventually be able to recover.
-        if (!tableExists(databaseHelper.getReadableDatabase(), Favorites.TABLE_NAME)) {
-            Log.e(TAG, "Tables are missing after onCreate has been called. Trying to recreate");
-            // This operation is a no-op if the table already exists.
-            databaseHelper.addFavoritesTable(databaseHelper.getWritableDatabase(), true);
-        }
-        databaseHelper.mHotseatRestoreTableExists = tableExists(
-                databaseHelper.getReadableDatabase(), Favorites.HYBRID_HOTSEAT_BACKUP_TABLE);
-
-        databaseHelper.initIds();
-        return databaseHelper;
-    }
-
     /**
      * Constructor used in tests and for restore.
      */
-    public DatabaseHelper(Context context, String dbName, boolean forMigration) {
+    public DatabaseHelper(Context context, String dbName,
+            ToLongFunction<UserHandle> userSerialProvider, Runnable onEmptyDbCreateCallback) {
         super(context, dbName, SCHEMA_VERSION);
         mContext = context;
-        mForMigration = forMigration;
+        mUserSerialProvider = userSerialProvider;
+        mOnEmptyDbCreateCallback = onEmptyDbCreateCallback;
     }
 
     protected void initIds() {
@@ -131,13 +107,11 @@
 
         mMaxItemId = 1;
 
-        addFavoritesTable(db, false);
+        addTableToDb(db, getDefaultUserSerial(), false /* optional */);
 
         // Fresh and clean launcher DB.
         mMaxItemId = initializeMaxItemId(db);
-        if (!mForMigration) {
-            onEmptyDbCreated();
-        }
+        mOnEmptyDbCreateCallback.run();
     }
 
     public void onAddOrDeleteOp(SQLiteDatabase db) {
@@ -147,38 +121,8 @@
         }
     }
 
-    /**
-     * Re-composite given key in respect to database. If the current db is
-     * {@link LauncherFiles#LAUNCHER_DB}, return the key as-is. Otherwise append the db name to
-     * given key. e.g. consider key="EMPTY_DATABASE_CREATED", dbName="minimal.db", the returning
-     * string will be "EMPTY_DATABASE_CREATED@minimal.db".
-     */
-    public String getKey(final String key) {
-        if (TextUtils.equals(getDatabaseName(), LauncherFiles.LAUNCHER_DB)) {
-            return key;
-        }
-        return key + "@" + getDatabaseName();
-    }
-
-    /**
-     * Overridden in tests.
-     */
-    protected void onEmptyDbCreated() {
-        // Set the flag for empty DB
-        LauncherPrefs.getPrefs(mContext).edit().putBoolean(getKey(EMPTY_DATABASE_CREATED), true)
-                .commit();
-    }
-
-    public long getSerialNumberForUser(UserHandle user) {
-        return UserCache.INSTANCE.get(mContext).getSerialNumberForUser(user);
-    }
-
-    public long getDefaultUserSerial() {
-        return getSerialNumberForUser(Process.myUserHandle());
-    }
-
-    private void addFavoritesTable(SQLiteDatabase db, boolean optional) {
-        Favorites.addTableToDb(db, getDefaultUserSerial(), optional);
+    private long getDefaultUserSerial() {
+        return mUserSerialProvider.applyAsLong(Process.myUserHandle());
     }
 
     @Override
diff --git a/src/com/android/launcher3/model/GridSizeMigrationUtil.java b/src/com/android/launcher3/model/GridSizeMigrationUtil.java
index eded5ea..9a6cde6 100644
--- a/src/com/android/launcher3/model/GridSizeMigrationUtil.java
+++ b/src/com/android/launcher3/model/GridSizeMigrationUtil.java
@@ -16,6 +16,9 @@
 
 package com.android.launcher3.model;
 
+import static com.android.launcher3.LauncherSettings.Favorites.TABLE_NAME;
+import static com.android.launcher3.LauncherSettings.Favorites.TMP_TABLE;
+import static com.android.launcher3.provider.LauncherDbUtils.copyTable;
 import static com.android.launcher3.provider.LauncherDbUtils.dropTable;
 
 import android.content.ComponentName;
@@ -34,16 +37,15 @@
 import androidx.annotation.NonNull;
 
 import com.android.launcher3.InvariantDeviceProfile;
-import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.graphics.LauncherPreviewRenderer;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.pm.InstallSessionHelper;
 import com.android.launcher3.provider.LauncherDbUtils.SQLiteTransaction;
 import com.android.launcher3.util.GridOccupancy;
 import com.android.launcher3.util.IntArray;
+import com.android.launcher3.util.MainThreadInitializedObject.SandboxContext;
 import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
 import com.android.launcher3.widget.WidgetManagerHelper;
 
@@ -89,81 +91,38 @@
         return needsToMigrate;
     }
 
-    /** See {@link #migrateGridIfNeeded(Context, InvariantDeviceProfile)} */
-    public static boolean migrateGridIfNeeded(Context context) {
-        if (context instanceof LauncherPreviewRenderer.PreviewContext) {
-            return true;
-        }
-        return migrateGridIfNeeded(context, null);
-    }
-
     /**
-     * When migrating the grid for preview, we copy the table
-     * {@link LauncherSettings.Favorites#TABLE_NAME} into
-     * {@link LauncherSettings.Favorites#PREVIEW_TABLE_NAME}, run grid size migration from the
-     * former to the later, then use the later table for preview.
-     *
-     * Similarly when doing the actual grid migration, the former grid option's table
-     * {@link LauncherSettings.Favorites#TABLE_NAME} is copied into the new grid option's
-     * {@link LauncherSettings.Favorites#TMP_TABLE}, we then run the grid size migration algorithm
+     * When migrating the grid, we copy the table
+     * {@link LauncherSettings.Favorites#TABLE_NAME} from {@code source} into
+     * {@link LauncherSettings.Favorites#TMP_TABLE}, run the grid size migration algorithm
      * to migrate the later to the former, and load the workspace from the default
      * {@link LauncherSettings.Favorites#TABLE_NAME}.
      *
      * @return false if the migration failed.
      */
-    public static boolean migrateGridIfNeeded(Context context, InvariantDeviceProfile idp) {
-        boolean migrateForPreview = idp != null;
-        if (!migrateForPreview) {
-            idp = LauncherAppState.getIDP(context);
-        }
+    public static boolean migrateGridIfNeeded(
+            @NonNull Context context,
+            @NonNull InvariantDeviceProfile idp,
+            @NonNull DatabaseHelper target,
+            @NonNull SQLiteDatabase source) {
 
         DeviceGridState srcDeviceState = new DeviceGridState(context);
         DeviceGridState destDeviceState = new DeviceGridState(idp);
         if (!needsToMigrate(srcDeviceState, destDeviceState)) {
             return true;
         }
+        copyTable(source, TABLE_NAME, target.getWritableDatabase(), TMP_TABLE, context);
 
         HashSet<String> validPackages = getValidPackages(context);
-
-        if (migrateForPreview) {
-            if (!LauncherSettings.Settings.call(
-                    context.getContentResolver(),
-                    LauncherSettings.Settings.METHOD_PREP_FOR_PREVIEW,
-                    destDeviceState.getDbFile()).getBoolean(
-                    LauncherSettings.Settings.EXTRA_VALUE)) {
-                return false;
-            }
-        } else if (!LauncherSettings.Settings.call(
-                context.getContentResolver(),
-                LauncherSettings.Settings.METHOD_UPDATE_CURRENT_OPEN_HELPER,
-                destDeviceState.getDbFile()).getBoolean(
-                LauncherSettings.Settings.EXTRA_VALUE)) {
-            return false;
-        }
-
         long migrationStartTime = System.currentTimeMillis();
-        try (SQLiteTransaction t = (SQLiteTransaction) LauncherSettings.Settings.call(
-                context.getContentResolver(),
-                LauncherSettings.Settings.METHOD_NEW_TRANSACTION).getBinder(
-                LauncherSettings.Settings.EXTRA_VALUE)) {
-
-            DbReader srcReader = new DbReader(t.getDb(),
-                    migrateForPreview ? LauncherSettings.Favorites.TABLE_NAME
-                            : LauncherSettings.Favorites.TMP_TABLE,
-                    context, validPackages);
-            DbReader destReader = new DbReader(t.getDb(),
-                    migrateForPreview ? LauncherSettings.Favorites.PREVIEW_TABLE_NAME
-                            : LauncherSettings.Favorites.TABLE_NAME,
-                    context, validPackages);
+        try (SQLiteTransaction t = new SQLiteTransaction(target.getWritableDatabase())) {
+            DbReader srcReader = new DbReader(t.getDb(), TMP_TABLE, context, validPackages);
+            DbReader destReader = new DbReader(t.getDb(), TABLE_NAME, context, validPackages);
 
             Point targetSize = new Point(destDeviceState.getColumns(), destDeviceState.getRows());
-            migrate(context, t.getDb(), srcReader, destReader, destDeviceState.getNumHotseat(),
+            migrate(target, srcReader, destReader, destDeviceState.getNumHotseat(),
                     targetSize, srcDeviceState, destDeviceState);
-
-            if (!migrateForPreview) {
-                dropTable(t.getDb(), LauncherSettings.Favorites.TMP_TABLE);
-            }
-
+            dropTable(t.getDb(), TMP_TABLE);
             t.commit();
             return true;
         } catch (Exception e) {
@@ -174,7 +133,7 @@
             Log.v(TAG, "Workspace migration completed in "
                     + (System.currentTimeMillis() - migrationStartTime));
 
-            if (!migrateForPreview) {
+            if (!(context instanceof SandboxContext)) {
                 // Save current configuration, so that the migration does not run again.
                 destDeviceState.writeToPrefs(context);
             }
@@ -182,7 +141,7 @@
     }
 
     public static boolean migrate(
-            @NonNull final Context context, @NonNull final SQLiteDatabase db,
+            @NonNull DatabaseHelper helper,
             @NonNull final DbReader srcReader, @NonNull final DbReader destReader,
             final int destHotseatSize, @NonNull final Point targetSize,
             @NonNull final DeviceGridState srcDeviceState,
@@ -234,8 +193,8 @@
         Collections.sort(workspaceToBeAdded);
 
         // Migrate hotseat
-        solveHotseatPlacement(db, srcReader,
-                destReader, context, destHotseatSize, dstHotseatItems, hotseatToBeAdded);
+        solveHotseatPlacement(helper, destHotseatSize,
+                srcReader, destReader, dstHotseatItems, hotseatToBeAdded);
 
         // Migrate workspace.
         // First we create a collection of the screens
@@ -255,8 +214,8 @@
             if (DEBUG) {
                 Log.d(TAG, "Migrating " + screenId);
             }
-            solveGridPlacement(db, srcReader,
-                    destReader, context, screenId, trgX, trgY, workspaceToBeAdded, false);
+            solveGridPlacement(helper, srcReader,
+                    destReader, screenId, trgX, trgY, workspaceToBeAdded, false);
             if (workspaceToBeAdded.isEmpty()) {
                 break;
             }
@@ -266,8 +225,8 @@
         // any of the screens, in this case we add them to new screens until all of them are placed.
         int screenId = destReader.mLastScreenId + 1;
         while (!workspaceToBeAdded.isEmpty()) {
-            solveGridPlacement(db, srcReader,
-                    destReader, context, screenId, trgX, trgY, workspaceToBeAdded, preservePages);
+            solveGridPlacement(helper, srcReader,
+                    destReader, screenId, trgX, trgY, workspaceToBeAdded, preservePages);
             screenId++;
         }
 
@@ -298,33 +257,33 @@
         });
     }
 
-    private static void insertEntryInDb(SQLiteDatabase db, Context context, DbEntry entry,
+    private static void insertEntryInDb(DatabaseHelper helper, DbEntry entry,
             String srcTableName, String destTableName) {
-        int id = copyEntryAndUpdate(db, context, entry, srcTableName, destTableName);
+        int id = copyEntryAndUpdate(helper, entry, srcTableName, destTableName);
 
         if (entry.itemType == LauncherSettings.Favorites.ITEM_TYPE_FOLDER) {
             for (Set<Integer> itemIds : entry.mFolderItems.values()) {
                 for (int itemId : itemIds) {
-                    copyEntryAndUpdate(db, context, itemId, id, srcTableName, destTableName);
+                    copyEntryAndUpdate(helper, itemId, id, srcTableName, destTableName);
                 }
             }
         }
     }
 
-    private static int copyEntryAndUpdate(SQLiteDatabase db, Context context,
+    private static int copyEntryAndUpdate(DatabaseHelper helper,
             DbEntry entry, String srcTableName, String destTableName) {
-        return copyEntryAndUpdate(db, context, entry, -1, -1, srcTableName, destTableName);
+        return copyEntryAndUpdate(helper, entry, -1, -1, srcTableName, destTableName);
     }
 
-    private static int copyEntryAndUpdate(SQLiteDatabase db, Context context,
+    private static int copyEntryAndUpdate(DatabaseHelper helper,
             int id, int folderId, String srcTableName, String destTableName) {
-        return copyEntryAndUpdate(db, context, null, id, folderId, srcTableName, destTableName);
+        return copyEntryAndUpdate(helper, null, id, folderId, srcTableName, destTableName);
     }
 
-    private static int copyEntryAndUpdate(SQLiteDatabase db, Context context,
-            DbEntry entry, int id, int folderId, String srcTableName, String destTableName) {
+    private static int copyEntryAndUpdate(DatabaseHelper helper, DbEntry entry,
+            int id, int folderId, String srcTableName, String destTableName) {
         int newId = -1;
-        Cursor c = db.query(srcTableName, null,
+        Cursor c = helper.getWritableDatabase().query(srcTableName, null,
                 LauncherSettings.Favorites._ID + " = '" + (entry != null ? entry.id : id) + "'",
                 null, null, null, null);
         while (c.moveToNext()) {
@@ -335,11 +294,9 @@
             } else {
                 values.put(LauncherSettings.Favorites.CONTAINER, folderId);
             }
-            newId = LauncherSettings.Settings.call(context.getContentResolver(),
-                    LauncherSettings.Settings.METHOD_NEW_ITEM_ID).getInt(
-                    LauncherSettings.Settings.EXTRA_VALUE);
+            newId = helper.generateNewItemId();
             values.put(LauncherSettings.Favorites._ID, newId);
-            db.insert(destTableName, null, values);
+            helper.getWritableDatabase().insert(destTableName, null, values);
         }
         c.close();
         return newId;
@@ -367,9 +324,9 @@
         return validPackages;
     }
 
-    private static void solveGridPlacement(@NonNull final SQLiteDatabase db,
+    private static void solveGridPlacement(@NonNull final DatabaseHelper helper,
             @NonNull final DbReader srcReader, @NonNull final DbReader destReader,
-            @NonNull final Context context, final int screenId, final int trgX, final int trgY,
+            final int screenId, final int trgX, final int trgY,
             @NonNull final List<DbEntry> sortedItemsToPlace, final boolean matchingScreenIdOnly) {
         final GridOccupancy occupied = new GridOccupancy(trgX, trgY);
         final Point trg = new Point(trgX, trgY);
@@ -391,7 +348,7 @@
                 continue;
             }
             if (findPlacementForEntry(entry, next, trg, occupied, screenId)) {
-                insertEntryInDb(db, context, entry, srcReader.mTableName, destReader.mTableName);
+                insertEntryInDb(helper, entry, srcReader.mTableName, destReader.mTableName);
                 iterator.remove();
             }
         }
@@ -428,9 +385,9 @@
         return false;
     }
 
-    private static void solveHotseatPlacement(@NonNull final SQLiteDatabase db,
+    private static void solveHotseatPlacement(
+            @NonNull final DatabaseHelper helper, final int hotseatSize,
             @NonNull final DbReader srcReader, @NonNull final DbReader destReader,
-            @NonNull final Context context, final int hotseatSize,
             @NonNull final  List<DbEntry> placedHotseatItems,
             @NonNull final List<DbEntry> itemsToPlace) {
 
@@ -447,7 +404,7 @@
                 // to something other than -1.
                 entry.cellX = i;
                 entry.cellY = 0;
-                insertEntryInDb(db, context, entry, srcReader.mTableName, destReader.mTableName);
+                insertEntryInDb(helper, entry, srcReader.mTableName, destReader.mTableName);
                 occupied[entry.screenId] = true;
             }
         }
diff --git a/src/com/android/launcher3/model/LoaderCursor.java b/src/com/android/launcher3/model/LoaderCursor.java
index a5dccc1..2054d93 100644
--- a/src/com/android/launcher3/model/LoaderCursor.java
+++ b/src/com/android/launcher3/model/LoaderCursor.java
@@ -16,16 +16,16 @@
 
 package com.android.launcher3.model;
 
+import static com.android.launcher3.LauncherSettings.Favorites.TABLE_NAME;
+
 import android.content.ComponentName;
 import android.content.ContentValues;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.LauncherActivityInfo;
 import android.content.pm.LauncherApps;
-import android.content.pm.PackageManager;
 import android.database.Cursor;
 import android.database.CursorWrapper;
-import android.net.Uri;
 import android.os.UserHandle;
 import android.provider.BaseColumns;
 import android.text.TextUtils;
@@ -66,9 +66,7 @@
     private final LongSparseArray<UserHandle> allUsers;
 
     private final LauncherAppState mApp;
-    private final Uri mContentUri;
     private final Context mContext;
-    private final PackageManager mPM;
     private final IconCache mIconCache;
     private final InvariantDeviceProfile mIDP;
 
@@ -108,17 +106,14 @@
     public int itemType;
     public int restoreFlag;
 
-    public LoaderCursor(Cursor cursor, Uri contentUri, LauncherAppState app,
-            UserManagerState userManagerState) {
+    public LoaderCursor(Cursor cursor, LauncherAppState app, UserManagerState userManagerState) {
         super(cursor);
 
         mApp = app;
         allUsers = userManagerState.allUsers;
-        mContentUri = contentUri;
         mContext = app.getContext();
         mIconCache = app.getIconCache();
         mIDP = app.getInvariantDeviceProfile();
-        mPM = mContext.getPackageManager();
 
         // Init column indices
         mIconIndex = getColumnIndexOrThrow(Favorites.ICON);
@@ -390,7 +385,7 @@
      */
     public ContentWriter updater() {
        return new ContentWriter(mContext, new ContentWriter.CommitParams(
-               mApp.getModel().getModelDbController().getDatabaseHelper(),
+               mApp.getModel().getModelDbController(),
                BaseColumns._ID + "= ?", new String[]{Integer.toString(id)}));
     }
 
@@ -409,8 +404,8 @@
     public boolean commitDeleted() {
         if (mItemsToRemove.size() > 0) {
             // Remove dead items
-            mContext.getContentResolver().delete(mContentUri, Utilities.createDbSelectionQuery(
-                    Favorites._ID, mItemsToRemove), null);
+            mApp.getModel().getModelDbController().delete(TABLE_NAME,
+                    Utilities.createDbSelectionQuery(Favorites._ID, mItemsToRemove), null);
             return true;
         }
         return false;
@@ -435,9 +430,8 @@
             // Update restored items that no longer require special handling
             ContentValues values = new ContentValues();
             values.put(Favorites.RESTORED, 0);
-            mContext.getContentResolver().update(mContentUri, values,
-                    Utilities.createDbSelectionQuery(
-                            Favorites._ID, mRestoredRows), null);
+            mApp.getModel().getModelDbController().update(TABLE_NAME, values,
+                    Utilities.createDbSelectionQuery(Favorites._ID, mRestoredRows), null);
         }
     }
 
diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java
index 9053d19..d4eded5 100644
--- a/src/com/android/launcher3/model/LoaderTask.java
+++ b/src/com/android/launcher3/model/LoaderTask.java
@@ -16,6 +16,7 @@
 
 package com.android.launcher3.model;
 
+import static com.android.launcher3.LauncherSettings.Favorites.TABLE_NAME;
 import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_HAS_SHORTCUT_PERMISSION;
 import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_QUIET_MODE_CHANGE_PERMISSION;
 import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_QUIET_MODE_ENABLED;
@@ -41,7 +42,6 @@
 import android.content.pm.PackageManager;
 import android.content.pm.ShortcutInfo;
 import android.graphics.Point;
-import android.net.Uri;
 import android.os.Bundle;
 import android.os.Trace;
 import android.os.UserHandle;
@@ -50,7 +50,6 @@
 import android.util.ArrayMap;
 import android.util.Log;
 import android.util.LongSparseArray;
-import android.util.TimingLogger;
 
 import androidx.annotation.Nullable;
 
@@ -200,25 +199,10 @@
         }
 
         Object traceToken = TraceHelper.INSTANCE.beginSection(TAG);
-        TimingLogger timingLogger = new TimingLogger(TAG, "run");
         LoaderMemoryLogger memoryLogger = new LoaderMemoryLogger();
         try (LauncherModel.LoaderTransaction transaction = mApp.getModel().beginLoader(this)) {
             List<ShortcutInfo> allShortcuts = new ArrayList<>();
-            Trace.beginSection("LoadWorkspace");
-            try {
-                loadWorkspace(allShortcuts, memoryLogger);
-            } finally {
-                Trace.endSection();
-            }
-            logASplit(timingLogger, "loadWorkspace");
-
-            if (FeatureFlags.CHANGE_MODEL_DELEGATE_LOADING_ORDER.get()) {
-                verifyNotStopped();
-                mModelDelegate.loadAndBindWorkspaceItems(mUserManagerState,
-                        mLauncherBinder.mCallbacksList, mShortcutKeyToPinnedShortcuts);
-                mModelDelegate.markActive();
-                logASplit(timingLogger, "workspaceDelegateItems");
-            }
+            loadWorkspace(allShortcuts, "", memoryLogger);
 
             // Sanitize data re-syncs widgets/shortcuts based on the workspace loaded from db.
             // sanitizeData should not be invoked if the workspace is loaded from a db different
@@ -228,21 +212,21 @@
                 verifyNotStopped();
                 sanitizeFolders(mItemsDeleted);
                 sanitizeWidgetsShortcutsAndPackages();
-                logASplit(timingLogger, "sanitizeData");
+                logASplit("sanitizeData");
             }
 
             verifyNotStopped();
             mLauncherBinder.bindWorkspace(true /* incrementBindId */, /* isBindSync= */ false);
-            logASplit(timingLogger, "bindWorkspace");
+            logASplit("bindWorkspace");
 
             mModelDelegate.workspaceLoadComplete();
             // Notify the installer packages of packages with active installs on the first screen.
             sendFirstScreenActiveInstallsBroadcast();
-            logASplit(timingLogger, "sendFirstScreenActiveInstallsBroadcast");
+            logASplit("sendFirstScreenActiveInstallsBroadcast");
 
             // Take a break
             waitForIdle();
-            logASplit(timingLogger, "step 1 complete");
+            logASplit("step 1 complete");
             verifyNotStopped();
 
             // second step
@@ -253,16 +237,16 @@
             } finally {
                 Trace.endSection();
             }
-            logASplit(timingLogger, "loadAllApps");
+            logASplit("loadAllApps");
 
             if (FeatureFlags.CHANGE_MODEL_DELEGATE_LOADING_ORDER.get()) {
                 mModelDelegate.loadAndBindAllAppsItems(mUserManagerState,
                         mLauncherBinder.mCallbacksList, mShortcutKeyToPinnedShortcuts);
-                logASplit(timingLogger, "allAppsDelegateItems");
+                logASplit("allAppsDelegateItems");
             }
             verifyNotStopped();
             mLauncherBinder.bindAllApps();
-            logASplit(timingLogger, "bindAllApps");
+            logASplit("bindAllApps");
 
             verifyNotStopped();
             IconCacheUpdateHandler updateHandler = mIconCache.getUpdateHandler();
@@ -270,75 +254,73 @@
             updateHandler.updateIcons(allActivityList,
                     LauncherActivityCachingLogic.newInstance(mApp.getContext()),
                     mApp.getModel()::onPackageIconsUpdated);
-            logASplit(timingLogger, "update icon cache");
+            logASplit("update icon cache");
 
             verifyNotStopped();
-            logASplit(timingLogger, "save shortcuts in icon cache");
+            logASplit("save shortcuts in icon cache");
             updateHandler.updateIcons(allShortcuts, new ShortcutCachingLogic(),
                     mApp.getModel()::onPackageIconsUpdated);
 
             // Take a break
             waitForIdle();
-            logASplit(timingLogger, "step 2 complete");
+            logASplit("step 2 complete");
             verifyNotStopped();
 
             // third step
             List<ShortcutInfo> allDeepShortcuts = loadDeepShortcuts();
-            logASplit(timingLogger, "loadDeepShortcuts");
+            logASplit("loadDeepShortcuts");
 
             verifyNotStopped();
             mLauncherBinder.bindDeepShortcuts();
-            logASplit(timingLogger, "bindDeepShortcuts");
+            logASplit("bindDeepShortcuts");
 
             verifyNotStopped();
-            logASplit(timingLogger, "save deep shortcuts in icon cache");
+            logASplit("save deep shortcuts in icon cache");
             updateHandler.updateIcons(allDeepShortcuts,
                     new ShortcutCachingLogic(), (pkgs, user) -> { });
 
             // Take a break
             waitForIdle();
-            logASplit(timingLogger, "step 3 complete");
+            logASplit("step 3 complete");
             verifyNotStopped();
 
             // fourth step
             List<ComponentWithLabelAndIcon> allWidgetsList =
                     mBgDataModel.widgetsModel.update(mApp, null);
-            logASplit(timingLogger, "load widgets");
+            logASplit("load widgets");
 
             verifyNotStopped();
             mLauncherBinder.bindWidgets();
-            logASplit(timingLogger, "bindWidgets");
+            logASplit("bindWidgets");
             verifyNotStopped();
 
             if (FeatureFlags.CHANGE_MODEL_DELEGATE_LOADING_ORDER.get()) {
                 mModelDelegate.loadAndBindOtherItems(mLauncherBinder.mCallbacksList);
-                logASplit(timingLogger, "otherDelegateItems");
+                logASplit("otherDelegateItems");
                 verifyNotStopped();
             }
 
             updateHandler.updateIcons(allWidgetsList,
                     new ComponentWithIconCachingLogic(mApp.getContext(), true),
                     mApp.getModel()::onWidgetLabelsUpdated);
-            logASplit(timingLogger, "save widgets in icon cache");
+            logASplit("save widgets in icon cache");
 
             // fifth step
             loadFolderNames();
 
             verifyNotStopped();
             updateHandler.finish();
-            logASplit(timingLogger, "finish icon update");
+            logASplit("finish icon update");
 
             mModelDelegate.modelLoadComplete();
             transaction.commit();
             memoryLogger.clearLogs();
         } catch (CancellationException e) {
             // Loader stopped, ignore
-            logASplit(timingLogger, "Cancelled");
+            logASplit("Cancelled");
         } catch (Exception e) {
             memoryLogger.printLogs();
             throw e;
-        } finally {
-            timingLogger.dumpToLog();
         }
         TraceHelper.INSTANCE.endSection(traceToken);
     }
@@ -348,25 +330,29 @@
         this.notify();
     }
 
-    private void loadWorkspace(
-            List<ShortcutInfo> allDeepShortcuts, LoaderMemoryLogger memoryLogger) {
-        loadWorkspace(allDeepShortcuts, Favorites.CONTENT_URI,
-                null /* selection */, memoryLogger);
-    }
+    protected void loadWorkspace(
+            List<ShortcutInfo> allDeepShortcuts,
+            String selection,
+            LoaderMemoryLogger memoryLogger) {
+        Trace.beginSection("LoadWorkspace");
+        try {
+            loadWorkspaceImpl(allDeepShortcuts, selection, memoryLogger);
+        } finally {
+            Trace.endSection();
+        }
+        logASplit("loadWorkspace");
 
-    protected void loadWorkspaceForPreviewSurfaceRenderer(
-            List<ShortcutInfo> allDeepShortcuts, Uri contentUri, String selection) {
-        loadWorkspace(allDeepShortcuts, contentUri, selection, null);
         if (FeatureFlags.CHANGE_MODEL_DELEGATE_LOADING_ORDER.get()) {
+            verifyNotStopped();
             mModelDelegate.loadAndBindWorkspaceItems(mUserManagerState,
                     mLauncherBinder.mCallbacksList, mShortcutKeyToPinnedShortcuts);
             mModelDelegate.markActive();
+            logASplit("workspaceDelegateItems");
         }
     }
 
-    protected void loadWorkspace(
+    private void loadWorkspaceImpl(
             List<ShortcutInfo> allDeepShortcuts,
-            Uri contentUri,
             String selection,
             @Nullable LoaderMemoryLogger memoryLogger) {
         final Context context = mApp.getContext();
@@ -377,7 +363,7 @@
         final WidgetManagerHelper widgetHelper = new WidgetManagerHelper(context);
 
         boolean clearDb = false;
-        if (!GridSizeMigrationUtil.migrateGridIfNeeded(context)) {
+        if (!mApp.getModel().getModelDbController().migrateGridIfNeeded()) {
             // Migration failed. Clear workspace.
             clearDb = true;
         }
@@ -402,8 +388,9 @@
             mFirstScreenBroadcast = new FirstScreenBroadcast(installingPkgs);
 
             mShortcutKeyToPinnedShortcuts = new HashMap<>();
+            ModelDbController dbController = mApp.getModel().getModelDbController();
             final LoaderCursor c = new LoaderCursor(
-                    contentResolver.query(contentUri, null, selection, null, null), contentUri,
+                    dbController.query(TABLE_NAME, null, selection, null, null),
                     mApp, mUserManagerState);
             final Bundle extras = c.getExtras();
             mDbName = extras == null ? null : extras.getString(Settings.EXTRA_DB_NAME);
@@ -1112,12 +1099,9 @@
         FileLog.d(TAG, widgetDimension.toString());
     }
 
-    private static void logASplit(@Nullable TimingLogger timingLogger, String label) {
-        if (timingLogger != null) {
-            timingLogger.addSplit(label);
-            if (DEBUG) {
-                Log.d(TAG, label);
-            }
+    private static void logASplit(String label) {
+        if (DEBUG) {
+            Log.d(TAG, label);
         }
     }
 }
diff --git a/src/com/android/launcher3/model/ModelDbController.java b/src/com/android/launcher3/model/ModelDbController.java
index 97bce8c..f0e5ef6 100644
--- a/src/com/android/launcher3/model/ModelDbController.java
+++ b/src/com/android/launcher3/model/ModelDbController.java
@@ -19,11 +19,10 @@
 import static android.util.Base64.NO_WRAP;
 
 import static com.android.launcher3.DefaultLayoutParser.RES_PARTNER_DEFAULT_LAYOUT;
+import static com.android.launcher3.LauncherSettings.Favorites.addTableToDb;
 import static com.android.launcher3.LauncherSettings.Settings.LAYOUT_DIGEST_KEY;
 import static com.android.launcher3.LauncherSettings.Settings.LAYOUT_DIGEST_LABEL;
 import static com.android.launcher3.LauncherSettings.Settings.LAYOUT_DIGEST_TAG;
-import static com.android.launcher3.model.DatabaseHelper.EMPTY_DATABASE_CREATED;
-import static com.android.launcher3.provider.LauncherDbUtils.copyTable;
 import static com.android.launcher3.provider.LauncherDbUtils.tableExists;
 
 import android.app.blob.BlobHandle;
@@ -31,7 +30,6 @@
 import android.content.ContentResolver;
 import android.content.ContentValues;
 import android.content.Context;
-import android.content.SharedPreferences;
 import android.content.pm.PackageManager;
 import android.content.pm.ProviderInfo;
 import android.content.res.Resources;
@@ -43,6 +41,7 @@
 import android.os.Bundle;
 import android.os.ParcelFileDescriptor;
 import android.os.Process;
+import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.Settings;
 import android.text.TextUtils;
@@ -54,18 +53,22 @@
 
 import com.android.launcher3.AutoInstallsLayout;
 import com.android.launcher3.AutoInstallsLayout.SourceResources;
+import com.android.launcher3.ConstantItem;
 import com.android.launcher3.DefaultLayoutParser;
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherFiles;
 import com.android.launcher3.LauncherPrefs;
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.LauncherSettings.Favorites;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.pm.UserCache;
 import com.android.launcher3.provider.LauncherDbUtils;
 import com.android.launcher3.provider.LauncherDbUtils.SQLiteTransaction;
 import com.android.launcher3.provider.RestoreDbTask;
 import com.android.launcher3.util.IOUtils;
 import com.android.launcher3.util.IntArray;
+import com.android.launcher3.util.MainThreadInitializedObject.SandboxContext;
 import com.android.launcher3.util.Partner;
 import com.android.launcher3.widget.LauncherWidgetHolder;
 
@@ -73,7 +76,6 @@
 
 import java.io.InputStream;
 import java.io.StringReader;
-import java.util.function.Supplier;
 
 /**
  * Utility class which maintains an instance of Launcher database and provides utility methods
@@ -82,6 +84,8 @@
 public class ModelDbController {
     private static final String TAG = "LauncherProvider";
 
+    private static final String EMPTY_DATABASE_CREATED = "EMPTY_DATABASE_CREATED";
+
     protected DatabaseHelper mOpenHelper;
 
     private final Context mContext;
@@ -92,26 +96,36 @@
 
     private synchronized void createDbIfNotExists() {
         if (mOpenHelper == null) {
-            mOpenHelper = DatabaseHelper.createDatabaseHelper(
-                    mContext, false /* forMigration */);
-
-            RestoreDbTask.restoreIfNeeded(mContext, mOpenHelper);
+            mOpenHelper = createDatabaseHelper(false /* forMigration */);
+            RestoreDbTask.restoreIfNeeded(mContext, this);
         }
     }
 
-    private synchronized boolean prepForMigration(String dbFile, String targetTableName,
-            Supplier<DatabaseHelper> src, Supplier<DatabaseHelper> dst) {
-        if (TextUtils.equals(dbFile, mOpenHelper.getDatabaseName())) {
-            Log.e(TAG, "prepForMigration - target db is same as current: " + dbFile);
-            return false;
-        }
+    protected DatabaseHelper createDatabaseHelper(boolean forMigration) {
+        boolean isSandbox = mContext instanceof SandboxContext;
+        String dbName = isSandbox ? null : InvariantDeviceProfile.INSTANCE.get(mContext).dbFile;
 
-        final DatabaseHelper helper = src.get();
-        mOpenHelper = dst.get();
-        copyTable(helper.getReadableDatabase(), Favorites.TABLE_NAME,
-                mOpenHelper.getWritableDatabase(), targetTableName, mContext);
-        helper.close();
-        return true;
+        // Set the flag for empty DB
+        Runnable onEmptyDbCreateCallback = forMigration ? () -> { }
+                : () -> LauncherPrefs.get(mContext).putSync(getEmptyDbCreatedKey(dbName).to(true));
+
+        DatabaseHelper databaseHelper = new DatabaseHelper(mContext, dbName,
+                this::getSerialNumberForUser, onEmptyDbCreateCallback);
+        // Table creation sometimes fails silently, which leads to a crash loop.
+        // This way, we will try to create a table every time after crash, so the device
+        // would eventually be able to recover.
+        if (!tableExists(databaseHelper.getReadableDatabase(), Favorites.TABLE_NAME)) {
+            Log.e(TAG, "Tables are missing after onCreate has been called. Trying to recreate");
+            // This operation is a no-op if the table already exists.
+            addTableToDb(databaseHelper.getWritableDatabase(),
+                    getSerialNumberForUser(Process.myUserHandle()),
+                    true /* optional */);
+        }
+        databaseHelper.mHotseatRestoreTableExists = tableExists(
+                databaseHelper.getReadableDatabase(), Favorites.HYBRID_HOTSEAT_BACKUP_TABLE);
+
+        databaseHelper.initIds();
+        return databaseHelper;
     }
 
     /**
@@ -267,42 +281,41 @@
     }
 
     /**
-     * Updates the current DB and copies all the existing data to the temp table
-     * @param dbFile name of the target db file name
+     * Migrates the DB if needed, and returns false if the migration failed
+     * and DB needs to be cleared.
+     * @return true if migration was success or ignored, false if migration failed
+     * and the DB should be reset.
      */
-    @WorkerThread
-    public boolean updateCurrentOpenHelper(String dbFile) {
+    public boolean migrateGridIfNeeded() {
         createDbIfNotExists();
-        return prepForMigration(
-                dbFile,
-                Favorites.TMP_TABLE,
-                () -> mOpenHelper,
-                () -> DatabaseHelper.createDatabaseHelper(
-                        mContext, true /* forMigration */));
+        InvariantDeviceProfile idp = LauncherAppState.getIDP(mContext);
+        if (!GridSizeMigrationUtil.needsToMigrate(mContext, idp)) {
+            return true;
+        }
+        String targetDbName = new DeviceGridState(idp).getDbFile();
+        if (TextUtils.equals(targetDbName, mOpenHelper.getDatabaseName())) {
+            Log.e(TAG, "migrateGridIfNeeded - target db is same as current: " + targetDbName);
+            return false;
+        }
+        DatabaseHelper oldHelper = mOpenHelper;
+        mOpenHelper = (mContext instanceof SandboxContext) ? oldHelper
+                : createDatabaseHelper(true /* forMigration */);
+        try {
+            return GridSizeMigrationUtil.migrateGridIfNeeded(mContext, idp, mOpenHelper,
+                   oldHelper.getWritableDatabase());
+        } finally {
+            if (mOpenHelper != oldHelper) {
+                oldHelper.close();
+            }
+        }
     }
 
     /**
-     * Returns the current DatabaseHelper.
-     * Only for tests
+     * Returns the underlying model database
      */
-    @WorkerThread
-    public DatabaseHelper getDatabaseHelper() {
+    public SQLiteDatabase getDb() {
         createDbIfNotExists();
-        return mOpenHelper;
-    }
-
-    /**
-     * Prepares the DB for preview by copying all existing data to preview table
-     */
-    @WorkerThread
-    public boolean prepareForPreview(String dbFile) {
-        createDbIfNotExists();
-        return prepForMigration(
-                dbFile,
-                Favorites.PREVIEW_TABLE_NAME,
-                () -> DatabaseHelper.createDatabaseHelper(
-                        mContext, dbFile, true /* forMigration */),
-                () -> mOpenHelper);
+        return mOpenHelper.getWritableDatabase();
     }
 
     private void onAddOrDeleteOp(SQLiteDatabase db) {
@@ -345,8 +358,7 @@
     }
 
     private void clearFlagEmptyDbCreated() {
-        LauncherPrefs.getPrefs(mContext).edit()
-                .remove(mOpenHelper.getKey(EMPTY_DATABASE_CREATED)).commit();
+        LauncherPrefs.get(mContext).removeSync(getEmptyDbCreatedKey(mOpenHelper.getDatabaseName()));
     }
 
     /**
@@ -359,9 +371,8 @@
     @WorkerThread
     public synchronized void loadDefaultFavoritesIfNecessary() {
         createDbIfNotExists();
-        SharedPreferences sp = LauncherPrefs.getPrefs(mContext);
 
-        if (sp.getBoolean(mOpenHelper.getKey(EMPTY_DATABASE_CREATED), false)) {
+        if (LauncherPrefs.get(mContext).get(getEmptyDbCreatedKey(mOpenHelper.getDatabaseName()))) {
             Log.d(TAG, "loading default workspace");
 
             LauncherWidgetHolder widgetHolder = mOpenHelper.newLauncherWidgetHolder();
@@ -479,4 +490,27 @@
         return new DefaultLayoutParser(mContext, widgetHolder,
                 mOpenHelper, mContext.getResources(), defaultLayout);
     }
+
+    /**
+     * Re-composite given key in respect to database. If the current db is
+     * {@link LauncherFiles#LAUNCHER_DB}, return the key as-is. Otherwise append the db name to
+     * given key. e.g. consider key="EMPTY_DATABASE_CREATED", dbName="minimal.db", the returning
+     * string will be "EMPTY_DATABASE_CREATED@minimal.db".
+     */
+    private ConstantItem<Boolean> getEmptyDbCreatedKey(String dbName) {
+        if (mContext instanceof SandboxContext) {
+            return LauncherPrefs.nonRestorableItem(EMPTY_DATABASE_CREATED,
+                    false /* default value */, false /* boot aware */);
+        }
+        String key = TextUtils.equals(dbName, LauncherFiles.LAUNCHER_DB)
+                ? EMPTY_DATABASE_CREATED : EMPTY_DATABASE_CREATED + "@" + dbName;
+        return LauncherPrefs.backedUpItem(key, false /* default value */, false /* boot aware */);
+    }
+
+    /**
+     * Returns the serial number for the provided user
+     */
+    public long getSerialNumberForUser(UserHandle user) {
+        return UserCache.INSTANCE.get(mContext).getSerialNumberForUser(user);
+    }
 }
diff --git a/src/com/android/launcher3/provider/LauncherDbUtils.java b/src/com/android/launcher3/provider/LauncherDbUtils.java
index 48969fc..c718dcc 100644
--- a/src/com/android/launcher3/provider/LauncherDbUtils.java
+++ b/src/com/android/launcher3/provider/LauncherDbUtils.java
@@ -101,7 +101,7 @@
         UserManagerState ums = new UserManagerState();
         ums.init(UserCache.INSTANCE.get(context),
                 context.getSystemService(UserManager.class));
-        LoaderCursor lc = new LoaderCursor(c, null, LauncherAppState.getInstance(context), ums);
+        LoaderCursor lc = new LoaderCursor(c, LauncherAppState.getInstance(context), ums);
         IntSet deletedShortcuts = new IntSet();
 
         while (lc.moveToNext()) {
diff --git a/src/com/android/launcher3/provider/RestoreDbTask.java b/src/com/android/launcher3/provider/RestoreDbTask.java
index ac72164..a6e064a 100644
--- a/src/com/android/launcher3/provider/RestoreDbTask.java
+++ b/src/com/android/launcher3/provider/RestoreDbTask.java
@@ -16,6 +16,8 @@
 
 package com.android.launcher3.provider;
 
+import static android.os.Process.myUserHandle;
+
 import static com.android.launcher3.InvariantDeviceProfile.TYPE_MULTI_DISPLAY;
 import static com.android.launcher3.LauncherPrefs.APP_WIDGET_IDS;
 import static com.android.launcher3.LauncherPrefs.OLD_APP_WIDGET_IDS;
@@ -47,8 +49,8 @@
 import com.android.launcher3.LauncherSettings.Favorites;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.logging.FileLog;
-import com.android.launcher3.model.DatabaseHelper;
 import com.android.launcher3.model.DeviceGridState;
+import com.android.launcher3.model.ModelDbController;
 import com.android.launcher3.model.data.AppInfo;
 import com.android.launcher3.model.data.LauncherAppWidgetInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
@@ -83,12 +85,12 @@
     /**
      * Tries to restore the backup DB if needed
      */
-    public static void restoreIfNeeded(Context context, DatabaseHelper helper) {
+    public static void restoreIfNeeded(Context context, ModelDbController dbController) {
         if (!isPending(context)) {
             return;
         }
-        if (!performRestore(context, helper)) {
-            helper.createEmptyDB(helper.getWritableDatabase());
+        if (!performRestore(context, dbController)) {
+            dbController.createEmptyDB();
         }
 
         // Obtain InvariantDeviceProfile first before setting pending to false, so
@@ -102,12 +104,12 @@
         idp.reinitializeAfterRestore(context);
     }
 
-    private static boolean performRestore(Context context, DatabaseHelper helper) {
-        SQLiteDatabase db = helper.getWritableDatabase();
+    private static boolean performRestore(Context context, ModelDbController controller) {
+        SQLiteDatabase db = controller.getDb();
         try (SQLiteTransaction t = new SQLiteTransaction(db)) {
             RestoreDbTask task = new RestoreDbTask();
-            task.sanitizeDB(context, helper, db, new BackupManager(context));
-            task.restoreAppWidgetIdsIfExists(context, helper);
+            task.sanitizeDB(context, controller, db, new BackupManager(context));
+            task.restoreAppWidgetIdsIfExists(context, controller);
             t.commit();
             return true;
         } catch (Exception e) {
@@ -129,10 +131,10 @@
      * @return number of items deleted.
      */
     @VisibleForTesting
-    protected int sanitizeDB(Context context, DatabaseHelper helper, SQLiteDatabase db,
+    protected int sanitizeDB(Context context, ModelDbController controller, SQLiteDatabase db,
             BackupManager backupManager) throws Exception {
         // Primary user ids
-        long myProfileId = helper.getDefaultUserSerial();
+        long myProfileId = controller.getSerialNumberForUser(myUserHandle());
         long oldProfileId = getDefaultProfileId(db);
         LongSparseArray<Long> oldManagedProfileIds = getManagedProfileIds(db, oldProfileId);
         LongSparseArray<Long> profileMapping = new LongSparseArray<>(oldManagedProfileIds.size()
@@ -144,7 +146,7 @@
             long oldManagedProfileId = oldManagedProfileIds.keyAt(i);
             UserHandle user = getUserForAncestralSerialNumber(backupManager, oldManagedProfileId);
             if (user != null) {
-                long newManagedProfileId = helper.getSerialNumberForUser(user);
+                long newManagedProfileId = controller.getSerialNumberForUser(user);
                 profileMapping.put(oldManagedProfileId, newManagedProfileId);
             }
         }
@@ -213,7 +215,7 @@
         }
 
         // Override shortcuts
-        maybeOverrideShortcuts(context, helper, db, myProfileId);
+        maybeOverrideShortcuts(context, controller, db, myProfileId);
 
         return itemsDeleted;
     }
@@ -321,11 +323,11 @@
                 .putSync(RESTORE_DEVICE.to(new DeviceGridState(context).getDeviceType()));
     }
 
-    private void restoreAppWidgetIdsIfExists(Context context, DatabaseHelper helper) {
+    private void restoreAppWidgetIdsIfExists(Context context, ModelDbController controller) {
         LauncherPrefs lp = LauncherPrefs.get(context);
         if (lp.has(APP_WIDGET_IDS, OLD_APP_WIDGET_IDS)) {
             AppWidgetHost host = new AppWidgetHost(context, APPWIDGET_HOST_ID);
-            AppWidgetsRestoredReceiver.restoreAppWidgetIds(context, helper,
+            AppWidgetsRestoredReceiver.restoreAppWidgetIds(context, controller,
                     IntArray.fromConcatString(lp.get(OLD_APP_WIDGET_IDS)).toArray(),
                     IntArray.fromConcatString(lp.get(APP_WIDGET_IDS)).toArray(),
                     host);
@@ -343,7 +345,7 @@
                 APP_WIDGET_IDS.to(IntArray.wrap(newIds).toConcatString()));
     }
 
-    protected static void maybeOverrideShortcuts(Context context, DatabaseHelper helper,
+    protected static void maybeOverrideShortcuts(Context context, ModelDbController controller,
             SQLiteDatabase db, long currentUser) {
         Map<String, LauncherActivityInfo> activityOverrides = ApiWrapper.getActivityOverrides(
                 context);
@@ -367,7 +369,7 @@
                 if (override != null) {
                     ContentValues values = new ContentValues();
                     values.put(Favorites.PROFILE_ID,
-                            helper.getSerialNumberForUser(override.getUser()));
+                            controller.getSerialNumberForUser(override.getUser()));
                     values.put(Favorites.INTENT, AppInfo.makeLaunchIntent(override).toUri(0));
                     db.update(Favorites.TABLE_NAME, values, String.format("%s=?", Favorites._ID),
                             new String[]{String.valueOf(c.getInt(idIndex))});
diff --git a/src/com/android/launcher3/util/ContentWriter.java b/src/com/android/launcher3/util/ContentWriter.java
index e509235..7c5ef4d 100644
--- a/src/com/android/launcher3/util/ContentWriter.java
+++ b/src/com/android/launcher3/util/ContentWriter.java
@@ -26,7 +26,7 @@
 import com.android.launcher3.LauncherSettings.Favorites;
 import com.android.launcher3.icons.BitmapInfo;
 import com.android.launcher3.icons.GraphicsUtils;
-import com.android.launcher3.model.DatabaseHelper;
+import com.android.launcher3.model.ModelDbController;
 import com.android.launcher3.pm.UserCache;
 
 /**
@@ -106,7 +106,7 @@
 
     public int commit() {
         if (mCommitParams != null) {
-            mCommitParams.mDatabaseHelper.getWritableDatabase().update(
+            mCommitParams.mDbController.update(
                     Favorites.TABLE_NAME, getValues(mContext),
                     mCommitParams.mWhere, mCommitParams.mSelectionArgs);
         }
@@ -115,12 +115,12 @@
 
     public static final class CommitParams {
 
-        final DatabaseHelper mDatabaseHelper;
+        final ModelDbController mDbController;
         final String mWhere;
         final String[] mSelectionArgs;
 
-        public CommitParams(DatabaseHelper helper, String where, String[] selectionArgs) {
-            mDatabaseHelper = helper;
+        public CommitParams(ModelDbController controller, String where, String[] selectionArgs) {
+            mDbController = controller;
             mWhere = where;
             mSelectionArgs = selectionArgs;
         }