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/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) {