Removing all usage of LauncherProvider

Also fixing a race condition in model when an item update/delete task
gets queued and executed after the model has reloaded (making the old
data obsolete)

Bug: 277345535
Bug: 263079498
Test: Presubmit
Flag: N/A
Change-Id: Ibd4bdbb3eece05b38b73a22a4be5f368df3754f0
diff --git a/ext_tests/src/com/android/launcher3/testing/DebugTestInformationHandler.java b/ext_tests/src/com/android/launcher3/testing/DebugTestInformationHandler.java
index dc28c6a..3819dd4 100644
--- a/ext_tests/src/com/android/launcher3/testing/DebugTestInformationHandler.java
+++ b/ext_tests/src/com/android/launcher3/testing/DebugTestInformationHandler.java
@@ -31,7 +31,7 @@
 
 import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.LauncherModel;
 import com.android.launcher3.ShortcutAndWidgetContainer;
 import com.android.launcher3.testing.shared.TestProtocol;
 
@@ -189,10 +189,11 @@
             case TestProtocol.REQUEST_CLEAR_DATA: {
                 final long identity = Binder.clearCallingIdentity();
                 try {
-                    LauncherSettings.Settings.call(mContext.getContentResolver(),
-                            LauncherSettings.Settings.METHOD_CREATE_EMPTY_DB);
-                    MAIN_EXECUTOR.submit(() ->
-                            LauncherAppState.getInstance(mContext).getModel().forceReload());
+                    MODEL_EXECUTOR.execute(() -> {
+                        LauncherModel model = LauncherAppState.getInstance(mContext).getModel();
+                        model.getModelDbController().createEmptyDB();
+                        MAIN_EXECUTOR.execute(model::forceReload);
+                    });
                     return response;
                 } finally {
                     Binder.restoreCallingIdentity(identity);
diff --git a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatEduController.java b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatEduController.java
index 048243e..b77c43f 100644
--- a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatEduController.java
+++ b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatEduController.java
@@ -95,9 +95,7 @@
             }
         }
         if (pageId == -1) {
-            pageId = LauncherSettings.Settings.call(mLauncher.getContentResolver(),
-                    LauncherSettings.Settings.METHOD_NEW_SCREEN_ID)
-                    .getInt(LauncherSettings.Settings.EXTRA_VALUE);
+            pageId = mLauncher.getModel().getModelDbController().getNewScreenId();
             mNewScreens = IntArray.wrap(pageId);
         }
         boolean isPortrait = !mLauncher.getDeviceProfile().isVerticalBarLayout();
diff --git a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatRestoreHelper.java b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatRestoreHelper.java
index 726abff..8c01d04 100644
--- a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatRestoreHelper.java
+++ b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatRestoreHelper.java
@@ -22,9 +22,10 @@
 import android.content.Context;
 
 import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.LauncherModel;
 import com.android.launcher3.model.GridBackupTable;
