diff --git a/mobile/android/base/BrowserApp.java b/mobile/android/base/BrowserApp.java index 0749d358f489..b8f67a501065 100644 --- a/mobile/android/base/BrowserApp.java +++ b/mobile/android/base/BrowserApp.java @@ -1698,7 +1698,7 @@ public class BrowserApp extends GeckoApp Telemetry.addToHistogram("PLACES_BOOKMARKS_COUNT", db.getCount(cr, "bookmarks")); Telemetry.addToHistogram("FENNEC_FAVICONS_COUNT", db.getCount(cr, "favicons")); Telemetry.addToHistogram("FENNEC_THUMBNAILS_COUNT", db.getCount(cr, "thumbnails")); - Telemetry.addToHistogram("FENNEC_READING_LIST_COUNT", db.getCount(getContentResolver(), "readinglist")); + Telemetry.addToHistogram("FENNEC_READING_LIST_COUNT", db.getReadingListAccessor().getCount(cr)); Telemetry.addToHistogram("BROWSER_IS_USER_DEFAULT", (isDefaultBrowser(Intent.ACTION_VIEW) ? 1 : 0)); if (Versions.feature16Plus) { Telemetry.addToHistogram("BROWSER_IS_ASSIST_DEFAULT", (isDefaultBrowser(Intent.ACTION_ASSIST) ? 1 : 0)); diff --git a/mobile/android/base/ReadingListHelper.java b/mobile/android/base/ReadingListHelper.java index 85e05dfee5e8..4e3b96ec4b60 100644 --- a/mobile/android/base/ReadingListHelper.java +++ b/mobile/android/base/ReadingListHelper.java @@ -9,6 +9,7 @@ import org.json.JSONObject; import org.mozilla.gecko.db.BrowserContract.ReadingListItems; import org.mozilla.gecko.db.BrowserDB; import org.mozilla.gecko.db.DBUtils; +import org.mozilla.gecko.db.ReadingListAccessor; import org.mozilla.gecko.favicons.Favicons; import org.mozilla.gecko.util.EventCallback; import org.mozilla.gecko.util.NativeEventListener; @@ -30,18 +31,17 @@ public final class ReadingListHelper implements NativeEventListener { protected final Context context; private final BrowserDB db; - - private final Uri readingListUriWithProfile; + private final ReadingListAccessor readingListAccessor; private final ContentObserver contentObserver; public ReadingListHelper(Context context, GeckoProfile profile) { this.context = context; this.db = profile.getDB(); + this.readingListAccessor = db.getReadingListAccessor(); EventDispatcher.getInstance().registerGeckoThreadListener((NativeEventListener) this, "Reader:AddToList", "Reader:UpdateList", "Reader:FaviconRequest", "Reader:ListStatusRequest", "Reader:RemoveFromList"); - readingListUriWithProfile = DBUtils.appendProfile(profile.getName(), ReadingListItems.CONTENT_URI); contentObserver = new ContentObserver(null) { @Override @@ -50,7 +50,7 @@ public final class ReadingListHelper implements NativeEventListener { } }; - context.getContentResolver().registerContentObserver(readingListUriWithProfile, false, contentObserver); + this.readingListAccessor.registerContentObserver(context, contentObserver); } public void uninit() { @@ -104,11 +104,11 @@ public final class ReadingListHelper implements NativeEventListener { ThreadUtils.postToBackgroundThread(new Runnable() { @Override public void run() { - if (db.isReadingListItem(cr, url)) { + if (readingListAccessor.isReadingListItem(cr, url)) { showToast(R.string.reading_list_duplicate, Toast.LENGTH_SHORT); callback.sendError("URL already in reading list: " + url); } else { - db.addReadingListItem(cr, values); + readingListAccessor.addReadingListItem(cr, values); showToast(R.string.reading_list_added, Toast.LENGTH_SHORT); callback.sendSuccess(url); } @@ -126,7 +126,7 @@ public final class ReadingListHelper implements NativeEventListener { ThreadUtils.postToBackgroundThread(new Runnable() { @Override public void run() { - db.updateReadingListItem(cr, values); + readingListAccessor.updateReadingListItem(cr, values); } }); } @@ -192,7 +192,7 @@ public final class ReadingListHelper implements NativeEventListener { ThreadUtils.postToBackgroundThread(new Runnable() { @Override public void run() { - db.removeReadingListItemWithURL(context.getContentResolver(), url); + readingListAccessor.removeReadingListItemWithURL(context.getContentResolver(), url); GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Reader:Removed", url)); showToast(R.string.page_removed, Toast.LENGTH_SHORT); } @@ -207,7 +207,7 @@ public final class ReadingListHelper implements NativeEventListener { ThreadUtils.postToBackgroundThread(new Runnable() { @Override public void run() { - final int inReadingList = db.isReadingListItem(context.getContentResolver(), url) ? 1 : 0; + final int inReadingList = readingListAccessor.isReadingListItem(context.getContentResolver(), url) ? 1 : 0; final JSONObject json = new JSONObject(); try { @@ -239,7 +239,7 @@ public final class ReadingListHelper implements NativeEventListener { ThreadUtils.postToBackgroundThread(new Runnable() { @Override public void run() { - final Cursor c = db.getReadingListUnfetched(context.getContentResolver()); + final Cursor c = readingListAccessor.getReadingListUnfetched(context.getContentResolver()); try { while (c.moveToNext()) { JSONObject json = new JSONObject(); diff --git a/mobile/android/base/db/BrowserDB.java b/mobile/android/base/db/BrowserDB.java index 9d4d5ee89fc4..876ad0fcbba4 100644 --- a/mobile/android/base/db/BrowserDB.java +++ b/mobile/android/base/db/BrowserDB.java @@ -16,7 +16,6 @@ import org.mozilla.gecko.favicons.decoders.LoadFaviconResult; import android.content.ContentProviderOperation; import android.content.ContentResolver; -import android.content.ContentValues; import android.content.Context; import android.database.ContentObserver; import android.database.Cursor; @@ -43,6 +42,7 @@ public interface BrowserDB { public abstract Searches getSearches(); public abstract TabsAccessor getTabsAccessor(); public abstract URLMetadata getURLMetadata(); + public abstract ReadingListAccessor getReadingListAccessor(); /** * Add default bookmarks to the database. @@ -118,17 +118,6 @@ public interface BrowserDB { */ public abstract Cursor getBookmarksInFolder(ContentResolver cr, long folderId); - /** - * Can return null. - */ - public abstract Cursor getReadingList(ContentResolver cr); - public abstract Cursor getReadingListUnfetched(ContentResolver cr); - public abstract boolean isReadingListItem(ContentResolver cr, String uri); - public abstract void addReadingListItem(ContentResolver cr, ContentValues values); - public abstract void updateReadingListItem(ContentResolver cr, ContentValues values); - public abstract void removeReadingListItemWithURL(ContentResolver cr, String uri); - - /** * Get the favicon from the database, if any, associated with the given favicon URL. (That is, * the URL of the actual favicon image, not the URL of the page with which the favicon is associated.) diff --git a/mobile/android/base/db/LocalBrowserDB.java b/mobile/android/base/db/LocalBrowserDB.java index 0b6ff3addff7..e09b3732a2ea 100644 --- a/mobile/android/base/db/LocalBrowserDB.java +++ b/mobile/android/base/db/LocalBrowserDB.java @@ -98,11 +98,11 @@ public class LocalBrowserDB implements BrowserDB { private final Uri mUpdateHistoryUriWithProfile; private final Uri mFaviconsUriWithProfile; private final Uri mThumbnailsUriWithProfile; - private final Uri mReadingListUriWithProfile; private LocalSearches searches; private LocalTabsAccessor tabsAccessor; private LocalURLMetadata urlMetadata; + private LocalReadingListAccessor readingListAccessor; private static final String[] DEFAULT_BOOKMARK_COLUMNS = new String[] { Bookmarks._ID, @@ -123,7 +123,6 @@ public class LocalBrowserDB implements BrowserDB { mCombinedUriWithProfile = DBUtils.appendProfile(profile, Combined.CONTENT_URI); mFaviconsUriWithProfile = DBUtils.appendProfile(profile, Favicons.CONTENT_URI); mThumbnailsUriWithProfile = DBUtils.appendProfile(profile, Thumbnails.CONTENT_URI); - mReadingListUriWithProfile = DBUtils.appendProfile(profile, ReadingListItems.CONTENT_URI); mUpdateHistoryUriWithProfile = mHistoryUriWithProfile.buildUpon() @@ -134,6 +133,7 @@ public class LocalBrowserDB implements BrowserDB { searches = new LocalSearches(mProfile); tabsAccessor = new LocalTabsAccessor(mProfile); urlMetadata = new LocalURLMetadata(mProfile); + readingListAccessor = new LocalReadingListAccessor(mProfile); } @Override @@ -151,6 +151,11 @@ public class LocalBrowserDB implements BrowserDB { return urlMetadata; } + @Override + public ReadingListAccessor getReadingListAccessor() { + return readingListAccessor; + } + /** * Not thread safe. A helper to allocate new IDs for arbitrary strings. */ @@ -576,9 +581,6 @@ public class LocalBrowserDB implements BrowserDB { } else if ("favicons".equals(database)) { uri = mFaviconsUriWithProfile; columns = new String[] { Favicons._ID }; - } else if ("readinglist".equals(database)) { - uri = mReadingListUriWithProfile; - columns = new String[] { ReadingListItems._ID }; } if (uri != null) { @@ -819,26 +821,6 @@ public class LocalBrowserDB implements BrowserDB { } } - @Override - public boolean isReadingListItem(ContentResolver cr, String uri) { - final Cursor c = cr.query(mReadingListUriWithProfile, - new String[] { ReadingListItems._ID }, - ReadingListItems.URL + " = ? ", - new String[] { uri }, - null); - - if (c == null) { - Log.e(LOGTAG, "Null cursor in isReadingListItem"); - return false; - } - - try { - return c.getCount() > 0; - } finally { - c.close(); - } - } - @Override public String getUrlForKeyword(ContentResolver cr, String keyword) { final Cursor c = cr.query(mBookmarksUriWithProfile, @@ -968,70 +950,6 @@ public class LocalBrowserDB implements BrowserDB { cr.delete(contentUri, urlEquals, urlArgs); } - @Override - public Cursor getReadingList(ContentResolver cr) { - return cr.query(mReadingListUriWithProfile, - ReadingListItems.DEFAULT_PROJECTION, - null, - null, - null); - } - - @Override - public Cursor getReadingListUnfetched(ContentResolver cr) { - return cr.query(mReadingListUriWithProfile, - new String[] { ReadingListItems._ID, ReadingListItems.URL }, - ReadingListItems.CONTENT_STATUS + " = " + ReadingListItems.STATUS_UNFETCHED, - null, - null); - - } - - @Override - public void addReadingListItem(ContentResolver cr, ContentValues values) { - // Check that required fields are present. - for (String field: ReadingListItems.REQUIRED_FIELDS) { - if (!values.containsKey(field)) { - throw new IllegalArgumentException("Missing required field for reading list item: " + field); - } - } - - // Clear delete flag if necessary - values.put(ReadingListItems.IS_DELETED, 0); - - // Restore deleted record if possible - final Uri insertUri = mReadingListUriWithProfile - .buildUpon() - .appendQueryParameter(BrowserContract.PARAM_INSERT_IF_NEEDED, "true") - .build(); - - final int updated = cr.update(insertUri, - values, - ReadingListItems.URL + " = ? ", - new String[] { values.getAsString(ReadingListItems.URL) }); - - debug("Updated " + updated + " rows to new modified time."); - } - - @Override - public void updateReadingListItem(ContentResolver cr, ContentValues values) { - if (!values.containsKey(ReadingListItems._ID)) { - throw new IllegalArgumentException("Cannot update reading list item without an ID"); - } - - final int updated = cr.update(mReadingListUriWithProfile, - values, - ReadingListItems._ID + " = ? ", - new String[] { values.getAsString(ReadingListItems._ID) }); - - debug("Updated " + updated + " reading list rows."); - } - - @Override - public void removeReadingListItemWithURL(ContentResolver cr, String uri) { - cr.delete(mReadingListUriWithProfile, ReadingListItems.URL + " = ? ", new String[] { uri }); - } - @Override public void registerBookmarkObserver(ContentResolver cr, ContentObserver observer) { cr.registerContentObserver(mBookmarksUriWithProfile, false, observer); diff --git a/mobile/android/base/db/LocalReadingListAccessor.java b/mobile/android/base/db/LocalReadingListAccessor.java new file mode 100644 index 000000000000..39e4c5cd0a35 --- /dev/null +++ b/mobile/android/base/db/LocalReadingListAccessor.java @@ -0,0 +1,128 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.gecko.db; + +import android.content.ContentResolver; +import android.content.ContentValues; +import android.content.Context; +import android.database.ContentObserver; +import android.database.Cursor; +import android.net.Uri; +import android.util.Log; + +public class LocalReadingListAccessor implements ReadingListAccessor { + private static final String LOG_TAG = "GeckoReadingListAcc"; + + private final Uri mReadingListUriWithProfile; + + public LocalReadingListAccessor(final String profile) { + mReadingListUriWithProfile = DBUtils.appendProfile(profile, BrowserContract.ReadingListItems.CONTENT_URI); + } + + @Override + public int getCount(ContentResolver cr) { + final String[] columns = new String[]{BrowserContract.ReadingListItems._ID}; + final Cursor cursor = cr.query(mReadingListUriWithProfile, columns, null, null, null); + int count = 0; + + try { + count = cursor.getCount(); + } finally { + cursor.close(); + } + + Log.d(LOG_TAG, "Got count " + count + " for reading list."); + return count; + } + + @Override + public Cursor getReadingList(ContentResolver cr) { + return cr.query(mReadingListUriWithProfile, + BrowserContract.ReadingListItems.DEFAULT_PROJECTION, + null, + null, + null); + } + + @Override + public Cursor getReadingListUnfetched(ContentResolver cr) { + return cr.query(mReadingListUriWithProfile, + new String[] { BrowserContract.ReadingListItems._ID, BrowserContract.ReadingListItems.URL }, + BrowserContract.ReadingListItems.CONTENT_STATUS + " = " + BrowserContract.ReadingListItems.STATUS_UNFETCHED, + null, + null); + } + + @Override + public boolean isReadingListItem(ContentResolver cr, String uri) { + final Cursor c = cr.query(mReadingListUriWithProfile, + new String[] { BrowserContract.ReadingListItems._ID }, + BrowserContract.ReadingListItems.URL + " = ? ", + new String[] { uri }, + null); + + if (c == null) { + Log.e(LOG_TAG, "Null cursor in isReadingListItem"); + return false; + } + + try { + return c.getCount() > 0; + } finally { + c.close(); + } + } + + + @Override + public void addReadingListItem(ContentResolver cr, ContentValues values) { + // Check that required fields are present. + for (String field: BrowserContract.ReadingListItems.REQUIRED_FIELDS) { + if (!values.containsKey(field)) { + throw new IllegalArgumentException("Missing required field for reading list item: " + field); + } + } + + // Clear delete flag if necessary + values.put(BrowserContract.ReadingListItems.IS_DELETED, 0); + + // Restore deleted record if possible + final Uri insertUri = mReadingListUriWithProfile + .buildUpon() + .appendQueryParameter(BrowserContract.PARAM_INSERT_IF_NEEDED, "true") + .build(); + + final int updated = cr.update(insertUri, + values, + BrowserContract.ReadingListItems.URL + " = ? ", + new String[] { values.getAsString(BrowserContract.ReadingListItems.URL) }); + + Log.d(LOG_TAG, "Updated " + updated + " rows to new modified time."); + } + + @Override + public void updateReadingListItem(ContentResolver cr, ContentValues values) { + if (!values.containsKey(BrowserContract.ReadingListItems._ID)) { + throw new IllegalArgumentException("Cannot update reading list item without an ID"); + } + + final int updated = cr.update(mReadingListUriWithProfile, + values, + BrowserContract.ReadingListItems._ID + " = ? ", + new String[] { values.getAsString(BrowserContract.ReadingListItems._ID) }); + + Log.d(LOG_TAG, "Updated " + updated + " reading list rows."); + } + + @Override + public void removeReadingListItemWithURL(ContentResolver cr, String uri) { + cr.delete(mReadingListUriWithProfile, BrowserContract.ReadingListItems.URL + " = ? ", new String[]{uri}); + } + + @Override + public void registerContentObserver(Context context, ContentObserver observer) { + context.getContentResolver().registerContentObserver(mReadingListUriWithProfile, false, observer); + } +} diff --git a/mobile/android/base/db/ReadingListAccessor.java b/mobile/android/base/db/ReadingListAccessor.java new file mode 100644 index 000000000000..1e8197cd3d52 --- /dev/null +++ b/mobile/android/base/db/ReadingListAccessor.java @@ -0,0 +1,32 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.gecko.db; + +import android.content.ContentResolver; +import android.content.ContentValues; +import android.content.Context; +import android.database.ContentObserver; +import android.database.Cursor; + +public interface ReadingListAccessor { + /** + * Can return null. + */ + Cursor getReadingList(ContentResolver cr); + + int getCount(ContentResolver cr); + + Cursor getReadingListUnfetched(ContentResolver cr); + + boolean isReadingListItem(ContentResolver cr, String uri); + + void addReadingListItem(ContentResolver cr, ContentValues values); + + void updateReadingListItem(ContentResolver cr, ContentValues values); + + void removeReadingListItemWithURL(ContentResolver cr, String uri); + + void registerContentObserver(Context context, ContentObserver observer); +} diff --git a/mobile/android/base/db/StubBrowserDB.java b/mobile/android/base/db/StubBrowserDB.java index 4b7ee21582e1..b67d483d1d6c 100644 --- a/mobile/android/base/db/StubBrowserDB.java +++ b/mobile/android/base/db/StubBrowserDB.java @@ -26,6 +26,48 @@ import android.database.ContentObserver; import android.database.Cursor; import android.graphics.drawable.BitmapDrawable; +class StubReadingListAccessor implements ReadingListAccessor { + @Override + public Cursor getReadingList(ContentResolver cr) { + return null; + } + + @Override + public int getCount(ContentResolver cr) { + return 0; + } + + @Override + public Cursor getReadingListUnfetched(ContentResolver cr) { + return null; + } + + @Override + public boolean isReadingListItem(ContentResolver cr, String uri) { + return false; + } + + @Override + public void addReadingListItem(ContentResolver cr, ContentValues values) { + + } + + @Override + public void updateReadingListItem(ContentResolver cr, ContentValues values) { + + } + + @Override + public void removeReadingListItemWithURL(ContentResolver cr, String uri) { + + } + + @Override + public void registerContentObserver(Context context, ContentObserver observer) { + + } +} + class StubSearches implements Searches { public StubSearches() { } @@ -91,6 +133,7 @@ public class StubBrowserDB implements BrowserDB { private final StubSearches searches = new StubSearches(); private final StubTabsAccessor tabsAccessor = new StubTabsAccessor(); private final StubURLMetadata urlMetadata = new StubURLMetadata(); + private final StubReadingListAccessor readingListAccessor = new StubReadingListAccessor(); @Override public Searches getSearches() { @@ -107,6 +150,11 @@ public class StubBrowserDB implements BrowserDB { return urlMetadata; } + @Override + public ReadingListAccessor getReadingListAccessor() { + return readingListAccessor; + } + protected static final Integer FAVICON_ID_NOT_FOUND = Integer.MIN_VALUE; public StubBrowserDB(String profile) { diff --git a/mobile/android/base/home/HomeFragment.java b/mobile/android/base/home/HomeFragment.java index 5ceedc84eb92..aeae417df7f4 100644 --- a/mobile/android/base/home/HomeFragment.java +++ b/mobile/android/base/home/HomeFragment.java @@ -363,7 +363,7 @@ public abstract class HomeFragment extends Fragment { break; case READING_LIST: - mDB.removeReadingListItemWithURL(cr, mUrl); + mDB.getReadingListAccessor().removeReadingListItemWithURL(cr, mUrl); GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Reader:Removed", mUrl)); break; diff --git a/mobile/android/base/home/ReadingListPanel.java b/mobile/android/base/home/ReadingListPanel.java index f411ad57e46c..c8524d33e612 100644 --- a/mobile/android/base/home/ReadingListPanel.java +++ b/mobile/android/base/home/ReadingListPanel.java @@ -15,6 +15,7 @@ import org.mozilla.gecko.TelemetryContract; import org.mozilla.gecko.db.BrowserContract.ReadingListItems; import org.mozilla.gecko.db.BrowserContract.URLColumns; import org.mozilla.gecko.db.BrowserDB; +import org.mozilla.gecko.db.ReadingListAccessor; import org.mozilla.gecko.home.HomeContextMenuInfo.RemoveItemType; import org.mozilla.gecko.home.HomePager.OnUrlOpenListener; @@ -164,16 +165,16 @@ public class ReadingListPanel extends HomeFragment { * Cursor loader for the list of reading list items. */ private static class ReadingListLoader extends SimpleCursorLoader { - private final BrowserDB mDB; + private final ReadingListAccessor accessor; public ReadingListLoader(Context context) { super(context); - mDB = GeckoProfile.get(context).getDB(); + accessor = GeckoProfile.get(context).getDB().getReadingListAccessor(); } @Override public Cursor loadCursor() { - return mDB.getReadingList(getContext().getContentResolver()); + return accessor.getReadingList(getContext().getContentResolver()); } } diff --git a/mobile/android/base/moz.build b/mobile/android/base/moz.build index 744e5d18dd70..80d35925df78 100644 --- a/mobile/android/base/moz.build +++ b/mobile/android/base/moz.build @@ -159,12 +159,14 @@ gbjar.sources += [ 'db/FormHistoryProvider.java', 'db/HomeProvider.java', 'db/LocalBrowserDB.java', + 'db/LocalReadingListAccessor.java', 'db/LocalSearches.java', 'db/LocalTabsAccessor.java', 'db/LocalURLMetadata.java', 'db/PasswordsProvider.java', 'db/PerProfileDatabaseProvider.java', 'db/PerProfileDatabases.java', + 'db/ReadingListAccessor.java', 'db/ReadingListProvider.java', 'db/RemoteClient.java', 'db/RemoteTab.java', diff --git a/mobile/android/base/overlays/service/sharemethods/AddToReadingList.java b/mobile/android/base/overlays/service/sharemethods/AddToReadingList.java index b445b3c57ac6..c4740cb9123c 100644 --- a/mobile/android/base/overlays/service/sharemethods/AddToReadingList.java +++ b/mobile/android/base/overlays/service/sharemethods/AddToReadingList.java @@ -34,7 +34,7 @@ public class AddToReadingList extends ShareMethod { values.put(Bookmarks.TITLE, shareData.title); values.put(Bookmarks.URL, shareData.url); - browserDB.addReadingListItem(resolver, values); + browserDB.getReadingListAccessor().addReadingListItem(resolver, values); return Result.SUCCESS; } diff --git a/mobile/android/base/overlays/ui/ShareDialog.java b/mobile/android/base/overlays/ui/ShareDialog.java index 70e6e07780be..b7c696e390d9 100644 --- a/mobile/android/base/overlays/ui/ShareDialog.java +++ b/mobile/android/base/overlays/ui/ShareDialog.java @@ -261,7 +261,7 @@ public class ShareDialog extends Locales.LocaleAwareActivity implements SendTabT final ContentResolver contentResolver = getApplicationContext().getContentResolver(); isBookmark = browserDB.isBookmark(contentResolver, pageURL); - isReadingListItem = browserDB.isReadingListItem(contentResolver, pageURL); + isReadingListItem = browserDB.getReadingListAccessor().isReadingListItem(contentResolver, pageURL); return null; }