Bug 1234319 - Post: purge ReadingListAccessor and ReadingListProvider r=mcomella
authorAndrzej Hunt <ahunt@mozilla.com>
Thu, 24 Mar 2016 13:16:34 -0700
changeset 316159 94e0c7b66782e265c22aa6a90c519ced4a2562de
parent 316158 3d82a3fa52dbe1b7621364a4c3ad3d92c5ce5d12
child 316160 d5064e39467cc518c50603cb5603caad8e9510e0
push id9480
push userjlund@mozilla.com
push dateMon, 25 Apr 2016 17:12:58 +0000
treeherdermozilla-aurora@0d6a91c76a9e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmcomella
bugs1234319
milestone48.0a1
Bug 1234319 - Post: purge ReadingListAccessor and ReadingListProvider r=mcomella MozReview-Commit-ID: 1JcJKqBzP48
mobile/android/base/AndroidManifest.xml.in
mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
mobile/android/base/java/org/mozilla/gecko/db/BrowserDB.java
mobile/android/base/java/org/mozilla/gecko/db/BrowserDatabaseHelper.java
mobile/android/base/java/org/mozilla/gecko/db/LocalBrowserDB.java
mobile/android/base/java/org/mozilla/gecko/db/LocalReadingListAccessor.java
mobile/android/base/java/org/mozilla/gecko/db/ReadingListAccessor.java
mobile/android/base/java/org/mozilla/gecko/db/ReadingListProvider.java
mobile/android/base/java/org/mozilla/gecko/db/StubBrowserDB.java
mobile/android/base/moz.build
mobile/android/tests/browser/robocop/robocop.ini
mobile/android/tests/browser/robocop/src/org/mozilla/gecko/tests/testReadingListProvider.java
--- a/mobile/android/base/AndroidManifest.xml.in
+++ b/mobile/android/base/AndroidManifest.xml.in
@@ -323,21 +323,16 @@
                   android:label="@string/sync_configure_engines_title_tabs"
                   android:authorities="@ANDROID_PACKAGE_NAME@.db.tabs"
                   android:exported="false"/>
 
         <provider android:name="org.mozilla.gecko.db.HomeProvider"
                   android:authorities="@ANDROID_PACKAGE_NAME@.db.home"
                   android:exported="false"/>
 
-        <provider android:name="org.mozilla.gecko.db.ReadingListProvider"
-                  android:authorities="@ANDROID_PACKAGE_NAME@.db.readinglist"
-                  android:label="@string/reading_list_title"
-                  android:exported="false"/>
-
         <provider android:name="org.mozilla.gecko.db.SearchHistoryProvider"
                   android:authorities="@ANDROID_PACKAGE_NAME@.db.searchhistory"
                   android:exported="false"/>
 
         <service
             android:exported="false"
             android:name="org.mozilla.gecko.updater.UpdateService"
             android:process="@MANGLED_ANDROID_PACKAGE_NAME@.UpdateService">
