diff --git a/app/src/main/java/fr/free/nrw/commons/CommonsApplication.java b/app/src/main/java/fr/free/nrw/commons/CommonsApplication.java index b7e1a6039..5faf0c1d0 100644 --- a/app/src/main/java/fr/free/nrw/commons/CommonsApplication.java +++ b/app/src/main/java/fr/free/nrw/commons/CommonsApplication.java @@ -21,8 +21,8 @@ import javax.inject.Named; import dagger.android.AndroidInjector; import dagger.android.DaggerApplication; import fr.free.nrw.commons.auth.SessionManager; +import fr.free.nrw.commons.category.CategoryDao; import fr.free.nrw.commons.contributions.ContributionDao; -import fr.free.nrw.commons.data.CategoryDao; import fr.free.nrw.commons.data.DBOpenHelper; import fr.free.nrw.commons.di.CommonsApplicationComponent; import fr.free.nrw.commons.di.CommonsApplicationModule; diff --git a/app/src/main/java/fr/free/nrw/commons/auth/AccountUtil.java b/app/src/main/java/fr/free/nrw/commons/auth/AccountUtil.java index 4114b19a9..a020c1fb7 100644 --- a/app/src/main/java/fr/free/nrw/commons/auth/AccountUtil.java +++ b/app/src/main/java/fr/free/nrw/commons/auth/AccountUtil.java @@ -8,13 +8,13 @@ import android.content.Context; import android.os.Bundle; import android.support.annotation.Nullable; -import fr.free.nrw.commons.contributions.ContributionsContentProvider; -import fr.free.nrw.commons.modifications.ModificationsContentProvider; import timber.log.Timber; import static android.accounts.AccountManager.ERROR_CODE_REMOTE_EXCEPTION; import static android.accounts.AccountManager.KEY_ACCOUNT_NAME; import static android.accounts.AccountManager.KEY_ACCOUNT_TYPE; +import static fr.free.nrw.commons.contributions.ContributionsContentProvider.CONTRIBUTION_AUTHORITY; +import static fr.free.nrw.commons.modifications.ModificationsContentProvider.MODIFICATIONS_AUTHORITY; public class AccountUtil { @@ -51,8 +51,8 @@ public class AccountUtil { } // FIXME: If the user turns it off, it shouldn't be auto turned back on - ContentResolver.setSyncAutomatically(account, ContributionsContentProvider.AUTHORITY, true); // Enable sync by default! - ContentResolver.setSyncAutomatically(account, ModificationsContentProvider.AUTHORITY, true); // Enable sync by default! + ContentResolver.setSyncAutomatically(account, CONTRIBUTION_AUTHORITY, true); // Enable sync by default! + ContentResolver.setSyncAutomatically(account, MODIFICATIONS_AUTHORITY, true); // Enable sync by default! } private AccountManager accountManager() { diff --git a/app/src/main/java/fr/free/nrw/commons/category/CategorizationFragment.java b/app/src/main/java/fr/free/nrw/commons/category/CategorizationFragment.java index 335f7364c..4787d0e4f 100644 --- a/app/src/main/java/fr/free/nrw/commons/category/CategorizationFragment.java +++ b/app/src/main/java/fr/free/nrw/commons/category/CategorizationFragment.java @@ -1,6 +1,5 @@ package fr.free.nrw.commons.category; -import android.content.ContentProviderClient; import android.content.SharedPreferences; import android.os.Bundle; import android.support.v7.app.AlertDialog; @@ -36,8 +35,6 @@ import butterknife.BindView; import butterknife.ButterKnife; import dagger.android.support.DaggerFragment; import fr.free.nrw.commons.R; -import fr.free.nrw.commons.data.Category; -import fr.free.nrw.commons.data.CategoryDao; import fr.free.nrw.commons.mwapi.MediaWikiApi; import fr.free.nrw.commons.upload.MwVolleyApi; import fr.free.nrw.commons.utils.StringSortingUtils; @@ -48,7 +45,6 @@ import timber.log.Timber; import static android.view.KeyEvent.ACTION_UP; import static android.view.KeyEvent.KEYCODE_BACK; -import static fr.free.nrw.commons.category.CategoryContentProvider.AUTHORITY; /** * Displays the category suggestion and selection screen. Category search is initiated here. @@ -70,12 +66,12 @@ public class CategorizationFragment extends DaggerFragment { @Inject MediaWikiApi mwApi; @Inject @Named("default_preferences") SharedPreferences prefs; + @Inject CategoryDao categoryDao; private RVRendererAdapter categoriesAdapter; private OnCategoriesSaveHandler onCategoriesSaveHandler; private HashMap> categoriesCache; private List selectedCategories = new ArrayList<>(); - private ContentProviderClient databaseClient; private final CategoriesAdapterFactory adapterFactory = new CategoriesAdapterFactory(item -> { if (item.isSelected()) { @@ -141,7 +137,6 @@ public class CategorizationFragment extends DaggerFragment { @Override public void onDestroy() { super.onDestroy(); - databaseClient.release(); } @Override @@ -179,7 +174,6 @@ public class CategorizationFragment extends DaggerFragment { setHasOptionsMenu(true); onCategoriesSaveHandler = (OnCategoriesSaveHandler) getActivity(); getActivity().setTitle(R.string.categories_activity_title); - databaseClient = getActivity().getContentResolver().acquireContentProviderClient(AUTHORITY); } private void updateCategoryList(String filter) { @@ -262,7 +256,7 @@ public class CategorizationFragment extends DaggerFragment { } private Observable recentCategories() { - return Observable.fromIterable(new CategoryDao(databaseClient).recentCategories(SEARCH_CATS_LIMIT)) + return Observable.fromIterable(categoryDao.recentCategories(SEARCH_CATS_LIMIT)) .map(s -> new CategoryItem(s, false)); } @@ -313,7 +307,6 @@ public class CategorizationFragment extends DaggerFragment { } private void updateCategoryCount(CategoryItem item) { - CategoryDao categoryDao = new CategoryDao(databaseClient); Category category = categoryDao.find(item.getName()); // Newly used category... diff --git a/app/src/main/java/fr/free/nrw/commons/data/Category.java b/app/src/main/java/fr/free/nrw/commons/category/Category.java similarity index 97% rename from app/src/main/java/fr/free/nrw/commons/data/Category.java rename to app/src/main/java/fr/free/nrw/commons/category/Category.java index 9a32f3a7a..f2d83d2e5 100644 --- a/app/src/main/java/fr/free/nrw/commons/data/Category.java +++ b/app/src/main/java/fr/free/nrw/commons/category/Category.java @@ -1,4 +1,4 @@ -package fr.free.nrw.commons.data; +package fr.free.nrw.commons.category; import android.net.Uri; diff --git a/app/src/main/java/fr/free/nrw/commons/category/CategoryContentProvider.java b/app/src/main/java/fr/free/nrw/commons/category/CategoryContentProvider.java index 3384a984b..dcc1bc6f2 100644 --- a/app/src/main/java/fr/free/nrw/commons/category/CategoryContentProvider.java +++ b/app/src/main/java/fr/free/nrw/commons/category/CategoryContentProvider.java @@ -17,9 +17,9 @@ import fr.free.nrw.commons.data.DBOpenHelper; import timber.log.Timber; import static android.content.UriMatcher.NO_MATCH; -import static fr.free.nrw.commons.data.CategoryDao.Table.ALL_FIELDS; -import static fr.free.nrw.commons.data.CategoryDao.Table.COLUMN_ID; -import static fr.free.nrw.commons.data.CategoryDao.Table.TABLE_NAME; +import static fr.free.nrw.commons.category.CategoryDao.Table.ALL_FIELDS; +import static fr.free.nrw.commons.category.CategoryDao.Table.COLUMN_ID; +import static fr.free.nrw.commons.category.CategoryDao.Table.TABLE_NAME; public class CategoryContentProvider extends ContentProvider { diff --git a/app/src/main/java/fr/free/nrw/commons/data/CategoryDao.java b/app/src/main/java/fr/free/nrw/commons/category/CategoryDao.java similarity index 83% rename from app/src/main/java/fr/free/nrw/commons/data/CategoryDao.java rename to app/src/main/java/fr/free/nrw/commons/category/CategoryDao.java index 8bae4a522..e63b04c26 100644 --- a/app/src/main/java/fr/free/nrw/commons/data/CategoryDao.java +++ b/app/src/main/java/fr/free/nrw/commons/category/CategoryDao.java @@ -1,4 +1,4 @@ -package fr.free.nrw.commons.data; +package fr.free.nrw.commons.category; import android.content.ContentProviderClient; import android.content.ContentValues; @@ -12,25 +12,31 @@ import java.util.ArrayList; import java.util.Date; import java.util.List; -import fr.free.nrw.commons.category.CategoryContentProvider; +import javax.inject.Inject; +import javax.inject.Named; +import javax.inject.Provider; public class CategoryDao { - private final ContentProviderClient client; + private final Provider clientProvider; - public CategoryDao(ContentProviderClient client) { - this.client = client; + @Inject + public CategoryDao(@Named("category") Provider clientProvider) { + this.clientProvider = clientProvider; } public void save(Category category) { + ContentProviderClient db = clientProvider.get(); try { if (category.getContentUri() == null) { - category.setContentUri(client.insert(CategoryContentProvider.BASE_URI, toContentValues(category))); + category.setContentUri(db.insert(CategoryContentProvider.BASE_URI, toContentValues(category))); } else { - client.update(category.getContentUri(), toContentValues(category), null, null); + db.update(category.getContentUri(), toContentValues(category), null, null); } } catch (RemoteException e) { throw new RuntimeException(e); + } finally { + db.release(); } } @@ -40,11 +46,12 @@ public class CategoryDao { * @param name Category's name * @return category from database, or null if not found */ - public @Nullable + @Nullable Category find(String name) { Cursor cursor = null; + ContentProviderClient db = clientProvider.get(); try { - cursor = client.query( + cursor = db.query( CategoryContentProvider.BASE_URI, Table.ALL_FIELDS, Table.COLUMN_NAME + "=?", @@ -60,6 +67,7 @@ public class CategoryDao { if (cursor != null) { cursor.close(); } + db.release(); } return null; } @@ -69,12 +77,13 @@ public class CategoryDao { * * @return a list containing recent categories */ - public @NonNull + @NonNull List recentCategories(int limit) { List items = new ArrayList<>(); Cursor cursor = null; + ContentProviderClient db = clientProvider.get(); try { - cursor = client.query( + cursor = db.query( CategoryContentProvider.BASE_URI, Table.ALL_FIELDS, null, @@ -91,6 +100,7 @@ public class CategoryDao { if (cursor != null) { cursor.close(); } + db.release(); } return items; } @@ -147,7 +157,7 @@ public class CategoryDao { onCreate(db); } - static void onUpdate(SQLiteDatabase db, int from, int to) { + public static void onUpdate(SQLiteDatabase db, int from, int to) { if (from == to) { return; } diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionDao.java b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionDao.java index ffaf3fc8d..9d3038e03 100644 --- a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionDao.java +++ b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionDao.java @@ -11,44 +11,81 @@ import android.text.TextUtils; import java.util.Date; +import javax.inject.Inject; +import javax.inject.Named; +import javax.inject.Provider; + import fr.free.nrw.commons.settings.Prefs; +import static fr.free.nrw.commons.contributions.ContributionDao.Table.ALL_FIELDS; import static fr.free.nrw.commons.contributions.ContributionsContentProvider.BASE_URI; import static fr.free.nrw.commons.contributions.ContributionsContentProvider.uriForId; public class ContributionDao { - private final ContentProviderClient client; + /* + This sorts in the following order: + Currently Uploading + Failed (Sorted in ascending order of time added - FIFO) + Queued to Upload (Sorted in ascending order of time added - FIFO) + Completed (Sorted in descending order of time added) - public ContributionDao(ContentProviderClient client) { - this.client = client; + This is why Contribution.STATE_COMPLETED is -1. + */ + static final String CONTRIBUTION_SORT = Table.COLUMN_STATE + " DESC, " + + Table.COLUMN_UPLOADED + " DESC , (" + + Table.COLUMN_TIMESTAMP + " * " + + Table.COLUMN_STATE + ")"; + + private final Provider clientProvider; + + @Inject + public ContributionDao(@Named("contribution") Provider clientProvider) { + this.clientProvider = clientProvider; + } + + Cursor loadAllContributions() { + ContentProviderClient db = clientProvider.get(); + try { + return db.query(BASE_URI, ALL_FIELDS, "", null, CONTRIBUTION_SORT); + } catch (RemoteException e) { + return null; + } finally { + db.release(); + } } public void save(Contribution contribution) { + ContentProviderClient db = clientProvider.get(); try { if (contribution.getContentUri() == null) { - contribution.setContentUri(client.insert(BASE_URI, toContentValues(contribution))); + contribution.setContentUri(db.insert(BASE_URI, toContentValues(contribution))); } else { - client.update(contribution.getContentUri(), toContentValues(contribution), null, null); + db.update(contribution.getContentUri(), toContentValues(contribution), null, null); } } catch (RemoteException e) { throw new RuntimeException(e); + } finally { + db.release(); } } public void delete(Contribution contribution) { + ContentProviderClient db = clientProvider.get(); try { if (contribution.getContentUri() == null) { // noooo throw new RuntimeException("tried to delete item with no content URI"); } else { - client.delete(contribution.getContentUri(), null, null); + db.delete(contribution.getContentUri(), null, null); } } catch (RemoteException e) { throw new RuntimeException(e); + } finally { + db.release(); } } - public static ContentValues toContentValues(Contribution contribution) { + ContentValues toContentValues(Contribution contribution) { ContentValues cv = new ContentValues(); cv.put(Table.COLUMN_FILENAME, contribution.getFilename()); if (contribution.getLocalUri() != null) { @@ -74,7 +111,7 @@ public class ContributionDao { return cv; } - public static Contribution fromCursor(Cursor cursor) { + public Contribution fromCursor(Cursor cursor) { // Hardcoding column positions! //Check that cursor has a value to avoid CursorIndexOutOfBoundsException if (cursor.getCount() > 0) { diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsActivity.java b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsActivity.java index f1fa1d328..5c1ecfaa0 100644 --- a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsActivity.java +++ b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsActivity.java @@ -43,7 +43,6 @@ import timber.log.Timber; import static android.content.ContentResolver.requestSync; import static fr.free.nrw.commons.contributions.Contribution.STATE_FAILED; import static fr.free.nrw.commons.contributions.ContributionDao.Table.ALL_FIELDS; -import static fr.free.nrw.commons.contributions.ContributionsContentProvider.AUTHORITY; import static fr.free.nrw.commons.contributions.ContributionsContentProvider.BASE_URI; import static fr.free.nrw.commons.settings.Prefs.UPLOADS_SHOWING; @@ -58,6 +57,7 @@ public class ContributionsActivity @Inject MediaWikiApi mediaWikiApi; @Inject SessionManager sessionManager; @Inject @Named("default_preferences") SharedPreferences prefs; + @Inject ContributionDao contributionDao; private Cursor allContributions; private ContributionsListFragment contributionsList; @@ -65,21 +65,6 @@ public class ContributionsActivity private UploadService uploadService; private boolean isUploadServiceConnected; private ArrayList observersWaitingForLoad = new ArrayList<>(); - private String CONTRIBUTION_SELECTION = ""; - - /* - This sorts in the following order: - Currently Uploading - Failed (Sorted in ascending order of time added - FIFO) - Queued to Upload (Sorted in ascending order of time added - FIFO) - Completed (Sorted in descending order of time added) - - This is why Contribution.STATE_COMPLETED is -1. - */ - private String CONTRIBUTION_SORT = ContributionDao.Table.COLUMN_STATE + " DESC, " - + ContributionDao.Table.COLUMN_UPLOADED + " DESC , (" - + ContributionDao.Table.COLUMN_TIMESTAMP + " * " - + ContributionDao.Table.COLUMN_STATE + ")"; private CompositeDisposable compositeDisposable = new CompositeDisposable(); @@ -121,14 +106,13 @@ public class ContributionsActivity @Override protected void onAuthCookieAcquired(String authCookie) { // Do a sync everytime we get here! - requestSync(sessionManager.getCurrentAccount(), ContributionsContentProvider.AUTHORITY, new Bundle()); + requestSync(sessionManager.getCurrentAccount(), ContributionsContentProvider.CONTRIBUTION_AUTHORITY, new Bundle()); Intent uploadServiceIntent = new Intent(this, UploadService.class); uploadServiceIntent.setAction(UploadService.ACTION_START_SERVICE); startService(uploadServiceIntent); bindService(uploadServiceIntent, uploadServiceConnection, Context.BIND_AUTO_CREATE); - allContributions = getContentResolver().query(BASE_URI, ALL_FIELDS, - CONTRIBUTION_SELECTION, null, CONTRIBUTION_SORT); + allContributions = contributionDao.loadAllContributions(); getSupportLoaderManager().initLoader(0, null, this); } @@ -186,7 +170,7 @@ public class ContributionsActivity public void retryUpload(int i) { allContributions.moveToPosition(i); - Contribution c = ContributionDao.fromCursor(allContributions); + Contribution c = contributionDao.fromCursor(allContributions); if (c.getState() == STATE_FAILED) { uploadService.queue(UploadService.ACTION_UPLOAD_FILE, c); Timber.d("Restarting for %s", c.toString()); @@ -197,10 +181,10 @@ public class ContributionsActivity public void deleteUpload(int i) { allContributions.moveToPosition(i); - Contribution c = ContributionDao.fromCursor(allContributions); + Contribution c = contributionDao.fromCursor(allContributions); if (c.getState() == STATE_FAILED) { Timber.d("Deleting failed contrib %s", c.toString()); - new ContributionDao(getContentResolver().acquireContentProviderClient(AUTHORITY)).delete(c); + contributionDao.delete(c); } else { Timber.d("Skipping deletion for non-failed contrib %s", c.toString()); } @@ -238,8 +222,8 @@ public class ContributionsActivity public Loader onCreateLoader(int i, Bundle bundle) { int uploads = prefs.getInt(UPLOADS_SHOWING, 100); return new CursorLoader(this, BASE_URI, - ALL_FIELDS, CONTRIBUTION_SELECTION, null, - CONTRIBUTION_SORT + "LIMIT " + uploads); + ALL_FIELDS, "", null, + ContributionDao.CONTRIBUTION_SORT + "LIMIT " + uploads); } @Override @@ -248,7 +232,7 @@ public class ContributionsActivity if (contributionsList.getAdapter() == null) { contributionsList.setAdapter(new ContributionsListAdapter(getApplicationContext(), - cursor, 0)); + cursor, 0, contributionDao)); } else { ((CursorAdapter) contributionsList.getAdapter()).swapCursor(cursor); } @@ -269,7 +253,7 @@ public class ContributionsActivity // not yet ready to return data return null; } else { - return ContributionDao.fromCursor((Cursor) contributionsList.getAdapter().getItem(i)); + return contributionDao.fromCursor((Cursor) contributionsList.getAdapter().getItem(i)); } } diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsContentProvider.java b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsContentProvider.java index 402f91aaa..4d82bdfb1 100644 --- a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsContentProvider.java +++ b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsContentProvider.java @@ -26,13 +26,13 @@ public class ContributionsContentProvider extends ContentProvider { private static final int CONTRIBUTIONS_ID = 2; private static final String BASE_PATH = "contributions"; private static final UriMatcher uriMatcher = new UriMatcher(NO_MATCH); - public static final String AUTHORITY = "fr.free.nrw.commons.contributions.contentprovider"; + public static final String CONTRIBUTION_AUTHORITY = "fr.free.nrw.commons.contributions.contentprovider"; - public static final Uri BASE_URI = Uri.parse("content://" + AUTHORITY + "/" + BASE_PATH); + public static final Uri BASE_URI = Uri.parse("content://" + CONTRIBUTION_AUTHORITY + "/" + BASE_PATH); static { - uriMatcher.addURI(AUTHORITY, BASE_PATH, CONTRIBUTIONS); - uriMatcher.addURI(AUTHORITY, BASE_PATH + "/#", CONTRIBUTIONS_ID); + uriMatcher.addURI(CONTRIBUTION_AUTHORITY, BASE_PATH, CONTRIBUTIONS); + uriMatcher.addURI(CONTRIBUTION_AUTHORITY, BASE_PATH + "/#", CONTRIBUTIONS_ID); } public static Uri uriForId(int id) { diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsListAdapter.java b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsListAdapter.java index 781b3c4c4..a31caf54f 100644 --- a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsListAdapter.java +++ b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsListAdapter.java @@ -11,8 +11,11 @@ import fr.free.nrw.commons.R; class ContributionsListAdapter extends CursorAdapter { - public ContributionsListAdapter(Context context, Cursor c, int flags) { + private final ContributionDao contributionDao; + + public ContributionsListAdapter(Context context, Cursor c, int flags, ContributionDao contributionDao) { super(context, c, flags); + this.contributionDao = contributionDao; } @Override @@ -26,7 +29,7 @@ class ContributionsListAdapter extends CursorAdapter { @Override public void bindView(View view, Context context, Cursor cursor) { final ContributionViewHolder views = (ContributionViewHolder)view.getTag(); - final Contribution contribution = ContributionDao.fromCursor(cursor); + final Contribution contribution = contributionDao.fromCursor(cursor); views.imageView.setMedia(contribution); views.titleView.setText(contribution.getDisplayTitle()); diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsSyncAdapter.java b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsSyncAdapter.java index 4be42b1e0..f8245032b 100644 --- a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsSyncAdapter.java +++ b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsSyncAdapter.java @@ -89,6 +89,7 @@ public class ContributionsSyncAdapter extends AbstractThreadedSyncAdapter { LogEventResult result; Boolean done = false; String queryContinue = null; + ContributionDao contributionDao = new ContributionDao(() -> contentProviderClient); while (!done) { try { @@ -121,7 +122,7 @@ public class ContributionsSyncAdapter extends AbstractThreadedSyncAdapter { "", -1, dateUpdated, dateUpdated, user, "", ""); contrib.setState(STATE_COMPLETED); - imageValues.add(ContributionDao.toContentValues(contrib)); + imageValues.add(contributionDao.toContentValues(contrib)); if (imageValues.size() % COMMIT_THRESHOLD == 0) { try { diff --git a/app/src/main/java/fr/free/nrw/commons/data/DBOpenHelper.java b/app/src/main/java/fr/free/nrw/commons/data/DBOpenHelper.java index c8d33e3da..35305c5ba 100644 --- a/app/src/main/java/fr/free/nrw/commons/data/DBOpenHelper.java +++ b/app/src/main/java/fr/free/nrw/commons/data/DBOpenHelper.java @@ -4,6 +4,7 @@ import android.content.Context; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; +import fr.free.nrw.commons.category.CategoryDao; import fr.free.nrw.commons.contributions.ContributionDao; import fr.free.nrw.commons.modifications.ModifierSequenceDao; diff --git a/app/src/main/java/fr/free/nrw/commons/di/CommonsApplicationModule.java b/app/src/main/java/fr/free/nrw/commons/di/CommonsApplicationModule.java index 99d3235e7..913eef57e 100644 --- a/app/src/main/java/fr/free/nrw/commons/di/CommonsApplicationModule.java +++ b/app/src/main/java/fr/free/nrw/commons/di/CommonsApplicationModule.java @@ -1,5 +1,6 @@ package fr.free.nrw.commons.di; +import android.content.ContentProviderClient; import android.content.SharedPreferences; import android.preference.PreferenceManager; import android.support.v4.util.LruCache; @@ -22,10 +23,14 @@ import fr.free.nrw.commons.nearby.NearbyPlaces; import fr.free.nrw.commons.upload.UploadController; import static android.content.Context.MODE_PRIVATE; +import static fr.free.nrw.commons.contributions.ContributionsContentProvider.CONTRIBUTION_AUTHORITY; +import static fr.free.nrw.commons.modifications.ModificationsContentProvider.MODIFICATIONS_AUTHORITY; @Module @SuppressWarnings({"WeakerAccess", "unused"}) public class CommonsApplicationModule { + public static final String CATEGORY_AUTHORITY = "fr.free.nrw.commons.categories.contentprovider"; + private CommonsApplication application; public CommonsApplicationModule(CommonsApplication application) { @@ -37,6 +42,24 @@ public class CommonsApplicationModule { return new AccountUtil(application); } + @Provides + @Named("category") + public ContentProviderClient provideCategoryContentProviderClient() { + return application.getContentResolver().acquireContentProviderClient(CATEGORY_AUTHORITY); + } + + @Provides + @Named("contribution") + public ContentProviderClient provideContributionContentProviderClient() { + return application.getContentResolver().acquireContentProviderClient(CONTRIBUTION_AUTHORITY); + } + + @Provides + @Named("modification") + public ContentProviderClient provideModificationContentProviderClient() { + return application.getContentResolver().acquireContentProviderClient(MODIFICATIONS_AUTHORITY); + } + @Provides @Named("application_preferences") public SharedPreferences providesApplicationSharedPreferences() { diff --git a/app/src/main/java/fr/free/nrw/commons/modifications/ModificationsContentProvider.java b/app/src/main/java/fr/free/nrw/commons/modifications/ModificationsContentProvider.java index e1877e79c..3f2c01930 100644 --- a/app/src/main/java/fr/free/nrw/commons/modifications/ModificationsContentProvider.java +++ b/app/src/main/java/fr/free/nrw/commons/modifications/ModificationsContentProvider.java @@ -23,15 +23,15 @@ public class ModificationsContentProvider extends ContentProvider { private static final int MODIFICATIONS = 1; private static final int MODIFICATIONS_ID = 2; - public static final String AUTHORITY = "fr.free.nrw.commons.modifications.contentprovider"; + public static final String MODIFICATIONS_AUTHORITY = "fr.free.nrw.commons.modifications.contentprovider"; public static final String BASE_PATH = "modifications"; - public static final Uri BASE_URI = Uri.parse("content://" + AUTHORITY + "/" + BASE_PATH); + public static final Uri BASE_URI = Uri.parse("content://" + MODIFICATIONS_AUTHORITY + "/" + BASE_PATH); private static final UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH); static { - uriMatcher.addURI(AUTHORITY, BASE_PATH, MODIFICATIONS); - uriMatcher.addURI(AUTHORITY, BASE_PATH + "/#", MODIFICATIONS_ID); + uriMatcher.addURI(MODIFICATIONS_AUTHORITY, BASE_PATH, MODIFICATIONS); + uriMatcher.addURI(MODIFICATIONS_AUTHORITY, BASE_PATH + "/#", MODIFICATIONS_ID); } public static Uri uriForId(int id) { diff --git a/app/src/main/java/fr/free/nrw/commons/modifications/ModificationsSyncAdapter.java b/app/src/main/java/fr/free/nrw/commons/modifications/ModificationsSyncAdapter.java index d000a2ed5..f81c273d2 100644 --- a/app/src/main/java/fr/free/nrw/commons/modifications/ModificationsSyncAdapter.java +++ b/app/src/main/java/fr/free/nrw/commons/modifications/ModificationsSyncAdapter.java @@ -26,6 +26,8 @@ import timber.log.Timber; public class ModificationsSyncAdapter extends AbstractThreadedSyncAdapter { @Inject MediaWikiApi mwApi; + @Inject ContributionDao contributionDao; + @Inject ModifierSequenceDao modifierSequenceDao; public ModificationsSyncAdapter(Context context, boolean autoInitialize) { super(context, autoInitialize); @@ -80,11 +82,10 @@ public class ModificationsSyncAdapter extends AbstractThreadedSyncAdapter { ContentProviderClient contributionsClient = null; try { - contributionsClient = getContext().getContentResolver().acquireContentProviderClient(ContributionsContentProvider.AUTHORITY); + contributionsClient = getContext().getContentResolver().acquireContentProviderClient(ContributionsContentProvider.CONTRIBUTION_AUTHORITY); while (!allModifications.isAfterLast()) { - ModifierSequence sequence = ModifierSequenceDao.fromCursor(allModifications); - ModifierSequenceDao dao = new ModifierSequenceDao(contributionsClient); + ModifierSequence sequence = modifierSequenceDao.fromCursor(allModifications); Contribution contrib; Cursor contributionCursor; @@ -94,7 +95,7 @@ public class ModificationsSyncAdapter extends AbstractThreadedSyncAdapter { throw new RuntimeException(e); } contributionCursor.moveToFirst(); - contrib = ContributionDao.fromCursor(contributionCursor); + contrib = contributionDao.fromCursor(contributionCursor); if (contrib.getState() == Contribution.STATE_COMPLETED) { String pageContent; @@ -122,7 +123,7 @@ public class ModificationsSyncAdapter extends AbstractThreadedSyncAdapter { // FIXME: Log this somewhere else Timber.d("Non success result! %s", editResult); } else { - dao.delete(sequence); + modifierSequenceDao.delete(sequence); } } allModifications.moveToNext(); diff --git a/app/src/main/java/fr/free/nrw/commons/modifications/ModifierSequenceDao.java b/app/src/main/java/fr/free/nrw/commons/modifications/ModifierSequenceDao.java index c98081c72..e6b741d7a 100644 --- a/app/src/main/java/fr/free/nrw/commons/modifications/ModifierSequenceDao.java +++ b/app/src/main/java/fr/free/nrw/commons/modifications/ModifierSequenceDao.java @@ -11,20 +11,51 @@ import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; +import javax.inject.Inject; +import javax.inject.Named; +import javax.inject.Provider; + public class ModifierSequenceDao { - private final ContentProviderClient client; + private final Provider clientProvider; - public ModifierSequenceDao(ContentProviderClient client) { - this.client = client; + @Inject + public ModifierSequenceDao(@Named("modification") Provider clientProvider) { + this.clientProvider = clientProvider; } - public static ModifierSequence fromCursor(Cursor cursor) { + public void save(ModifierSequence sequence) { + ContentProviderClient db = clientProvider.get(); + try { + if (sequence.getContentUri() == null) { + sequence.setContentUri(db.insert(ModificationsContentProvider.BASE_URI, toContentValues(sequence))); + } else { + db.update(sequence.getContentUri(), toContentValues(sequence), null, null); + } + } catch (RemoteException e) { + throw new RuntimeException(e); + } finally { + db.release(); + } + } + + public void delete(ModifierSequence sequence) { + ContentProviderClient db = clientProvider.get(); + try { + db.delete(sequence.getContentUri(), null, null); + } catch (RemoteException e) { + throw new RuntimeException(e); + } finally { + db.release(); + } + } + + ModifierSequence fromCursor(Cursor cursor) { // Hardcoding column positions! ModifierSequence ms = null; try { ms = new ModifierSequence(Uri.parse(cursor.getString(1)), - new JSONObject(cursor.getString(2))); + new JSONObject(cursor.getString(2))); } catch (JSONException e) { throw new RuntimeException(e); } @@ -33,26 +64,6 @@ public class ModifierSequenceDao { return ms; } - public void save(ModifierSequence sequence) { - try { - if (sequence.getContentUri() == null) { - sequence.setContentUri(client.insert(ModificationsContentProvider.BASE_URI, toContentValues(sequence))); - } else { - client.update(sequence.getContentUri(), toContentValues(sequence), null, null); - } - } catch (RemoteException e) { - throw new RuntimeException(e); - } - } - - public void delete(ModifierSequence sequence) { - try { - client.delete(sequence.getContentUri(), null, null); - } catch (RemoteException e) { - throw new RuntimeException(e); - } - } - private JSONObject toJSON(ModifierSequence sequence) { JSONObject data = new JSONObject(); try { diff --git a/app/src/main/java/fr/free/nrw/commons/upload/MultipleShareActivity.java b/app/src/main/java/fr/free/nrw/commons/upload/MultipleShareActivity.java index 15acdcd4a..a41938cf7 100644 --- a/app/src/main/java/fr/free/nrw/commons/upload/MultipleShareActivity.java +++ b/app/src/main/java/fr/free/nrw/commons/upload/MultipleShareActivity.java @@ -2,7 +2,6 @@ package fr.free.nrw.commons.upload; import android.Manifest; import android.app.ProgressDialog; -import android.content.ContentProviderClient; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; @@ -55,6 +54,7 @@ public class MultipleShareActivity extends AuthenticatedActivity @Inject MediaWikiApi mwApi; @Inject SessionManager sessionManager; @Inject UploadController uploadController; + @Inject ModifierSequenceDao modifierSequenceDao; @Inject @Named("default_preferences") SharedPreferences prefs; private ArrayList photosList = null; @@ -166,19 +166,18 @@ public class MultipleShareActivity extends AuthenticatedActivity @Override public void onCategoriesSave(List categories) { if (categories.size() > 0) { - ModifierSequenceDao dao = new ModifierSequenceDao(getContentResolver().acquireContentProviderClient(ModificationsContentProvider.AUTHORITY)); for (Contribution contribution : photosList) { ModifierSequence categoriesSequence = new ModifierSequence(contribution.getContentUri()); categoriesSequence.queueModifier(new CategoryModifier(categories.toArray(new String[]{}))); categoriesSequence.queueModifier(new TemplateRemoveModifier("Uncategorized")); - dao.save(categoriesSequence); + modifierSequenceDao.save(categoriesSequence); } } // FIXME: Make sure that the content provider is up // This is the wrong place for it, but bleh - better than not having it turned on by default for people who don't go throughl ogin - ContentResolver.setSyncAutomatically(sessionManager.getCurrentAccount(), ModificationsContentProvider.AUTHORITY, true); // Enable sync by default! + ContentResolver.setSyncAutomatically(sessionManager.getCurrentAccount(), ModificationsContentProvider.MODIFICATIONS_AUTHORITY, true); // Enable sync by default! finish(); } diff --git a/app/src/main/java/fr/free/nrw/commons/upload/ShareActivity.java b/app/src/main/java/fr/free/nrw/commons/upload/ShareActivity.java index 0992cbb31..2f0a4977e 100644 --- a/app/src/main/java/fr/free/nrw/commons/upload/ShareActivity.java +++ b/app/src/main/java/fr/free/nrw/commons/upload/ShareActivity.java @@ -76,6 +76,7 @@ public class ShareActivity @Inject CacheController cacheController; @Inject SessionManager sessionManager; @Inject UploadController uploadController; + @Inject ModifierSequenceDao modifierSequenceDao; @Inject @Named("default_preferences") SharedPreferences prefs; private String source; @@ -166,13 +167,12 @@ public class ShareActivity categoriesSequence.queueModifier(new CategoryModifier(categories.toArray(new String[]{}))); categoriesSequence.queueModifier(new TemplateRemoveModifier("Uncategorized")); - ModifierSequenceDao dao = new ModifierSequenceDao(getContentResolver().acquireContentProviderClient(ModificationsContentProvider.AUTHORITY)); - dao.save(categoriesSequence); + modifierSequenceDao.save(categoriesSequence); } // FIXME: Make sure that the content provider is up // This is the wrong place for it, but bleh - better than not having it turned on by default for people who don't go throughl ogin - ContentResolver.setSyncAutomatically(sessionManager.getCurrentAccount(), ModificationsContentProvider.AUTHORITY, true); // Enable sync by default! + ContentResolver.setSyncAutomatically(sessionManager.getCurrentAccount(), ModificationsContentProvider.MODIFICATIONS_AUTHORITY, true); // Enable sync by default! finish(); } diff --git a/app/src/main/java/fr/free/nrw/commons/upload/UploadService.java b/app/src/main/java/fr/free/nrw/commons/upload/UploadService.java index 023252b84..1c8f9ac58 100644 --- a/app/src/main/java/fr/free/nrw/commons/upload/UploadService.java +++ b/app/src/main/java/fr/free/nrw/commons/upload/UploadService.java @@ -4,7 +4,6 @@ import android.annotation.SuppressLint; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; -import android.content.ContentProviderClient; import android.content.ContentResolver; import android.content.ContentValues; import android.content.Intent; @@ -52,9 +51,9 @@ public class UploadService extends HandlerService { @Inject MediaWikiApi mwApi; @Inject SessionManager sessionManager; @Inject @Named("default_preferences") SharedPreferences prefs; + @Inject ContributionDao contributionDao; private NotificationManager notificationManager; - private ContentProviderClient contributionsProviderClient; private NotificationCompat.Builder curProgressNotification; private int toUpload; @@ -67,7 +66,6 @@ public class UploadService extends HandlerService { public static final int NOTIFICATION_UPLOAD_IN_PROGRESS = 1; public static final int NOTIFICATION_UPLOAD_COMPLETE = 2; public static final int NOTIFICATION_UPLOAD_FAILED = 3; - private ContributionDao dao; public UploadService() { super("UploadService"); @@ -107,7 +105,7 @@ public class UploadService extends HandlerService { startForeground(NOTIFICATION_UPLOAD_IN_PROGRESS, curProgressNotification.build()); contribution.setTransferred(transferred); - dao.save(contribution); + contributionDao.save(contribution); } } @@ -115,7 +113,6 @@ public class UploadService extends HandlerService { @Override public void onDestroy() { super.onDestroy(); - contributionsProviderClient.release(); Timber.d("UploadService.onDestroy; %s are yet to be uploaded", unfinishedUploads); } @@ -124,8 +121,6 @@ public class UploadService extends HandlerService { super.onCreate(); notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); - contributionsProviderClient = this.getContentResolver().acquireContentProviderClient(ContributionsContentProvider.AUTHORITY); - dao = new ContributionDao(contributionsProviderClient); } @Override @@ -147,7 +142,7 @@ public class UploadService extends HandlerService { contribution.setState(Contribution.STATE_QUEUED); contribution.setTransferred(0); - dao.save(contribution); + contributionDao.save(contribution); toUpload++; if (curProgressNotification != null && toUpload != 1) { curProgressNotification.setContentText(getResources().getQuantityString(R.plurals.uploads_pending_notification_indicator, toUpload, toUpload)); @@ -262,7 +257,7 @@ public class UploadService extends HandlerService { contribution.setImageUrl(uploadResult.getImageUrl()); contribution.setState(Contribution.STATE_COMPLETED); contribution.setDateUploaded(uploadResult.getDateUploaded()); - dao.save(contribution); + contributionDao.save(contribution); } } catch (IOException e) { Timber.d("I have a network fuckup"); @@ -274,7 +269,7 @@ public class UploadService extends HandlerService { toUpload--; if (toUpload == 0) { // Sync modifications right after all uplaods are processed - ContentResolver.requestSync(sessionManager.getCurrentAccount(), ModificationsContentProvider.AUTHORITY, new Bundle()); + ContentResolver.requestSync(sessionManager.getCurrentAccount(), ModificationsContentProvider.MODIFICATIONS_AUTHORITY, new Bundle()); stopForeground(true); } } @@ -293,7 +288,7 @@ public class UploadService extends HandlerService { notificationManager.notify(NOTIFICATION_UPLOAD_FAILED, failureNotification); contribution.setState(Contribution.STATE_FAILED); - dao.save(contribution); + contributionDao.save(contribution); } private String findUniqueFilename(String fileName) throws IOException { diff --git a/app/src/test/java/fr/free/nrw/commons/data/CategoryDaoTest.java b/app/src/test/java/fr/free/nrw/commons/category/CategoryDaoTest.java similarity index 96% rename from app/src/test/java/fr/free/nrw/commons/data/CategoryDaoTest.java rename to app/src/test/java/fr/free/nrw/commons/category/CategoryDaoTest.java index 62d4f4d2c..1cf0c338b 100644 --- a/app/src/test/java/fr/free/nrw/commons/data/CategoryDaoTest.java +++ b/app/src/test/java/fr/free/nrw/commons/category/CategoryDaoTest.java @@ -1,4 +1,4 @@ -package fr.free.nrw.commons.data; +package fr.free.nrw.commons.category; import android.content.ContentProviderClient; import android.content.ContentValues; @@ -27,8 +27,7 @@ import java.util.List; import fr.free.nrw.commons.BuildConfig; import fr.free.nrw.commons.TestCommonsApplication; -import fr.free.nrw.commons.category.CategoryContentProvider; -import fr.free.nrw.commons.data.CategoryDao.Table; +import fr.free.nrw.commons.category.CategoryDao.Table; import static fr.free.nrw.commons.category.CategoryContentProvider.BASE_URI; import static fr.free.nrw.commons.category.CategoryContentProvider.uriForId; @@ -50,20 +49,20 @@ import static org.mockito.Mockito.when; public class CategoryDaoTest { @Mock - ContentProviderClient client; + private ContentProviderClient client; @Mock - SQLiteDatabase database; + private SQLiteDatabase database; @Captor - ArgumentCaptor captor; + private ArgumentCaptor captor; @Captor - ArgumentCaptor queryCaptor; + private ArgumentCaptor queryCaptor; private CategoryDao testObject; @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); - testObject = new CategoryDao(client); + testObject = new CategoryDao(() -> client); } @Test diff --git a/app/src/test/java/fr/free/nrw/commons/contributions/ContributionDaoTest.java b/app/src/test/java/fr/free/nrw/commons/contributions/ContributionDaoTest.java index c7394003a..15e37e640 100644 --- a/app/src/test/java/fr/free/nrw/commons/contributions/ContributionDaoTest.java +++ b/app/src/test/java/fr/free/nrw/commons/contributions/ContributionDaoTest.java @@ -31,10 +31,13 @@ import static fr.free.nrw.commons.contributions.Contribution.SOURCE_CAMERA; import static fr.free.nrw.commons.contributions.Contribution.SOURCE_GALLERY; import static fr.free.nrw.commons.contributions.Contribution.STATE_COMPLETED; import static fr.free.nrw.commons.contributions.Contribution.STATE_QUEUED; -import static fr.free.nrw.commons.contributions.ContributionDao.*; +import static fr.free.nrw.commons.contributions.ContributionDao.Table; import static fr.free.nrw.commons.contributions.ContributionsContentProvider.BASE_URI; import static fr.free.nrw.commons.contributions.ContributionsContentProvider.uriForId; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; import static org.mockito.Matchers.any; import static org.mockito.Matchers.eq; import static org.mockito.Matchers.isA; @@ -49,22 +52,22 @@ public class ContributionDaoTest { private static final String LOCAL_URI = "http://example.com/"; @Mock - ContentProviderClient client; + private ContentProviderClient client; @Mock - SQLiteDatabase database; + private SQLiteDatabase database; @Captor - ArgumentCaptor captor; + private ArgumentCaptor captor; private Uri contentUri; private ContributionDao testObject; @Before - public void setUp() throws Exception { + public void setUp() { MockitoAnnotations.initMocks(this); contentUri = uriForId(111); - testObject = new ContributionDao(client); + testObject = new ContributionDao(() -> client); } @Test @@ -288,7 +291,7 @@ public class ContributionDaoTest { long uploaded = 456L; MatrixCursor mc = createCursor(created, uploaded, false, LOCAL_URI); - Contribution c = ContributionDao.fromCursor(mc); + Contribution c = testObject.fromCursor(mc); assertEquals(uriForId(111), c.getContentUri()); assertEquals("file", c.getFilename()); @@ -312,7 +315,7 @@ public class ContributionDaoTest { public void createFromCursor_nullableTimestamps() { MatrixCursor mc = createCursor(0L, 0L, false, LOCAL_URI); - Contribution c = ContributionDao.fromCursor(mc); + Contribution c = testObject.fromCursor(mc); assertNull(c.getTimestamp()); assertNull(c.getDateCreated()); @@ -323,7 +326,7 @@ public class ContributionDaoTest { public void createFromCursor_nullableLocalUri() { MatrixCursor mc = createCursor(0L, 0L, false, ""); - Contribution c = ContributionDao.fromCursor(mc); + Contribution c = testObject.fromCursor(mc); assertNull(c.getLocalUri()); assertNull(c.getDateCreated()); @@ -333,10 +336,10 @@ public class ContributionDaoTest { @Test public void createFromCursor_booleanEncoding() { MatrixCursor mcFalse = createCursor(0L, 0L, false, LOCAL_URI); - assertFalse(ContributionDao.fromCursor(mcFalse).getMultiple()); + assertFalse(testObject.fromCursor(mcFalse).getMultiple()); MatrixCursor mcHammer = createCursor(0L, 0L, true, LOCAL_URI); - assertTrue(ContributionDao.fromCursor(mcHammer).getMultiple()); + assertTrue(testObject.fromCursor(mcHammer).getMultiple()); } @NonNull diff --git a/app/src/test/java/fr/free/nrw/commons/modifications/ModifierSequenceDaoTest.java b/app/src/test/java/fr/free/nrw/commons/modifications/ModifierSequenceDaoTest.java index 888c758ff..ef290500d 100644 --- a/app/src/test/java/fr/free/nrw/commons/modifications/ModifierSequenceDaoTest.java +++ b/app/src/test/java/fr/free/nrw/commons/modifications/ModifierSequenceDaoTest.java @@ -52,14 +52,14 @@ public class ModifierSequenceDaoTest { @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); - testObject = new ModifierSequenceDao(client); + testObject = new ModifierSequenceDao(() -> client); } @Test public void createFromCursorWithEmptyModifiers() { MatrixCursor cursor = createCursor(""); - ModifierSequence seq = ModifierSequenceDao.fromCursor(cursor); + ModifierSequence seq = testObject.fromCursor(cursor); assertEquals(EXPECTED_MEDIA_URI, seq.getMediaUri().toString()); assertEquals(BASE_URI.buildUpon().appendPath("1").toString(), seq.getContentUri().toString()); @@ -70,7 +70,7 @@ public class ModifierSequenceDaoTest { public void createFromCursorWtihCategoryModifier() { MatrixCursor cursor = createCursor("{\"name\": \"CategoriesModifier\", \"data\": {}}"); - ModifierSequence seq = ModifierSequenceDao.fromCursor(cursor); + ModifierSequence seq = testObject.fromCursor(cursor); assertEquals(1, seq.getModifiers().size()); assertTrue(seq.getModifiers().get(0) instanceof CategoryModifier); @@ -80,7 +80,7 @@ public class ModifierSequenceDaoTest { public void createFromCursorWithRemoveModifier() { MatrixCursor cursor = createCursor("{\"name\": \"TemplateRemoverModifier\", \"data\": {}}"); - ModifierSequence seq = ModifierSequenceDao.fromCursor(cursor); + ModifierSequence seq = testObject.fromCursor(cursor); assertEquals(1, seq.getModifiers().size()); assertTrue(seq.getModifiers().get(0) instanceof TemplateRemoveModifier); @@ -89,7 +89,7 @@ public class ModifierSequenceDaoTest { @Test public void deleteSequence() throws Exception { when(client.delete(isA(Uri.class), isNull(String.class), isNull(String[].class))).thenReturn(1); - ModifierSequence seq = ModifierSequenceDao.fromCursor(createCursor("")); + ModifierSequence seq = testObject.fromCursor(createCursor("")); testObject.delete(seq); @@ -99,7 +99,7 @@ public class ModifierSequenceDaoTest { @Test(expected = RuntimeException.class) public void deleteTranslatesRemoteExceptions() throws Exception { when(client.delete(isA(Uri.class), isNull(String.class), isNull(String[].class))).thenThrow(new RemoteException("")); - ModifierSequence seq = ModifierSequenceDao.fromCursor(createCursor("")); + ModifierSequence seq = testObject.fromCursor(createCursor("")); testObject.delete(seq); } @@ -110,9 +110,9 @@ public class ModifierSequenceDaoTest { String expectedData = "{\"modifiers\":[" + modifierJson + "]}"; MatrixCursor cursor = createCursor(modifierJson); - testObject.save(ModifierSequenceDao.fromCursor(cursor)); + testObject.save(testObject.fromCursor(cursor)); - verify(client).update(eq(ModifierSequenceDao.fromCursor(cursor).getContentUri()), contentValuesCaptor.capture(), isNull(String.class), isNull(String[].class)); + verify(client).update(eq(testObject.fromCursor(cursor).getContentUri()), contentValuesCaptor.capture(), isNull(String.class), isNull(String[].class)); ContentValues cv = contentValuesCaptor.getValue(); assertEquals(2, cv.size()); assertEquals(EXPECTED_MEDIA_URI, cv.get(ModifierSequenceDao.Table.COLUMN_MEDIA_URI));