-import com.android.launcher3.provider.LauncherDbUtils;
+import com.android.launcher3.model.ModelDbController;
+import com.android.launcher3.provider.LauncherDbUtils.SQLiteTransaction;
 
 /**
  * A helper class to manage migration revert restoration for hybrid hotseat
@@ -36,16 +37,13 @@
      */
     public static void createBackup(Context context) {
         MODEL_EXECUTOR.execute(() -> {
-            try (LauncherDbUtils.SQLiteTransaction transaction = (LauncherDbUtils.SQLiteTransaction)
-                    LauncherSettings.Settings.call(
-                            context.getContentResolver(),
-                            LauncherSettings.Settings.METHOD_NEW_TRANSACTION)
-                            .getBinder(LauncherSettings.Settings.EXTRA_VALUE)) {
+            ModelDbController dbController = LauncherAppState.getInstance(context)
+                    .getModel().getModelDbController();
+            try (SQLiteTransaction transaction = dbController.newTransaction()) {
                 GridBackupTable backupTable = new GridBackupTable(context, transaction.getDb());
                 backupTable.createCustomBackupTable(HYBRID_HOTSEAT_BACKUP_TABLE);
                 transaction.commit();
-                LauncherSettings.Settings.call(context.getContentResolver(),
-                        LauncherSettings.Settings.METHOD_REFRESH_HOTSEAT_RESTORE_TABLE);
+                dbController.refreshHotseatRestoreTable();
             }
         });
     }
@@ -55,18 +53,15 @@
      */
     public static void restoreBackup(Context context) {
         MODEL_EXECUTOR.execute(() -> {
-            try (LauncherDbUtils.SQLiteTransaction transaction = (LauncherDbUtils.SQLiteTransaction)
-                    LauncherSettings.Settings.call(
-                            context.getContentResolver(),
-                            LauncherSettings.Settings.METHOD_NEW_TRANSACTION)
-                            .getBinder(LauncherSettings.Settings.EXTRA_VALUE)) {
+            LauncherModel model = LauncherAppState.getInstance(context).getModel();
+            try (SQLiteTransaction transaction = model.getModelDbController().newTransaction()) {
                 if (!tableExists(transaction.getDb(), HYBRID_HOTSEAT_BACKUP_TABLE)) {
                     return;
                 }
                 GridBackupTable backupTable = new GridBackupTable(context, transaction.getDb());
                 backupTable.restoreFromCustomBackupTable(HYBRID_HOTSEAT_BACKUP_TABLE, true);
                 transaction.commit();
-                LauncherAppState.getInstance(context).getModel().forceReload();
+                model.forceReload();
             }
         });
     }
diff --git a/quickstep/tests/src/com/android/quickstep/ViewInflationDuringSwipeUp.java b/quickstep/tests/src/com/android/quickstep/ViewInflationDuringSwipeUp.java
index acb2db5..8cc8487 100644
--- a/quickstep/tests/src/com/android/quickstep/ViewInflationDuringSwipeUp.java
+++ b/quickstep/tests/src/com/android/quickstep/ViewInflationDuringSwipeUp.java
@@ -17,8 +17,8 @@
 
 import static androidx.test.InstrumentationRegistry.getContext;
 import static androidx.test.InstrumentationRegistry.getInstrumentation;
-import static androidx.test.InstrumentationRegistry.getTargetContext;
 
+import static com.android.launcher3.WorkspaceLayoutManager.FIRST_SCREEN_ID;
 import static com.android.launcher3.testcomponent.TestCommandReceiver.EXTRA_VALUE;
 import static com.android.launcher3.testcomponent.TestCommandReceiver.SET_LIST_VIEW_SERVICE_BINDER;
 import static com.android.launcher3.util.WidgetUtils.createWidgetInfo;
@@ -32,7 +32,6 @@
 import static org.mockito.Mockito.spy;
 
 import android.appwidget.AppWidgetManager;
-import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.os.Bundle;
@@ -42,14 +41,16 @@
 import android.view.ViewConfiguration;
 import android.widget.RemoteViews;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.LargeTest;
 import androidx.test.filters.Suppress;
-import androidx.test.runner.AndroidJUnit4;
 import androidx.test.uiautomator.By;
 import androidx.test.uiautomator.UiDevice;
 import androidx.test.uiautomator.Until;
 
-import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherModel;
+import com.android.launcher3.celllayout.FavoriteItemsTransaction;
 import com.android.launcher3.model.data.LauncherAppWidgetInfo;
 import com.android.launcher3.tapl.LaunchedAppState;
 import com.android.launcher3.testcomponent.ListViewService;
@@ -57,6 +58,7 @@
 import com.android.launcher3.testcomponent.TestCommandReceiver;
 import com.android.launcher3.ui.TaplTestsLauncher3;
 import com.android.launcher3.ui.TestViewHelpers;
+import com.android.launcher3.util.Executors;
 import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
 import com.android.quickstep.NavigationModeSwitchRule.NavigationModeSwitch;
 
@@ -67,6 +69,7 @@
 import org.mockito.stubbing.Answer;
 
 import java.lang.reflect.Field;
+import java.util.concurrent.atomic.AtomicInteger;
 import java.util.function.IntConsumer;
 
 /**
@@ -84,9 +87,9 @@
 @RunWith(AndroidJUnit4.class)
 public class ViewInflationDuringSwipeUp extends AbstractQuickStepTest {
 
-    private ContentResolver mResolver;
     private SparseArray<ViewConfiguration> mConfigMap;
     private InitTracker mInitTracker;
+    private LauncherModel mModel;
 
     @Before
     public void setUp() throws Exception {
@@ -101,8 +104,8 @@
 
         TaplTestsLauncher3.initialize(this);
 
-        mResolver = mTargetContext.getContentResolver();
-        LauncherSettings.Settings.call(mResolver, LauncherSettings.Settings.METHOD_CREATE_EMPTY_DB);
+        mModel = LauncherAppState.getInstance(mTargetContext).getModel();
+        Executors.MODEL_EXECUTOR.submit(mModel.getModelDbController()::createEmptyDB).get();
 
         // Get static configuration map
         Field field = ViewConfiguration.class.getDeclaredField("sConfigurations");
@@ -182,26 +185,30 @@
     private void executeSwipeUpTestWithWidget(IntConsumer widgetIdCreationCallback,
             IntConsumer updateBeforeSwipeUp, String finalWidgetText) {
         try {
-            // Clear all existing data
-            LauncherSettings.Settings.call(mResolver,
-                    LauncherSettings.Settings.METHOD_CREATE_EMPTY_DB);
-            LauncherSettings.Settings.call(mResolver,
-                    LauncherSettings.Settings.METHOD_CLEAR_EMPTY_DB_FLAG);
             LauncherAppWidgetProviderInfo info = TestViewHelpers.findWidgetProvider(false);
+
             // Make sure the widget is big enough to show a list of items
             info.minSpanX = 2;
             info.minSpanY = 2;
             info.spanX = 2;
             info.spanY = 2;
-            LauncherAppWidgetInfo item = createWidgetInfo(info, getTargetContext(), true);
+            AtomicInteger widgetId = new AtomicInteger();
+            new FavoriteItemsTransaction(mTargetContext)
+                    .addItem(() -> {
+                        LauncherAppWidgetInfo item = createWidgetInfo(info, mTargetContext, true);
+                        item.screenId = FIRST_SCREEN_ID;
+                        widgetId.set(item.appWidgetId);
+                        return item;
+                    })
+                    .commitAndLoadHome(mLauncher);
 
-            addItemToScreen(item);
+
+
             assertTrue("Widget is not present",
                     mLauncher.goHome().tryGetWidget(info.label, DEFAULT_UI_TIMEOUT) != null);
-            int widgetId = item.appWidgetId;
 
             // Verify widget id
-            widgetIdCreationCallback.accept(widgetId);
+            widgetIdCreationCallback.accept(widgetId.get());
 
             // Go to overview once so that all views are initialized and cached
             startAppFast(resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR));
@@ -214,7 +221,7 @@
             LaunchedAppState launchedAppState = mLauncher.getLaunchedAppState();
 
             // Update widget
-            updateBeforeSwipeUp.accept(widgetId);
+            updateBeforeSwipeUp.accept(widgetId.get());
 
             launchedAppState.switchToOverview();
             assertEquals("Views inflated during swipe up", 0, mInitTracker.viewInitCount);
diff --git a/src/com/android/launcher3/AutoInstallsLayout.java b/src/com/android/launcher3/AutoInstallsLayout.java
index ede7e2f..c7cdfa8 100644
--- a/src/com/android/launcher3/AutoInstallsLayout.java
+++ b/src/com/android/launcher3/AutoInstallsLayout.java
@@ -17,6 +17,8 @@
 package com.android.launcher3;
 
 import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
+import static com.android.launcher3.LauncherSettings.Favorites.TABLE_NAME;
+import static com.android.launcher3.provider.LauncherDbUtils.itemIdMatch;
 
 import android.content.ComponentName;
 import android.content.ContentValues;
@@ -30,7 +32,6 @@
 import android.content.res.Resources.NotFoundException;
 import android.content.res.XmlResourceParser;
 import android.database.sqlite.SQLiteDatabase;
-import android.net.Uri;
 import android.os.Bundle;
 import android.os.Process;
 import android.text.TextUtils;
@@ -44,7 +45,6 @@
 import androidx.annotation.WorkerThread;
 import androidx.annotation.XmlRes;
 
-import com.android.launcher3.LauncherProvider.SqlArguments;
 import com.android.launcher3.LauncherSettings.Favorites;
 import com.android.launcher3.model.data.AppInfo;
 import com.android.launcher3.model.data.LauncherAppWidgetInfo;
@@ -619,9 +619,7 @@
             // failed to add, and less than 2 were actually added
             if (folderItems.size() < 2) {
                 // Delete the folder
-                Uri uri = Favorites.getContentUri(folderId);
-                SqlArguments args = new SqlArguments(uri, null, null);
-                mDb.delete(args.table, args.where, args.args);
+                mDb.delete(TABLE_NAME, itemIdMatch(folderId), null);
                 addedId = -1;
 
                 // If we have a single item, promote it to where the folder
@@ -634,7 +632,7 @@
                     copyInteger(myValues, childValues, Favorites.CELLY);
 
                     addedId = folderItems.get(0);
-                    mDb.update(Favorites.TABLE_NAME, childValues,
+                    mDb.update(TABLE_NAME, childValues,
                             Favorites._ID + "=" + addedId, null);
                 }
             }
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index 4e066b0..f225f85 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -143,6 +143,8 @@
     @NonNull
     private final ModelDelegate mModelDelegate;
 
+    private int mLastLoadId = -1;
+
     // Runnable to check if the shortcuts permission has changed.
     @NonNull
     private final Runnable mDataValidationCheck = new Runnable() {
@@ -553,6 +555,7 @@
                 if (mLoaderTask != task) {
                     throw new CancellationException("Loader already stopped");
                 }
+                mLastLoadId++;
                 mTask = task;
                 mIsLoaderTaskRunning = true;
                 mModelLoaded = false;
@@ -722,4 +725,12 @@
             return mCallbacksList.toArray(new Callbacks[mCallbacksList.size()]);
         }
     }
+
+    /**
+     * Returns the ID for the last model load. If the load ID doesn't match for a transaction, the
+     * transaction should be ignored.
+     */
+    public int getLastLoadId() {
+        return mLastLoadId;
+    }
 }
diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java
index 9abec50..440e146 100644
--- a/src/com/android/launcher3/LauncherProvider.java
+++ b/src/com/android/launcher3/LauncherProvider.java
@@ -16,21 +16,18 @@
 
 package com.android.launcher3;
 
-import android.annotation.TargetApi;
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
+
 import android.appwidget.AppWidgetManager;
 import android.content.ComponentName;
 import android.content.ContentProvider;
-import android.content.ContentProviderOperation;
-import android.content.ContentProviderResult;
 import android.content.ContentUris;
 import android.content.ContentValues;
-import android.content.OperationApplicationException;
 import android.database.Cursor;
 import android.database.sqlite.SQLiteQueryBuilder;
 import android.net.Uri;
 import android.os.Binder;
-import android.os.Build;
-import android.os.Bundle;
 import android.os.Process;
 import android.text.TextUtils;
 import android.util.Log;
@@ -38,12 +35,11 @@
 import com.android.launcher3.LauncherSettings.Favorites;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.model.ModelDbController;
-import com.android.launcher3.provider.LauncherDbUtils.SQLiteTransaction;
 import com.android.launcher3.widget.LauncherWidgetHolder;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
-import java.util.ArrayList;
+import java.util.function.ToIntFunction;
 
 public class LauncherProvider extends ContentProvider {
     private static final String TAG = "LauncherProvider";
@@ -74,10 +70,6 @@
         return true;
     }
 
-    public ModelDbController getModelDbController() {
-        return LauncherAppState.getInstance(getContext()).getModel().getModelDbController();
-    }
-
     @Override
     public String getType(Uri uri) {
         SqlArguments args = new SqlArguments(uri, null, null);
@@ -91,180 +83,91 @@
     @Override
     public Cursor query(Uri uri, String[] projection, String selection,
             String[] selectionArgs, String sortOrder) {
-
         SqlArguments args = new SqlArguments(uri, selection, selectionArgs);
         SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
         qb.setTables(args.table);
 
-        Cursor result = getModelDbController().query(
-                args.table, projection, args.where, args.args, sortOrder);
-        result.setNotificationUri(getContext().getContentResolver(), uri);
-        return result;
-    }
-
-    private void reloadLauncherIfExternal() {
-        if (Binder.getCallingPid() != Process.myPid()) {
-            LauncherAppState app = LauncherAppState.getInstanceNoCreate();
-            if (app != null) {
-                app.getModel().forceReload();
-            }
-        }
+        Cursor[] result = new Cursor[1];
+        executeControllerTask(controller -> {
+            result[0] = controller.query(args.table, projection, args.where, args.args, sortOrder);
+            return 0;
+        });
+        return result[0];
     }
 
     @Override
-    public Uri insert(Uri uri, ContentValues initialValues) {
-        // In very limited cases, we support system|signature permission apps to modify the db.
-        if (Binder.getCallingPid() != Process.myPid()) {
-            if (!initializeExternalAdd(initialValues)) {
-                return null;
-            }
-        }
+    public Uri insert(Uri uri, ContentValues values) {
+        int rowId = executeControllerTask(controller -> {
+            // 1. Ensure that externally added items have a valid item id
+            int id = controller.generateNewItemId();
+            values.put(LauncherSettings.Favorites._ID, id);
 
-        SqlArguments args = new SqlArguments(uri);
-        int rowId = getModelDbController().insert(args.table, initialValues);
-        if (rowId < 0) return null;
+            // 2. In the case of an app widget, and if no app widget id is specified, we
+            // attempt allocate and bind the widget.
+            Integer itemType = values.getAsInteger(Favorites.ITEM_TYPE);
+            if (itemType != null
+                    && itemType.intValue() == Favorites.ITEM_TYPE_APPWIDGET
+                    && !values.containsKey(Favorites.APPWIDGET_ID)) {
 
-        uri = ContentUris.withAppendedId(uri, rowId);
-        reloadLauncherIfExternal();
-        return uri;
-    }
+                ComponentName cn = ComponentName.unflattenFromString(
+                        values.getAsString(Favorites.APPWIDGET_PROVIDER));
+                if (cn == null) {
+                    return 0;
+                }
 
-    private boolean initializeExternalAdd(ContentValues values) {
-        // 1. Ensure that externally added items have a valid item id
-        int id = getModelDbController().generateNewItemId();
-        values.put(LauncherSettings.Favorites._ID, id);
-
-        // 2. In the case of an app widget, and if no app widget id is specified, we
-        // attempt allocate and bind the widget.
-        Integer itemType = values.getAsInteger(LauncherSettings.Favorites.ITEM_TYPE);
-        if (itemType != null &&
-                itemType.intValue() == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET &&
-                !values.containsKey(LauncherSettings.Favorites.APPWIDGET_ID)) {
-
-            final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(getContext());
-            ComponentName cn = ComponentName.unflattenFromString(
-                    values.getAsString(Favorites.APPWIDGET_PROVIDER));
-
-            if (cn != null) {
                 LauncherWidgetHolder widgetHolder = LauncherWidgetHolder.newInstance(getContext());
                 try {
                     int appWidgetId = widgetHolder.allocateAppWidgetId();
                     values.put(LauncherSettings.Favorites.APPWIDGET_ID, appWidgetId);
-                    if (!appWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId,cn)) {
+                    if (!AppWidgetManager.getInstance(getContext())
+                            .bindAppWidgetIdIfAllowed(appWidgetId, cn)) {
                         widgetHolder.deleteAppWidgetId(appWidgetId);
-                        return false;
+                        return 0;
                     }
                 } catch (RuntimeException e) {
                     Log.e(TAG, "Failed to initialize external widget", e);
-                    return false;
+                    return 0;
                 } finally {
                     // Necessary to destroy the holder to free up possible activity context
                     widgetHolder.destroy();
                 }
-            } else {
-                return false;
             }
-        }
 
-        return true;
-    }
+            SqlArguments args = new SqlArguments(uri);
+            return controller.insert(args.table, values);
+        });
 
-    @Override
-    public int bulkInsert(Uri uri, ContentValues[] values) {
-        SqlArguments args = new SqlArguments(uri);
-        getModelDbController().bulkInsert(args.table, values);
-        reloadLauncherIfExternal();
-        return values.length;
-    }
-
-    @TargetApi(Build.VERSION_CODES.M)
-    @Override
-    public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations)
-            throws OperationApplicationException {
-        try (SQLiteTransaction t = getModelDbController().newTransaction()) {
-            final int numOperations = operations.size();
-            final ContentProviderResult[] results = new ContentProviderResult[numOperations];
-            for (int i = 0; i < numOperations; i++) {
-                ContentProviderOperation op = operations.get(i);
-                results[i] = op.apply(this, results, i);
-            }
-            t.commit();
-            reloadLauncherIfExternal();
-            return results;
-        }
+        return rowId < 0 ? null : ContentUris.withAppendedId(uri, rowId);
     }
 
     @Override
     public int delete(Uri uri, String selection, String[] selectionArgs) {
         SqlArguments args = new SqlArguments(uri, selection, selectionArgs);
-        int count = getModelDbController().delete(args.table, args.where, args.args);
-        if (count > 0) {
-            reloadLauncherIfExternal();
-        }
-        return count;
+        return executeControllerTask(c -> c.delete(args.table, args.where, args.args));
     }
 
     @Override
     public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
         SqlArguments args = new SqlArguments(uri, selection, selectionArgs);
-        int count = getModelDbController().update(args.table, values, args.where, args.args);
-        reloadLauncherIfExternal();
-        return count;
+        return executeControllerTask(c -> c.update(args.table, values, args.where, args.args));
     }
 
-    @Override
-    public Bundle call(String method, final String arg, final Bundle extras) {
-        if (Binder.getCallingUid() != Process.myUid()) {
-            return null;
+    private int executeControllerTask(ToIntFunction<ModelDbController> task) {
+        if (Binder.getCallingPid() == Process.myPid()) {
+            throw new IllegalArgumentException("Same process should call model directly");
         }
-
-        switch (method) {
-            case LauncherSettings.Settings.METHOD_CLEAR_EMPTY_DB_FLAG: {
-                getModelDbController().clearEmptyDbFlag();
-                return null;
-            }
-            case LauncherSettings.Settings.METHOD_DELETE_EMPTY_FOLDERS: {
-                Bundle result = new Bundle();
-                result.putIntArray(LauncherSettings.Settings.EXTRA_VALUE,
-                        getModelDbController().deleteEmptyFolders().toArray());
-                return result;
-            }
-            case LauncherSettings.Settings.METHOD_NEW_ITEM_ID: {
-                Bundle result = new Bundle();
-                result.putInt(LauncherSettings.Settings.EXTRA_VALUE,
-                        getModelDbController().generateNewItemId());
-                return result;
-            }
-            case LauncherSettings.Settings.METHOD_NEW_SCREEN_ID: {
-                Bundle result = new Bundle();
-                result.putInt(LauncherSettings.Settings.EXTRA_VALUE,
-                        getModelDbController().getNewScreenId());
-                return result;
-            }
-            case LauncherSettings.Settings.METHOD_CREATE_EMPTY_DB: {
-                getModelDbController().createEmptyDB();
-                return null;
-            }
-            case LauncherSettings.Settings.METHOD_LOAD_DEFAULT_FAVORITES: {
-                getModelDbController().loadDefaultFavoritesIfNecessary();
-                return null;
-            }
-            case LauncherSettings.Settings.METHOD_REMOVE_GHOST_WIDGETS: {
-                getModelDbController().removeGhostWidgets();
-                return null;
-            }
-            case LauncherSettings.Settings.METHOD_NEW_TRANSACTION: {
-                Bundle result = new Bundle();
-                result.putBinder(LauncherSettings.Settings.EXTRA_VALUE,
-                        getModelDbController().newTransaction());
-                return result;
-            }
-            case LauncherSettings.Settings.METHOD_REFRESH_HOTSEAT_RESTORE_TABLE: {
-                getModelDbController().refreshHotseatRestoreTable();
-                return null;
-            }
+        try {
+            return MODEL_EXECUTOR.submit(() -> {
+                LauncherModel model = LauncherAppState.getInstance(getContext()).getModel();
+                int count = task.applyAsInt(model.getModelDbController());
+                if (count > 0) {
+                    MAIN_EXECUTOR.submit(model::forceReload);
+                }
+                return count;
+            }).get();
+        } catch (Exception e) {
+            throw new IllegalStateException(e);
         }
-        return null;
     }
 
     static class SqlArguments {
diff --git a/src/com/android/launcher3/LauncherSettings.java b/src/com/android/launcher3/LauncherSettings.java
index 2397429..105d5f3 100644
--- a/src/com/android/launcher3/LauncherSettings.java
+++ b/src/com/android/launcher3/LauncherSettings.java
@@ -16,10 +16,7 @@
 
 package com.android.launcher3;
 
-import android.content.ContentResolver;
 import android.database.sqlite.SQLiteDatabase;
-import android.net.Uri;
-import android.os.Bundle;
 import android.provider.BaseColumns;
 
 import com.android.launcher3.model.data.ItemInfo;
@@ -155,24 +152,6 @@
         public static final String TMP_TABLE = "favorites_tmp";
 
         /**
-         * The content:// style URL for "favorites" table
-         */
-        public static final Uri CONTENT_URI = Uri.parse("content://"
-                + LauncherProvider.AUTHORITY + "/" + TABLE_NAME);
-
-        /**
-         * The content:// style URL for a given row, identified by its id.
-         *
-         * @param id The row id.
-         *
-         * @return The unique content URL for the specified row.
-         */
-        public static Uri getContentUri(int id) {
-            return Uri.parse("content://" + LauncherProvider.AUTHORITY
-                    + "/" + TABLE_NAME + "/" + id);
-        }
-
-        /**
          * The container holding the favorite
          * <P>Type: INTEGER</P>
          */
@@ -339,42 +318,8 @@
      * Launcher settings
      */
     public static final class Settings {
-
-        public static final Uri CONTENT_URI = Uri.parse("content://" +
-                LauncherProvider.AUTHORITY + "/settings");
-
-        public static final String METHOD_CLEAR_EMPTY_DB_FLAG = "clear_empty_db_flag";
-
-        public static final String METHOD_DELETE_EMPTY_FOLDERS = "delete_empty_folders";
-
-        public static final String METHOD_NEW_ITEM_ID = "generate_new_item_id";
-        public static final String METHOD_NEW_SCREEN_ID = "generate_new_screen_id";
-
-        public static final String METHOD_CREATE_EMPTY_DB = "create_empty_db";
-
-        public static final String METHOD_LOAD_DEFAULT_FAVORITES = "load_default_favorites";
-
-        public static final String METHOD_REMOVE_GHOST_WIDGETS = "remove_ghost_widgets";
-
-        public static final String METHOD_NEW_TRANSACTION = "new_db_transaction";
-
-        public static final String METHOD_REFRESH_HOTSEAT_RESTORE_TABLE = "restore_hotseat_table";
-
-        public static final String EXTRA_VALUE = "value";
-
-        public static final String EXTRA_DB_NAME = "db_name";
-
         public static final String LAYOUT_DIGEST_KEY = "launcher3.layout.provider.blob";
         public static final String LAYOUT_DIGEST_LABEL = "launcher-layout";
         public static final String LAYOUT_DIGEST_TAG = "ignore";
-
-        public static Bundle call(ContentResolver cr, String method) {
-            return call(cr, method, null /* arg */);
-        }
-
-        public static Bundle call(ContentResolver cr, String method, String arg) {
-            return cr.call(CONTENT_URI, method, arg, null);
-        }
-
     }
 }
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 52755d4..adaf20f 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -897,9 +897,8 @@
         mWorkspaceScreens.remove(emptyScreenId);
         mScreenOrder.removeValue(emptyScreenId);
 
-        int newScreenId = LauncherSettings.Settings.call(getContext().getContentResolver(),
-                        LauncherSettings.Settings.METHOD_NEW_SCREEN_ID)
-                .getInt(LauncherSettings.Settings.EXTRA_VALUE);
+        int newScreenId = LauncherAppState.getInstance(getContext())
+                .getModel().getModelDbController().getNewScreenId();
         // Launcher database isn't aware of empty pages that are already bound, so we need to
         // skip those IDs manually.
         while (mWorkspaceScreens.containsKey(newScreenId)) {
diff --git a/src/com/android/launcher3/model/BaseLauncherBinder.java b/src/com/android/launcher3/model/BaseLauncherBinder.java
index 5d85b1c..1f165d1 100644
--- a/src/com/android/launcher3/model/BaseLauncherBinder.java
+++ b/src/com/android/launcher3/model/BaseLauncherBinder.java
@@ -106,6 +106,7 @@
         synchronized (mBgDataModel) {
             if (incrementBindId) {
                 mBgDataModel.lastBindId++;
+                mBgDataModel.lastLoadId = mApp.getModel().getLastLoadId();
             }
             mMyBindingId = mBgDataModel.lastBindId;
             return new DisjointWorkspaceBinder(workspacePages);
@@ -126,6 +127,7 @@
             mBgDataModel.extraItems.forEach(extraItems::add);
             if (incrementBindId) {
                 mBgDataModel.lastBindId++;
+                mBgDataModel.lastLoadId = mApp.getModel().getLastLoadId();
             }
             mMyBindingId = mBgDataModel.lastBindId;
             workspaceItemCount = mBgDataModel.itemsIdMap.size();
diff --git a/src/com/android/launcher3/model/BgDataModel.java b/src/com/android/launcher3/model/BgDataModel.java
index 1f56707..7bcd038 100644
--- a/src/com/android/launcher3/model/BgDataModel.java
+++ b/src/com/android/launcher3/model/BgDataModel.java
@@ -126,6 +126,11 @@
     public int lastBindId = 0;
 
     /**
+     * Load id for which the callbacks were successfully bound
+     */
+    public int lastLoadId = -1;
+
+    /**
      * Clears all the data
      */
     public synchronized void clear() {
diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java
index 4f542fe..3daf4af 100644
--- a/src/com/android/launcher3/model/LoaderTask.java
+++ b/src/com/android/launcher3/model/LoaderTask.java
@@ -33,7 +33,6 @@
 import android.annotation.SuppressLint;
 import android.appwidget.AppWidgetProviderInfo;
 import android.content.ComponentName;
-import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
@@ -61,7 +60,6 @@
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherModel;
 import com.android.launcher3.LauncherSettings.Favorites;
-import com.android.launcher3.LauncherSettings.Settings;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.folder.Folder;
@@ -361,16 +359,15 @@
             String selection,
             @Nullable LoaderMemoryLogger memoryLogger) {
         final Context context = mApp.getContext();
-        final ContentResolver contentResolver = context.getContentResolver();
         final PackageManagerHelper pmHelper = new PackageManagerHelper(context);
         final boolean isSafeMode = pmHelper.isSafeMode();
         final boolean isSdCardReady = Utilities.isBootCompleted();
         final WidgetManagerHelper widgetHelper = new WidgetManagerHelper(context);
 
-        mApp.getModel().getModelDbController().tryMigrateDB();
+        ModelDbController dbController = mApp.getModel().getModelDbController();
+        dbController.tryMigrateDB();
         Log.d(TAG, "loadWorkspace: loading default favorites");
-        mApp.getModel().getModelDbController().loadDefaultFavoritesIfNecessary();
-        Settings.call(contentResolver, Settings.METHOD_LOAD_DEFAULT_FAVORITES);
+        dbController.loadDefaultFavoritesIfNecessary();
 
         synchronized (mBgDataModel) {
             mBgDataModel.clear();
@@ -384,12 +381,11 @@
             mFirstScreenBroadcast = new FirstScreenBroadcast(installingPkgs);
 
             mShortcutKeyToPinnedShortcuts = new HashMap<>();
-            ModelDbController dbController = mApp.getModel().getModelDbController();
             final LoaderCursor c = new LoaderCursor(
                     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);
+            mDbName = extras == null ? null : extras.getString(ModelDbController.EXTRA_DB_NAME);
             try {
                 final LongSparseArray<Boolean> unlockedUsers = new LongSparseArray<>();
 
@@ -906,9 +902,7 @@
     private void sanitizeFolders(boolean itemsDeleted) {
         if (itemsDeleted) {
             // Remove any empty folder
-            int[] deletedFolderIds = Settings.call(mApp.getContext().getContentResolver(),
-                            Settings.METHOD_DELETE_EMPTY_FOLDERS)
-                    .getIntArray(Settings.EXTRA_VALUE);
+            IntArray deletedFolderIds = mApp.getModel().getModelDbController().deleteEmptyFolders();
             synchronized (mBgDataModel) {
                 for (int folderId : deletedFolderIds) {
                     mBgDataModel.workspaceItems.remove(mBgDataModel.folders.get(folderId));
@@ -921,11 +915,9 @@
 
     private void sanitizeWidgetsShortcutsAndPackages() {
         Context context = mApp.getContext();
-        ContentResolver contentResolver = context.getContentResolver();
 
         // Remove any ghost widgets
-        Settings.call(contentResolver,
-                Settings.METHOD_REMOVE_GHOST_WIDGETS);
+        mApp.getModel().getModelDbController().removeGhostWidgets();
 
         // Update pinned state of model shortcuts
         mBgDataModel.updateShortcutPinnedState(context);
diff --git a/src/com/android/launcher3/model/ModelDbController.java b/src/com/android/launcher3/model/ModelDbController.java
index d5cc82b..1b1b38f 100644
--- a/src/com/android/launcher3/model/ModelDbController.java
+++ b/src/com/android/launcher3/model/ModelDbController.java
@@ -37,7 +37,6 @@
 import android.database.SQLException;
 import android.database.sqlite.SQLiteDatabase;
 import android.net.Uri;
-import android.os.Binder;
 import android.os.Bundle;
 import android.os.ParcelFileDescriptor;
 import android.os.Process;
@@ -85,6 +84,7 @@
     private static final String TAG = "LauncherProvider";
 
     private static final String EMPTY_DATABASE_CREATED = "EMPTY_DATABASE_CREATED";
+    public static final String EXTRA_DB_NAME = "db_name";
 
     protected DatabaseHelper mOpenHelper;
 
@@ -140,7 +140,7 @@
                 table, projection, selection, selectionArgs, null, null, sortOrder);
 
         final Bundle extra = new Bundle();
-        extra.putString(LauncherSettings.Settings.EXTRA_DB_NAME, mOpenHelper.getDatabaseName());
+        extra.putString(EXTRA_DB_NAME, mOpenHelper.getDatabaseName());
         result.setExtras(extra);
         return result;
     }
@@ -162,28 +162,6 @@
     }
 
     /**
-     * Similar to insert but for adding multiple values in a transaction.
-     */
-    @WorkerThread
-    public int bulkInsert(String table, ContentValues[] values) {
-        createDbIfNotExists();
-
-        SQLiteDatabase db = mOpenHelper.getWritableDatabase();
-        try (SQLiteTransaction t = new SQLiteTransaction(db)) {
-            int numValues = values.length;
-            for (int i = 0; i < numValues; i++) {
-                addModifiedTime(values[i]);
-                if (mOpenHelper.dbInsertAndCheck(db, table, values[i]) < 0) {
-                    return 0;
-                }
-            }
-            onAddOrDeleteOp(db);
-            t.commit();
-        }
-        return values.length;
-    }
-
-    /**
      * Refer {@link SQLiteDatabase#delete(String, String, String[])}
      */
     @WorkerThread
@@ -191,10 +169,6 @@
         createDbIfNotExists();
         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
 
-        if (Binder.getCallingPid() != Process.myPid()
-                && Favorites.TABLE_NAME.equalsIgnoreCase(table)) {
-            mOpenHelper.removeGhostWidgets(mOpenHelper.getWritableDatabase());
-        }
         int count = db.delete(table, selection, selectionArgs);
         if (count > 0) {
             onAddOrDeleteOp(db);
@@ -250,6 +224,7 @@
     public void createEmptyDB() {
         createDbIfNotExists();
         mOpenHelper.createEmptyDB(mOpenHelper.getWritableDatabase());
+        LauncherPrefs.get(mContext).putSync(getEmptyDbCreatedKey().to(true));
     }
 
     /**
@@ -304,6 +279,10 @@
      */
     private boolean migrateGridIfNeeded() {
         createDbIfNotExists();
+        if (LauncherPrefs.get(mContext).get(getEmptyDbCreatedKey())) {
+            // If we have already create a new DB, ignore migration
+            return false;
+        }
         InvariantDeviceProfile idp = LauncherAppState.getIDP(mContext);
         if (!GridSizeMigrationUtil.needsToMigrate(mContext, idp)) {
             return true;
@@ -374,7 +353,7 @@
     }
 
     private void clearFlagEmptyDbCreated() {
-        LauncherPrefs.get(mContext).removeSync(getEmptyDbCreatedKey(mOpenHelper.getDatabaseName()));
+        LauncherPrefs.get(mContext).removeSync(getEmptyDbCreatedKey());
     }
 
     /**
@@ -388,7 +367,7 @@
     public synchronized void loadDefaultFavoritesIfNecessary() {
         createDbIfNotExists();
 
-        if (LauncherPrefs.get(mContext).get(getEmptyDbCreatedKey(mOpenHelper.getDatabaseName()))) {
+        if (LauncherPrefs.get(mContext).get(getEmptyDbCreatedKey())) {
             Log.d(TAG, "loading default workspace");
 
             LauncherWidgetHolder widgetHolder = mOpenHelper.newLauncherWidgetHolder();
@@ -507,6 +486,10 @@
                 mOpenHelper, mContext.getResources(), defaultLayout);
     }
 
+    private ConstantItem<Boolean> getEmptyDbCreatedKey() {
+        return getEmptyDbCreatedKey(mOpenHelper.getDatabaseName());
+    }
+
     /**
      * 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
diff --git a/src/com/android/launcher3/model/ModelWriter.java b/src/com/android/launcher3/model/ModelWriter.java
index f2afaeb..a6b4d59 100644
--- a/src/com/android/launcher3/model/ModelWriter.java
+++ b/src/com/android/launcher3/model/ModelWriter.java
@@ -16,13 +16,12 @@
 
 package com.android.launcher3.model;
 
+import static com.android.launcher3.LauncherSettings.Favorites.TABLE_NAME;
+import static com.android.launcher3.provider.LauncherDbUtils.itemIdMatch;
 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
 
-import android.content.ContentProviderOperation;
-import android.content.ContentResolver;
 import android.content.ContentValues;
 import android.content.Context;
-import android.net.Uri;
 import android.text.TextUtils;
 import android.util.Log;
 
@@ -32,9 +31,7 @@
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherModel;
 import com.android.launcher3.LauncherModel.CallbackTask;
-import com.android.launcher3.LauncherProvider;
 import com.android.launcher3.LauncherSettings.Favorites;
-import com.android.launcher3.LauncherSettings.Settings;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.celllayout.CellPosMapper;
 import com.android.launcher3.celllayout.CellPosMapper.CellPos;
@@ -45,6 +42,7 @@
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.LauncherAppWidgetInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
+import com.android.launcher3.provider.LauncherDbUtils.SQLiteTransaction;
 import com.android.launcher3.util.ContentWriter;
 import com.android.launcher3.util.Executors;
 import com.android.launcher3.util.ItemInfoMatcher;
@@ -80,7 +78,7 @@
     private final boolean mVerifyChanges;
 
     // Keep track of delete operations that occur when an Undo option is present; we may not commit.
-    private final List<Runnable> mDeleteRunnables = new ArrayList<>();
+    private final List<ModelTask> mDeleteRunnables = new ArrayList<>();
     private boolean mPreparingToUndo;
     private final CellPosMapper mCellPosMapper;
 
@@ -217,8 +215,7 @@
         item.spanX = spanX;
         item.spanY = spanY;
         notifyItemModified(item);
-
-        MODEL_EXECUTOR.execute(new UpdateItemRunnable(item, () ->
+        new UpdateItemRunnable(item, () ->
                 new ContentWriter(mContext)
                         .put(Favorites.CONTAINER, item.container)
                         .put(Favorites.CELLX, item.cellX)
@@ -226,7 +223,8 @@
                         .put(Favorites.RANK, item.rank)
                         .put(Favorites.SPANX, item.spanX)
                         .put(Favorites.SPANY, item.spanY)
-                        .put(Favorites.SCREEN, item.screenId)));
+                        .put(Favorites.SCREEN, item.screenId))
+                .executeOnModelThread();
     }
 
     /**
@@ -234,11 +232,11 @@
      */
     public void updateItemInDatabase(ItemInfo item) {
         notifyItemModified(item);
-        MODEL_EXECUTOR.execute(new UpdateItemRunnable(item, () -> {
+        new UpdateItemRunnable(item, () -> {
             ContentWriter writer = new ContentWriter(mContext);
             item.onAddToDatabase(writer);
             return writer;
-        }));
+        }).executeOnModelThread();
     }
 
     private void notifyItemModified(ItemInfo item) {
@@ -253,13 +251,12 @@
             int container, int screenId, int cellX, int cellY) {
         updateItemInfoProps(item, container, screenId, cellX, cellY);
 
-        final ContentResolver cr = mContext.getContentResolver();
-        item.id = Settings.call(cr, Settings.METHOD_NEW_ITEM_ID).getInt(Settings.EXTRA_VALUE);
+        item.id = mModel.getModelDbController().generateNewItemId();
         notifyOtherCallbacks(c -> c.bindItems(Collections.singletonList(item), false));
 
         ModelVerifier verifier = new ModelVerifier();
         final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
-        MODEL_EXECUTOR.execute(() -> {
+        newModelTask(() -> {
             // Write the item on background thread, as some properties might have been updated in
             // the background.
             final ContentWriter writer = new ContentWriter(mContext);
@@ -272,7 +269,7 @@
                 mBgDataModel.addItem(mContext, item, true);
                 verifier.verifyModel();
             }
-        });
+        }).executeOnModelThread();
     }
 
     /**
@@ -303,15 +300,13 @@
                 Collectors.joining(","))
                 + ". Reason: [" + (TextUtils.isEmpty(reason) ? "unknown" : reason) + "]");
         notifyDelete(items);
-        enqueueDeleteRunnable(() -> {
+        enqueueDeleteRunnable(newModelTask(() -> {
             for (ItemInfo item : items) {
-                final Uri uri = Favorites.getContentUri(item.id);
-                mContext.getContentResolver().delete(uri, null, null);
-
+                mModel.getModelDbController().delete(TABLE_NAME, itemIdMatch(item.id), null);
                 mBgDataModel.removeItem(mContext, item);
                 verifier.verifyModel();
             }
-        });
+        }));
     }
 
     /**
@@ -321,7 +316,7 @@
         ModelVerifier verifier = new ModelVerifier();
         notifyDelete(Collections.singleton(info));
 
-        enqueueDeleteRunnable(() -> {
+        enqueueDeleteRunnable(newModelTask(() -> {
             mModel.getModelDbController().delete(Favorites.TABLE_NAME,
                     Favorites.CONTAINER + "=" + info.id, null);
             mBgDataModel.removeItem(mContext, info.contents);
@@ -331,7 +326,7 @@
                     Favorites._ID + "=" + info.id, null);
             mBgDataModel.removeItem(mContext, info);
             verifier.verifyModel();
-        });
+        }));
     }
 
     /**
@@ -343,7 +338,7 @@
         if (holder != null && !info.isCustomWidget() && info.isWidgetIdAllocated()) {
             // Deleting an app widget ID is a void call but writes to disk before returning
             // to the caller...
-            enqueueDeleteRunnable(() -> holder.deleteAppWidgetId(info.appWidgetId));
+            enqueueDeleteRunnable(newModelTask(() -> holder.deleteAppWidgetId(info.appWidgetId)));
         }
         deleteItemFromDatabase(info, reason);
     }
@@ -373,19 +368,17 @@
      * {@link #commitDelete()} is called (or abandoned if {@link #abortDelete} is called).
      * Otherwise, we run the Runnable immediately.
      */
-    private void enqueueDeleteRunnable(Runnable r) {
+    private void enqueueDeleteRunnable(ModelTask r) {
         if (mPreparingToUndo) {
             mDeleteRunnables.add(r);
         } else {
-            MODEL_EXECUTOR.execute(r);
+            r.executeOnModelThread();
         }
     }
 
     public void commitDelete() {
         mPreparingToUndo = false;
-        for (Runnable runnable : mDeleteRunnables) {
-            MODEL_EXECUTOR.execute(runnable);
-        }
+        mDeleteRunnables.forEach(ModelTask::executeOnModelThread);
         mDeleteRunnables.clear();
     }
 
@@ -426,10 +419,9 @@
         }
 
         @Override
-        public void run() {
-            Uri uri = Favorites.getContentUri(mItemId);
-            mContext.getContentResolver().update(uri, mWriter.get().getValues(mContext),
-                    null, null);
+        public void runImpl() {
+            mModel.getModelDbController().update(
+                    TABLE_NAME, mWriter.get().getValues(mContext), itemIdMatch(mItemId), null);
             updateItemArrays(mItem, mItemId);
         }
     }
@@ -444,27 +436,24 @@
         }
 
         @Override
-        public void run() {
-            ArrayList<ContentProviderOperation> ops = new ArrayList<>();
-            int count = mItems.size();
-            for (int i = 0; i < count; i++) {
-                ItemInfo item = mItems.get(i);
-                final int itemId = item.id;
-                final Uri uri = Favorites.getContentUri(itemId);
-                ContentValues values = mValues.get(i);
-
-                ops.add(ContentProviderOperation.newUpdate(uri).withValues(values).build());
-                updateItemArrays(item, itemId);
-            }
-            try {
-                mContext.getContentResolver().applyBatch(LauncherProvider.AUTHORITY, ops);
+        public void runImpl() {
+            try (SQLiteTransaction t = mModel.getModelDbController().newTransaction()) {
+                int count = mItems.size();
+                for (int i = 0; i < count; i++) {
+                    ItemInfo item = mItems.get(i);
+                    final int itemId = item.id;
+                    mModel.getModelDbController().update(
+                            TABLE_NAME, mValues.get(i), itemIdMatch(itemId), null);
+                    updateItemArrays(item, itemId);
+                }
+                t.commit();
             } catch (Exception e) {
                 e.printStackTrace();
             }
         }
     }
 
-    private abstract class UpdateItemBaseRunnable implements Runnable {
+    private abstract class UpdateItemBaseRunnable extends ModelTask {
         private final StackTraceElement[] mStackTrace;
         private final ModelVerifier mVerifier = new ModelVerifier();
 
@@ -515,6 +504,35 @@
         }
     }
 
+    private abstract class ModelTask implements Runnable {
+
+        private final int mLoadId = mBgDataModel.lastLoadId;
+
+        @Override
+        public final void run() {
+            if (mLoadId != mModel.getLastLoadId()) {
+                Log.d(TAG, "Model changed before the task could execute");
+                return;
+            }
+            runImpl();
+        }
+
+        public final void executeOnModelThread() {
+            MODEL_EXECUTOR.execute(this);
+        }
+
+        public abstract void runImpl();
+    }
+
+    private ModelTask newModelTask(Runnable r) {
+        return new ModelTask() {
+            @Override
+            public void runImpl() {
+                r.run();
+            }
+        };
+    }
+
     /**
      * Utility class to verify model updates are propagated properly to the callback.
      */
diff --git a/src/com/android/launcher3/model/WorkspaceItemSpaceFinder.java b/src/com/android/launcher3/model/WorkspaceItemSpaceFinder.java
index 93fc6a5..1fc8a03 100644
--- a/src/com/android/launcher3/model/WorkspaceItemSpaceFinder.java
+++ b/src/com/android/launcher3/model/WorkspaceItemSpaceFinder.java
@@ -82,9 +82,7 @@
 
         if (!found) {
             // Still no position found. Add a new screen to the end.
-            screenId = LauncherSettings.Settings.call(app.getContext().getContentResolver(),
-                    LauncherSettings.Settings.METHOD_NEW_SCREEN_ID)
-                    .getInt(LauncherSettings.Settings.EXTRA_VALUE);
+            screenId = app.getModel().getModelDbController().getNewScreenId();
 
             // Save the screen id for binding in the workspace
             workspaceScreens.add(screenId);
diff --git a/src/com/android/launcher3/provider/LauncherDbUtils.java b/src/com/android/launcher3/provider/LauncherDbUtils.java
index c718dcc..1f908eb 100644
--- a/src/com/android/launcher3/provider/LauncherDbUtils.java
+++ b/src/com/android/launcher3/provider/LauncherDbUtils.java
@@ -28,7 +28,6 @@
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
 import android.graphics.drawable.Icon;
-import android.os.Binder;
 import android.os.PersistableBundle;
 import android.os.Process;
 import android.os.UserManager;
@@ -50,6 +49,13 @@
  */
 public class LauncherDbUtils {
 
+    /**
+     * Returns a string which can be used as a where clause for DB query to match the given itemId
+     */
+    public static String itemIdMatch(int itemId) {
+        return "_id=" + itemId;
+    }
+
     public static IntArray queryIntArray(boolean distinct, SQLiteDatabase db, String tableName,
             String columnName, String selection, String groupBy, String orderBy) {
         IntArray out = new IntArray();
@@ -166,7 +172,7 @@
     /**
      * Utility class to simplify managing sqlite transactions
      */
-    public static class SQLiteTransaction extends Binder implements AutoCloseable {
+    public static class SQLiteTransaction implements AutoCloseable {
         private final SQLiteDatabase mDb;
 
         public SQLiteTransaction(SQLiteDatabase db) {
diff --git a/tests/src/com/android/launcher3/celllayout/FavoriteItemsTransaction.java b/tests/src/com/android/launcher3/celllayout/FavoriteItemsTransaction.java
index 80cc5aa..6f6a443 100644
--- a/tests/src/com/android/launcher3/celllayout/FavoriteItemsTransaction.java
+++ b/tests/src/com/android/launcher3/celllayout/FavoriteItemsTransaction.java
@@ -15,24 +15,28 @@
  */
 package com.android.launcher3.celllayout;
 
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
 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;
 import static com.android.launcher3.util.TestUtil.runOnExecutorSync;
 
-import android.content.ContentValues;
 import android.content.Context;
 
+import androidx.test.uiautomator.UiDevice;
+
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherModel;
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.model.BgDataModel.Callbacks;
 import com.android.launcher3.model.ModelDbController;
 import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.provider.LauncherDbUtils.SQLiteTransaction;
+import com.android.launcher3.tapl.LauncherInstrumentation;
 import com.android.launcher3.util.ContentWriter;
 
 import java.util.ArrayList;
-import java.util.List;
 import java.util.function.Supplier;
 
 public class FavoriteItemsTransaction {
@@ -67,15 +71,16 @@
             controller.clearEmptyDbFlag();
 
             // Add new data
-            List<ContentValues> values = new ArrayList<>();
-            int count = mItemsToSubmit.size();
-            for (int i = 0; i < count; i++) {
-                ContentWriter writer = new ContentWriter(mContext);
-                mItemsToSubmit.get(i).get().onAddToDatabase(writer);
-                writer.put(LauncherSettings.Favorites._ID, i);
-                values.add(writer.getValues(mContext));
+            try (SQLiteTransaction transaction = controller.newTransaction()) {
+                int count = mItemsToSubmit.size();
+                for (int i = 0; i < count; i++) {
+                    ContentWriter writer = new ContentWriter(mContext);
+                    mItemsToSubmit.get(i).get().onAddToDatabase(writer);
+                    writer.put(LauncherSettings.Favorites._ID, i);
+                    controller.insert(TABLE_NAME, writer.getValues(mContext));
+                }
+                transaction.commit();
             }
-            controller.bulkInsert(TABLE_NAME, values.toArray(new ContentValues[0]));
         });
 
         // Reload model
@@ -91,4 +96,15 @@
         runOnExecutorSync(MAIN_EXECUTOR, () -> { });
         runOnExecutorSync(MAIN_EXECUTOR, () -> model.removeCallbacks(mockCb));
     }
+
+    /**
+     * Commits the transaction and waits for home load
+     */
+    public void commitAndLoadHome(LauncherInstrumentation inst) {
+        commit();
+
+        // Launch the home activity
+        UiDevice.getInstance(getInstrumentation()).pressHome();
+        inst.waitForLauncherInitialized();
+    }
 }
diff --git a/tests/src/com/android/launcher3/celllayout/ReorderWidgets.java b/tests/src/com/android/launcher3/celllayout/ReorderWidgets.java
index b07a402..7ec78bb 100644
--- a/tests/src/com/android/launcher3/celllayout/ReorderWidgets.java
+++ b/tests/src/com/android/launcher3/celllayout/ReorderWidgets.java
@@ -61,7 +61,6 @@
     public void setup() throws Throwable {
         mWorkspaceBuilder = new TestWorkspaceBuilder(mTargetContext);
         TaplTestsLauncher3.initialize(this);
-        clearHomescreen();
     }
 
     /**
@@ -111,10 +110,10 @@
                 new FavoriteItemsTransaction(mTargetContext);
         transaction = buildWorkspaceFromBoards(testCase.mStart, transaction);
         transaction.commit();
+        mLauncher.waitForLauncherInitialized();
         // resetLoaderState triggers the launcher to start loading the workspace which allows
         // waitForLauncherCondition to wait for that condition, otherwise the condition would
         // always be true and it wouldn't wait for the changes to be applied.
-        resetLoaderState();
         waitForLauncherCondition("Workspace didn't finish loading", l -> !l.isWorkspaceLoading());
         Widget widget = mLauncher.getWorkspace().getWidgetAtCell(mainWidgetCellPos.getCellX(),
                 mainWidgetCellPos.getCellY());
diff --git a/tests/src/com/android/launcher3/celllayout/TestWorkspaceBuilder.java b/tests/src/com/android/launcher3/celllayout/TestWorkspaceBuilder.java
index cde733e..8d06e33 100644
--- a/tests/src/com/android/launcher3/celllayout/TestWorkspaceBuilder.java
+++ b/tests/src/com/android/launcher3/celllayout/TestWorkspaceBuilder.java
@@ -78,12 +78,6 @@
         return transaction;
     }
 
-    private int getID() {
-        return LauncherSettings.Settings.call(
-                        mResolver, LauncherSettings.Settings.METHOD_NEW_ITEM_ID)
-                .getInt(LauncherSettings.Settings.EXTRA_VALUE);
-    }
-
     private AppInfo getApp() {
         return new AppInfo(APP_COMPONENT_NAME, "test icon", mMyUser,
                 AppInfo.makeLaunchIntent(APP_COMPONENT_NAME));
@@ -127,7 +121,6 @@
         return () -> {
             LauncherAppWidgetProviderInfo info = findWidgetProvider(false);
             LauncherAppWidgetInfo item = createWidgetInfo(info, getApplicationContext(), true);
-            item.id = getID();
             item.cellX = widgetRect.getCellX();
             item.cellY = widgetRect.getCellY();
             item.spanX = widgetRect.getSpanX();
@@ -139,7 +132,6 @@
 
     private ItemInfo createIconInCell(CellLayoutBoard.IconPoint iconPoint, int screenId) {
         WorkspaceItemInfo item = new WorkspaceItemInfo(getApp());
-        item.id = getID();
         item.screenId = screenId;
         item.cellX = iconPoint.getCoord().x;
         item.cellY = iconPoint.getCoord().y;
@@ -150,7 +142,6 @@
 
     private ItemInfo getHotseatValues(int x) {
         WorkspaceItemInfo item = new WorkspaceItemInfo(getApp());
-        item.id = getID();
         item.cellX = x;
         item.cellY = 0;
         item.minSpanY = item.minSpanX = item.spanX = item.spanY = 1;
diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
index 4506fa3..98c4c61 100644
--- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
+++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
@@ -19,7 +19,6 @@
 
 import static com.android.launcher3.ui.TaplTestsLauncher3.getAppPackageName;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
-import static com.android.launcher3.util.TestUtil.getOnUiThread;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
@@ -40,6 +39,7 @@
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.system.OsConstants;
 import android.util.Log;
 
 import androidx.test.InstrumentationRegistry;
@@ -49,11 +49,8 @@
 import androidx.test.uiautomator.Until;
 
 import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.Utilities;
-import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.statemanager.StateManager;
 import com.android.launcher3.tapl.HomeAllApps;
 import com.android.launcher3.tapl.HomeAppIcon;
@@ -66,7 +63,6 @@
 import com.android.launcher3.util.SimpleBroadcastReceiver;
 import com.android.launcher3.util.TestUtil;
 import com.android.launcher3.util.Wait;
-import com.android.launcher3.util.WidgetUtils;
 import com.android.launcher3.util.rule.FailureWatcher;
 import com.android.launcher3.util.rule.LauncherActivityRule;
 import com.android.launcher3.util.rule.SamplerRule;
@@ -83,8 +79,10 @@
 import org.junit.rules.TestRule;
 
 import java.io.IOException;
+import java.util.concurrent.Callable;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
 import java.util.function.Consumer;
 import java.util.function.Function;
 import java.util.function.Supplier;
@@ -298,37 +296,19 @@
     }
 
     /**
-     * Removes all icons from homescreen and hotseat.
+     * Runs the callback on the UI thread and returns the result.
      */
-    public void clearHomescreen() {
-        LauncherSettings.Settings.call(mTargetContext.getContentResolver(),
-                LauncherSettings.Settings.METHOD_CREATE_EMPTY_DB);
-        LauncherSettings.Settings.call(mTargetContext.getContentResolver(),
-                LauncherSettings.Settings.METHOD_CLEAR_EMPTY_DB_FLAG);
-        resetLoaderState();
-    }
-
-    protected void resetLoaderState() {
+    protected <T> T getOnUiThread(final Callable<T> callback) {
         try {
-            mMainThreadExecutor.execute(
-                    () -> LauncherAppState.getInstance(
-                            mTargetContext).getModel().forceReload());
-        } catch (Throwable t) {
-            throw new IllegalArgumentException(t);
+            return mMainThreadExecutor.submit(callback).get(DEFAULT_UI_TIMEOUT,
+                    TimeUnit.MILLISECONDS);
+        } catch (TimeoutException e) {
+            Log.e(TAG, "Timeout in getOnUiThread, sending SIGABRT", e);
+            Process.sendSignal(Process.myPid(), OsConstants.SIGABRT);
+            throw new RuntimeException(e);
+        } catch (Throwable e) {
+            throw new RuntimeException(e);
         }
-        mLauncher.waitForLauncherInitialized();
-    }
-
-    /**
-     * Adds {@param item} on the homescreen on the 0th screen
-     */
-    public void addItemToScreen(ItemInfo item) {
-        WidgetUtils.addItemToScreen(item, mTargetContext);
-        resetLoaderState();
-
-        // Launch the home activity
-        mDevice.pressHome();
-        mLauncher.waitForLauncherInitialized();
     }
 
     protected <T> T getFromLauncher(Function<Launcher, T> f) {
diff --git a/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java
index fddbc37..82bf644 100644
--- a/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java
@@ -30,6 +30,7 @@
 import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.launcher3.celllayout.FavoriteItemsTransaction;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.LauncherAppWidgetInfo;
 import com.android.launcher3.testcomponent.WidgetConfigActivity;
@@ -85,8 +86,7 @@
      * @param acceptConfig accept the config activity
      */
     private void runTest(boolean acceptConfig) throws Throwable {
-        clearHomescreen();
-        mDevice.pressHome();
+        new FavoriteItemsTransaction(mTargetContext).commitAndLoadHome(mLauncher);
 
         // Drag widget to homescreen
         WidgetConfigStartupMonitor monitor = new WidgetConfigStartupMonitor();
diff --git a/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java
index 5cdc886..b869c9f 100644
--- a/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java
@@ -23,9 +23,10 @@
 import android.platform.test.annotations.IwTest;
 import android.platform.test.annotations.PlatinumTest;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.LargeTest;
-import androidx.test.runner.AndroidJUnit4;
 
+import com.android.launcher3.celllayout.FavoriteItemsTransaction;
 import com.android.launcher3.model.data.LauncherAppWidgetInfo;
 import com.android.launcher3.tapl.Widget;
 import com.android.launcher3.tapl.WidgetResizeFrame;
@@ -55,8 +56,7 @@
     @Test
     @PortraitLandscape
     public void testDragIcon() throws Throwable {
-        clearHomescreen();
-        mDevice.pressHome();
+        new FavoriteItemsTransaction(mTargetContext).commitAndLoadHome(mLauncher);
 
         waitForLauncherCondition("Workspace didn't finish loading", l -> !l.isWorkspaceLoading());
 
@@ -93,8 +93,8 @@
     @Test
     @PortraitLandscape
     public void testDragCustomShortcut() throws Throwable {
-        clearHomescreen();
-        mDevice.pressHome();
+        new FavoriteItemsTransaction(mTargetContext).commitAndLoadHome(mLauncher);
+
         mLauncher.getWorkspace().openAllWidgets()
                 .getWidget("com.android.launcher3.testcomponent.CustomShortcutConfigActivity")
                 .dragToWorkspace(false, true);
diff --git a/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java
index 99d2201..7db3161 100644
--- a/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java
@@ -15,9 +15,13 @@
  */
 package com.android.launcher3.ui.widget;
 
-import static androidx.test.InstrumentationRegistry.getTargetContext;
-
-import static com.android.launcher3.util.TestUtil.getOnUiThread;
+import static com.android.launcher3.LauncherSettings.Favorites.TABLE_NAME;
+import static com.android.launcher3.WorkspaceLayoutManager.FIRST_SCREEN_ID;
+import static com.android.launcher3.model.data.LauncherAppWidgetInfo.FLAG_ID_NOT_VALID;
+import static com.android.launcher3.model.data.LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY;
+import static com.android.launcher3.model.data.LauncherAppWidgetInfo.FLAG_RESTORE_STARTED;
+import static com.android.launcher3.provider.LauncherDbUtils.itemIdMatch;
+import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
 import static com.android.launcher3.util.WidgetUtils.createWidgetInfo;
 
 import static org.junit.Assert.assertEquals;
@@ -27,7 +31,6 @@
 
 import android.appwidget.AppWidgetManager;
 import android.content.ComponentName;
-import android.content.ContentResolver;
 import android.content.pm.PackageInstaller;
 import android.content.pm.PackageInstaller.SessionParams;
 import android.content.pm.PackageManager;
@@ -35,11 +38,14 @@
 import android.os.Bundle;
 import android.widget.RemoteViews;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.LargeTest;
-import androidx.test.runner.AndroidJUnit4;
 
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherModel;
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.R;
+import com.android.launcher3.celllayout.FavoriteItemsTransaction;
 import com.android.launcher3.model.data.LauncherAppWidgetInfo;
 import com.android.launcher3.pm.InstallSessionHelper;
 import com.android.launcher3.tapl.Widget;
@@ -58,6 +64,7 @@
 
 import java.util.HashSet;
 import java.util.Set;
+import java.util.function.Consumer;
 
 /**
  * Tests for bind widget flow.
@@ -71,24 +78,18 @@
     @Rule
     public ShellCommandRule mGrantWidgetRule = ShellCommandRule.grantWidgetBind();
 
-    private ContentResolver mResolver;
-
     // Objects created during test, which should be cleaned up in the end.
     private Cursor mCursor;
     // App install session id.
     private int mSessionId = -1;
 
+    private LauncherModel mModel;
+
     @Override
     @Before
     public void setUp() throws Exception {
         super.setUp();
-
-        mResolver = mTargetContext.getContentResolver();
-
-        // Clear all existing data
-        LauncherSettings.Settings.call(mResolver, LauncherSettings.Settings.METHOD_CREATE_EMPTY_DB);
-        LauncherSettings.Settings.call(mResolver,
-                LauncherSettings.Settings.METHOD_CLEAR_EMPTY_DB_FLAG);
+        mModel = LauncherAppState.getInstance(mTargetContext).getModel();
     }
 
     @After
@@ -104,34 +105,24 @@
 
     @Test
     public void testBindNormalWidget_withConfig() {
-        LauncherAppWidgetProviderInfo info = TestViewHelpers.findWidgetProvider(true);
-        LauncherAppWidgetInfo item = createWidgetInfo(info, getTargetContext(), true);
-
-        addItemToScreen(item);
+        LauncherAppWidgetProviderInfo info = addWidgetToScreen(true, true, i -> { });
         verifyWidgetPresent(info);
     }
 
     @Test
     public void testBindNormalWidget_withoutConfig() {
-        LauncherAppWidgetProviderInfo info = TestViewHelpers.findWidgetProvider(false);
-        LauncherAppWidgetInfo item = createWidgetInfo(info, getTargetContext(), true);
-
-        addItemToScreen(item);
+        LauncherAppWidgetProviderInfo info = addWidgetToScreen(false, true, i -> { });
         verifyWidgetPresent(info);
     }
 
     @Test
     public void testUnboundWidget_removed() {
-        LauncherAppWidgetProviderInfo info = TestViewHelpers.findWidgetProvider(false);
-        LauncherAppWidgetInfo item = createWidgetInfo(info, getTargetContext(), false);
-        item.appWidgetId = -33;
-
-        addItemToScreen(item);
+        LauncherAppWidgetProviderInfo info = addWidgetToScreen(false, false,
+                item -> item.appWidgetId = -33);
 
         final Workspace workspace = mLauncher.getWorkspace();
         // Item deleted from db
-        mCursor = mResolver.query(LauncherSettings.Favorites.getContentUri(item.id),
-                null, null, null, null, null);
+        mCursor = queryItem();
         assertEquals(0, mCursor.getCount());
 
         // The view does not exist
@@ -141,36 +132,26 @@
     @Test
     public void testPendingWidget_autoRestored() {
         // A non-restored widget with no config screen gets restored automatically.
-        LauncherAppWidgetProviderInfo info = TestViewHelpers.findWidgetProvider(false);
-
         // Do not bind the widget
-        LauncherAppWidgetInfo item = createWidgetInfo(info, getTargetContext(), false);
-        item.restoreStatus = LauncherAppWidgetInfo.FLAG_ID_NOT_VALID;
-
-        addItemToScreen(item);
+        LauncherAppWidgetProviderInfo info = addWidgetToScreen(false, false,
+                item -> item.restoreStatus = FLAG_ID_NOT_VALID);
         verifyWidgetPresent(info);
     }
 
     @Test
     public void testPendingWidget_withConfigScreen() {
         // A non-restored widget with config screen get bound and shows a 'Click to setup' UI.
-        LauncherAppWidgetProviderInfo info = TestViewHelpers.findWidgetProvider(true);
-
         // Do not bind the widget
-        LauncherAppWidgetInfo item = createWidgetInfo(info, getTargetContext(), false);
-        item.restoreStatus = LauncherAppWidgetInfo.FLAG_ID_NOT_VALID;
-
-        addItemToScreen(item);
+        LauncherAppWidgetProviderInfo info = addWidgetToScreen(true, false,
+                item -> item.restoreStatus = FLAG_ID_NOT_VALID);
         verifyPendingWidgetPresent();
 
-        // Item deleted from db
-        mCursor = mResolver.query(LauncherSettings.Favorites.getContentUri(item.id),
-                null, null, null, null, null);
+        mCursor = queryItem();
         mCursor.moveToNext();
 
         // Widget has a valid Id now.
         assertEquals(0, mCursor.getInt(mCursor.getColumnIndex(LauncherSettings.Favorites.RESTORED))
-                & LauncherAppWidgetInfo.FLAG_ID_NOT_VALID);
+                & FLAG_ID_NOT_VALID);
         assertNotNull(AppWidgetManager.getInstance(mTargetContext)
                 .getAppWidgetInfo(mCursor.getInt(mCursor.getColumnIndex(
                         LauncherSettings.Favorites.APPWIDGET_ID))));
@@ -186,7 +167,6 @@
         appWidgetManager.updateAppWidgetOptions(appWidgetId, b);
         appWidgetManager.updateAppWidget(appWidgetId, remoteViews);
 
-
         // verify changes are reflected
         waitForLauncherCondition("App widget options did not update",
                 l -> appWidgetManager.getAppWidgetOptions(appWidgetId).getBoolean(
@@ -198,17 +178,12 @@
 
     @Test
     public void testPendingWidget_notRestored_removed() {
-        LauncherAppWidgetInfo item = getInvalidWidgetInfo();
-        item.restoreStatus = LauncherAppWidgetInfo.FLAG_ID_NOT_VALID
-                | LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY;
-
-        addItemToScreen(item);
+        addPendingItemToScreen(getInvalidWidgetInfo(), FLAG_ID_NOT_VALID | FLAG_PROVIDER_NOT_READY);
 
         assertTrue("Pending widget exists",
                 mLauncher.getWorkspace().tryGetPendingWidget(0) == null);
         // Item deleted from db
-        mCursor = mResolver.query(LauncherSettings.Favorites.getContentUri(item.id),
-                null, null, null, null, null);
+        mCursor = queryItem();
         assertEquals(0, mCursor.getCount());
     }
 
@@ -216,32 +191,25 @@
     public void testPendingWidget_notRestored_brokenInstall() {
         // A widget which is was being installed once, even if its not being
         // installed at the moment is not removed.
-        LauncherAppWidgetInfo item = getInvalidWidgetInfo();
-        item.restoreStatus = LauncherAppWidgetInfo.FLAG_ID_NOT_VALID
-                | LauncherAppWidgetInfo.FLAG_RESTORE_STARTED
-                | LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY;
-
-        addItemToScreen(item);
+        addPendingItemToScreen(getInvalidWidgetInfo(),
+                FLAG_ID_NOT_VALID | FLAG_RESTORE_STARTED | FLAG_PROVIDER_NOT_READY);
         verifyPendingWidgetPresent();
 
         // Verify item still exists in db
-        mCursor = mResolver.query(LauncherSettings.Favorites.getContentUri(item.id),
-                null, null, null, null, null);
+        mCursor = queryItem();
         assertEquals(1, mCursor.getCount());
 
         // Widget still has an invalid id.
         mCursor.moveToNext();
-        assertEquals(LauncherAppWidgetInfo.FLAG_ID_NOT_VALID,
+        assertEquals(FLAG_ID_NOT_VALID,
                 mCursor.getInt(mCursor.getColumnIndex(LauncherSettings.Favorites.RESTORED))
-                        & LauncherAppWidgetInfo.FLAG_ID_NOT_VALID);
+                        & FLAG_ID_NOT_VALID);
     }
 
     @Test
     public void testPendingWidget_notRestored_activeInstall() throws Exception {
         // A widget which is being installed is not removed
         LauncherAppWidgetInfo item = getInvalidWidgetInfo();
-        item.restoreStatus = LauncherAppWidgetInfo.FLAG_ID_NOT_VALID
-                | LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY;
 
         // Create an active installer session
         SessionParams params = new SessionParams(SessionParams.MODE_FULL_INSTALL);
@@ -249,19 +217,18 @@
         PackageInstaller installer = mTargetContext.getPackageManager().getPackageInstaller();
         mSessionId = installer.createSession(params);
 
-        addItemToScreen(item);
+        addPendingItemToScreen(item, FLAG_ID_NOT_VALID | FLAG_PROVIDER_NOT_READY);
         verifyPendingWidgetPresent();
 
         // Verify item still exists in db
-        mCursor = mResolver.query(LauncherSettings.Favorites.getContentUri(item.id),
-                null, null, null, null, null);
+        mCursor = queryItem();
         assertEquals(1, mCursor.getCount());
 
         // Widget still has an invalid id.
         mCursor.moveToNext();
-        assertEquals(LauncherAppWidgetInfo.FLAG_ID_NOT_VALID,
+        assertEquals(FLAG_ID_NOT_VALID,
                 mCursor.getInt(mCursor.getColumnIndex(LauncherSettings.Favorites.RESTORED))
-                        & LauncherAppWidgetInfo.FLAG_ID_NOT_VALID);
+                        & FLAG_ID_NOT_VALID);
     }
 
     private void verifyWidgetPresent(LauncherAppWidgetProviderInfo info) {
@@ -276,6 +243,28 @@
                 widget != null);
     }
 
+    private void addPendingItemToScreen(LauncherAppWidgetInfo item, int restoreStatus) {
+        item.restoreStatus = restoreStatus;
+        item.screenId = FIRST_SCREEN_ID;
+        new FavoriteItemsTransaction(mTargetContext)
+                .addItem(() -> item)
+                .commitAndLoadHome(mLauncher);
+    }
+
+    private LauncherAppWidgetProviderInfo addWidgetToScreen(boolean hasConfigureScreen,
+            boolean bindWidget, Consumer<LauncherAppWidgetInfo> itemOverride) {
+        LauncherAppWidgetProviderInfo info = TestViewHelpers.findWidgetProvider(hasConfigureScreen);
+        new FavoriteItemsTransaction(mTargetContext)
+                .addItem(() -> {
+                    LauncherAppWidgetInfo item = createWidgetInfo(info, mTargetContext, bindWidget);
+                    item.screenId = FIRST_SCREEN_ID;
+                    itemOverride.accept(item);
+                    return item;
+                })
+                .commitAndLoadHome(mLauncher);
+        return info;
+    }
+
     /**
      * Returns a LauncherAppWidgetInfo with package name which is not present on the device
      */
@@ -313,4 +302,14 @@
         item.container = LauncherSettings.Favorites.CONTAINER_DESKTOP;
         return item;
     }
+
+    private Cursor queryItem() {
+        try {
+            return MODEL_EXECUTOR.submit(() ->
+                mModel.getModelDbController().query(
+                                TABLE_NAME, null, itemIdMatch(0), null, null)).get();
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
 }
diff --git a/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java b/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java
index bf9eb5a..384fa46 100644
--- a/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java
@@ -33,6 +33,7 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.launcher3.LauncherSettings.Favorites;
+import com.android.launcher3.celllayout.FavoriteItemsTransaction;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.LauncherAppWidgetInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
@@ -134,8 +135,7 @@
 
     private void runTest(String activityMethod, boolean isWidget, ItemOperator itemMatcher,
             Intent... commandIntents) throws Throwable {
-        clearHomescreen();
-        mDevice.pressHome();
+        new FavoriteItemsTransaction(mTargetContext).commitAndLoadHome(mLauncher);
 
         // Open Pin item activity
         BlockingBroadcastReceiver openMonitor = new BlockingBroadcastReceiver(
diff --git a/tests/src/com/android/launcher3/util/LauncherModelHelper.java b/tests/src/com/android/launcher3/util/LauncherModelHelper.java
index 9dc04a1..4580082 100644
--- a/tests/src/com/android/launcher3/util/LauncherModelHelper.java
+++ b/tests/src/com/android/launcher3/util/LauncherModelHelper.java
@@ -53,7 +53,6 @@
 import com.android.launcher3.LauncherModel;
 import com.android.launcher3.LauncherModel.ModelUpdateTask;
 import com.android.launcher3.LauncherPrefs;
-import com.android.launcher3.LauncherProvider;
 import com.android.launcher3.model.AllAppsList;
 import com.android.launcher3.model.BgDataModel;
 import com.android.launcher3.model.BgDataModel.Callbacks;
@@ -241,12 +240,6 @@
 
             mPm = spy(getBaseContext().getPackageManager());
             mDbDir = new File(getCacheDir(), UUID.randomUUID().toString());
-            setupProvider(LauncherProvider.AUTHORITY, new LauncherProvider() {
-                @Override
-                public boolean onCreate() {
-                    return true;
-                }
-            });
         }
 
         @Override
diff --git a/tests/src/com/android/launcher3/util/WidgetUtils.java b/tests/src/com/android/launcher3/util/WidgetUtils.java
index b0df055..027a31a 100644
--- a/tests/src/com/android/launcher3/util/WidgetUtils.java
+++ b/tests/src/com/android/launcher3/util/WidgetUtils.java
@@ -18,18 +18,14 @@
 import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
 
-import static com.android.launcher3.WorkspaceLayoutManager.FIRST_SCREEN_ID;
-
 import android.appwidget.AppWidgetManager;
 import android.appwidget.AppWidgetProviderInfo;
 import android.content.ComponentName;
-import android.content.ContentResolver;
 import android.content.Context;
 import android.os.Bundle;
 import android.os.Process;
 
 import com.android.launcher3.LauncherSettings;
-import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.LauncherAppWidgetInfo;
 import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
 import com.android.launcher3.widget.LauncherWidgetHolder;
@@ -88,33 +84,6 @@
     }
 
     /**
-     * Adds {@param item} on the homescreen on the 0th screen
-     */
-    public static void addItemToScreen(ItemInfo item, Context targetContext) {
-        ContentResolver resolver = targetContext.getContentResolver();
-        int screenId = FIRST_SCREEN_ID;
-        // Update the screen id counter for the provider.
-        LauncherSettings.Settings.call(resolver,
-                LauncherSettings.Settings.METHOD_NEW_SCREEN_ID);
-
-        if (screenId > FIRST_SCREEN_ID) {
-            screenId = FIRST_SCREEN_ID;
-        }
-
-        // Insert the item
-        ContentWriter writer = new ContentWriter(targetContext);
-        item.id = LauncherSettings.Settings.call(
-                resolver, LauncherSettings.Settings.METHOD_NEW_ITEM_ID)
-                .getInt(LauncherSettings.Settings.EXTRA_VALUE);
-        item.screenId = screenId;
-        item.onAddToDatabase(writer);
-        writer.put(LauncherSettings.Favorites._ID, item.id);
-        resolver.insert(LauncherSettings.Favorites.CONTENT_URI,
-                writer.getValues(targetContext));
-    }
-
-
-    /**
      * Creates a {@link AppWidgetProviderInfo} for the provided component name
      */
     public static AppWidgetProviderInfo createAppWidgetProviderInfo(ComponentName cn) {