--- a/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
+++ b/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
@@ -1738,17 +1738,16 @@ public class BrowserApp extends GeckoApp
                 overridePendingTransition(0, 0);
             }
 
         } else if ("Telemetry:Gather".equals(event)) {
             final BrowserDB db = getProfile().getDB();
             final ContentResolver cr = getContentResolver();
             Telemetry.addToHistogram("PLACES_PAGES_COUNT", db.getCount(cr, "history"));
             Telemetry.addToHistogram("FENNEC_BOOKMARKS_COUNT", db.getCount(cr, "bookmarks"));
-            Telemetry.addToHistogram("FENNEC_READING_LIST_COUNT", db.getReadingListAccessor().getCount(cr));
             Telemetry.addToHistogram("BROWSER_IS_USER_DEFAULT", (isDefaultBrowser(Intent.ACTION_VIEW) ? 1 : 0));
             Telemetry.addToHistogram("FENNEC_CUSTOM_HOMEPAGE", (TextUtils.isEmpty(getHomepage()) ? 0 : 1));
             final SharedPreferences prefs = GeckoSharedPrefs.forProfile(getContext());
             final boolean hasCustomHomepanels = prefs.contains(HomeConfigPrefsBackend.PREFS_CONFIG_KEY) || prefs.contains(HomeConfigPrefsBackend.PREFS_CONFIG_KEY_OLD);
             Telemetry.addToHistogram("FENNEC_HOMEPANELS_CUSTOM", hasCustomHomepanels ? 1 : 0);
 
             if (Versions.feature16Plus) {
                 Telemetry.addToHistogram("BROWSER_IS_ASSIST_DEFAULT", (isDefaultBrowser(Intent.ACTION_ASSIST) ? 1 : 0));
--- a/mobile/android/base/java/org/mozilla/gecko/db/BrowserDB.java
+++ b/mobile/android/base/java/org/mozilla/gecko/db/BrowserDB.java
@@ -38,17 +38,16 @@ public interface BrowserDB {
 
     public static enum FilterFlags {
         EXCLUDE_PINNED_SITES
     }
 
     public abstract Searches getSearches();
     public abstract TabsAccessor getTabsAccessor();
     public abstract URLMetadata getURLMetadata();
-    public abstract ReadingListAccessor getReadingListAccessor();
     @RobocopTarget UrlAnnotations getUrlAnnotations();
 
     /**
      * Add default bookmarks to the database.
      * Takes an offset; returns a new offset.
      */
     public abstract int addDefaultBookmarks(Context context, ContentResolver cr, int offset);
 
--- a/mobile/android/base/java/org/mozilla/gecko/db/BrowserDatabaseHelper.java
+++ b/mobile/android/base/java/org/mozilla/gecko/db/BrowserDatabaseHelper.java
@@ -1045,17 +1045,20 @@ public final class BrowserDatabaseHelper
         debug("Rewriting reading list table.");
         createReadingListTable(db, "tmp_rl");
 
         // Remove indexes. We don't need them now, and we'll be throwing away the table.
         db.execSQL("DROP INDEX IF EXISTS reading_list_url");
         db.execSQL("DROP INDEX IF EXISTS reading_list_guid");
         db.execSQL("DROP INDEX IF EXISTS reading_list_content_status");
 
-        final String thisDevice = ReadingListProvider.PLACEHOLDER_THIS_DEVICE;
+        // This used to be a part of the no longer existing ReadingListProvider, since we're deleting
+        // this table later in the second migration, and since sync for this table never existed,
+        // we don't care about the device name here.
+        final String thisDevice = "_fake_device_name_that_will_be_discarded_in_the_next_migration_";
         db.execSQL("INSERT INTO tmp_rl (" +
                    // Here are the columns we can preserve.
                    ReadingListItems._ID + ", " +
                    ReadingListItems.URL + ", " +
                    ReadingListItems.TITLE + ", " +
                    ReadingListItems.RESOLVED_TITLE + ", " +       // = TITLE (if CONTENT_STATUS = STATUS_FETCHED_ARTICLE)
                    ReadingListItems.RESOLVED_URL + ", " +         // = URL (if CONTENT_STATUS = STATUS_FETCHED_ARTICLE)
                    ReadingListItems.EXCERPT + ", " +
--- a/mobile/android/base/java/org/mozilla/gecko/db/LocalBrowserDB.java
+++ b/mobile/android/base/java/org/mozilla/gecko/db/LocalBrowserDB.java
@@ -100,17 +100,16 @@ public class LocalBrowserDB implements B
     private final Uri mFaviconsUriWithProfile;
     private final Uri mThumbnailsUriWithProfile;
     private final Uri mTopSitesUriWithProfile;
     private final Uri mSearchHistoryUri;
 
     private LocalSearches searches;
     private LocalTabsAccessor tabsAccessor;
     private LocalURLMetadata urlMetadata;
-    private LocalReadingListAccessor readingListAccessor;
     private LocalUrlAnnotations urlAnnotations;
 
     private static final String[] DEFAULT_BOOKMARK_COLUMNS =
             new String[] { Bookmarks._ID,
                            Bookmarks.GUID,
                            Bookmarks.URL,
                            Bookmarks.TITLE,
                            Bookmarks.TYPE,
@@ -135,17 +134,16 @@ public class LocalBrowserDB implements B
                 mHistoryUriWithProfile.buildUpon()
                                       .appendQueryParameter(BrowserContract.PARAM_INCREMENT_VISITS, "true")
                                       .appendQueryParameter(BrowserContract.PARAM_INSERT_IF_NEEDED, "true")
                                       .build();
 
         searches = new LocalSearches(mProfile);
         tabsAccessor = new LocalTabsAccessor(mProfile);
         urlMetadata = new LocalURLMetadata(mProfile);
-        readingListAccessor = new LocalReadingListAccessor(mProfile);
         urlAnnotations = new LocalUrlAnnotations(mProfile);
     }
 
     @Override
     public Searches getSearches() {
         return searches;
     }
 
@@ -154,21 +152,16 @@ public class LocalBrowserDB implements B
         return tabsAccessor;
     }
 
     @Override
     public URLMetadata getURLMetadata() {
         return urlMetadata;
     }
 
-    @Override
-    public ReadingListAccessor getReadingListAccessor() {
-        return readingListAccessor;
-    }
-
     @RobocopTarget
     @Override
     public UrlAnnotations getUrlAnnotations() {
         return urlAnnotations;
     }
 
     /**
      * Not thread safe. A helper to allocate new IDs for arbitrary strings.
deleted file mode 100644
--- a/mobile/android/base/java/org/mozilla/gecko/db/LocalReadingListAccessor.java
+++ /dev/null
@@ -1,211 +0,0 @@
-/* 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.ContentUris;
-import android.content.ContentValues;
-import android.content.Context;
-import android.database.ContentObserver;
-import android.database.Cursor;
-import android.net.Uri;
-import android.util.Log;
-
-import org.mozilla.gecko.annotation.RobocopTarget;
-import org.mozilla.gecko.GeckoAppShell;
-import org.mozilla.gecko.GeckoEvent;
-import org.mozilla.gecko.reader.ReaderModeUtils;
-import org.mozilla.gecko.db.BrowserContract.ReadingListItems;
-
-
-@RobocopTarget
-public class LocalReadingListAccessor implements ReadingListAccessor {
-    private static final String LOG_TAG = "GeckoReadingListAcc";
-
-    private static final String NOT_DELETED = ReadingListItems.IS_DELETED + " = 0";
-    private static final String NEITHER_DELETED_NOR_ARCHIVED = ReadingListItems.IS_ARCHIVED + " = 0 AND " + ReadingListItems.IS_DELETED + " = 0";
-    private static final String ITEMS_TO_FETCH = ReadingListItems.CONTENT_STATUS + " = " + ReadingListItems.STATUS_UNFETCHED + " AND " + NEITHER_DELETED_NOR_ARCHIVED;
-    private static final String SORT_ORDER_RECENT_FIRST = "COALESCE(" + ReadingListItems.SERVER_STORED_ON + ", " + ReadingListItems.ADDED_ON + ") DESC";
-
-    private final Uri mReadingListUriWithProfile;
-
-    public LocalReadingListAccessor(final String profile) {
-        mReadingListUriWithProfile = DBUtils.appendProfile(profile, ReadingListItems.CONTENT_URI);
-    }
-
-    // Return a count of non-deleted items.
-    @Override
-    public int getCount(ContentResolver cr) {
-        final String[] columns = new String[]{ReadingListItems._ID};
-        final Cursor cursor = cr.query(mReadingListUriWithProfile, columns, NOT_DELETED, null, null);
-
-        try {
-            return cursor.getCount();
-        } finally {
-            cursor.close();
-        }
-    }
-
-    @Override
-    public Cursor getReadingList(ContentResolver cr) {
-        // Return non-deleted, non-archived items, ordered by either server stored data or local added date,
-        // descending.
-        // This isn't ideal -- it depends on upload order! -- but the alternative is that a client with a
-        // very skewed clock will force its items to the front or end of the list on other devices.
-        return cr.query(mReadingListUriWithProfile,
-                        ReadingListItems.DEFAULT_PROJECTION,
-                        NEITHER_DELETED_NOR_ARCHIVED,
-                        null,
-                        SORT_ORDER_RECENT_FIRST);
-    }
-
-    @Override
-    public Cursor getReadingListUnfetched(ContentResolver cr) {
-        // Return unfetched, non-deleted, non-archived items, sorted by date added, newest first.
-        // This allows us to fetch the top of the list first.
-        return cr.query(mReadingListUriWithProfile,
-                        new String[] { ReadingListItems._ID, ReadingListItems.URL },
-                        ITEMS_TO_FETCH,
-                        null,
-                        SORT_ORDER_RECENT_FIRST);
-    }
-
-    @Override
-    public boolean isReadingListItem(final ContentResolver cr, String uri) {
-        uri = ReaderModeUtils.stripAboutReaderUrl(uri);
-
-        final Cursor c = cr.query(mReadingListUriWithProfile,
-                                  new String[] { ReadingListItems._ID },
-                                  ReadingListItems.URL + " = ? OR " + ReadingListItems.RESOLVED_URL + " = ?",
-                                  new String[] { uri, uri },
-                                  null);
-
-        if (c == null) {
-            Log.e(LOG_TAG, "Null cursor in isReadingListItem");
-            return false;
-        }
-
-        try {
-            return c.moveToNext();
-        } finally {
-            c.close();
-        }
-    }
-
-
-    @Override
-    public long 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);
-            }
-        }
-
-        // URL is a required field so no key check needed.
-        final String url = ReaderModeUtils.stripAboutReaderUrl(values.getAsString(ReadingListItems.URL));
-        values.put(ReadingListItems.URL, url);
-
-        // We're adding locally, so we can specify these.
-        values.put(ReadingListItems.ADDED_ON, System.currentTimeMillis());
-        values.put(ReadingListItems.ADDED_BY, ReadingListProvider.PLACEHOLDER_THIS_DEVICE);
-
-        // We never un-delete (and we can't; we wipe as we go).
-        // Re-add if necessary and allow the server to resolve conflicts.
-        final long id = ContentUris.parseId(cr.insert(mReadingListUriWithProfile, values));
-
-        GeckoAppShell.notifyObservers("Reader:Added", url);
-
-        return id;
-    }
-
-    @Override
-    public long addBasicReadingListItem(ContentResolver cr, String url, String title) {
-        if (url == null) {
-            throw new IllegalArgumentException("URL must not be null.");
-        }
-        final ContentValues values = new ContentValues();
-        values.put(ReadingListItems.URL, url);
-        if (title != null) {
-            values.put(ReadingListItems.TITLE, title);
-        } else {
-            values.putNull(ReadingListItems.TITLE);
-        }
-
-        return addReadingListItem(cr, values);
-    }
-
-    @Override
-    public void updateReadingListItem(ContentResolver cr, ContentValues values) {
-        if (!values.containsKey(ReadingListItems._ID)) {
-            throw new IllegalArgumentException("Cannot update reading list item without an ID");
-        }
-
-        if (values.containsKey(ReadingListItems.URL)) {
-            values.put(ReadingListItems.URL, ReaderModeUtils.stripAboutReaderUrl(values.getAsString(ReadingListItems.URL)));
-        }
-
-        final int updated = cr.update(mReadingListUriWithProfile,
-                                      values,
-                                      ReadingListItems._ID + " = ? ",
-                                      new String[] { values.getAsString(ReadingListItems._ID) });
-
-        Log.d(LOG_TAG, "Updated " + updated + " reading list rows.");
-    }
-
-    @Override
-    public void removeReadingListItemWithURL(final ContentResolver cr, String uri) {
-        cr.delete(mReadingListUriWithProfile,
-                  ReadingListItems.URL + " = ? OR " + ReadingListItems.RESOLVED_URL + " = ?",
-                  new String[]{ uri, uri });
-
-        GeckoAppShell.notifyObservers("Reader:Removed", uri);
-    }
-
-    @Override
-    public void deleteItem(ContentResolver cr, long itemID) {
-        // TODO: For completness, we should send a "Reader:Removed"
-        // GeckoEvent, but we don't have the uri. Luckily, this is
-        // only called in testing at the moment.
-        cr.delete(ContentUris.appendId(mReadingListUriWithProfile.buildUpon(), itemID).build(),
-                  null, null);
-    }
-
-    @Override
-    public void registerContentObserver(Context context, ContentObserver observer) {
-        context.getContentResolver().registerContentObserver(mReadingListUriWithProfile, false, observer);
-    }
-
-    @Override
-    public void markAsRead(ContentResolver cr, long itemID) {
-        final ContentValues values = new ContentValues();
-        values.put(ReadingListItems.MARKED_READ_BY, ReadingListProvider.PLACEHOLDER_THIS_DEVICE);
-        values.put(ReadingListItems.MARKED_READ_ON, System.currentTimeMillis());
-        values.put(ReadingListItems.IS_UNREAD, 0);
-
-        // The ContentProvider will take care of updating the sync metadata.
-        cr.update(mReadingListUriWithProfile, values, ReadingListItems._ID + " = " + itemID, null);
-    }
-
-    @Override
-    public void markAsUnread(ContentResolver cr, long itemID) {
-        final ContentValues values = new ContentValues();
-        values.put(ReadingListItems.IS_UNREAD, 1);
-
-        cr.update(mReadingListUriWithProfile, values, ReadingListItems._ID + " = " + itemID, null);
-    }
-
-    @Override
-    public void updateContent(ContentResolver cr, long itemID, String resolvedTitle, String resolvedURL, String excerpt) {
-        final ContentValues values = new ContentValues();
-        values.put(ReadingListItems.CONTENT_STATUS, ReadingListItems.STATUS_FETCHED_ARTICLE);
-        values.put(ReadingListItems.RESOLVED_URL, resolvedURL);
-        values.put(ReadingListItems.RESOLVED_TITLE, resolvedTitle);
-        values.put(ReadingListItems.EXCERPT, excerpt);
-
-        // The ContentProvider will take care of updating the sync metadata.
-        cr.update(mReadingListUriWithProfile, values, ReadingListItems._ID + " = " + itemID, null);
-    }
-}
deleted file mode 100644
--- a/mobile/android/base/java/org/mozilla/gecko/db/ReadingListAccessor.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/* 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 org.mozilla.gecko.annotation.RobocopTarget;
-
-@RobocopTarget
-public interface ReadingListAccessor {
-    /**
-     * Returns non-deleted, non-archived items.
-     * Fennec doesn't currently offer a way to display archived items.
-     *
-     * Can return <code>null</code>.
-     */
-    Cursor getReadingList(ContentResolver cr);
-
-    int getCount(ContentResolver cr);
-
-    Cursor getReadingListUnfetched(ContentResolver cr);
-
-    boolean isReadingListItem(ContentResolver cr, String uri);
-
-    long addReadingListItem(ContentResolver cr, ContentValues values);
-    long addBasicReadingListItem(ContentResolver cr, String url, String title);
-
-    void updateReadingListItem(ContentResolver cr, ContentValues values);
-
-    void removeReadingListItemWithURL(ContentResolver cr, String uri);
-
-    void registerContentObserver(Context context, ContentObserver observer);
-
-    void markAsRead(ContentResolver cr, long itemID);
-    void markAsUnread(ContentResolver cr, long itemID);
-    void updateContent(ContentResolver cr, long itemID, String resolvedTitle, String resolvedURL, String excerpt);
-    void deleteItem(ContentResolver cr, long itemID);
-}
deleted file mode 100644
--- a/mobile/android/base/java/org/mozilla/gecko/db/ReadingListProvider.java
+++ /dev/null
@@ -1,414 +0,0 @@
-/* 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.ContentUris;
-import android.content.ContentValues;
-import android.content.UriMatcher;
-import android.database.Cursor;
-import android.database.SQLException;
-import android.database.sqlite.SQLiteDatabase;
-import android.database.sqlite.SQLiteQueryBuilder;
-import android.net.Uri;
-import android.text.TextUtils;
-import android.util.Log;
-import org.mozilla.gecko.db.DBUtils.UpdateOperation;
-
-import static org.mozilla.gecko.db.BrowserContract.ReadingListItems.*;
-
-public class ReadingListProvider extends SharedBrowserDatabaseProvider {
-    private static final String LOGTAG = "GeckoRLProvider";
-
-    static final String TABLE_READING_LIST = TABLE_NAME;
-
-    static final int ITEMS = 101;
-    static final int ITEMS_ID = 102;
-    static final UriMatcher URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
-
-    public static final String PLACEHOLDER_THIS_DEVICE = "$local";
-
-    static {
-        URI_MATCHER.addURI(BrowserContract.READING_LIST_AUTHORITY, "items", ITEMS);
-        URI_MATCHER.addURI(BrowserContract.READING_LIST_AUTHORITY, "items/#", ITEMS_ID);
-    }
-
-    /**
-     * Updates items that match the selection criteria. If no such items is found
-     * one is inserted with the attributes passed in. Returns 0 if no item updated.
-     *
-     * Only use this method for callers, not internally -- it futzes with the provided
-     * values to set syncing flags.
-     *
-     * @return Number of items updated or inserted
-     */
-    public int updateOrInsertItem(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
-        if (!values.containsKey(CLIENT_LAST_MODIFIED)) {
-            values.put(CLIENT_LAST_MODIFIED, System.currentTimeMillis());
-        }
-
-        if (isCallerSync(uri)) {
-            int updated = updateItemsWithFlags(uri, values, null, selection, selectionArgs);
-            if (updated > 0) {
-                return updated;
-            }
-            return insertItem(uri, values) != -1 ? 1 : 0;
-        }
-
-        // Assume updated.
-        final ContentValues flags = processChangeValues(values);
-
-        int updated = updateItemsWithFlags(uri, values, flags, selection, selectionArgs);
-        if (updated <= 0) {
-            // Must be an insertion. Let's make sure we're NEW and discard any update flags.
-            values.put(SYNC_STATUS, SYNC_STATUS_NEW);
-            values.put(SYNC_CHANGE_FLAGS, SYNC_CHANGE_NONE);
-            updated = insertItem(uri, values) != -1 ? 1 : 0;
-        }
-        return updated;
-    }
-
-    /**
-     * This method does two things:
-     * * Based on the values provided, it computes and returns an incremental status change
-     *   that can be applied to the database to track changes for syncing. This should be
-     *   applied with {@link UpdateOperation#BITWISE_OR}.
-     * * It mutates the provided values to mark absolute field changes.
-     *
-     * @return null if no values were provided, or no change needs to be recorded.
-     */
-    private ContentValues processChangeValues(ContentValues values) {
-        if (values == null || values.size() == 0) {
-            return null;
-        }
-
-        final ContentValues out = new ContentValues();
-        int flag = 0;
-        if (values.containsKey(MARKED_READ_BY) ||
-            values.containsKey(MARKED_READ_ON) ||
-            values.containsKey(IS_UNREAD)) {
-            flag |= SYNC_CHANGE_UNREAD_CHANGED;
-        }
-
-        if (values.containsKey(IS_FAVORITE)) {
-            flag |= SYNC_CHANGE_FAVORITE_CHANGED;
-        }
-
-        if (values.containsKey(RESOLVED_URL) ||
-            values.containsKey(RESOLVED_TITLE) ||
-            values.containsKey(EXCERPT)) {
-            flag |= SYNC_CHANGE_RESOLVED;
-        }
-
-        if (flag == 0) {
-            return null;
-        }
-
-        out.put(SYNC_CHANGE_FLAGS, flag);
-        return out;
-    }
-
-    /**
-     * Updates items that match the selection criteria.
-     *
-     * @return Number of items updated or inserted
-     */
-    public int updateItemsWithFlags(Uri uri, ContentValues values, ContentValues flags, String selection, String[] selectionArgs) {
-        trace("Updating ReadingListItems on URI: " + uri);
-        final SQLiteDatabase db = getWritableDatabase(uri);
-        if (!values.containsKey(CLIENT_LAST_MODIFIED)) {
-            values.put(CLIENT_LAST_MODIFIED, System.currentTimeMillis());
-        }
-
-        if (flags == null) {
-            // This code path is used by Sync. Bypass metadata changes.
-            return db.update(TABLE_READING_LIST, values, selection, selectionArgs);
-        }
-
-        // Set synced items to MODIFIED; otherwise, leave the sync status untouched.
-        final ContentValues setModified = new ContentValues();
-        setModified.put(SYNC_STATUS, "CASE " + SYNC_STATUS +
-                                     " WHEN " + SYNC_STATUS_SYNCED +
-                                     " THEN " + SYNC_STATUS_MODIFIED +
-                                     " ELSE " + SYNC_STATUS +
-                                     " END");
-
-        final ContentValues[] valuesAndFlags = {values, flags, setModified};
-        final UpdateOperation[] ops = {UpdateOperation.ASSIGN, UpdateOperation.BITWISE_OR, UpdateOperation.EXPRESSION};
-
-        return DBUtils.updateArrays(db, TABLE_READING_LIST, valuesAndFlags, ops, selection, selectionArgs);
-    }
-
-    /**
-     * Inserts a new item into the DB. CLIENT_LAST_MODIFIED is generated if it is not specified.
-     *
-     * Non-Sync callers will have ADDED_ON and ADDED_BY set appropriately if they are missing;
-     * the assumption is that this is a new item added on this device.
-     *
-     * @return ID of the newly inserted item
-     */
-    private long insertItem(Uri uri, ContentValues values) {
-        if (!values.containsKey(CLIENT_LAST_MODIFIED)) {
-            values.put(CLIENT_LAST_MODIFIED, System.currentTimeMillis());
-        }
-
-        // We trust the syncing code to specify SYNC_STATUS_SYNCED.
-        if (!isCallerSync(uri)) {
-            values.put(SYNC_STATUS, SYNC_STATUS_NEW);
-            if (!values.containsKey(ADDED_ON)) {
-                values.put(ADDED_ON, System.currentTimeMillis());
-            }
-            if (!values.containsKey(ADDED_BY)) {
-                values.put(ADDED_BY, PLACEHOLDER_THIS_DEVICE);
-            }
-        }
-
-        final String url = values.getAsString(URL);
-        debug("Inserting item in database with URL: " + url);
-        try {
-            return getWritableDatabase(uri).insertOrThrow(TABLE_READING_LIST, null, values);
-        } catch (SQLException e) {
-            Log.e(LOGTAG, "Insert failed.", e);
-            throw e;
-        }
-    }
-
-    private static final ContentValues DELETED_VALUES;
-    static {
-        final ContentValues values = new ContentValues();
-        values.put(IS_DELETED, 1);
-
-        values.put(URL, "");             // Non-null column.
-        values.putNull(RESOLVED_URL);
-        values.putNull(RESOLVED_TITLE);
-        values.putNull(TITLE);
-        values.putNull(EXCERPT);
-        values.putNull(ADDED_BY);
-        values.putNull(MARKED_READ_BY);
-
-        // Mark it as deleted for sync purposes.
-        values.put(SYNC_STATUS, SYNC_STATUS_DELETED);
-        values.put(SYNC_CHANGE_FLAGS, SYNC_CHANGE_NONE);
-        DELETED_VALUES = values;
-    }
-
-    /**
-     * Deletes items. Item is marked as 'deleted' so that sync can
-     * detect the change.
-     *
-     * It's the caller's responsibility to handle both original and resolved URLs.
-     * @return Number of deleted items
-     */
-    int deleteItems(final Uri uri, String selection, String[] selectionArgs) {
-        debug("Deleting item entry for URI: " + uri);
-        final SQLiteDatabase db = getWritableDatabase(uri);
-
-        // TODO: also ensure that we delete affected items from the disk cache. Bug 1133158.
-        if (isCallerSync(uri)) {
-            debug("Directly deleting from reading list.");
-            return db.delete(TABLE_READING_LIST, selection, selectionArgs);
-        }
-
-        // If we don't have a GUID for this item, then it hasn't made it
-        // to the server. Just delete it.
-        // If we do have a GUID, blank the row and mark it as deleted.
-        int total = 0;
-        final String whereNullGUID = DBUtils.concatenateWhere(selection, GUID + " IS NULL");
-        final String whereNotNullGUID = DBUtils.concatenateWhere(selection, GUID + " IS NOT NULL");
-
-        total += db.delete(TABLE_READING_LIST, whereNullGUID, selectionArgs);
-        total += updateItemsWithFlags(uri, DELETED_VALUES, null, whereNotNullGUID, selectionArgs);
-
-        return total;
-    }
-
-    int deleteItemByID(final Uri uri, long id) {
-        debug("Deleting item entry for ID: " + id);
-        final SQLiteDatabase db = getWritableDatabase(uri);
-
-        // TODO: also ensure that we delete affected items from the disk cache. Bug 1133158.
-        if (isCallerSync(uri)) {
-            debug("Directly deleting from reading list.");
-            final String selection = _ID + " = " + id;
-            return db.delete(TABLE_READING_LIST, selection, null);
-        }
-
-        // If we don't have a GUID for this item, then it hasn't made it
-        // to the server. Just delete it.
-        final String whereNullGUID = _ID + " = " + id + " AND " + GUID + " IS NULL";
-        final int raw = db.delete(TABLE_READING_LIST, whereNullGUID, null);
-        if (raw > 0) {
-            // _ID is unique, so this should only ever be 1, but it definitely means
-            // we don't need to try the second part.
-            return raw;
-        }
-
-        // If we do have a GUID, blank the row and mark it as deleted.
-        final String whereNotNullGUID = _ID + " = " + id + " AND " + GUID + " IS NOT NULL";
-        final ContentValues values = new ContentValues(DELETED_VALUES);
-        values.put(CLIENT_LAST_MODIFIED, System.currentTimeMillis());
-        return updateItemsWithFlags(uri, values, null, whereNotNullGUID, null);
-    }
-
-    @Override
-    @SuppressWarnings("fallthrough")
-    public int updateInTransaction(final Uri uri, ContentValues values, String selection, String[] selectionArgs) {
-        trace("Calling update in transaction on URI: " + uri);
-
-        int updated = 0;
-        int match = URI_MATCHER.match(uri);
-
-        switch (match) {
-            case ITEMS_ID:
-                debug("Update on ITEMS_ID: " + uri);
-                selection = DBUtils.concatenateWhere(selection, TABLE_READING_LIST + "._id = ?");
-                selectionArgs = DBUtils.appendSelectionArgs(selectionArgs,
-                        new String[] { Long.toString(ContentUris.parseId(uri)) });
-
-            case ITEMS: {
-                debug("Updating ITEMS: " + uri);
-                if (shouldUpdateOrInsert(uri)) {
-                    // updateOrInsertItem handles change flags for us.
-                    updated = updateOrInsertItem(uri, values, selection, selectionArgs);
-                } else {
-                    // Don't use flags if we're inserting from sync.
-                    ContentValues flags = isCallerSync(uri) ? null : processChangeValues(values);
-                    updated = updateItemsWithFlags(uri, values, flags, selection, selectionArgs);
-                }
-                break;
-            }
-
-            default:
-                throw new UnsupportedOperationException("Unknown update URI " + uri);
-        }
-
-        debug("Updated " + updated + " rows for URI: " + uri);
-        return updated;
-    }
-
-
-    @Override
-    @SuppressWarnings("fallthrough")
-    public int deleteInTransaction(Uri uri, String selection, String[] selectionArgs) {
-        trace("Calling delete in transaction on URI: " + uri);
-
-        // This will never clean up any items that we're about to delete, so we
-        // might as well run it first!
-        cleanUpSomeDeletedRecords(uri, TABLE_READING_LIST);
-
-        int numDeleted = 0;
-        int match = URI_MATCHER.match(uri);
-
-        switch (match) {
-            case ITEMS_ID:
-                debug("Deleting on ITEMS_ID: " + uri);
-                numDeleted = deleteItemByID(uri, ContentUris.parseId(uri));
-                break;
-
-            case ITEMS:
-                debug("Deleting ITEMS: " + uri);
-                numDeleted = deleteItems(uri, selection, selectionArgs);
-                break;
-
-            default:
-                throw new UnsupportedOperationException("Unknown update URI " + uri);
-        }
-
-        debug("Deleted " + numDeleted + " rows for URI: " + uri);
-        return numDeleted;
-    }
-
-    @Override
-    public Uri insertInTransaction(Uri uri, ContentValues values) {
-        trace("Calling insert in transaction on URI: " + uri);
-        long id = -1;
-        int match = URI_MATCHER.match(uri);
-
-        switch (match) {
-            case ITEMS:
-                trace("Insert on ITEMS: " + uri);
-                id = insertItem(uri, values);
-                break;
-
-            default:
-                // Log here because we typically insert in a batch, and that will muffle.
-                Log.e(LOGTAG, "Unknown insert URI " + uri);
-                throw new UnsupportedOperationException("Unknown insert URI " + uri);
-        }
-
-        debug("Inserted ID in database: " + id);
-
-        if (id >= 0) {
-            return ContentUris.withAppendedId(uri, id);
-        }
-
-        Log.e(LOGTAG, "Got to end of insertInTransaction without returning an id!");
-        return null;
-    }
-
-    @Override
-    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
-        String groupBy = null;
-        SQLiteDatabase db = getReadableDatabase(uri);
-        SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
-        String limit = uri.getQueryParameter(BrowserContract.PARAM_LIMIT);
-
-        final int match = URI_MATCHER.match(uri);
-        switch (match) {
-            case ITEMS_ID:
-                trace("Query on ITEMS_ID: " + uri);
-                selection = DBUtils.concatenateWhere(selection, _ID + " = ?");
-                selectionArgs = DBUtils.appendSelectionArgs(selectionArgs,
-                        new String[] { Long.toString(ContentUris.parseId(uri)) });
-
-            case ITEMS:
-                trace("Query on ITEMS: " + uri);
-                if (!shouldShowDeleted(uri)) {
-                    selection = DBUtils.concatenateWhere(IS_DELETED + " = 0", selection);
-                }
-                break;
-
-            default:
-                throw new UnsupportedOperationException("Unknown query URI " + uri);
-        }
-
-        if (TextUtils.isEmpty(sortOrder)) {
-            sortOrder = DEFAULT_SORT_ORDER;
-        }
-
-        trace("Running built query.");
-        qb.setTables(TABLE_READING_LIST);
-        Cursor cursor = qb.query(db, projection, selection, selectionArgs, groupBy, null, sortOrder, limit);
-        cursor.setNotificationUri(getContext().getContentResolver(), uri);
-
-        return cursor;
-    }
-
-    @Override
-    public String getType(Uri uri) {
-        trace("Getting URI type: " + uri);
-
-        final int match = URI_MATCHER.match(uri);
-        switch (match) {
-            case ITEMS:
-                trace("URI is ITEMS: " + uri);
-                return CONTENT_TYPE;
-
-            case ITEMS_ID:
-                trace("URI is ITEMS_ID: " + uri);
-                return CONTENT_ITEM_TYPE;
-        }
-
-        debug("URI has unrecognized type: " + uri);
-        return null;
-    }
-
-    @Override
-    protected String getDeletedItemSelection(long earlierThan) {
-        if (earlierThan == -1L) {
-            return IS_DELETED + " = 1";
-        }
-        return IS_DELETED + " = 1 AND " + CLIENT_LAST_MODIFIED + " <= " + earlierThan;
-    }
-}
--- a/mobile/android/base/java/org/mozilla/gecko/db/StubBrowserDB.java
+++ b/mobile/android/base/java/org/mozilla/gecko/db/StubBrowserDB.java
@@ -22,77 +22,16 @@ import org.mozilla.gecko.feeds.subscript
 import android.content.ContentProviderOperation;
 import android.content.ContentResolver;
 import android.content.ContentValues;
 import android.content.Context;
 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 long addReadingListItem(ContentResolver cr, ContentValues values) {
-        return 0L;
-    }
-
-    @Override
-    public long addBasicReadingListItem(ContentResolver cr, String url, String title) {
-        return 0L;
-    }
-
-    @Override
-    public void updateReadingListItem(ContentResolver cr, ContentValues values) {
-    }
-
-    @Override
-    public void removeReadingListItemWithURL(ContentResolver cr, String uri) {
-    }
-
-    @Override
-    public void registerContentObserver(Context context, ContentObserver observer) {
-    }
-
-    @Override
-    public void markAsRead(ContentResolver cr, long itemID) {
-    }
-
-    @Override
-    public void markAsUnread(ContentResolver cr, long itemID) {
-    }
-
-    @Override
-    public void updateContent(ContentResolver cr, long itemID, String resolvedTitle, String resolvedURL, String excerpt) {
-    }
-
-    @Override
-    public void deleteItem(ContentResolver cr, long itemID) {
-
-    }
-}
-
 class StubSearches implements Searches {
     public StubSearches() {
     }
 
     public void insert(ContentResolver cr, String query) {
     }
 }
 
