Bug 1131257 - Part 1: split LocalReadingListDB out of LocalBrowserDB. r=margaret

Centralizing reading list access logic will make Bug 1130461 much easier. This bug is the first part of that.

We follow the same pattern as for URLMetadata, TabsAccessor, and Searches; BrowserDB hands over a single class that's specialized to handle the Reading List.
This commit is contained in:
Richard Newman 2015-02-10 16:42:13 -08:00
Родитель bc3c2931e6
Коммит 8713840e28
12 изменённых файлов: 236 добавлений и 118 удалений

Просмотреть файл

@ -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));

Просмотреть файл

@ -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();

Просмотреть файл

@ -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 <code>null</code>.
*/
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.)

Просмотреть файл

@ -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);

Просмотреть файл

@ -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);
}
}

Просмотреть файл

@ -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 <code>null</code>.
*/
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);
}

Просмотреть файл

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

Просмотреть файл

@ -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;

Просмотреть файл

@ -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());
}
}

Просмотреть файл

@ -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',

Просмотреть файл

@ -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;
}

Просмотреть файл

@ -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;
}