@@ -201,17 +140,16 @@ class StubUrlAnnotations implements UrlA
 /*
  * This base implementation just stubs all methods. For the
  * real implementations, see LocalBrowserDB.java.
  */
 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();
     private final StubUrlAnnotations urlAnnotations = new StubUrlAnnotations();
     private SuggestedSites suggestedSites = null;
 
     @Override
     public Searches getSearches() {
         return searches;
     }
 
@@ -221,21 +159,16 @@ public class StubBrowserDB implements Br
     }
 
     @Override
     public URLMetadata getURLMetadata() {
         return urlMetadata;
     }
 
     @Override
-    public ReadingListAccessor getReadingListAccessor() {
-        return readingListAccessor;
-    }
-
-    @Override
     public UrlAnnotations getUrlAnnotations() {
         return urlAnnotations;
     }
 
     protected static final Integer FAVICON_ID_NOT_FOUND = Integer.MIN_VALUE;
 
     public StubBrowserDB(String profile) {
     }
@@ -293,58 +226,37 @@ public class StubBrowserDB implements Br
     public void clearHistory(ContentResolver cr, boolean clearSearchHistory) {
     }
 
     @RobocopTarget
     public Cursor getBookmarksInFolder(ContentResolver cr, long folderId) {
         return null;
     }
 
-    public Cursor getReadingList(ContentResolver cr) {
-        return null;
-    }
-
-    public Cursor getReadingListUnfetched(ContentResolver cr) {
-        return null;
-    }
-
     @RobocopTarget
     public boolean isBookmark(ContentResolver cr, String uri) {
         return false;
     }
 
-    public boolean isReadingListItem(ContentResolver cr, String uri) {
-        return false;
-    }
-
     public String getUrlForKeyword(ContentResolver cr, String keyword) {
         return null;
     }
 
     protected void bumpParents(ContentResolver cr, String param, String value) {
     }
 
     @RobocopTarget
     public boolean addBookmark(ContentResolver cr, String title, String uri) {
         return false;
     }
 
     @RobocopTarget
     public void removeBookmarksWithURL(ContentResolver cr, String uri) {
     }
 
-    public void addReadingListItem(ContentResolver cr, ContentValues values) {
-    }
-
-    public void updateReadingListItem(ContentResolver cr, ContentValues values) {
-    }
-
-    public void removeReadingListItemWithURL(ContentResolver cr, String uri) {
-    }
-
     public void registerBookmarkObserver(ContentResolver cr, ContentObserver observer) {
     }
 
     @RobocopTarget
     public void updateBookmark(ContentResolver cr, int id, String uri, String title, String keyword) {
     }
 
     public LoadFaviconResult getFaviconForUrl(ContentResolver cr, String faviconURL) {
--- a/mobile/android/base/moz.build
+++ b/mobile/android/base/moz.build
@@ -217,27 +217,24 @@ gbjar.sources += ['java/org/mozilla/geck
     'db/BaseTable.java',
     'db/BrowserDatabaseHelper.java',
     'db/BrowserDB.java',
     'db/BrowserProvider.java',
     'db/DBUtils.java',
     'db/FormHistoryProvider.java',
     'db/HomeProvider.java',
     'db/LocalBrowserDB.java',
-    'db/LocalReadingListAccessor.java',
     'db/LocalSearches.java',
     'db/LocalTabsAccessor.java',
     'db/LocalUrlAnnotations.java',
     'db/LocalURLMetadata.java',
     'db/LoginsProvider.java',
     'db/PasswordsProvider.java',
     'db/PerProfileDatabaseProvider.java',
     'db/PerProfileDatabases.java',
-    'db/ReadingListAccessor.java',
-    'db/ReadingListProvider.java',
     'db/RemoteClient.java',
     'db/RemoteTab.java',
     'db/Searches.java',
     'db/SearchHistoryProvider.java',
     'db/SharedBrowserDatabaseProvider.java',
     'db/SQLiteBridgeContentProvider.java',
     'db/StubBrowserDB.java',
     'db/SuggestedSites.java',
--- a/mobile/android/tests/browser/robocop/robocop.ini
+++ b/mobile/android/tests/browser/robocop/robocop.ini
@@ -55,17 +55,16 @@ skip-if = !nightly_build # Bug 1261270: 
 [src/org/mozilla/gecko/tests/testPasswordProvider.java]
 # [src/org/mozilla/gecko/tests/testPermissions.java] # see bug 757475
 [src/org/mozilla/gecko/tests/testPictureLinkContextMenu.java]
 [src/org/mozilla/gecko/tests/testPrefsObserver.java]
 [src/org/mozilla/gecko/tests/testPrivateBrowsing.java]
 [src/org/mozilla/gecko/tests/testPromptGridInput.java]
 # bug 1001657
 skip-if = android_version == "18"
-[src/org/mozilla/gecko/tests/testReadingListProvider.java]
 [src/org/mozilla/gecko/tests/testSearchHistoryProvider.java]
 [src/org/mozilla/gecko/tests/testSearchSuggestions.java]
 # disabled on 4.3, bug 1145867
 skip-if = android_version == "18"
 [src/org/mozilla/gecko/tests/testSessionOOMSave.java]
 # disabled on 4.3, bug 1144888
 skip-if = android_version == "18"
 [src/org/mozilla/gecko/tests/testSessionOOMRestore.java]
deleted file mode 100644
--- a/mobile/android/tests/browser/robocop/src/org/mozilla/gecko/tests/testReadingListProvider.java
+++ /dev/null
@@ -1,783 +0,0 @@
-/* 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.tests;
-
-import java.util.HashSet;
-import java.util.Random;
-import java.util.concurrent.Callable;
-
-import android.app.Activity;
-import android.content.ContentResolver;
-import org.mozilla.gecko.BrowserApp;
-import org.mozilla.gecko.db.BrowserContract;
-import org.mozilla.gecko.db.BrowserContract.ReadingListItems;
-import org.mozilla.gecko.db.ReadingListAccessor;
-import org.mozilla.gecko.db.ReadingListProvider;
-
-import android.content.ContentProvider;
-import android.content.ContentUris;
-import android.content.ContentValues;
-import android.database.Cursor;
-import android.database.sqlite.SQLiteDatabase;
-import android.net.Uri;
-
-import static org.mozilla.gecko.db.BrowserContract.ReadingListItems.*;
-import static org.mozilla.gecko.db.BrowserContract.ReadingListItems.ADDED_ON;
-import static org.mozilla.gecko.db.BrowserContract.ReadingListItems.CLIENT_LAST_MODIFIED;
-import static org.mozilla.gecko.db.BrowserContract.ReadingListItems.EXCERPT;
-import static org.mozilla.gecko.db.BrowserContract.ReadingListItems.GUID;
-import static org.mozilla.gecko.db.BrowserContract.ReadingListItems.IS_UNREAD;
-import static org.mozilla.gecko.db.BrowserContract.ReadingListItems.RESOLVED_TITLE;
-import static org.mozilla.gecko.db.BrowserContract.ReadingListItems.RESOLVED_URL;
-import static org.mozilla.gecko.db.BrowserContract.ReadingListItems.SERVER_LAST_MODIFIED;
-import static org.mozilla.gecko.db.BrowserContract.ReadingListItems.SERVER_STORED_ON;
-import static org.mozilla.gecko.db.BrowserContract.ReadingListItems.SYNC_CHANGE_FLAGS;
-import static org.mozilla.gecko.db.BrowserContract.ReadingListItems.SYNC_CHANGE_NONE;
-import static org.mozilla.gecko.db.BrowserContract.ReadingListItems.SYNC_CHANGE_RESOLVED;
-import static org.mozilla.gecko.db.BrowserContract.ReadingListItems.SYNC_CHANGE_UNREAD_CHANGED;
-import static org.mozilla.gecko.db.BrowserContract.ReadingListItems.SYNC_STATUS;
-import static org.mozilla.gecko.db.BrowserContract.ReadingListItems.SYNC_STATUS_MODIFIED;
-import static org.mozilla.gecko.db.BrowserContract.ReadingListItems.SYNC_STATUS_NEW;
-import static org.mozilla.gecko.db.BrowserContract.ReadingListItems.SYNC_STATUS_SYNCED;
-import static org.mozilla.gecko.db.BrowserContract.URLColumns.TITLE;
-import static org.mozilla.gecko.db.BrowserContract.URLColumns.URL;
-
-public class testReadingListProvider extends ContentProviderTest {
-
-    private static final String DB_NAME = "browser.db";
-
-    // List of tests to be run sorted by dependency.
-    private final TestCase[] TESTS_TO_RUN = {
-            new TestInsertItems(),
-            new TestDeleteItems(),
-            new TestUpdateItems(),
-            new TestBatchOperations(),
-            new TestBrowserProviderNotifications(),
-            new TestStateSequencing(),
-    };
-
-    // Columns used to test for item equivalence.
-    final String[] TEST_COLUMNS = { TITLE,
-                                    URL,
-                                    EXCERPT,
-                                    ADDED_ON };
-
-    // Indicates that insertions have been tested. ContentProvider.insert
-    // has been proven to work.
-    private boolean mContentProviderInsertTested = false;
-
-    // Indicates that updates have been tested. ContentProvider.update
-    // has been proven to work.
-    private boolean mContentProviderUpdateTested = false;
-
-    /**
-     * Factory function that makes new ReadingListProvider instances.
-     * <p>
-     * We want a fresh provider each test, so this should be invoked in
-     * <code>setUp</code> before each individual test.
-     */
-    private static final Callable<ContentProvider> sProviderFactory = new Callable<ContentProvider>() {
-        @Override
-        public ContentProvider call() {
-            return new ReadingListProvider();
-        }
-    };
-
-    @Override
-    public void setUp() throws Exception {
-        super.setUp(sProviderFactory, BrowserContract.READING_LIST_AUTHORITY, DB_NAME);
-        for (TestCase test: TESTS_TO_RUN) {
-            mTests.add(test);
-        }
-    }
-
-    public void testReadingListProviderTests() throws Exception {
-        for (Runnable test : mTests) {
-            setTestName(test.getClass().getSimpleName());
-            ensureEmptyDatabase();
-            test.run();
-        }
-
-        // Ensure browser initialization is complete before completing test,
-        // so that the minidumps directory is consistently created.
-        blockForGeckoReady();
-    }
-
-    /**
-     * Verify that we can insert a reading list item into the DB.
-     */
-    private class TestInsertItems extends TestCase {
-        @Override
-        public void test() throws Exception {
-            ContentValues b = createFillerReadingListItem();
-            long id = ContentUris.parseId(mProvider.insert(CONTENT_URI, b));
-            Cursor c = getItemById(id);
-
-            try {
-                mAsserter.ok(c.moveToFirst(), "Inserted item found", "");
-                assertRowEqualsContentValues(c, b);
-
-                mAsserter.is(c.getInt(c.getColumnIndex(CONTENT_STATUS)),
-                             STATUS_UNFETCHED,
-                             "Inserted item has correct default content status");
-            } finally {
-                c.close();
-            }
-
-            testInsertWithNullCol(URL);
-            mContentProviderInsertTested = true;
-        }
-
-        /**
-         * Test that insertion fails when a required column
-         * is null.
-         */
-        private void testInsertWithNullCol(String colName) {
-            ContentValues b = createFillerReadingListItem();
-            b.putNull(colName);
-
-            try {
-                ContentUris.parseId(mProvider.insert(CONTENT_URI, b));
-                // If we get to here, the flawed insertion succeeded. Fail the test.
-                mAsserter.ok(false, "Insertion did not succeed with " + colName + " == null", "");
-            } catch (NullPointerException e) {
-                // Indicates test was successful.
-            }
-        }
-    }
-
-    /**
-     * Verify that we can remove a reading list item from the DB.
-     */
-    private class TestDeleteItems extends TestCase {
-
-        @Override
-        public void test() throws Exception {
-            final long one = insertAnItemWithAssertion();
-            final long two = insertAnItemWithAssertion();
-
-            assignGUID(one);
-
-            // Test that the item with a GUID is only marked as deleted and
-            // not removed from the database.
-            testNonSyncDelete(one, true);
-
-            // The item without a GUID is just deleted immediately.
-            testNonSyncDelete(two, false);
-
-            // Test that the item with a GUID is removed from the database when deleted by Sync.
-            testSyncDelete(one);
-
-            final long three = insertAnItemWithAssertion();
-
-            // Test that deleting works with only a URI.
-            testDeleteWithItemURI(three);
-        }
-
-        private void assignGUID(final long id) {
-            final ContentValues values = new ContentValues();
-            values.put(GUID, "abcdefghi");
-            mProvider.update(CONTENT_URI, values, _ID + " = " + id, null);
-        }
-
-        /**
-         * Delete an item with PARAM_IS_SYNC unset and verify that item was only marked
-         * as deleted and not actually removed from the database. Also verify that the item
-         * marked as deleted doesn't show up in a query.
-         *
-         * Note that items are deleted immediately if they don't have a GUID.
-         *
-         * @param id of the item to be deleted
-         * @param hasGUID if true, we expect the item to stick around and be marked.
-         */
-        private void testNonSyncDelete(long id, boolean hasGUID) {
-            final int deleted = mProvider.delete(CONTENT_URI,
-                                                 _ID + " = " + id,
-                                                 null);
-
-            mAsserter.is(deleted, 1, "Inserted item was deleted");
-
-            // PARAM_SHOW_DELETED in the URI allows items marked as deleted to be
-            // included in the query.
-            Uri uri = appendUriParam(CONTENT_URI, BrowserContract.PARAM_SHOW_DELETED, "1");
-            if (hasGUID) {
-                assertItemExistsByID(uri, id, "Deleted item was only marked as deleted");
-            } else {
-                assertItemDoesNotExistByID(uri, id, "Deleted item had no GUID, so was really deleted.");
-            }
-
-            // Test that the 'deleted' item does not show up in a query when PARAM_SHOW_DELETED
-            // is not specified in the URI.
-            assertItemDoesNotExistByID(id, "Inserted item can't be found after deletion");
-        }
-
-        /**
-         * Delete an item with PARAM_IS_SYNC=1 and verify that item
-         * was actually removed from the database.
-         *
-         * @param id of the item to be deleted
-         */
-        private void testSyncDelete(long id) {
-            final int deleted = mProvider.delete(appendUriParam(CONTENT_URI, BrowserContract.PARAM_IS_SYNC, "1"),
-                    _ID + " = " + id,
-                    null);
-
-            mAsserter.is(deleted, 1, "Inserted item was deleted");
-
-            Uri uri = appendUriParam(CONTENT_URI, BrowserContract.PARAM_SHOW_DELETED, "1");
-            assertItemDoesNotExistByID(uri, id, "Inserted item is now actually deleted");
-        }
-
-        /**
-         * Delete an item with its URI and verify that the item
-         * was actually removed from the database.
-         *
-         * @param id of the item to be deleted
-         */
-        private void testDeleteWithItemURI(long id) {
-            final int deleted = mProvider.delete(ContentUris.withAppendedId(CONTENT_URI, id), null, null);
-            mAsserter.is(deleted, 1, "Inserted item was deleted using URI with id");
-        }
-    }
-
-    /**
-     * Verify that we can update reading list items.
-     */
-    private class TestUpdateItems extends TestCase {
-
-        @Override
-        public void test() throws Exception {
-            // We should be able to insert into the DB.
-            ensureCanInsert();
-
-            ContentValues original = createFillerReadingListItem();
-            long id = ContentUris.parseId(mProvider.insert(CONTENT_URI, original));
-            int updated = 0;
-            Long originalDateCreated = null;
-            Long originalDateModified = null;
-            ContentValues updates = new ContentValues();
-            Cursor c = getItemById(id);
-            try {
-                mAsserter.ok(c.moveToFirst(), "Inserted item found", "");
-
-                originalDateCreated = c.getLong(c.getColumnIndex(ADDED_ON));
-                originalDateModified = c.getLong(c.getColumnIndex(CLIENT_LAST_MODIFIED));
-
-                updates.put(TITLE, original.getAsString(TITLE) + "CHANGED");
-                updates.put(URL, original.getAsString(URL) + "/more/stuff");
-                updates.put(EXCERPT, original.getAsString(EXCERPT) + "CHANGED");
-
-                updated = mProvider.update(CONTENT_URI, updates,
-                                           _ID + " = ?",
-                                           new String[] { String.valueOf(id) });
-
-                mAsserter.is(updated, 1, "Inserted item was updated");
-            } finally {
-                c.close();
-            }
-
-            // Name change for clarity. These values will be compared with the
-            // current cursor row.
-            final ContentValues expectedValues = updates;
-            c = getItemById(id);
-            try {
-                mAsserter.ok(c.moveToFirst(), "Updated item found", "");
-                mAsserter.isnot(c.getLong(c.getColumnIndex(CLIENT_LAST_MODIFIED)),
-                originalDateModified,
-                "Date modified should have changed");
-
-                // ADDED_ON shouldn't have changed.
-                expectedValues.put(ADDED_ON, originalDateCreated);
-                assertRowEqualsContentValues(c, expectedValues, /* compareDateModified */ false, TEST_COLUMNS);
-            } finally {
-                c.close();
-            }
-
-            // Test that updates on an item that doesn't exist does not modify any rows.
-            testUpdateWithInvalidID();
-
-            mContentProviderUpdateTested = true;
-        }
-
-        /**
-         * Test that updates on an item that doesn't exist does
-         * not modify any rows.
-         */
-        private void testUpdateWithInvalidID() {
-            ensureEmptyDatabase();
-            final ContentValues b = createFillerReadingListItem();
-            final long id = ContentUris.parseId(mProvider.insert(CONTENT_URI, b));
-            final long INVALID_ID = id + 1;
-            final ContentValues updates = new ContentValues();
-            updates.put(TITLE, b.getAsString(TITLE) + "CHANGED");
-            final int updated = mProvider.update(CONTENT_URI, updates,
-                                                 _ID + " = ?",
-                                                 new String[] { String.valueOf(INVALID_ID) });
-            mAsserter.is(updated, 0, "Should not be able to update item with an invalid ID");
-        }
-    }
-
-    private class TestBatchOperations extends TestCase {
-        private static final int ITEM_COUNT = 10;
-
-        /**
-         * Insert a bunch of items into the DB with the bulkInsert
-         * method and verify that they are there.
-         */
-        private void testBulkInsert() {
-            ensureEmptyDatabase();
-            final ContentValues allVals[] = new ContentValues[ITEM_COUNT];
-            final HashSet<String> urls = new HashSet<String>();
-            for (int i = 0; i < ITEM_COUNT; i++) {
-                final String url =  "http://www.test.org/" + i;
-                allVals[i] = new ContentValues();
-                allVals[i].put(TITLE, "Test" + i);
-                allVals[i].put(URL, url);
-                allVals[i].put(EXCERPT, "EXCERPT" + i);
-                urls.add(url);
-            }
-
-            int inserts = mProvider.bulkInsert(CONTENT_URI, allVals);
-            mAsserter.is(inserts, ITEM_COUNT, "Excepted number of inserts matches");
-
-            final Cursor c = mProvider.query(CONTENT_URI, null,
-                                             null,
-                                             null,
-                                             null);
-            try {
-                while (c.moveToNext()) {
-                    final String url = c.getString(c.getColumnIndex(URL));
-                    mAsserter.ok(urls.contains(url), "Bulk inserted item with url == " + url + " was found in the DB", "");
-                    // We should only be seeing each item once. Remove from set to prevent dups.
-                    urls.remove(url);
-                }
-            } finally {
-                c.close();
-            }
-        }
-
-        @Override
-        public void test() {
-            testBulkInsert();
-        }
-    }
-
-    /*
-     * Verify that insert, update, delete, and bulkInsert operations
-     * notify the ambient content resolver. Each operation calls the
-     * content resolver notifyChange method synchronously, so it is
-     * okay to test sequentially.
-     */
-    private class TestBrowserProviderNotifications extends TestCase {
-
-        @Override
-        public void test() {
-            // We should be able to insert into the DB.
-            ensureCanInsert();
-            // We should be able to update the DB.
-            ensureCanUpdate();
-
-            final String CONTENT_URI = ReadingListItems.CONTENT_URI.toString();
-
-            mResolver.notifyChangeList.clear();
-
-            // Insert
-            final ContentValues h = createFillerReadingListItem();
-            long id = ContentUris.parseId(mProvider.insert(ReadingListItems.CONTENT_URI, h));
-
-            mAsserter.isnot(id,
-                            -1L,
-                            "Inserted item has valid id");
-
-            ensureOnlyChangeNotifiedStartsWith(CONTENT_URI, "insert");
-
-            // Update
-            mResolver.notifyChangeList.clear();
-            h.put(TITLE, "http://newexample.com");
-
-            long numUpdated = mProvider.update(ReadingListItems.CONTENT_URI, h,
-                                               _ID + " = ?",
-                                               new String[] { String.valueOf(id) });
-
-            mAsserter.is(numUpdated,
-                         1L,
-                         "Correct number of items are updated");
-
-            ensureOnlyChangeNotifiedStartsWith(CONTENT_URI, "update");
-
-            // Delete
-            mResolver.notifyChangeList.clear();
-            long numDeleted = mProvider.delete(ReadingListItems.CONTENT_URI, null, null);
-
-            mAsserter.is(numDeleted,
-                         1L,
-                         "Correct number of items are deleted");
-
-            ensureOnlyChangeNotifiedStartsWith(CONTENT_URI, "delete");
-
-            // Bulk insert
-            mResolver.notifyChangeList.clear();
-            final ContentValues[] hs = { createFillerReadingListItem(),
-                                         createFillerReadingListItem(),
-                                         createFillerReadingListItem() };
-
-            long numBulkInserted = mProvider.bulkInsert(ReadingListItems.CONTENT_URI, hs);
-
-            mAsserter.is(numBulkInserted,
-                         3L,
-                         "Correct number of items are bulkInserted");
-
-            ensureOnlyChangeNotifiedStartsWith(CONTENT_URI, "bulkInsert");
-        }
-
-        protected void ensureOnlyChangeNotifiedStartsWith(String expectedUri, String operation) {
-            mAsserter.is(Long.valueOf(mResolver.notifyChangeList.size()),
-                         1L,
-                         "Content observer was notified exactly once by " + operation);
-
-            final Uri uri = mResolver.notifyChangeList.poll();
-
-            mAsserter.isnot(uri,
-                            null,
-                            "Notification from " + operation + " was valid");
-
-            mAsserter.ok(uri.toString().startsWith(expectedUri),
-                         "Content observer was notified exactly once by " + operation,
-                         "");
-        }
-    }
-
-    private class TestStateSequencing extends TestCase {
-        @Override
-        protected void test() throws Exception {
-            final ReadingListAccessor accessor = getTestProfile().getDB().getReadingListAccessor();
-            final ContentResolver cr = getActivity().getContentResolver();
-            final Uri syncURI = CONTENT_URI.buildUpon()
-                                           .appendQueryParameter(BrowserContract.PARAM_IS_SYNC, "1")
-                                           .appendQueryParameter(BrowserContract.PARAM_SHOW_DELETED, "1")
-                                           .build();
-
-            mAsserter.ok(accessor != null, "We have an accessor.", null);
-
-            // Verify that the accessor thinks we're empty.
-            mAsserter.ok(0 == accessor.getCount(cr), "We have no items.", null);
-
-            // Insert an item via the accessor.
-            final long addedItem = accessor.addBasicReadingListItem(cr, "http://example.org/", "Example A");
-
-            mAsserter.ok(1 == accessor.getCount(cr), "We have one item.", null);
-            final Cursor cursor = accessor.getReadingList(cr);
-            try {
-                mAsserter.ok(cursor.moveToNext(), "The cursor isn't empty.", null);
-                mAsserter.ok(1 == cursor.getCount(), "The cursor agrees.", null);
-            } finally {
-                cursor.close();
-            }
-
-            // Verify that it has no GUID, that its state is NEW, etc.
-            // This requires fetching more fields than the accessor uses.
-            final Cursor all = getEverything(syncURI);
-            try {
-                mAsserter.ok(all.moveToNext(), "The cursor isn't empty.", null);
-                mAsserter.ok(1 == all.getCount(), "The cursor agrees.", null);
-
-                ContentValues expected = new ContentValues();
-                expected.putNull(GUID);
-                expected.put(SYNC_STATUS, SYNC_STATUS_NEW);
-                expected.put(SYNC_CHANGE_FLAGS, SYNC_CHANGE_NONE);
-                expected.put(URL, "http://example.org/");
-                expected.put(TITLE, "Example A");
-                expected.putNull(RESOLVED_URL);
-                expected.putNull(RESOLVED_TITLE);
-
-                final String[] testColumns = {GUID, SYNC_STATUS, SYNC_CHANGE_FLAGS, URL, TITLE, RESOLVED_URL, RESOLVED_TITLE, URL, TITLE};
-                assertRowEqualsContentValues(all, expected, false, testColumns);
-            } finally {
-                all.close();
-            }
-
-            // Pretend that it was just synced.
-            final long serverTime = System.currentTimeMillis();
-            final ContentValues wasSynced = new ContentValues();
-            wasSynced.put(GUID, "eeeeeeeeeeeeee");
-            wasSynced.put(SYNC_STATUS, SYNC_STATUS_SYNCED);
-            wasSynced.put(SYNC_CHANGE_FLAGS, SYNC_CHANGE_NONE);
-            wasSynced.put(SERVER_STORED_ON, serverTime);
-            wasSynced.put(SERVER_LAST_MODIFIED, serverTime);
-
-            mAsserter.ok(1 == mProvider.update(syncURI, wasSynced, _ID + " = " + addedItem, null), "Updated one item.", null);
-            final Cursor afterSync = getEverything(syncURI);
-            try {
-                mAsserter.ok(afterSync.moveToNext(), "The cursor isn't empty.", null);
-                final String[] testColumns = {GUID, SYNC_STATUS, SYNC_CHANGE_FLAGS, SERVER_STORED_ON, SERVER_LAST_MODIFIED};
-                assertRowEqualsContentValues(afterSync, wasSynced, false, testColumns);
-            } finally {
-                afterSync.close();
-            }
-
-            // Make changes to the record that exercise the various change flags, verifying that
-            // the correct flags are set.
-            final long beforeMarkedRead = System.currentTimeMillis();
-            accessor.markAsRead(cr, addedItem);
-            final ContentValues markedAsRead = new ContentValues();
-            markedAsRead.put(GUID, "eeeeeeeeeeeeee");
-            markedAsRead.put(SYNC_STATUS, SYNC_STATUS_MODIFIED);
-            markedAsRead.put(SYNC_CHANGE_FLAGS, SYNC_CHANGE_UNREAD_CHANGED);
-            markedAsRead.put(IS_UNREAD, 0);
-
-            final Cursor afterMarkedRead = getEverything(syncURI);
-            try {
-                mAsserter.ok(afterMarkedRead.moveToNext(), "The cursor isn't empty.", null);
-                final String[] testColumns = {GUID, SYNC_STATUS, SYNC_CHANGE_FLAGS, IS_UNREAD};
-                assertRowEqualsContentValues(afterMarkedRead, markedAsRead, false, testColumns);
-                assertModifiedInRange(afterMarkedRead, beforeMarkedRead);
-            } finally {
-                afterMarkedRead.close();
-            }
-
-            // Now our content is here!
-            final long beforeContentUpdated = System.currentTimeMillis();
-            accessor.updateContent(cr, addedItem, "New title", "http://www.example.com/article", "The excerpt is long.");
-
-            // After this the content status should have changed, and we should be flagged to sync.
-            final ContentValues contentUpdated = new ContentValues();
-            contentUpdated.put(GUID, "eeeeeeeeeeeeee");
-            contentUpdated.put(SYNC_STATUS, SYNC_STATUS_MODIFIED);
-            contentUpdated.put(SYNC_CHANGE_FLAGS, SYNC_CHANGE_UNREAD_CHANGED | SYNC_CHANGE_RESOLVED);
-            contentUpdated.put(IS_UNREAD, 0);
-            contentUpdated.put(CONTENT_STATUS, STATUS_FETCHED_ARTICLE);
-
-            final Cursor afterContentUpdated = getEverything(syncURI);
-            try {
-                mAsserter.ok(afterContentUpdated.moveToNext(), "The cursor isn't empty.", null);
-                final String[] testColumns = {GUID, SYNC_STATUS, SYNC_CHANGE_FLAGS, IS_UNREAD, CONTENT_STATUS};
-                assertRowEqualsContentValues(afterContentUpdated, contentUpdated, false, testColumns);
-                assertModifiedInRange(afterContentUpdated, beforeContentUpdated);
-            } finally {
-                afterContentUpdated.close();
-            }
-
-            // Delete the record, and verify that its Sync state is DELETED.
-            final long beforeDeletion = System.currentTimeMillis();
-            accessor.deleteItem(cr, addedItem);
-            final ContentValues itemDeleted = new ContentValues();
-            itemDeleted.put(GUID, "eeeeeeeeeeeeee");
-            itemDeleted.put(SYNC_STATUS, SYNC_STATUS_DELETED);
-            itemDeleted.put(SYNC_CHANGE_FLAGS, SYNC_CHANGE_NONE);
-            // TODO: CONTENT_STATUS on deletion?
-
-            final Cursor afterDeletion = getEverything(syncURI);
-            try {
-                mAsserter.ok(afterDeletion.moveToNext(), "The cursor isn't empty.", null);
-                final String[] testColumns = {GUID, SYNC_STATUS, SYNC_CHANGE_FLAGS};
-                assertRowEqualsContentValues(afterDeletion, itemDeleted, false, testColumns);
-                assertModifiedInRange(afterDeletion, beforeDeletion);
-            } finally {
-                afterDeletion.close();
-            }
-
-            // The accessor will no longer return the record.
-            mAsserter.ok(0 == accessor.getCount(cr), "No items found.", null);
-
-            // Add a new record as Sync -- it should start in state SYNCED.
-            final ContentValues newRecord = new ContentValues();
-            final long newServerTime = System.currentTimeMillis() - 50000;
-            newRecord.put(GUID, "ffeeeeeeeeeeee");
-            newRecord.put(SYNC_STATUS, SYNC_STATUS_SYNCED);
-            newRecord.put(SYNC_CHANGE_FLAGS, SYNC_CHANGE_NONE);
-            newRecord.put(SERVER_STORED_ON, newServerTime);
-            newRecord.put(SERVER_LAST_MODIFIED, newServerTime);
-            newRecord.put(CLIENT_LAST_MODIFIED, System.currentTimeMillis());
-            newRecord.put(URL, "http://www.mozilla.org/");
-            newRecord.put(TITLE, "Mozilla");
-
-            final long newID = ContentUris.parseId(cr.insert(syncURI, newRecord));
-            mAsserter.ok(newID > 0, "New ID is greater than 0.", null);
-            mAsserter.ok(newID != addedItem, "New ID differs from last ID.", null);
-
-            final Cursor afterNewInsert = getEverything(syncURI);
-            try {
-                mAsserter.ok(afterNewInsert.moveToNext(), "The cursor isn't empty.", null);
-                mAsserter.ok(2 == afterNewInsert.getCount(), "The cursor has two rows.", null);
-
-                // Default sort order means newest first.
-                final String[] testColumns = {GUID, SYNC_STATUS, SYNC_CHANGE_FLAGS, SERVER_STORED_ON, SERVER_LAST_MODIFIED, CLIENT_LAST_MODIFIED, URL, TITLE};
-                assertRowEqualsContentValues(afterNewInsert, newRecord, false, testColumns);
-            } finally {
-                afterNewInsert.close();
-            }
-
-            // Make a change to it. Verify that it's now changed with the right flags.
-            final long beforeNewRead = System.currentTimeMillis();
-            accessor.markAsRead(cr, newID);
-            newRecord.put(SYNC_STATUS, SYNC_STATUS_MODIFIED);
-            newRecord.put(SYNC_CHANGE_FLAGS, SYNC_CHANGE_UNREAD_CHANGED);
-
-            final Cursor afterNewRead = getEverything(syncURI);
-            try {
-                mAsserter.ok(afterNewRead.moveToNext(), "The cursor isn't empty.", null);
-                mAsserter.ok(2 == afterNewRead.getCount(), "The cursor has two rows.", null);
-
-                // Default sort order means newest first.
-                final String[] testColumns = {GUID, SYNC_STATUS, SYNC_CHANGE_FLAGS, SERVER_STORED_ON, SERVER_LAST_MODIFIED, URL, TITLE};
-                assertRowEqualsContentValues(afterNewRead, newRecord, false, testColumns);
-                assertModifiedInRange(afterNewRead, beforeNewRead);
-            } finally {
-                afterNewRead.close();
-            }
-        }
-
-        private void assertModifiedInRange(Cursor cursor, long earliest) {
-            final long dbModified = cursor.getLong(cursor.getColumnIndexOrThrow(CLIENT_LAST_MODIFIED));
-            mAsserter.ok(dbModified >= earliest, "DB timestamp is at least as late as earliest.", null);
-            mAsserter.ok(dbModified <= System.currentTimeMillis(), "DB timestamp is earlier than now.", null);
-        }
-    }
-
-    /**
-     * Removes all items from the DB.
-     */
-    private void ensureEmptyDatabase() {
-        getWritableDatabase(CONTENT_URI).delete(TABLE_NAME, null, null);
-    }
-
-
-    private SQLiteDatabase getWritableDatabase(Uri uri) {
-        Uri testUri = appendUriParam(uri, BrowserContract.PARAM_IS_TEST, "1");
-        DelegatingTestContentProvider delegateProvider = (DelegatingTestContentProvider) mProvider;
-        ReadingListProvider readingListProvider = (ReadingListProvider) delegateProvider.getTargetProvider();
-        return readingListProvider.getWritableDatabaseForTesting(testUri);
-    }
-
-    /**
-     * Checks that the values in the cursor's current row match those
-     * in the ContentValues object.
-     *
-     * @param testColumns
-     * @param cursorWithActual over the row to be checked
-     * @param expectedValues to be checked
-     */
-    private void assertRowEqualsContentValues(Cursor cursorWithActual, ContentValues expectedValues, boolean compareDateModified, String[] testColumns) {
-        for (String column: testColumns) {
-            String expected = expectedValues.getAsString(column);
-            String actual = cursorWithActual.getString(cursorWithActual.getColumnIndex(column));
-            mAsserter.is(actual, expected, "Item has correct " + column);
-        }
-
-        if (compareDateModified) {
-            String expected = expectedValues.getAsString(CLIENT_LAST_MODIFIED);
-            String actual = cursorWithActual.getString(cursorWithActual.getColumnIndex(CLIENT_LAST_MODIFIED));
-            mAsserter.is(actual, expected, "Item has correct " + CLIENT_LAST_MODIFIED);
-        }
-    }
-
-    private void assertRowEqualsContentValues(Cursor cursorWithActual, ContentValues expectedValues) {
-        assertRowEqualsContentValues(cursorWithActual, expectedValues, true, TEST_COLUMNS);
-    }
-
-    private ContentValues fillContentValues(String title, String url, String excerpt) {
-        ContentValues values = new ContentValues();
-
-        values.put(TITLE, title);
-        values.put(URL, url);
-        values.put(EXCERPT, excerpt);
-        values.put(ADDED_ON, System.currentTimeMillis());
-
-        return values;
-    }
-
-    private ContentValues createFillerReadingListItem() {
-        Random rand = new Random();
-        return fillContentValues("Example", "http://example.com/?num=" + rand.nextInt(), "foo bar");
-    }
-
-    private Cursor getEverything(Uri uri) {
-        return mProvider.query(uri,
-                               null,
-                               null,
-                               null,
-                               null);
-    }
-
-    private Cursor getItemById(Uri uri, long id, String[] projection) {
-        return mProvider.query(uri, projection,
-                               _ID + " = ?",
-                               new String[] { String.valueOf(id) },
-                               null);
-    }
-
-    private Cursor getItemById(long id) {
-        return getItemById(CONTENT_URI, id, null);
-    }
-
-    private Cursor getItemById(Uri uri, long id) {
-        return getItemById(uri, id, null);
-    }
-
-    /**
-     * Verifies that ContentProvider insertions have been tested.
-     */
-    private void ensureCanInsert() {
-        if (!mContentProviderInsertTested) {
-            mAsserter.ok(false, "ContentProvider insertions have not been tested yet.", "");
-        }
-    }
-
-    /**
-     * Verifies that ContentProvider updates have been tested.
-     */
-    private void ensureCanUpdate() {
-        if (!mContentProviderUpdateTested) {
-            mAsserter.ok(false, "ContentProvider updates have not been tested yet.", "");
-        }
-    }
-
-    private long insertAnItemWithAssertion() {
-        // We should be able to insert into the DB.
-        ensureCanInsert();
-
-        ContentValues v = createFillerReadingListItem();
-        long id = ContentUris.parseId(mProvider.insert(CONTENT_URI, v));
-
-        assertItemExistsByID(id, "Inserted item found");
-        return id;
-    }
-
-    private void assertItemExistsByID(Uri uri, long id, String msg) {
-        Cursor c = getItemById(uri, id);
-        try {
-            mAsserter.ok(c.moveToFirst(), msg, "");
-        } finally {
-            c.close();
-        }
-    }
-
-    private void assertItemExistsByID(long id, String msg) {
-        Cursor c = getItemById(id);
-        try {
-            mAsserter.ok(c.moveToFirst(), msg, "");
-        } finally {
-            c.close();
-        }
-    }
-
-    private void assertItemDoesNotExistByID(long id, String msg) {
-        Cursor c = getItemById(id);
-        try {
-            mAsserter.ok(!c.moveToFirst(), msg, "");
-        } finally {
-            c.close();
-        }
-    }
-
-    private void assertItemDoesNotExistByID(Uri uri, long id, String msg) {
-        Cursor c = getItemById(uri, id);
-        try {
-            mAsserter.ok(!c.moveToFirst(), msg, "");
-        } finally {
-            c.close();
-        }
-    }
-}