Bug 1237880 - Remove code for syncing Reading List. r=rnewman
authorNick Alexander <nalexander@mozilla.com>
Tue, 19 Jan 2016 15:06:28 -0800
changeset 317900 31202b9037c8aac5e5b172c9a00337a0605880cf
parent 317899 ed222e27eb642c27d4d24dd437121f1047c97744
child 317901 9300568c634b2f76eb5893b3630e1efe76c7f49d
push id1079
push userjlund@mozilla.com
push dateFri, 15 Apr 2016 21:02:33 +0000
treeherdermozilla-release@575fbf6786d5 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersrnewman
bugs1237880
milestone46.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1237880 - Remove code for syncing Reading List. r=rnewman
configure.in
mobile/android/base/AppConstants.java.in
mobile/android/base/android-services.mozbuild
mobile/android/base/moz.build
mobile/android/services/src/main/java/org/mozilla/gecko/fxa/authenticator/AndroidFxAccount.java
mobile/android/services/src/main/java/org/mozilla/gecko/reading/ClientMetadata.java
mobile/android/services/src/main/java/org/mozilla/gecko/reading/ClientReadingListRecord.java
mobile/android/services/src/main/java/org/mozilla/gecko/reading/FetchSpec.java
mobile/android/services/src/main/java/org/mozilla/gecko/reading/LocalReadingListStorage.java
mobile/android/services/src/main/java/org/mozilla/gecko/reading/ReadingListBackoffObserver.java
mobile/android/services/src/main/java/org/mozilla/gecko/reading/ReadingListChangeAccumulator.java
mobile/android/services/src/main/java/org/mozilla/gecko/reading/ReadingListClient.java
mobile/android/services/src/main/java/org/mozilla/gecko/reading/ReadingListClientContentValuesFactory.java
mobile/android/services/src/main/java/org/mozilla/gecko/reading/ReadingListClientRecordFactory.java
mobile/android/services/src/main/java/org/mozilla/gecko/reading/ReadingListDeleteDelegate.java
mobile/android/services/src/main/java/org/mozilla/gecko/reading/ReadingListInvalidAuthenticationException.java
mobile/android/services/src/main/java/org/mozilla/gecko/reading/ReadingListRecord.java
mobile/android/services/src/main/java/org/mozilla/gecko/reading/ReadingListRecordDelegate.java
mobile/android/services/src/main/java/org/mozilla/gecko/reading/ReadingListRecordResponse.java
mobile/android/services/src/main/java/org/mozilla/gecko/reading/ReadingListRecordUploadDelegate.java
mobile/android/services/src/main/java/org/mozilla/gecko/reading/ReadingListResponse.java
mobile/android/services/src/main/java/org/mozilla/gecko/reading/ReadingListStorage.java
mobile/android/services/src/main/java/org/mozilla/gecko/reading/ReadingListStorageResponse.java
mobile/android/services/src/main/java/org/mozilla/gecko/reading/ReadingListSyncAdapter.java
mobile/android/services/src/main/java/org/mozilla/gecko/reading/ReadingListSyncService.java
mobile/android/services/src/main/java/org/mozilla/gecko/reading/ReadingListSynchronizer.java
mobile/android/services/src/main/java/org/mozilla/gecko/reading/ReadingListSynchronizerDelegate.java
mobile/android/services/src/main/java/org/mozilla/gecko/reading/ReadingListWipeDelegate.java
mobile/android/services/src/main/java/org/mozilla/gecko/reading/ServerReadingListRecord.java
--- a/configure.in
+++ b/configure.in
@@ -3795,17 +3795,16 @@ if test -n "$MOZ_RTSP"; then
 fi
 USE_ARM_KUSER=
 BUILD_CTYPES=1
 MOZ_USE_NATIVE_POPUP_WINDOWS=
 MOZ_ANDROID_HISTORY=
 MOZ_WEBSMS_BACKEND=
 MOZ_ANDROID_BEAM=
 MOZ_LOCALE_SWITCHER=
-MOZ_ANDROID_READING_LIST_SERVICE=
 MOZ_ANDROID_SEARCH_ACTIVITY=
 MOZ_ANDROID_DOWNLOADS_INTEGRATION=
 MOZ_ANDROID_GCM=
 MOZ_ANDROID_MLS_STUMBLER=
 MOZ_EXCLUDE_HYPHENATION_DICTIONARIES=
 MOZ_INSTALL_TRACKING=
 MOZ_SWITCHBOARD=
 ACCESSIBILITY=1
@@ -4874,23 +4873,16 @@ fi
 dnl ========================================================
 dnl = Include Search Activity on Android
 dnl ========================================================
 if test -n "$MOZ_ANDROID_SEARCH_ACTIVITY"; then
     AC_DEFINE(MOZ_ANDROID_SEARCH_ACTIVITY)
 fi
 
 dnl ========================================================
-dnl = Include Reading List service on Android
-dnl ========================================================
-if test -n "$MOZ_ANDROID_READING_LIST_SERVICE"; then
-    AC_DEFINE(MOZ_ANDROID_READING_LIST_SERVICE)
-fi
-
-dnl ========================================================
 dnl = Include Mozilla Location Service Stumbler on Android
 dnl ========================================================
 if test -n "$MOZ_ANDROID_MLS_STUMBLER"; then
     AC_DEFINE(MOZ_ANDROID_MLS_STUMBLER)
 fi
 
 dnl =========================================================
 dnl = Whether to exclude hyphenations files in the build
@@ -8537,17 +8529,16 @@ AC_SUBST(MOZ_D3DCOMPILER_XP_CAB)
 
 AC_SUBST(MOZ_ANDROID_HISTORY)
 AC_SUBST(MOZ_WEBSMS_BACKEND)
 AC_SUBST(MOZ_ANDROID_BEAM)
 AC_SUBST(MOZ_LOCALE_SWITCHER)
 AC_SUBST(MOZ_DISABLE_GECKOVIEW)
 AC_SUBST(MOZ_ANDROID_GCM)
 AC_SUBST(MOZ_ANDROID_GECKOLIBS_AAR)
-AC_SUBST(MOZ_ANDROID_READING_LIST_SERVICE)
 AC_SUBST(MOZ_ANDROID_SEARCH_ACTIVITY)
 AC_SUBST(MOZ_ANDROID_MLS_STUMBLER)
 AC_SUBST(MOZ_ANDROID_DOWNLOADS_INTEGRATION)
 AC_SUBST(MOZ_ANDROID_APPLICATION_CLASS)
 AC_SUBST(MOZ_ANDROID_BROWSER_INTENT_CLASS)
 AC_SUBST(MOZ_ANDROID_SEARCH_INTENT_CLASS)
 AC_SUBST(MOZ_ANDROID_DOWNLOAD_CONTENT_SERVICE)
 AC_SUBST(MOZ_EXCLUDE_HYPHENATION_DICTIONARIES)
--- a/mobile/android/base/AppConstants.java.in
+++ b/mobile/android/base/AppConstants.java.in
@@ -189,23 +189,16 @@ public class AppConstants {
 
     public static final boolean MOZ_SERVICES_HEALTHREPORT =
 //#ifdef MOZ_SERVICES_HEALTHREPORT
     true;
 //#else
     false;
 //#endif
 
-    public static final boolean MOZ_ANDROID_READING_LIST_SERVICE =
-//#ifdef MOZ_ANDROID_READING_LIST_SERVICE
-    true;
-//#else
-    false;
-//#endif
-
     public static final boolean MOZ_TELEMETRY_ON_BY_DEFAULT =
 //#ifdef MOZ_TELEMETRY_ON_BY_DEFAULT
     true;
 //#else
     false;
 //#endif
 
     public static final String TELEMETRY_PREF_NAME =
--- a/mobile/android/base/android-services.mozbuild
+++ b/mobile/android/base/android-services.mozbuild
@@ -1060,34 +1060,8 @@ sync_java_files = [TOPSRCDIR + '/mobile/
     'sync/UnexpectedJSONException.java',
     'sync/UnknownSynchronizerConfigurationVersionException.java',
     'sync/Utils.java',
     'tokenserver/TokenServerClient.java',
     'tokenserver/TokenServerClientDelegate.java',
     'tokenserver/TokenServerException.java',
     'tokenserver/TokenServerToken.java',
 ]]
-reading_list_service_java_files = [TOPSRCDIR + '/mobile/android/services/src/main/java/org/mozilla/gecko/' + x for x in [
-    'reading/ClientMetadata.java',
-    'reading/ClientReadingListRecord.java',
-    'reading/FetchSpec.java',
-    'reading/LocalReadingListStorage.java',
-    'reading/ReadingListBackoffObserver.java',
-    'reading/ReadingListChangeAccumulator.java',
-    'reading/ReadingListClient.java',
-    'reading/ReadingListClientContentValuesFactory.java',
-    'reading/ReadingListClientRecordFactory.java',
-    'reading/ReadingListDeleteDelegate.java',
-    'reading/ReadingListInvalidAuthenticationException.java',
-    'reading/ReadingListRecord.java',
-    'reading/ReadingListRecordDelegate.java',
-    'reading/ReadingListRecordResponse.java',
-    'reading/ReadingListRecordUploadDelegate.java',
-    'reading/ReadingListResponse.java',
-    'reading/ReadingListStorage.java',
-    'reading/ReadingListStorageResponse.java',
-    'reading/ReadingListSyncAdapter.java',
-    'reading/ReadingListSynchronizer.java',
-    'reading/ReadingListSynchronizerDelegate.java',
-    'reading/ReadingListSyncService.java',
-    'reading/ReadingListWipeDelegate.java',
-    'reading/ServerReadingListRecord.java',
-]]
--- a/mobile/android/base/moz.build
+++ b/mobile/android/base/moz.build
@@ -662,20 +662,16 @@ else:
 if max_sdk_version >= 11:
     gbjar.sources += ['java/org/mozilla/gecko/' + x for x in [
         'tabs/TabStrip.java',
         'tabs/TabStripAdapter.java',
         'tabs/TabStripItemView.java',
         'tabs/TabStripView.java'
     ]]
 
-# Selectively include reading list service code.
-if CONFIG['MOZ_ANDROID_READING_LIST_SERVICE']:
-    gbjar.sources += reading_list_service_java_files
-
 gbjar.extra_jars += [
     OBJDIR + '/../javaaddons/javaaddons-1.0.jar',
     'gecko-R.jar',
     'gecko-mozglue.jar',
     'gecko-thirdparty.jar',
     'gecko-util.jar',
     'sync-thirdparty.jar',
     'services.jar',
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/authenticator/AndroidFxAccount.java
+++ b/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/authenticator/AndroidFxAccount.java
@@ -82,19 +82,16 @@ public class AndroidFxAccount {
   // Services may request OAuth tokens from the Firefox Account dynamically.
   // Each such token is prefixed with "oauth::" and a service-dependent scope.
   // Such tokens should be destroyed when the account is removed from the device.
   // This list collects all the known "oauth::" token types in order to delete them when necessary.
   private static final List<String> KNOWN_OAUTH_TOKEN_TYPES;
 
   static {
     final List<String> list = new ArrayList<>();
-    if (AppConstants.MOZ_ANDROID_READING_LIST_SERVICE) {
-      list.add(ReadingListConstants.AUTH_TOKEN_TYPE);
-    }
     list.add(PROFILE_OAUTH_TOKEN_TYPE);
     KNOWN_OAUTH_TOKEN_TYPES = Collections.unmodifiableList(list);
   }
 
   public static final Map<String, Boolean> DEFAULT_AUTHORITIES_TO_SYNC_AUTOMATICALLY_MAP;
   static {
     final HashMap<String, Boolean> m = new HashMap<String, Boolean>();
     // By default, Firefox Sync is enabled.
deleted file mode 100644
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/reading/ClientMetadata.java
+++ /dev/null
@@ -1,19 +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.reading;
-
-public class ClientMetadata {
-  public final long id;                 // A client numeric ID. We don't always have a GUID.
-  public final long lastModified;       // A client timestamp.
-  public final boolean isDeleted;
-  public final boolean isArchived;
-
-  public ClientMetadata(final long id, final long lastModified, final boolean isDeleted, final boolean isArchived) {
-    this.id = id;
-    this.lastModified = lastModified;
-    this.isDeleted = isDeleted;
-    this.isArchived = isArchived;
-  }
-}
deleted file mode 100644
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/reading/ClientReadingListRecord.java
+++ /dev/null
@@ -1,79 +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.reading;
-
-import org.mozilla.gecko.sync.ExtendedJSONObject;
-
-public class ClientReadingListRecord extends ReadingListRecord {
-  final ExtendedJSONObject fields;
-  public ClientMetadata clientMetadata;
-
-  private String getDefaultAddedBy() {
-    return "Test Device";                // TODO
-  }
-
-  /**
-   * Provided `fields` are *not copied*.
-   */
-  public ClientReadingListRecord(final ServerMetadata serverMetadata, final ClientMetadata clientMetadata, final ExtendedJSONObject fields) {
-    super(serverMetadata);
-    this.clientMetadata = clientMetadata == null ? new ClientMetadata(-1L, -1L, false, false) : clientMetadata;
-    this.fields = fields;
-  }
-
-  public ClientReadingListRecord(String url, String title, String addedBy) {
-    this(url, title, addedBy, System.currentTimeMillis(), false, false);
-  }
-
-  public ClientReadingListRecord(String url, String title, String addedBy, long lastModified, boolean isDeleted, boolean isArchived) {
-    super(null);
-
-    // Required.
-    if (url == null) {
-      throw new IllegalArgumentException("url must be provided.");
-    }
-
-    final ExtendedJSONObject f = new ExtendedJSONObject();
-    f.put("url", url);
-    f.put("title", title == null ? "" : title);
-    f.put("added_by", addedBy == null ? getDefaultAddedBy() : addedBy);
-
-    this.fields = f;
-    this.clientMetadata = new ClientMetadata(-1L, lastModified, isDeleted, isArchived);
-  }
-
-  public ExtendedJSONObject toJSON() {
-    final ExtendedJSONObject object = this.fields.deepCopy();
-    final String guid = getGUID();
-
-    if (guid != null) {
-      object.put("id", guid);
-    }
-    return object;
-  }
-
-  @Override
-  public String getAddedBy() {
-    return this.fields.getString("added_by");
-  }
-
-  @Override
-  public String getURL() {
-    return this.fields.getString("url");    // TODO: resolved_url
-  }
-
-  @Override
-  public String getTitle() {
-    return this.fields.getString("title");  // TODO: resolved_title
-  }
-
-  /**
-   * Produce a record just like the server record, but with the
-   * appropriate additional metadata, such as the local numeric ID.
-   */
-  public ClientReadingListRecord givenServerRecord(ServerReadingListRecord down) {
-    return new ClientReadingListRecord(down.serverMetadata, this.clientMetadata, down.fields);
-  }
-}
\ No newline at end of file
deleted file mode 100644
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/reading/FetchSpec.java
+++ /dev/null
@@ -1,99 +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.reading;
-
-import java.net.URI;
-import java.net.URISyntaxException;
-
-import ch.boye.httpclientandroidlib.client.utils.URIBuilder;
-
-/**
- * Defines the parameters that can be added to a reading list fetch URI.
- */
-public class FetchSpec {
-  private final String queryString;
-
-  private FetchSpec(final String q) {
-    this.queryString = q;
-  }
-
-  public URI getURI(final URI serviceURI) throws URISyntaxException {
-    return new URIBuilder(serviceURI).setCustomQuery(queryString).build();
-  }
-
-  public URI getURI(final URI serviceURI, final String path) throws URISyntaxException {
-    final String currentPath = serviceURI.getPath();
-    final String newPath = (currentPath == null ? "" : currentPath) + path;
-    return new URIBuilder(serviceURI).setPath(newPath)
-                                     .setCustomQuery(queryString)
-                                     .build();
-  }
-
-  public static class Builder {
-    final StringBuilder b = new StringBuilder();
-    boolean first = true;
-
-    public FetchSpec build() {
-      return new FetchSpec(b.toString());
-    }
-
-    private void ampersand() {
-      if (first) {
-        first = false;
-        return;
-      }
-      b.append('&');
-    }
-
-    public Builder setUnread(boolean unread) {
-      ampersand();
-      b.append("unread=");
-      b.append(unread);
-      return this;
-    }
-
-    private void qualifyAttribute(String qual, String attr) {
-      ampersand();
-      b.append(qual);
-      b.append(attr);
-      b.append('=');
-    }
-
-    public Builder setMinAttribute(String attr, int val) {
-      qualifyAttribute("min_", attr);
-      b.append(val);
-      return this;
-    }
-
-    public Builder setMaxAttribute(String attr, int val) {
-      qualifyAttribute("max_", attr);
-      b.append(val);
-      return this;
-    }
-
-    public Builder setNotAttribute(String attr, String val) {
-      qualifyAttribute("not_", attr);
-      b.append(val);
-      return this;
-    }
-
-    public Builder setSince(long since) {
-      if (since == -1L) {
-        return this;
-      }
-
-      ampersand();
-      b.append("_since=");
-      b.append(since);
-      return this;
-    }
-
-    public Builder setExcludeDeleted() {
-      ampersand();
-      b.append("not_deleted=true");
-      return this;
-    }
-  }
-}
\ No newline at end of file
deleted file mode 100644
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/reading/LocalReadingListStorage.java
+++ /dev/null
@@ -1,436 +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.reading;
-
-import static org.mozilla.gecko.db.BrowserContract.ReadingListItems.SYNC_CHANGE_FAVORITE_CHANGED;
-import static org.mozilla.gecko.db.BrowserContract.ReadingListItems.SYNC_CHANGE_FLAGS;
-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 java.util.ArrayList;
-import java.util.Queue;
-import java.util.concurrent.ConcurrentLinkedQueue;
-
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.db.BrowserContract;
-import org.mozilla.gecko.db.BrowserContract.ReadingListItems;
-import org.mozilla.gecko.sync.repositories.android.RepoUtils;
-
-import android.content.ContentProviderClient;
-import android.content.ContentProviderOperation;
-import android.content.ContentProviderResult;
-import android.content.ContentValues;
-import android.content.OperationApplicationException;
-import android.database.Cursor;
-import android.net.Uri;
-import android.os.RemoteException;
-
-public class LocalReadingListStorage implements ReadingListStorage {
-
-  private static final String WHERE_STATUS_NEW = "(" + SYNC_STATUS + " = " + SYNC_STATUS_NEW + ")";
-
-  final class LocalReadingListChangeAccumulator implements ReadingListChangeAccumulator {
-    private static final String LOG_TAG = "RLChanges";
-
-    /**
-     * These are changes that result from uploading new or changed records to the server.
-     * They always correspond to local records.
-     */
-    private final Queue<ClientReadingListRecord> changes;
-
-    /**
-     * These are deletions that result from uploading new or changed records to the server.
-     * They should always correspond to local records.
-     * These are not common: they should only occur if a conflict occurs.
-     */
-    private final Queue<ClientReadingListRecord> deletions;
-    private final Queue<String> deletedGUIDs;
-
-    /**
-     * These are additions or changes fetched from the server.
-     * At the point of collection we don't know if they're records
-     * that exist locally.
-     *
-     * Batching these here, rather than in the client or the synchronizer,
-     * puts the storage implementation in control of when batches are flushed,
-     * or if batches are used at all.
-     */
-    private final Queue<ServerReadingListRecord> additionsOrChanges;
-
-    LocalReadingListChangeAccumulator() {
-      this.changes = new ConcurrentLinkedQueue<>();
-      this.deletions = new ConcurrentLinkedQueue<>();
-      this.deletedGUIDs = new ConcurrentLinkedQueue<>();
-      this.additionsOrChanges = new ConcurrentLinkedQueue<>();
-    }
-
-    public boolean flushDeletions() throws RemoteException {
-      if (deletions.isEmpty() && deletedGUIDs.isEmpty()) {
-        return true;
-      }
-
-      long[] ids = new long[deletions.size()];
-      String[] guids = new String[deletions.size() + deletedGUIDs.size()];
-      int iID = 0;
-      int iGUID = 0;
-      for (ClientReadingListRecord record : deletions) {
-        if (record.clientMetadata.id > -1L) {
-          ids[iID++] = record.clientMetadata.id;
-        } else {
-          final String guid = record.getGUID();
-          if (guid == null) {
-            continue;
-          }
-          guids[iGUID++] = guid;
-        }
-      }
-      for (String guid : deletedGUIDs) {
-        guids[iGUID++] = guid;
-      }
-
-      if (iID > 0) {
-        client.delete(URI_WITH_DELETED, RepoUtils.computeSQLLongInClause(ids, ReadingListItems._ID), null);
-      }
-
-      if (iGUID > 0) {
-        client.delete(URI_WITH_DELETED, RepoUtils.computeSQLInClause(iGUID, ReadingListItems.GUID), guids);
-      }
-
-      deletions.clear();
-      deletedGUIDs.clear();
-      return true;
-    }
-
-    public boolean flushRecordChanges() throws RemoteException {
-      if (changes.isEmpty() && additionsOrChanges.isEmpty()) {
-        return true;
-      }
-
-      // For each returned record, apply it to the local store and clear all sync flags.
-      // We can do this because the server always returns the entire record.
-      //
-      // <https://github.com/mozilla-services/readinglist/issues/138> tracks not doing so
-      // for certain patches, which allows us to optimize here.
-      ArrayList<ContentProviderOperation> operations = new ArrayList<>(changes.size() + additionsOrChanges.size());
-      for (ClientReadingListRecord rec : changes) {
-        operations.add(makeUpdateOp(rec));
-      }
-
-      for (ServerReadingListRecord rec : additionsOrChanges) {
-        // TODO: skip records for which the local copy of the server timestamp
-        // matches the timestamp in the incoming record.
-        // TODO: we can do this by maintaining a lookup table, rather
-        // than hitting the DB. When we do an insert after an upload, say, we
-        // can make a note of it so the next download flush doesn't apply it twice.
-        operations.add(makeUpdateOrInsertOp(rec));
-      }
-
-      // TODO: tell delegate of success or failure.
-      try {
-        Logger.debug(LOG_TAG, "Applying " + operations.size() + " operations.");
-        ContentProviderResult[] results = client.applyBatch(operations);
-      } catch (OperationApplicationException e) {
-        // Oops.
-        Logger.warn(LOG_TAG, "Applying operations failed.", e);
-        return false;
-      }
-      return true;
-    }
-
-    private ContentProviderOperation makeUpdateOrInsertOp(ServerReadingListRecord rec) throws RemoteException {
-      final ClientReadingListRecord clientRec = new ClientReadingListRecord(rec.serverMetadata, null, rec.fields);
-
-      // TODO: use UPDATE OR INSERT equivalent, rather than querying here.
-      if (hasGUID(rec.serverMetadata.guid)) {
-        return makeUpdateOp(clientRec);
-      }
-
-      final ContentValues values = ReadingListClientContentValuesFactory.fromClientRecord(clientRec);
-      return ContentProviderOperation.newInsert(URI_WITHOUT_DELETED)
-                                     .withValues(values)
-                                     .build();
-    }
-
-    private ContentProviderOperation makeUpdateOp(ClientReadingListRecord rec) {
-      // We can't use SQLiteQueryBuilder, because it can't do UPDATE,
-      // nor can it give us a WHERE clause.
-      final StringBuilder selection = new StringBuilder();
-      final String[] selectionArgs;
-
-      // We don't apply changes that we've already applied.
-      // We know they've already been applied because our local copy of the
-      // server's version code/timestamp matches the value in the incoming record.
-      long serverLastModified = rec.getServerLastModified();
-      if (serverLastModified != -1L) {
-        // This should always be the case here.
-        selection.append("(" + ReadingListItems.SERVER_LAST_MODIFIED + " IS NOT ");
-        selection.append(serverLastModified);
-        selection.append(") AND ");
-      }
-
-      if (rec.clientMetadata.id > -1L) {
-        selection.append("(");
-        selection.append(ReadingListItems._ID + " = ");
-        selection.append(rec.clientMetadata.id);
-        selection.append(")");
-        selectionArgs = null;
-      } else if (rec.serverMetadata.guid != null) {
-        selection.append("(" + ReadingListItems.GUID + " = ?)");
-        selectionArgs = new String[] { rec.serverMetadata.guid };
-      } else {
-        final String url = rec.fields.getString("url");
-        final String resolvedURL = rec.fields.getString("resolved_url");
-
-        if (url == null && resolvedURL == null) {
-          // We're outta luck.
-          return null;
-        }
-
-        selection.append("((" + ReadingListItems.URL + " = ?) OR (" + ReadingListItems.RESOLVED_URL + " = ?))");
-        if (url != null && resolvedURL != null) {
-          selectionArgs = new String[] { url, resolvedURL };
-        } else {
-          final String arg = url == null ? resolvedURL : url;
-          selectionArgs = new String[] { arg, arg };
-        }
-      }
-
-      final ContentValues values = ReadingListClientContentValuesFactory.fromClientRecord(rec);
-      return ContentProviderOperation.newUpdate(URI_WITHOUT_DELETED)
-                                     .withSelection(selection.toString(), selectionArgs)
-                                     .withValues(values)
-                                     .build();
-    }
-
-    @Override
-    public void finish() throws Exception {
-      flushDeletions();
-      flushRecordChanges();
-    }
-
-    @Override
-    public void addDeletion(ClientReadingListRecord record) {
-      deletions.add(record);
-    }
-
-    @Override
-    public void addDeletion(String guid) {
-      deletedGUIDs.add(guid);
-    }
-
-    @Override
-    public void addChangedRecord(ClientReadingListRecord record) {
-      changes.add(record);
-    }
-
-    @Override
-    public void addDownloadedRecord(ServerReadingListRecord down) {
-      final Boolean deleted = down.fields.getBoolean("deleted");
-      if (deleted != null && deleted.booleanValue()) {
-        addDeletion(down.getGUID());
-      } else {
-        additionsOrChanges.add(down);
-      }
-    }
-  }
-
-  private final ContentProviderClient client;
-  private final Uri URI_WITHOUT_DELETED = BrowserContract.READING_LIST_AUTHORITY_URI
-      .buildUpon()
-      .appendPath("items")
-      .appendQueryParameter(BrowserContract.PARAM_IS_SYNC, "1")
-      .appendQueryParameter(BrowserContract.PARAM_SHOW_DELETED, "0")
-      .build();
-
-  private final Uri URI_WITH_DELETED = BrowserContract.READING_LIST_AUTHORITY_URI
-      .buildUpon()
-      .appendPath("items")
-      .appendQueryParameter(BrowserContract.PARAM_IS_SYNC, "1")
-      .appendQueryParameter(BrowserContract.PARAM_SHOW_DELETED, "1")
-      .build();
-
-  public LocalReadingListStorage(final ContentProviderClient client) {
-    this.client = client;
-  }
-
-  public boolean hasGUID(String guid) throws RemoteException {
-    final String[] projection = new String[] { ReadingListItems.GUID };
-    final String selection = ReadingListItems.GUID + " = ?";
-    final String[] selectionArgs = new String[] { guid };
-    final Cursor cursor = this.client.query(URI_WITHOUT_DELETED, projection, selection, selectionArgs, null);
-    try {
-      return cursor.moveToFirst();
-    } finally {
-      cursor.close();
-    }
-  }
-
-  public Cursor getModifiedWithSelection(final String selection) {
-    final String[] projection = new String[] {
-      ReadingListItems.GUID,
-      ReadingListItems.IS_FAVORITE,
-      ReadingListItems.RESOLVED_TITLE,
-      ReadingListItems.RESOLVED_URL,
-      ReadingListItems.EXCERPT,
-      // TODO: ReadingListItems.IS_ARTICLE,
-      // TODO: ReadingListItems.WORD_COUNT,
-    };
-
-    try {
-      return client.query(URI_WITHOUT_DELETED, projection, selection, null, null);
-    } catch (RemoteException e) {
-      throw new IllegalStateException(e);
-    }
-  }
-
-  @Override
-  public Cursor getModified() {
-    final String selection = ReadingListItems.SYNC_STATUS + " = " + ReadingListItems.SYNC_STATUS_MODIFIED;
-    return getModifiedWithSelection(selection);
-  }
-
-  // Return changed items that aren't just status changes.
-  // This isn't necessary because we insist on processing status changes before modified items.
-  // Currently we only need this for tests...
-  public Cursor getNonStatusModified() {
-    final String selection = ReadingListItems.SYNC_STATUS + " = " + ReadingListItems.SYNC_STATUS_MODIFIED +
-                             " AND ((" + ReadingListItems.SYNC_CHANGE_FLAGS + " & " + ReadingListItems.SYNC_CHANGE_RESOLVED + ") > 0)";
-
-    return getModifiedWithSelection(selection);
-  }
-
-  // These will never conflict (in the case of unread status changes), or
-  // we don't care if they overwrite the server value (in the case of favorite changes).
-  // N.B., don't actually send each field if the appropriate change flag isn't set!
-  @Override
-  public Cursor getStatusChanges() {
-    final String[] projection = new String[] {
-      ReadingListItems.GUID,
-      ReadingListItems.IS_FAVORITE,
-      ReadingListItems.IS_UNREAD,
-      ReadingListItems.MARKED_READ_BY,
-      ReadingListItems.MARKED_READ_ON,
-      ReadingListItems.SYNC_CHANGE_FLAGS,
-    };
-
-    final String selection =
-        SYNC_STATUS + " = " + SYNC_STATUS_MODIFIED + " AND " +
-        "((" + SYNC_CHANGE_FLAGS + " & (" + SYNC_CHANGE_UNREAD_CHANGED + " | " + SYNC_CHANGE_FAVORITE_CHANGED + ")) > 0)";
-
-    try {
-      return client.query(URI_WITHOUT_DELETED, projection, selection, null, null);
-    } catch (RemoteException e) {
-      throw new IllegalStateException(e);
-    }
-  }
-
-  @Override
-  public Cursor getDeletedItems() {
-    final String[] projection = new String[] {
-      ReadingListItems.GUID,
-    };
-
-    final String selection = "(" + ReadingListItems.IS_DELETED + " = 1) AND (" + ReadingListItems.GUID + " IS NOT NULL)";
-    try {
-      return client.query(URI_WITH_DELETED, projection, selection, null, null);
-    } catch (RemoteException e) {
-      throw new IllegalStateException(e);
-    }
-  }
-
-  @Override
-  public Cursor getNew() {
-    // N.B., query for items that have no GUID, regardless of status.
-    // They should all be marked as NEW, but belt and braces.
-    final String selection = WHERE_STATUS_NEW + " OR (" + ReadingListItems.GUID + " IS NULL)";
-
-    try {
-      return client.query(URI_WITHOUT_DELETED, null, selection, null, null);
-    } catch (RemoteException e) {
-      throw new IllegalStateException(e);
-    }
-  }
-
-  @Override
-  public Cursor getAll() {
-    try {
-      return client.query(URI_WITHOUT_DELETED, null, null, null, null);
-    } catch (RemoteException e) {
-      throw new IllegalStateException(e);
-    }
-  }
-
-  private ContentProviderOperation updateAddedByNames(final String local) {
-    String[] selectionArgs = new String[] {"$local"};
-    String selection = WHERE_STATUS_NEW + " AND (" + ReadingListItems.ADDED_BY + " = ?)";
-    return ContentProviderOperation.newUpdate(URI_WITHOUT_DELETED)
-                                   .withValue(ReadingListItems.ADDED_BY, local)
-                                   .withSelection(selection, selectionArgs)
-                                   .build();
-  }
-
-  private ContentProviderOperation updateMarkedReadByNames(final String local) {
-    String[] selectionArgs = new String[] {"$local"};
-    String selection = ReadingListItems.MARKED_READ_BY + " = ?";
-    return ContentProviderOperation.newUpdate(URI_WITHOUT_DELETED)
-                                   .withValue(ReadingListItems.MARKED_READ_BY, local)
-                                   .withSelection(selection, selectionArgs)
-                                   .build();
-  }
-
-  /**
-   * Consumers of the reading list provider don't know the device name.
-   * Rather than smearing that logic into callers, or requiring the database
-   * to be able to figure out the name of the device, we have the SyncAdapter
-   * do it.
-   *
-   * After all, the SyncAdapter knows everything -- prefs, channels, profiles,
-   * Firefox Account details, etc.
-   *
-   * To allow this, the CP writes the magic string "$local" wherever a device
-   * name is needed. Here in storage, we run a quick UPDATE pass prior to
-   * synchronizing, so the device name is 'calcified' at the time of the first
-   * sync of that record. The SyncAdapter calls this prior to invoking the
-   * synchronizer.
-   */
-  public void updateLocalNames(final String local) {
-    ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>(2);
-    ops.add(updateAddedByNames(local));
-    ops.add(updateMarkedReadByNames(local));
-
-    try {
-      client.applyBatch(ops);
-    } catch (RemoteException e) {
-      return;
-    } catch (OperationApplicationException e) {
-      return;
-    }
-  }
-
-  @Override
-  public ReadingListChangeAccumulator getChangeAccumulator() {
-    return new LocalReadingListChangeAccumulator();
-  }
-
-  /**
-   * Unused: we implicitly do this when we apply the server record.
-   */
-  /*
-  public void markStatusChangedItemsAsSynced(Collection<String> uploaded) {
-    ContentValues values = new ContentValues();
-    values.put(ReadingListItems.SYNC_CHANGE_FLAGS, ReadingListItems.SYNC_CHANGE_NONE);
-    values.put(ReadingListItems.SYNC_STATUS, ReadingListItems.SYNC_STATUS_SYNCED);
-    final String where = RepoUtils.computeSQLInClause(uploaded.size(), ReadingListItems.GUID);
-    final String[] args = uploaded.toArray(new String[uploaded.size()]);
-    try {
-      client.update(URI_WITHOUT_DELETED, values, where, args);
-    } catch (RemoteException e) {
-      // Nothing we can do.
-    }
-  }
-  */
-}
deleted file mode 100644
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/reading/ReadingListBackoffObserver.java
+++ /dev/null
@@ -1,54 +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.reading;
-
-import java.util.concurrent.atomic.AtomicLong;
-
-import org.mozilla.gecko.sync.Utils;
-import org.mozilla.gecko.sync.net.HttpResponseObserver;
-import org.mozilla.gecko.sync.net.MozResponse;
-
-import ch.boye.httpclientandroidlib.HttpResponse;
-import ch.boye.httpclientandroidlib.client.methods.HttpUriRequest;
-
-public class ReadingListBackoffObserver implements HttpResponseObserver {
-  protected final String host;
-  protected final AtomicLong largestBackoffObservedInSeconds = new AtomicLong(-1);
-
-  public ReadingListBackoffObserver(String host) {
-    this.host = host;
-    Utils.throwIfNull(host);
-  }
-
-  @Override
-  public void observeHttpResponse(HttpUriRequest request, HttpResponse response) {
-    // Ignore non-Reading List storage requests.
-    if (!host.equals(request.getURI().getHost())) {
-      return;
-    }
-
-    final MozResponse res = new MozResponse(response);
-    long backoffInSeconds = -1;
-    try {
-      backoffInSeconds = Math.max(res.backoffInSeconds(), res.retryAfterInSeconds());
-    } catch (NumberFormatException e) {
-      // Ignore.
-    }
-
-    if (backoffInSeconds <= 0) {
-      return;
-    }
-
-    while (true) {
-      long existingBackoff = largestBackoffObservedInSeconds.get();
-      if (existingBackoff >= backoffInSeconds) {
-        return;
-      }
-      if (largestBackoffObservedInSeconds.compareAndSet(existingBackoff, backoffInSeconds)) {
-        return;
-      }
-    }
-  }
-}
deleted file mode 100644
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/reading/ReadingListChangeAccumulator.java
+++ /dev/null
@@ -1,20 +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.reading;
-
-/**
- * Grab one of these, then you can add records to it by parsing
- * server responses. Finishing it will flush those changes (e.g.,
- * via UPDATE) to the DB.
- */
-public interface ReadingListChangeAccumulator {
-  void addDeletion(String guid);
-  void addDeletion(ClientReadingListRecord record);
-
-  // addChangedRecord is also used to apply the server's reconciliation results after upload.
-  void addChangedRecord(ClientReadingListRecord record);
-  void addDownloadedRecord(ServerReadingListRecord down);
-  void finish() throws Exception;
-}
deleted file mode 100644
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/reading/ReadingListClient.java
+++ /dev/null
@@ -1,689 +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.reading;
-
-import java.io.IOException;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.security.GeneralSecurityException;
-import java.util.Queue;
-import java.util.concurrent.Executor;
-
-import org.mozilla.gecko.background.ReadingListConstants;
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.reading.ReadingListResponse.ResponseFactory;
-import org.mozilla.gecko.sync.ExtendedJSONObject;
-import org.mozilla.gecko.sync.net.AuthHeaderProvider;
-import org.mozilla.gecko.sync.net.BaseResource;
-import org.mozilla.gecko.sync.net.BaseResourceDelegate;
-import org.mozilla.gecko.sync.net.BasicAuthHeaderProvider;
-import org.mozilla.gecko.sync.net.MozResponse;
-import org.mozilla.gecko.sync.net.Resource;
-
-import ch.boye.httpclientandroidlib.HttpResponse;
-import ch.boye.httpclientandroidlib.client.ClientProtocolException;
-import ch.boye.httpclientandroidlib.client.methods.HttpRequestBase;
-import ch.boye.httpclientandroidlib.impl.client.DefaultHttpClient;
-
-/**
- * This client exposes an API for the reading list service, documented at
- * https://github.com/mozilla-services/readinglist/
- */
-public class ReadingListClient {
-  static final String LOG_TAG = ReadingListClient.class.getSimpleName();
-  private final AuthHeaderProvider auth;
-
-  private final URI articlesURI;              // .../articles
-  private final URI articlesBaseURI;          // .../articles/
-
-  /**
-   * Use a {@link BasicAuthHeaderProvider} for testing, and an FxA OAuth provider for the real service.
-   */
-  public ReadingListClient(final URI serviceURI, final AuthHeaderProvider auth) {
-    this.articlesURI = serviceURI.resolve("articles");
-    this.articlesBaseURI = serviceURI.resolve("articles/");
-    this.auth = auth;
-  }
-
-  private BaseResource getRelativeArticleResource(final String rel) {
-    return new BaseResource(this.articlesBaseURI.resolve(rel));
-  }
-
-  private static final class DelegatingUploadResourceDelegate extends UploadResourceDelegate<ReadingListRecordResponse> {
-    private final ClientReadingListRecord         up;
-    private final ReadingListRecordUploadDelegate uploadDelegate;
-
-    DelegatingUploadResourceDelegate(Resource resource,
-                                     AuthHeaderProvider auth,
-                                     ResponseFactory<ReadingListRecordResponse> factory,
-                                     ClientReadingListRecord up,
-                                     ReadingListRecordUploadDelegate uploadDelegate) {
-      super(resource, auth, factory);
-      this.up = up;
-      this.uploadDelegate = uploadDelegate;
-    }
-
-    @Override
-    void onFailure(MozResponse response) {
-      Logger.warn(LOG_TAG, "Upload got failure response " + response.httpResponse().getStatusLine());
-      response.logResponseBody(LOG_TAG);
-      if (response.getStatusCode() == 400) {
-        // Error response.
-        uploadDelegate.onBadRequest(up, response);
-      } else {
-        uploadDelegate.onFailure(up, response);
-      }
-    }
-
-    @Override
-    void onFailure(Exception ex) {
-      Logger.warn(LOG_TAG, "Upload failed.", ex);
-      uploadDelegate.onFailure(up, ex);
-    }
-
-    @Override
-    void onSuccess(ReadingListRecordResponse response) {
-      Logger.debug(LOG_TAG, "Upload: onSuccess: " + response.httpResponse().getStatusLine());
-      final ServerReadingListRecord down;
-      try {
-        down = response.getRecord();
-        Logger.debug(LOG_TAG, "Upload succeeded. Got GUID " + down.getGUID());
-      } catch (Exception e) {
-        uploadDelegate.onFailure(up, e);
-        return;
-      }
-
-      uploadDelegate.onSuccess(up, response, down);
-    }
-
-    @Override
-    void onSeeOther(ReadingListRecordResponse response) {
-      uploadDelegate.onConflict(up, response);
-    }
-  }
-
-  private static abstract class ReadingListResourceDelegate<T extends ReadingListResponse> extends BaseResourceDelegate {
-    private final ReadingListResponse.ResponseFactory<T> factory;
-    private final AuthHeaderProvider auth;
-
-    public ReadingListResourceDelegate(Resource resource, AuthHeaderProvider auth, ReadingListResponse.ResponseFactory<T> factory) {
-      super(resource);
-      this.auth = auth;
-      this.factory = factory;
-    }
-
-    abstract void onSuccess(T response);
-    abstract void onNonSuccess(T response);
-    abstract void onFailure(MozResponse response);
-    abstract void onFailure(Exception ex);
-
-    @Override
-    public void handleHttpResponse(HttpResponse response) {
-      final T resp = factory.getResponse(response);
-      if (resp.wasSuccessful()) {
-        onSuccess(resp);
-      } else {
-        onNonSuccess(resp);
-      }
-    }
-
-    @Override
-    public void handleTransportException(GeneralSecurityException e) {
-      onFailure(e);
-    }
-
-    @Override
-    public void handleHttpProtocolException(ClientProtocolException e) {
-      onFailure(e);
-    }
-
-    @Override
-    public void handleHttpIOException(IOException e) {
-      onFailure(e);
-    }
-
-    @Override
-    public String getUserAgent() {
-      return ReadingListConstants.USER_AGENT;
-    }
-
-    @Override
-    public AuthHeaderProvider getAuthHeaderProvider() {
-      return auth;
-    }
-
-    @Override
-    public void addHeaders(HttpRequestBase request, DefaultHttpClient client) {
-    }
-  }
-
-  /**
-   * An intermediate delegate class that handles all of the shared storage behavior,
-   * such as handling If-Modified-Since.
-   */
-  private static abstract class StorageResourceDelegate<T extends ReadingListResponse> extends ReadingListResourceDelegate<T> {
-    private final long ifModifiedSince;
-
-    public StorageResourceDelegate(Resource resource,
-                                   AuthHeaderProvider auth,
-                                   ReadingListResponse.ResponseFactory<T> factory,
-                                   long ifModifiedSince) {
-      super(resource, auth, factory);
-      this.ifModifiedSince = ifModifiedSince;
-    }
-
-    @Override
-    public void addHeaders(HttpRequestBase request, DefaultHttpClient client) {
-      if (ifModifiedSince != -1L) {
-        // TODO: format?
-        request.addHeader("If-Modified-Since", "" + ifModifiedSince);
-      }
-      super.addHeaders(request, client);
-    }
-  }
-
-  /**
-   * Wraps the @{link ReadingListRecordDelegate} interface to yield a {@link StorageResourceDelegate}.
-   */
-  private static abstract class RecordResourceDelegate<T extends ReadingListResponse> extends StorageResourceDelegate<T> {
-    protected final ReadingListRecordDelegate recordDelegate;
-
-    public RecordResourceDelegate(Resource resource,
-                                  AuthHeaderProvider auth,
-                                  ReadingListRecordDelegate recordDelegate,
-                                  ReadingListResponse.ResponseFactory<T> factory,
-                                  long ifModifiedSince) {
-      super(resource, auth, factory, ifModifiedSince);
-      this.recordDelegate = recordDelegate;
-    }
-
-    abstract void onNotFound(ReadingListResponse resp);
-
-    @Override
-    void onNonSuccess(T resp) {
-      Logger.debug(LOG_TAG, "Got non-success record response " + resp.getStatusCode());
-      resp.logResponseBody(LOG_TAG);
-
-      switch (resp.getStatusCode()) {
-      case 304:
-        onNotModified(resp);
-        break;
-      case 404:
-        onNotFound(resp);
-        break;
-      default:
-        onFailure(resp);
-      }
-    }
-
-    @Override
-    void onFailure(MozResponse response) {
-      recordDelegate.onFailure(response);
-    }
-
-    @Override
-    void onFailure(Exception ex) {
-      recordDelegate.onFailure(ex);
-    }
-
-    void onNotModified(T resp) {
-      recordDelegate.onComplete(resp);
-    }
-  }
-
-  private static final class SingleRecordResourceDelegate extends RecordResourceDelegate<ReadingListRecordResponse> {
-    private final String guid;
-
-    SingleRecordResourceDelegate(Resource resource,
-                                 AuthHeaderProvider auth,
-                                 ReadingListRecordDelegate recordDelegate,
-                                 ResponseFactory<ReadingListRecordResponse> factory,
-                                 long ifModifiedSince, String guid) {
-      super(resource, auth, recordDelegate, factory, ifModifiedSince);
-      this.guid = guid;
-    }
-
-    @Override
-    void onSuccess(ReadingListRecordResponse response) {
-      final ServerReadingListRecord record;
-      try {
-        record = response.getRecord();
-      } catch (Exception e) {
-        recordDelegate.onFailure(e);
-        return;
-      }
-
-      recordDelegate.onRecordReceived(record);
-      recordDelegate.onComplete(response);
-    }
-
-    @Override
-    void onNotFound(ReadingListResponse resp) {
-      recordDelegate.onRecordMissingOrDeleted(guid, resp);
-    }
-  }
-
-  private static final class MultipleRecordResourceDelegate extends RecordResourceDelegate<ReadingListStorageResponse> {
-    MultipleRecordResourceDelegate(Resource resource,
-                                   AuthHeaderProvider auth,
-                                   ReadingListRecordDelegate recordDelegate,
-                                   ResponseFactory<ReadingListStorageResponse> factory,
-                                   long ifModifiedSince) {
-      super(resource, auth, recordDelegate, factory, ifModifiedSince);
-    }
-
-    @Override
-    void onSuccess(ReadingListStorageResponse response) {
-      try {
-        final Iterable<ServerReadingListRecord> records = response.getRecords();
-        for (ServerReadingListRecord readingListRecord : records) {
-          recordDelegate.onRecordReceived(readingListRecord);
-        }
-      } catch (Exception e) {
-        recordDelegate.onFailure(e);
-        return;
-      }
-
-      recordDelegate.onComplete(response);
-    }
-
-    @Override
-    void onNotFound(ReadingListResponse resp) {
-      // Should not occur against articlesURI root.
-      recordDelegate.onFailure(resp);
-    }
-  }
-
-  private static abstract class UploadResourceDelegate<T extends ReadingListResponse> extends StorageResourceDelegate<T> {
-    public UploadResourceDelegate(Resource resource,
-                                  AuthHeaderProvider auth,
-                                  ReadingListResponse.ResponseFactory<T> factory,
-                                  long ifModifiedSince) {
-      super(resource, auth, factory, ifModifiedSince);
-    }
-
-    public UploadResourceDelegate(Resource resource,
-                                  AuthHeaderProvider auth,
-                                  ReadingListResponse.ResponseFactory<T> factory) {
-      this(resource, auth, factory, -1L);
-    }
-
-    @Override
-    void onNonSuccess(T resp) {
-      if (resp.getStatusCode() == 303) {
-        onSeeOther(resp);
-        return;
-      }
-      onFailure(resp);
-    }
-
-    abstract void onSeeOther(T resp);
-  }
-
-
-  /**
-   * Recursively calls `patch` with items from the queue, delivering callbacks
-   * to the provided delegate. Calls `onBatchDone` when the queue is exhausted.
-   *
-   * Uses the provided executor to flatten the recursive call stack.
-   */
-  private abstract class BatchingUploadDelegate implements ReadingListRecordUploadDelegate {
-    private final Queue<ClientReadingListRecord>  queue;
-    private final ReadingListRecordUploadDelegate batchUploadDelegate;
-    private final Executor                        executor;
-
-    BatchingUploadDelegate(Queue<ClientReadingListRecord> queue,
-                           ReadingListRecordUploadDelegate batchUploadDelegate,
-                           Executor executor) {
-      this.queue = queue;
-      this.batchUploadDelegate = batchUploadDelegate;
-      this.executor = executor;
-    }
-
-    abstract void again(ClientReadingListRecord record);
-
-    void next() {
-      final ClientReadingListRecord record = queue.poll();
-      executor.execute(new Runnable() {
-        @Override
-        public void run() {
-          if (record == null) {
-            batchUploadDelegate.onBatchDone();
-            return;
-          }
-
-          again(record);
-        }
-      });
-    }
-
-    @Override
-    public void onSuccess(ClientReadingListRecord up,
-                          ReadingListRecordResponse response,
-                          ServerReadingListRecord down) {
-      batchUploadDelegate.onSuccess(up, response, down);
-      next();
-    }
-
-    @Override
-    public void onInvalidUpload(ClientReadingListRecord up,
-                                ReadingListResponse response) {
-      batchUploadDelegate.onInvalidUpload(up, response);
-      next();
-    }
-
-    @Override
-    public void onFailure(ClientReadingListRecord up, MozResponse response) {
-      batchUploadDelegate.onFailure(up, response);
-      next();
-    }
-
-    @Override
-    public void onFailure(ClientReadingListRecord up, Exception ex) {
-      batchUploadDelegate.onFailure(up, ex);
-      next();
-    }
-
-    @Override
-    public void onConflict(ClientReadingListRecord up,
-                           ReadingListResponse response) {
-      batchUploadDelegate.onConflict(up, response);
-      next();
-    }
-
-    @Override
-    public void onBadRequest(ClientReadingListRecord up, MozResponse response) {
-      batchUploadDelegate.onBadRequest(up, response);
-      next();
-    }
-
-    @Override
-    public void onBatchDone() {
-      // This should never occur, but if it does, pass through.
-      batchUploadDelegate.onBatchDone();
-    }
-  }
-
-  private class PostBatchingUploadDelegate extends BatchingUploadDelegate {
-    PostBatchingUploadDelegate(Queue<ClientReadingListRecord> queue,
-                               ReadingListRecordUploadDelegate batchUploadDelegate,
-                               Executor executor) {
-      super(queue, batchUploadDelegate, executor);
-    }
-
-    @Override
-    void again(ClientReadingListRecord record) {
-      add(record, PostBatchingUploadDelegate.this);
-    }
-  }
-
-  private class PatchBatchingUploadDelegate extends BatchingUploadDelegate {
-    PatchBatchingUploadDelegate(Queue<ClientReadingListRecord> queue,
-                                ReadingListRecordUploadDelegate batchUploadDelegate,
-                                Executor executor) {
-      super(queue, batchUploadDelegate, executor);
-    }
-
-    @Override
-    void again(ClientReadingListRecord record) {
-      patch(record, PatchBatchingUploadDelegate.this);
-    }
-  }
-
-  private class DeleteBatchingDelegate implements ReadingListDeleteDelegate {
-    private final Queue<String> queue;
-    private final ReadingListDeleteDelegate batchDeleteDelegate;
-    private final Executor executor;
-
-    DeleteBatchingDelegate(Queue<String> guids,
-                           ReadingListDeleteDelegate batchDeleteDelegate,
-                           Executor executor) {
-      this.queue = guids;
-      this.batchDeleteDelegate = batchDeleteDelegate;
-      this.executor = executor;
-    }
-
-    void next() {
-      final String guid = queue.poll();
-      executor.execute(new Runnable() {
-        @Override
-        public void run() {
-          if (guid == null) {
-            batchDeleteDelegate.onBatchDone();
-            return;
-          }
-
-          again(guid);
-        }
-      });
-    }
-
-    void again(String guid) {
-      delete(guid, DeleteBatchingDelegate.this, -1L);
-    }
-
-    @Override
-    public void onSuccess(ReadingListRecordResponse response,
-                          ReadingListRecord record) {
-      batchDeleteDelegate.onSuccess(response, record);
-      next();
-    }
-
-    @Override
-    public void onPreconditionFailed(String guid, MozResponse response) {
-      batchDeleteDelegate.onPreconditionFailed(guid, response);
-      next();
-    }
-
-    @Override
-    public void onRecordMissingOrDeleted(String guid, MozResponse response) {
-      batchDeleteDelegate.onRecordMissingOrDeleted(guid, response);
-      next();
-    }
-
-    @Override
-    public void onFailure(Exception e) {
-      batchDeleteDelegate.onFailure(e);
-      next();
-    }
-
-    @Override
-    public void onFailure(MozResponse response) {
-      batchDeleteDelegate.onFailure(response);
-      next();
-    }
-
-    @Override
-    public void onBatchDone() {
-      // This should never occur, but if it does, pass through.
-      batchDeleteDelegate.onBatchDone();
-    }
-  }
-
-  // Deliberately declare `delegate` non-final so we can't capture it below. We prefer
-  // to use `recordDelegate` explicitly.
-  public void getOne(final String guid, ReadingListRecordDelegate delegate, final long ifModifiedSince) {
-    final BaseResource r = getRelativeArticleResource(guid);
-    r.delegate = new SingleRecordResourceDelegate(r, auth, delegate, ReadingListRecordResponse.FACTORY, ifModifiedSince, guid);
-    if (ReadingListConstants.DEBUG) {
-      Logger.info(LOG_TAG, "Getting record " + guid);
-    }
-    r.get();
-  }
-
-  // Deliberately declare `delegate` non-final so we can't capture it below. We prefer
-  // to use `recordDelegate` explicitly.
-  public void getAll(final FetchSpec spec, ReadingListRecordDelegate delegate, final long ifModifiedSince) throws URISyntaxException {
-    final BaseResource r = new BaseResource(spec.getURI(this.articlesURI));
-    r.delegate = new MultipleRecordResourceDelegate(r, auth, delegate, ReadingListStorageResponse.FACTORY, ifModifiedSince);
-    if (ReadingListConstants.DEBUG) {
-      Logger.info(LOG_TAG, "Getting all records from " + r.getURIString());
-    }
-    r.get();
-  }
-
-  /**
-   * Mutates the provided queue.
-   */
-  public void patch(final Queue<ClientReadingListRecord> queue, final Executor executor, final ReadingListRecordUploadDelegate batchUploadDelegate) {
-    if (queue.isEmpty()) {
-      batchUploadDelegate.onBatchDone();
-      return;
-    }
-
-    final ReadingListRecordUploadDelegate uploadDelegate = new PatchBatchingUploadDelegate(queue, batchUploadDelegate, executor);
-
-    patch(queue.poll(), uploadDelegate);
-  }
-
-  public void patch(final ClientReadingListRecord up, final ReadingListRecordUploadDelegate uploadDelegate) {
-    final String guid = up.getGUID();
-    if (guid == null) {
-      uploadDelegate.onFailure(up, new IllegalArgumentException("Supplied record must have a GUID."));
-      return;
-    }
-
-    final BaseResource r = getRelativeArticleResource(guid);
-    r.delegate = new DelegatingUploadResourceDelegate(r, auth, ReadingListRecordResponse.FACTORY, up,
-                                                      uploadDelegate);
-
-    final ExtendedJSONObject body = up.toJSON();
-    if (ReadingListConstants.DEBUG) {
-      Logger.info(LOG_TAG, "Patching record " + guid + ": " + body.toJSONString());
-    }
-    r.patch(body);
-  }
-
-  /**
-   * Mutates the provided queue.
-   */
-  public void add(final Queue<ClientReadingListRecord> queue, final Executor executor, final ReadingListRecordUploadDelegate batchUploadDelegate) {
-    if (queue.isEmpty()) {
-      batchUploadDelegate.onBatchDone();
-      return;
-    }
-
-    final ReadingListRecordUploadDelegate uploadDelegate = new PostBatchingUploadDelegate(queue, batchUploadDelegate, executor);
-
-    add(queue.poll(), uploadDelegate);
-  }
-
-  public void add(final ClientReadingListRecord up, final ReadingListRecordUploadDelegate uploadDelegate) {
-    final BaseResource r = new BaseResource(this.articlesURI);
-    r.delegate = new DelegatingUploadResourceDelegate(r, auth, ReadingListRecordResponse.FACTORY, up,
-                                                      uploadDelegate);
-
-    final ExtendedJSONObject body = up.toJSON();
-    if (ReadingListConstants.DEBUG) {
-      Logger.info(LOG_TAG, "Uploading new record: " + body.toJSONString());
-    }
-    r.post(body);
-  }
-
-  public void delete(final Queue<String> guids, final Executor executor, final ReadingListDeleteDelegate batchDeleteDelegate) {
-    if (guids.isEmpty()) {
-      batchDeleteDelegate.onBatchDone();
-      return;
-    }
-
-    final ReadingListDeleteDelegate deleteDelegate = new DeleteBatchingDelegate(guids, batchDeleteDelegate, executor);
-
-    delete(guids.poll(), deleteDelegate, -1L);
-  }
-
-  public void delete(final String guid, final ReadingListDeleteDelegate delegate, final long ifUnmodifiedSince) {
-    final BaseResource r = getRelativeArticleResource(guid);
-
-    // If If-Unmodified-Since is provided, and the record has been modified,
-    // we'll receive a 412 Precondition Failed.
-    // If the record is missing or already deleted, a 404 will be returned.
-    // Otherwise, the response will be the deleted record.
-    r.delegate = new ReadingListResourceDelegate<ReadingListRecordResponse>(r, auth, ReadingListRecordResponse.FACTORY) {
-      @Override
-      public void addHeaders(HttpRequestBase request, DefaultHttpClient client) {
-        if (ifUnmodifiedSince != -1) {
-          request.addHeader("If-Unmodified-Since", "" + ifUnmodifiedSince);
-        }
-        super.addHeaders(request, client);
-      }
-
-      @Override
-      void onFailure(MozResponse response) {
-        switch (response.getStatusCode()) {
-        case 412:
-          delegate.onPreconditionFailed(guid, response);
-          return;
-        }
-        delegate.onFailure(response);
-      }
-
-      @Override
-      void onSuccess(ReadingListRecordResponse response) {
-        final ReadingListRecord record;
-        try {
-          record = response.getRecord();
-        } catch (Exception e) {
-          delegate.onFailure(e);
-          return;
-        }
-
-        delegate.onSuccess(response, record);
-      }
-
-      @Override
-      void onFailure(Exception ex) {
-        delegate.onFailure(ex);
-      }
-
-      @Override
-      void onNonSuccess(ReadingListRecordResponse response) {
-        if (response.getStatusCode() == 404) {
-          // Already deleted!
-          delegate.onRecordMissingOrDeleted(guid, response);
-        }
-      }
-    };
-
-    if (ReadingListConstants.DEBUG) {
-      Logger.debug(LOG_TAG, "Deleting " + r.getURIString());
-    }
-    r.delete();
-  }
-
-  // TODO: modified times etc.
-  public void wipe(final ReadingListWipeDelegate delegate) {
-    Logger.info(LOG_TAG, "Wiping server.");
-    final BaseResource r = new BaseResource(this.articlesURI);
-
-    r.delegate = new ReadingListResourceDelegate<ReadingListStorageResponse>(r, auth, ReadingListStorageResponse.FACTORY) {
-
-      @Override
-      void onSuccess(ReadingListStorageResponse response) {
-        Logger.info(LOG_TAG, "Wipe succeded.");
-        delegate.onSuccess(response);
-      }
-
-      @Override
-      void onNonSuccess(ReadingListStorageResponse response) {
-        Logger.warn(LOG_TAG, "Wipe failed: " + response.getStatusCode());
-        onFailure(response);
-      }
-
-      @Override
-      void onFailure(MozResponse response) {
-        Logger.warn(LOG_TAG, "Wipe failed: " + response.getStatusCode());
-        delegate.onFailure(response);
-      }
-
-      @Override
-      void onFailure(Exception ex) {
-        Logger.warn(LOG_TAG, "Wipe failed.", ex);
-        delegate.onFailure(ex);
-      }
-    };
-
-    r.delete();
-  }
-}
deleted file mode 100644
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/reading/ReadingListClientContentValuesFactory.java
+++ /dev/null
@@ -1,94 +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.reading;
-
-import java.util.Map.Entry;
-import java.util.Set;
-
-import org.mozilla.gecko.db.BrowserContract.ReadingListItems;
-import org.mozilla.gecko.reading.ReadingListRecord.ServerMetadata;
-import org.mozilla.gecko.sync.ExtendedJSONObject;
-
-import android.content.ContentValues;
-
-public class ReadingListClientContentValuesFactory {
-  public static ContentValues fromClientRecord(ClientReadingListRecord record) {
-    // Do each of these.
-    ExtendedJSONObject fields = record.fields;
-    ServerMetadata sm = record.serverMetadata;
-
-    final ContentValues values = new ContentValues();
-
-    if (sm.guid != null) {
-      values.put(ReadingListItems.GUID, sm.guid);
-    }
-
-    if (sm.lastModified > -1L) {
-      values.put(ReadingListItems.SERVER_LAST_MODIFIED, sm.lastModified);
-    }
-
-    final Set<Entry<String, Object>> entries = fields.entrySet();
-
-    for (Entry<String,Object> entry : entries) {
-      final String key = entry.getKey();
-      final String field = mapField(key);
-      if (field == null) {
-        continue;
-      }
-
-      final Object v = entry.getValue();
-      if (v == null) {
-        values.putNull(field);
-      } else if (v instanceof Boolean) {
-        values.put(field, ((Boolean) v) ? 1 : 0);
-      } else if (v instanceof Long) {
-        values.put(field, (Long) v);
-      } else if (v instanceof Integer) {
-        values.put(field, (Integer) v);
-      } else if (v instanceof String) {
-        values.put(field, (String) v);
-      } else if (v instanceof Double) {
-        values.put(field, (Double) v);
-      } else {
-        throw new IllegalArgumentException("Unknown value " + v + " of type " + v.getClass().getSimpleName());
-      }
-    }
-
-    // Clear the sync flags.
-    values.put(ReadingListItems.SYNC_STATUS, ReadingListItems.SYNC_STATUS_SYNCED);
-    values.put(ReadingListItems.SYNC_CHANGE_FLAGS, ReadingListItems.SYNC_CHANGE_NONE);
-
-    return values;
-  }
-
-  /**
-   * Only returns valid columns.
-   */
-  private static String mapField(String key) {
-    if (key == null) {
-      return null;
-    }
-
-    switch (key) {
-    case "unread":
-      return "is_unread";
-    case "favorite":
-      return "is_favorite";
-    case "archived":
-      return "is_archived";
-    case "deleted":
-      return "is_deleted";
-    }
-
-    // Validation.
-    for (int i = 0; i < ReadingListItems.ALL_FIELDS.length; ++i) {
-      if (key.equals(ReadingListItems.ALL_FIELDS[i])) {
-        return key;
-      }
-    }
-
-    return null;
-  }
-}
deleted file mode 100644
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/reading/ReadingListClientRecordFactory.java
+++ /dev/null
@@ -1,225 +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.reading;
-
-import org.mozilla.gecko.AppConstants.Versions;
-import org.mozilla.gecko.db.BrowserContract.ReadingListItems;
-import org.mozilla.gecko.reading.ReadingListRecord.ServerMetadata;
-import org.mozilla.gecko.sync.ExtendedJSONObject;
-
-import android.annotation.TargetApi;
-import android.database.AbstractWindowedCursor;
-import android.database.Cursor;
-import android.database.CursorWindow;
-import android.os.Build;
-
-/**
- * This class converts database rows into {@link ClientReadingListRecord}s.
- *
- * In doing so it has to:
- *
- *  * Translate column names.
- *  * Convert INTEGER columns into booleans.
- *  * Eliminate fields that aren't present in the wire format.
- *  * Extract fields that are part of {@link ClientMetadata} instances.
- *
- * The caller is responsible for closing the cursor.
- */
-public class ReadingListClientRecordFactory {
-  public static final int MAX_SERVER_STRING_CHARS = 1024;
-
-  private final Cursor cursor;
-
-  private final String[] fields;
-  private final int[] columns;
-
-  public ReadingListClientRecordFactory(final Cursor cursor, final String[] fields) throws IllegalArgumentException {
-    this.cursor = cursor;
-
-    // Does this cursor have an _ID?
-    final int idIndex = cursor.getColumnIndex(ReadingListItems._ID);
-    final int extra = (idIndex != -1) ? 1 : 0;
-    final int cols = fields.length + extra;
-
-    this.fields = new String[cols];
-    this.columns = new int[cols];
-
-    for (int i = 0; i < fields.length; ++i) {
-      final int index = cursor.getColumnIndex(fields[i]);
-      if (index == -1) {
-        continue;
-      }
-      this.fields[i] = mapColumn(fields[i]);
-      this.columns[i] = index;
-    }
-
-    if (idIndex != -1) {
-      this.fields[fields.length] = "_id";
-      this.columns[fields.length] = idIndex;
-    }
-  }
-
-  public ReadingListClientRecordFactory(final Cursor cursor) {
-    this(cursor, ReadingListItems.ALL_FIELDS);
-  }
-
-  private void putNull(ExtendedJSONObject o, String field) {
-    o.put(field, null);
-  }
-
-  /**
-   * Map column names to protocol field names.
-   */
-  private static String mapColumn(final String column) {
-    switch (column) {
-    case "is_unread":
-      return "unread";
-    case "is_favorite":
-      return "favorite";
-    case "is_archived":
-      return "archived";
-    }
-    return column;
-  }
-
-  private void put(ExtendedJSONObject o, String field, String value) {
-    // All server strings are a max of 1024 characters.
-    o.put(field, value.length() > MAX_SERVER_STRING_CHARS ? value.substring(0, MAX_SERVER_STRING_CHARS - 1) + "…" : value);
-  }
-
-  private void put(ExtendedJSONObject o, String field, long value) {
-    // Convert to boolean.
-    switch (field) {
-    case "unread":
-    case "favorite":
-    case "archived":
-    case "is_article":
-      o.put(field, value == 1);
-      return;
-    }
-    o.put(field, value);
-  }
-
-  @TargetApi(Build.VERSION_CODES.HONEYCOMB)
-  private final void fillHoneycomb(ExtendedJSONObject o, Cursor c, String f, int i) {
-    if (f == null) {
-      return;
-    }
-    switch (c.getType(i)) {
-    case Cursor.FIELD_TYPE_NULL:
-      putNull(o, f);
-      return;
-    case Cursor.FIELD_TYPE_STRING:
-      put(o, f, c.getString(i));
-      return;
-    case Cursor.FIELD_TYPE_INTEGER:
-      put(o, f, c.getLong(i));
-      return;
-    case Cursor.FIELD_TYPE_FLOAT:
-      o.put(f, c.getDouble(i));
-      return;
-    case Cursor.FIELD_TYPE_BLOB:
-      // TODO: this probably doesn't serialize correctly.
-      o.put(f, c.getBlob(i));
-      return;
-    default:
-      // Do nothing.
-      return;
-    }
-  }
-
-  @SuppressWarnings("deprecation")
-  private final void fillGingerbread(ExtendedJSONObject o, Cursor c, String f, int i) {
-    if (!(c instanceof AbstractWindowedCursor)) {
-      throw new IllegalStateException("Unable to handle cursors that don't have a CursorWindow!");
-    }
-
-    final AbstractWindowedCursor sqc = (AbstractWindowedCursor) c;
-    final CursorWindow w = sqc.getWindow();
-    final int pos = c.getPosition();
-    if (w.isNull(pos, i)) {
-      putNull(o, f);
-    } else if (w.isString(pos, i)) {
-      put(o, f, c.getString(i));
-    } else if (w.isLong(pos, i)) {
-      put(o, f, c.getLong(i));
-    } else if (w.isFloat(pos, i)) {
-      o.put(f, c.getDouble(i));
-    } else if (w.isBlob(pos, i)) {
-      // TODO: this probably doesn't serialize correctly.
-      o.put(f, c.getBlob(i));
-    }
-  }
-
-  /**
-   * TODO: optionally produce a partial record by examining SYNC_CHANGE_FLAGS/SYNC_STATUS.
-   */
-  public ClientReadingListRecord fromCursorRow() {
-    final ExtendedJSONObject object = new ExtendedJSONObject();
-    for (int i = 0; i < this.fields.length; ++i) {
-      final String field = fields[i];
-      if (field == null) {
-        continue;
-      }
-      final int column = this.columns[i];
-      if (Versions.feature11Plus) {
-        fillHoneycomb(object, this.cursor, field, column);
-      } else {
-        fillGingerbread(object, this.cursor, field, column);
-      }
-    }
-
-    // Apply cross-field constraints.
-    if (object.containsKey("unread") && object.getBoolean("unread")) {
-      object.remove("marked_read_by");
-      object.remove("marked_read_on");
-    }
-
-    // Construct server metadata and client metadata from the object.
-    final long serverLastModified = object.getLong("last_modified", -1L);
-    final String guid = object.containsKey("guid") ? object.getString("guid") : null;
-    final ServerMetadata sm = new ServerMetadata(guid, serverLastModified);
-
-    final long clientLastModified = object.getLong("client_last_modified", -1L);
-
-    // This has already been translated...
-    final boolean isArchived = object.getBoolean("archived");
-
-    // ... but this is a client-only field, so it needs to be converted.
-    final boolean isDeleted = object.getLong("is_deleted", 0L) == 1L;
-    final long localID = object.getLong("_id", -1L);
-    final ClientMetadata cm = new ClientMetadata(localID, clientLastModified, isDeleted, isArchived);
-
-    // Remove things that aren't part of the spec.
-    object.remove("last_modified");
-    object.remove("guid");
-    object.remove("client_last_modified");
-    object.remove("is_deleted");
-
-    // We never want to upload stored_on; for new items it'll be null (and cause Bug 1153358),
-    // and for existing items it should never change.
-    object.remove("stored_on");
-
-    object.remove(ReadingListItems.CONTENT_STATUS);
-    object.remove(ReadingListItems.SYNC_STATUS);
-    object.remove(ReadingListItems.SYNC_CHANGE_FLAGS);
-    object.remove(ReadingListItems.CLIENT_LAST_MODIFIED);
-
-    return new ClientReadingListRecord(sm, cm, object);
-  }
-
-  /**
-   * Return a record from a cursor.
-   * Make sure that the columns you specify in the constructor are a subset
-   * of the columns in the cursor, or you'll have a bad time.
-   */
-  public ClientReadingListRecord getNext() {
-    if (!cursor.moveToNext()) {
-      return null;
-    }
-
-    return fromCursorRow();
-  }
-}
\ No newline at end of file
deleted file mode 100644
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/reading/ReadingListDeleteDelegate.java
+++ /dev/null
@@ -1,21 +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.reading;
-
-import org.mozilla.gecko.sync.net.MozResponse;
-
-/**
- * Response delegate for a server DELETE.
- * Only one of these methods will be called, and it will be called precisely once,
- * unless batching is used.
- */
-public interface ReadingListDeleteDelegate {
-  void onSuccess(ReadingListRecordResponse response, ReadingListRecord record);
-  void onPreconditionFailed(String guid, MozResponse response);
-  void onRecordMissingOrDeleted(String guid, MozResponse response);
-  void onFailure(Exception e);
-  void onFailure(MozResponse response);
-  void onBatchDone();
-}
deleted file mode 100644
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/reading/ReadingListInvalidAuthenticationException.java
+++ /dev/null
@@ -1,18 +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.reading;
-
-import org.mozilla.gecko.sync.net.MozResponse;
-
-public class ReadingListInvalidAuthenticationException extends Exception {
-  private static final long serialVersionUID = 7112459541558266597L;
-
-  public final MozResponse response;
-
-  public ReadingListInvalidAuthenticationException(MozResponse response) {
-    super();
-    this.response = response;
-  }
-}
deleted file mode 100644
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/reading/ReadingListRecord.java
+++ /dev/null
@@ -1,55 +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.reading;
-
-import org.mozilla.gecko.sync.ExtendedJSONObject;
-
-/**
- * This models the wire protocol format, not database contents.
- */
-public abstract class ReadingListRecord {
-  public static class ServerMetadata {
-    public final String guid;             // Null if not yet uploaded successfully.
-    public final long lastModified;       // A server timestamp.
-
-    public ServerMetadata(String guid, long lastModified) {
-      this.guid = guid;
-      this.lastModified = lastModified;
-    }
-
-    /**
-     * From server record.
-     */
-    public ServerMetadata(ExtendedJSONObject obj) {
-      this(obj.getString("id"), obj.containsKey("last_modified") ? obj.getLong("last_modified") : -1L);
-    }
-  }
-
-  public final ServerMetadata serverMetadata;
-
-  public String getGUID() {
-    if (serverMetadata == null) {
-      return null;
-    }
-
-    return serverMetadata.guid;
-  }
-
-  public long getServerLastModified() {
-    if (serverMetadata == null) {
-      return -1L;
-    }
-
-    return serverMetadata.lastModified;
-  }
-
-  protected ReadingListRecord(final ServerMetadata serverMetadata) {
-    this.serverMetadata = serverMetadata;
-  }
-
-  public abstract String getURL();
-  public abstract String getTitle();
-  public abstract String getAddedBy();
-}
deleted file mode 100644
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/reading/ReadingListRecordDelegate.java
+++ /dev/null
@@ -1,26 +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.reading;
-
-import org.mozilla.gecko.sync.net.MozResponse;
-
-/**
- * Delegate for downloading records.
- *
- * onRecordReceived will be called at most once per record.
- * onComplete will be called at the end of a successful download.
- *
- * Otherwise, one of the failure methods will be called.
- *
- * onRecordMissingOrDeleted will only be called when fetching a single
- * record by ID.
- */
-public interface ReadingListRecordDelegate {
-  void onRecordReceived(ServerReadingListRecord record);
-  void onComplete(ReadingListResponse response);
-  void onFailure(MozResponse response);
-  void onFailure(Exception error);
-  void onRecordMissingOrDeleted(String guid, ReadingListResponse resp);
-}
deleted file mode 100644
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/reading/ReadingListRecordResponse.java
+++ /dev/null
@@ -1,41 +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.reading;
-
-import java.io.IOException;
-
-import org.json.simple.parser.ParseException;
-import org.mozilla.gecko.sync.NonObjectJSONException;
-
-import ch.boye.httpclientandroidlib.HttpResponse;
-
-/**
- * A storage response that contains a single record.
- */
-public class ReadingListRecordResponse extends ReadingListResponse {
-  @Override
-  public boolean wasSuccessful() {
-    final int code = getStatusCode();
-    if (code == 200 || code == 201 || code == 204) {
-      return true;
-    }
-    return super.wasSuccessful();
-  }
-
-  public static final ReadingListResponse.ResponseFactory<ReadingListRecordResponse> FACTORY = new ReadingListResponse.ResponseFactory<ReadingListRecordResponse>() {
-    @Override
-    public ReadingListRecordResponse getResponse(HttpResponse r) {
-      return new ReadingListRecordResponse(r);
-    }
-  };
-
-  public ReadingListRecordResponse(HttpResponse res) {
-    super(res);
-  }
-
-  public ServerReadingListRecord getRecord() throws IllegalStateException, NonObjectJSONException, IOException, ParseException {
-    return new ServerReadingListRecord(jsonObjectBody());
-  }
-}
\ No newline at end of file
deleted file mode 100644
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/reading/ReadingListRecordUploadDelegate.java
+++ /dev/null
@@ -1,20 +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.reading;
-
-import org.mozilla.gecko.sync.net.MozResponse;
-
-public interface ReadingListRecordUploadDelegate {
-  // Called once per batch.
-  public void onBatchDone();
-
-  // One of these is called once per record.
-  public void onSuccess(ClientReadingListRecord up, ReadingListRecordResponse response, ServerReadingListRecord down);
-  public void onConflict(ClientReadingListRecord up, ReadingListResponse response);
-  public void onInvalidUpload(ClientReadingListRecord up, ReadingListResponse response);
-  public void onBadRequest(ClientReadingListRecord up, MozResponse response);
-  public void onFailure(ClientReadingListRecord up, Exception ex);
-  public void onFailure(ClientReadingListRecord up, MozResponse response);
-}
deleted file mode 100644
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/reading/ReadingListResponse.java
+++ /dev/null
@@ -1,26 +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.reading;
-
-import org.mozilla.gecko.sync.net.MozResponse;
-
-import ch.boye.httpclientandroidlib.HttpResponse;
-
-/**
- * A MozResponse that knows about all of the general RL-related headers, like Last-Modified.
- */
-public abstract class ReadingListResponse extends MozResponse {
-  static interface ResponseFactory<T extends ReadingListResponse> {
-    public T getResponse(HttpResponse r);
-  }
-
-  public ReadingListResponse(HttpResponse res) {
-    super(res);
-  }
-
-  public long getLastModified() {
-    return getLongHeader("Last-Modified");
-  }
-}
\ No newline at end of file
deleted file mode 100644
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/reading/ReadingListStorage.java
+++ /dev/null
@@ -1,16 +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.reading;
-
-import android.database.Cursor;
-
-public interface ReadingListStorage {
-  Cursor getModified();
-  Cursor getDeletedItems();
-  Cursor getStatusChanges();
-  Cursor getNew();
-  Cursor getAll();
-  ReadingListChangeAccumulator getChangeAccumulator();
-}
deleted file mode 100644
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/reading/ReadingListStorageResponse.java
+++ /dev/null
@@ -1,75 +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.reading;
-
-import java.io.IOException;
-import java.util.Iterator;
-
-import org.json.simple.JSONArray;
-import org.json.simple.JSONObject;
-import org.json.simple.parser.ParseException;
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.sync.ExtendedJSONObject;
-import org.mozilla.gecko.sync.UnexpectedJSONException;
-
-import ch.boye.httpclientandroidlib.HttpResponse;
-
-/**
- * A storage response that contains multiple records.
- */
-public class ReadingListStorageResponse extends ReadingListResponse {
-  public static final ReadingListResponse.ResponseFactory<ReadingListStorageResponse> FACTORY = new ReadingListResponse.ResponseFactory<ReadingListStorageResponse>() {
-    @Override
-    public ReadingListStorageResponse getResponse(HttpResponse r) {
-      return new ReadingListStorageResponse(r);
-    }
-  };
-
-  private static final String LOG_TAG = "StorageResponse";
-
-  public ReadingListStorageResponse(HttpResponse res) {
-    super(res);
-  }
-
-  public Iterable<ServerReadingListRecord> getRecords() throws IOException, ParseException, UnexpectedJSONException {
-    final ExtendedJSONObject body = jsonObjectBody();
-    final JSONArray items = body.getArray("items");
-
-    final int expected = getTotalRecords();
-    final int actual = items.size();
-    if (actual < expected) {
-      Logger.warn(LOG_TAG, "Unexpected number of records. Got " + actual + ", expected " + expected);
-    }
-
-    return new Iterable<ServerReadingListRecord>() {
-      @Override
-      public Iterator<ServerReadingListRecord> iterator() {
-        return new Iterator<ServerReadingListRecord>() {
-          int position = 0;
-
-          @Override
-          public boolean hasNext() {
-            return position < actual;
-          }
-
-          @Override
-          public ServerReadingListRecord next() {
-            final Object o = items.get(position++);
-            return new ServerReadingListRecord(new ExtendedJSONObject((JSONObject) o));
-          }
-
-          @Override
-          public void remove() {
-            throw new RuntimeException("Cannot remove from iterator.");
-          }
-        };
-      }
-    };
-  }
-
-  public int getTotalRecords() {
-    return getIntegerHeader("Total-Records");
-  }
-}
\ No newline at end of file
deleted file mode 100644
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/reading/ReadingListSyncAdapter.java
+++ /dev/null
@@ -1,320 +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.reading;
-
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.util.Collection;
-import java.util.EnumSet;
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.TimeUnit;
-
-import org.mozilla.gecko.background.ReadingListConstants;
-import org.mozilla.gecko.background.common.PrefsBranch;
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.background.fxa.FxAccountUtils;
-import org.mozilla.gecko.db.BrowserContract;
-import org.mozilla.gecko.db.BrowserContract.ReadingListItems;
-import org.mozilla.gecko.fxa.FirefoxAccounts;
-import org.mozilla.gecko.fxa.FirefoxAccounts.SyncHint;
-import org.mozilla.gecko.fxa.FxAccountConstants;
-import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
-import org.mozilla.gecko.fxa.sync.FxAccountSyncDelegate;
-import org.mozilla.gecko.fxa.sync.FxAccountSyncDelegate.Result;
-import org.mozilla.gecko.sync.BackoffHandler;
-import org.mozilla.gecko.sync.PrefsBackoffHandler;
-import org.mozilla.gecko.sync.net.AuthHeaderProvider;
-import org.mozilla.gecko.sync.net.BaseResource;
-import org.mozilla.gecko.sync.net.BearerAuthHeaderProvider;
-
-import android.accounts.Account;
-import android.accounts.AccountManager;
-import android.content.AbstractThreadedSyncAdapter;
-import android.content.ContentProviderClient;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.SharedPreferences;
-import android.content.SyncResult;
-import android.os.Bundle;
-
-public class ReadingListSyncAdapter extends AbstractThreadedSyncAdapter {
-  public static final String PREF_LOCAL_NAME = "device.localname";
-
-  private static final String LOG_TAG = ReadingListSyncAdapter.class.getSimpleName();
-  private static final long TIMEOUT_SECONDS = 60;
-  protected final ExecutorService executor;
-
-  // Don't sync again if we successfully synced within this duration.
-  private static final int AFTER_SUCCESS_SYNC_DELAY_SECONDS = 5 * 60; // 5 minutes.
-  // Don't sync again if we unsuccessfully synced within this duration.
-  private static final int AFTER_ERROR_SYNC_DELAY_SECONDS = 15 * 60; // 15 minutes.
-
-  public ReadingListSyncAdapter(Context context, boolean autoInitialize) {
-    super(context, autoInitialize);
-    this.executor = Executors.newSingleThreadExecutor();
-  }
-
-  protected static abstract class SyncAdapterSynchronizerDelegate implements ReadingListSynchronizerDelegate {
-    private final FxAccountSyncDelegate syncDelegate;
-    private final ContentProviderClient cpc;
-    private final SyncResult result;
-
-    SyncAdapterSynchronizerDelegate(FxAccountSyncDelegate syncDelegate,
-                                    ContentProviderClient cpc,
-                                    SyncResult result) {
-      this.syncDelegate = syncDelegate;
-      this.cpc = cpc;
-      this.result = result;
-    }
-
-    abstract public void onInvalidAuthentication();
-
-    @Override
-    public void onUnableToSync(Exception e) {
-      Logger.warn(LOG_TAG, "Unable to sync.", e);
-      if (e instanceof ReadingListInvalidAuthenticationException) {
-        onInvalidAuthentication();
-      }
-      cpc.release();
-      syncDelegate.handleError(e);
-    }
-
-    @Override
-    public void onDeletionsUploadComplete() {
-      Logger.debug(LOG_TAG, "Step: onDeletionsUploadComplete");
-      this.result.stats.numEntries += 1;   // TODO: Bug 1140809.
-    }
-
-    @Override
-    public void onStatusUploadComplete(Collection<String> uploaded,
-                                       Collection<String> failed) {
-      Logger.debug(LOG_TAG, "Step: onStatusUploadComplete");
-      this.result.stats.numEntries += 1;   // TODO: Bug 1140809.
-    }
-
-    @Override
-    public void onNewItemUploadComplete(Collection<String> uploaded,
-                                        Collection<String> failed) {
-      Logger.debug(LOG_TAG, "Step: onNewItemUploadComplete");
-      this.result.stats.numEntries += 1;   // TODO: Bug 1140809.
-    }
-
-    @Override
-    public void onModifiedUploadComplete() {
-      Logger.debug(LOG_TAG, "Step: onModifiedUploadComplete");
-      this.result.stats.numEntries += 1;   // TODO: Bug 1140809.
-    }
-
-    @Override
-    public void onDownloadComplete() {
-      Logger.debug(LOG_TAG, "Step: onDownloadComplete");
-      this.result.stats.numInserts += 1;   // TODO: Bug 1140809.
-    }
-
-    @Override
-    public void onComplete() {
-      Logger.info(LOG_TAG, "Reading list synchronization complete.");
-      cpc.release();
-      syncDelegate.handleSuccess();
-    }
-  }
-
-  private void syncWithAuthorization(final Context context,
-                                     final URI endpoint,
-                                     final SyncResult syncResult,
-                                     final FxAccountSyncDelegate syncDelegate,
-                                     final String authToken,
-                                     final SharedPreferences sharedPrefs,
-                                     final Bundle extras) {
-    final AuthHeaderProvider auth = new BearerAuthHeaderProvider(authToken);
-
-    final PrefsBranch branch = new PrefsBranch(sharedPrefs, "readinglist.");
-    final ReadingListClient remote = new ReadingListClient(endpoint, auth);
-    final ContentProviderClient cpc = getContentProviderClient(context); // Released by the inner SyncAdapterSynchronizerDelegate.
-
-    final LocalReadingListStorage local = new LocalReadingListStorage(cpc);
-    String localName = branch.getString(PREF_LOCAL_NAME, null);
-    if (localName == null) {
-      localName = FxAccountUtils.defaultClientName(context);
-    }
-
-    // Make sure DB rows don't refer to placeholder values.
-    local.updateLocalNames(localName);
-
-    final ReadingListSynchronizer synchronizer = new ReadingListSynchronizer(branch, remote, local);
-
-    synchronizer.syncAll(new SyncAdapterSynchronizerDelegate(syncDelegate, cpc, syncResult) {
-      @Override
-      public void onInvalidAuthentication() {
-        // The reading list server rejected our oauth token! Invalidate it. Next
-        // time through, we'll request a new one, which will drive the login
-        // state machine, produce a new assertion, and eventually a fresh token.
-        Logger.info(LOG_TAG, "Invalidating oauth token after 401!");
-        AccountManager.get(context).invalidateAuthToken(FxAccountConstants.ACCOUNT_TYPE, authToken);
-      }
-    });
-    // TODO: backoffs, and everything else handled by a SessionCallback.
-  }
-
-  @Override
-  public void onPerformSync(final Account account, final Bundle extras, final String authority, final ContentProviderClient provider, final SyncResult syncResult) {
-    Logger.setThreadLogTag(ReadingListConstants.GLOBAL_LOG_TAG);
-    Logger.resetLogging();
-
-    final EnumSet<SyncHint> syncHints = FirefoxAccounts.getHintsToSyncFromBundle(extras);
-    FirefoxAccounts.logSyncHints(syncHints);
-
-    final Context context = getContext();
-    final AndroidFxAccount fxAccount = new AndroidFxAccount(context, account);
-
-    // Don't sync Reading List if we're in a non-default configuration, but allow testing against stage.
-    final String accountServerURI = fxAccount.getAccountServerURI();
-    final boolean usingDefaultAuthServer = FxAccountConstants.DEFAULT_AUTH_SERVER_ENDPOINT.equals(accountServerURI);
-    final boolean usingStageAuthServer = FxAccountConstants.STAGE_AUTH_SERVER_ENDPOINT.equals(accountServerURI);
-    if (!usingDefaultAuthServer && !usingStageAuthServer) {
-      Logger.error(LOG_TAG, "Skipping Reading List sync because Firefox Account is not using prod or stage auth server.");
-      // Stop syncing the Reading List entirely.
-      ContentResolver.setIsSyncable(account, BrowserContract.READING_LIST_AUTHORITY, 0);
-      return;
-    }
-    final String tokenServerURI = fxAccount.getTokenServerURI();
-    final boolean usingDefaultSyncServer = FxAccountConstants.DEFAULT_TOKEN_SERVER_ENDPOINT.equals(tokenServerURI);
-    final boolean usingStageSyncServer = FxAccountConstants.STAGE_TOKEN_SERVER_ENDPOINT.equals(tokenServerURI);
-    if (!usingDefaultSyncServer && !usingStageSyncServer) {
-      Logger.error(LOG_TAG, "Skipping Reading List sync because Sync is not using the prod or stage Sync (token) server.");
-      Logger.debug(LOG_TAG, "If the user has chosen to not store Sync data with Mozilla, we shouldn't store Reading List data with Mozilla .");
-      // Stop syncing the Reading List entirely.
-      ContentResolver.setIsSyncable(account, BrowserContract.READING_LIST_AUTHORITY, 0);
-      return;
-    }
-
-    Result result = Result.Error;
-    final BlockingQueue<Result> latch = new LinkedBlockingQueue<Result>(1);
-    final FxAccountSyncDelegate syncDelegate = new FxAccountSyncDelegate(latch, syncResult);
-
-    // Allow testing against stage.
-    final String endpointString;
-    if (usingStageAuthServer) {
-      endpointString = ReadingListConstants.DEFAULT_DEV_ENDPOINT;
-    } else {
-      endpointString = ReadingListConstants.DEFAULT_PROD_ENDPOINT;
-    }
-
-    Logger.info(LOG_TAG, "Syncing reading list against endpoint: " + endpointString);
-    final URI endpointURI;
-    try {
-      endpointURI = new URI(endpointString);
-    } catch (URISyntaxException e) {
-      // Should never happen.
-      Logger.error(LOG_TAG, "Unexpected malformed URI for reading list service: " + endpointString);
-      syncDelegate.handleError(e);
-      return;
-    }
-
-    final AccountManager accountManager = AccountManager.get(context);
-    // If we have an auth failure that requires user intervention, FxA will show system
-    // notifications prompting the user to re-connect as it advances the internal account state.
-    // true causes the auth token fetch to return null on failure immediately, rather than doing
-    // Mysterious Internal Work to try to get the token.
-    final boolean notifyAuthFailure = true;
-    try {
-      final SharedPreferences sharedPrefs = fxAccount.getReadingListPrefs();
-
-      final BackoffHandler storageBackoffHandler = new PrefsBackoffHandler(sharedPrefs, "storage");
-      final long storageBackoffDelayMilliseconds = storageBackoffHandler.delayMilliseconds();
-      if (!syncHints.contains(SyncHint.SCHEDULE_NOW) && !syncHints.contains(SyncHint.IGNORE_REMOTE_SERVER_BACKOFF) && storageBackoffDelayMilliseconds > 0) {
-        Logger.warn(LOG_TAG, "Not syncing: storage requested additional backoff: " + storageBackoffDelayMilliseconds + " milliseconds.");
-        syncDelegate.rejectSync();
-        return;
-      }
-
-      final BackoffHandler rateLimitBackoffHandler = new PrefsBackoffHandler(sharedPrefs, "rate");
-      final long rateLimitBackoffDelayMilliseconds = rateLimitBackoffHandler.delayMilliseconds();
-      if (!syncHints.contains(SyncHint.SCHEDULE_NOW) && !syncHints.contains(SyncHint.IGNORE_LOCAL_RATE_LIMIT) && rateLimitBackoffDelayMilliseconds > 0) {
-        Logger.warn(LOG_TAG, "Not syncing: local rate limiting for another: " + rateLimitBackoffDelayMilliseconds + " milliseconds.");
-        syncDelegate.rejectSync();
-        return;
-      }
-
-      final String authToken = accountManager.blockingGetAuthToken(account, ReadingListConstants.AUTH_TOKEN_TYPE, notifyAuthFailure);
-      if (authToken == null) {
-        throw new RuntimeException("Couldn't get oauth token!  Aborting sync.");
-      }
-
-      final ReadingListBackoffObserver observer = new ReadingListBackoffObserver(endpointURI.getHost());
-      BaseResource.addHttpResponseObserver(observer);
-      try {
-        syncWithAuthorization(context, endpointURI, syncResult, syncDelegate, authToken, sharedPrefs, extras);
-        result = latch.poll(TIMEOUT_SECONDS, TimeUnit.SECONDS);
-      } finally {
-        BaseResource.removeHttpResponseObserver(observer);
-        long backoffInSeconds = observer.largestBackoffObservedInSeconds.get();
-        if (backoffInSeconds > 0) {
-          Logger.warn(LOG_TAG, "Observed " + backoffInSeconds + "-second backoff request.");
-          storageBackoffHandler.extendEarliestNextRequest(System.currentTimeMillis() + 1000 * backoffInSeconds);
-        }
-      }
-
-      if (result == null) {
-        // The poll timed out. Let's call this an error.
-        result = Result.Error;
-      }
-
-      switch (result) {
-      case Success:
-        requestPeriodicSync(account, ReadingListSyncAdapter.AFTER_SUCCESS_SYNC_DELAY_SECONDS);
-        break;
-      case Error:
-        requestPeriodicSync(account, ReadingListSyncAdapter.AFTER_ERROR_SYNC_DELAY_SECONDS);
-        break;
-      case Postponed:
-        break;
-      case Rejected:
-        break;
-      }
-
-      Logger.info(LOG_TAG, "Reading list sync done.");
-    } catch (Exception e) {
-      // We can get lots of exceptions here; handle them uniformly.
-      Logger.error(LOG_TAG, "Got error syncing.", e);
-      syncDelegate.handleError(e);
-    }
-
-    /*
-     * TODO:
-     * * Account error notifications. How do we avoid these overlapping with Sync?
-     * * Pickling. How do we avoid pickling twice if you use both Sync and RL?
-     */
-
-    /*
-     * TODO:
-     * * Auth.
-     * * Server URI lookup.
-     * * Syncing.
-     * * Error handling.
-     * * Forcing syncs/interactive use.
-     */
-  }
-
-  private ContentProviderClient getContentProviderClient(Context context) {
-    final ContentResolver contentResolver = context.getContentResolver();
-    final ContentProviderClient client = contentResolver.acquireContentProviderClient(ReadingListItems.CONTENT_URI);
-    return client;
-  }
-
-  /**
-   * Updates the existing system periodic sync interval to the specified duration.
-   *
-   * @param intervalSeconds the requested period, which Android will vary by up to 4%.
-   */
-  protected void requestPeriodicSync(final Account account, final long intervalSeconds) {
-    final String authority = BrowserContract.AUTHORITY;
-    Logger.info(LOG_TAG, "Scheduling periodic sync for " + intervalSeconds + ".");
-    ContentResolver.addPeriodicSync(account, authority, Bundle.EMPTY, intervalSeconds);
-  }
-}
deleted file mode 100644
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/reading/ReadingListSyncService.java
+++ /dev/null
@@ -1,28 +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.reading;
-
-import android.app.Service;
-import android.content.Intent;
-import android.os.IBinder;
-
-public class ReadingListSyncService extends Service {
-  private static final Object syncAdapterLock = new Object();
-  private static ReadingListSyncAdapter syncAdapter;
-
-  @Override
-  public void onCreate() {
-    synchronized (syncAdapterLock) {
-      if (syncAdapter == null) {
-        syncAdapter = new ReadingListSyncAdapter(getApplicationContext(), true);
-      }
-    }
-  }
-
-  @Override
-  public IBinder onBind(Intent intent) {
-    return syncAdapter.getSyncAdapterBinder();
-  }
-}
deleted file mode 100644
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/reading/ReadingListSynchronizer.java
+++ /dev/null
@@ -1,985 +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.reading;
-
-import java.io.IOException;
-import java.net.URISyntaxException;
-import java.util.LinkedList;
-import java.util.Queue;
-import java.util.concurrent.Executor;
-import java.util.concurrent.Executors;
-
-import org.json.simple.parser.ParseException;
-import org.mozilla.gecko.background.common.PrefsBranch;
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.db.BrowserContract.ReadingListItems;
-import org.mozilla.gecko.reading.ReadingListRecord.ServerMetadata;
-import org.mozilla.gecko.sync.ExtendedJSONObject;
-import org.mozilla.gecko.sync.NonObjectJSONException;
-import org.mozilla.gecko.sync.net.MozResponse;
-
-import android.database.Cursor;
-import android.text.TextUtils;
-
-/**
- * This class implements the multi-phase synchronizing approach described
- * at <https://github.com/mozilla-services/readinglist/wiki/Client-phases>.
- *
- * This is also where delegate-based control flow comes to die.
- */
-public class ReadingListSynchronizer {
-  public static final String LOG_TAG = ReadingListSynchronizer.class.getSimpleName();
-
-  public static final String PREF_LAST_MODIFIED = "download.serverlastmodified";
-
-  private final PrefsBranch prefs;
-  private final ReadingListClient remote;
-  private final ReadingListStorage local;
-  private final Executor executor;
-
-  private interface StageDelegate {
-    void next();
-    void fail();
-    void fail(Exception e);
-  }
-
-  private abstract static class NextDelegate implements StageDelegate {
-    private final Executor executor;
-    NextDelegate(final Executor executor) {
-      this.executor = executor;
-    }
-
-    abstract void doNext();
-    abstract void doFail(Exception e);
-
-    @Override
-    public void next() {
-      executor.execute(new Runnable() {
-        @Override
-        public void run() {
-          doNext();
-        }
-      });
-    }
-
-    @Override
-    public void fail() {
-      fail(null);
-    }
-
-    @Override
-    public void fail(final Exception e) {
-      executor.execute(new Runnable() {
-        @Override
-        public void run() {
-          doFail(e);
-        }
-      });
-    }
-  }
-
-  public ReadingListSynchronizer(final PrefsBranch prefs, final ReadingListClient remote, final ReadingListStorage local) {
-    this(prefs, remote, local, Executors.newSingleThreadExecutor());
-  }
-
-  public ReadingListSynchronizer(final PrefsBranch prefs, final ReadingListClient remote, final ReadingListStorage local, Executor executor) {
-    this.prefs = prefs;
-    this.remote = remote;
-    this.local = local;
-    this.executor = executor;
-  }
-
-  private static final class NewItemUploadDelegate implements ReadingListRecordUploadDelegate {
-    public volatile int failures = 0;
-    private final ReadingListChangeAccumulator acc;
-    private final StageDelegate next;
-
-    NewItemUploadDelegate(ReadingListChangeAccumulator acc, StageDelegate next) {
-      this.acc = acc;
-      this.next = next;
-    }
-
-    @Override
-    public void onSuccess(ClientReadingListRecord up,
-                          ReadingListRecordResponse response,
-                          ServerReadingListRecord down) {
-      // Apply the resulting record. The server will have populated some fields.
-      acc.addChangedRecord(up.givenServerRecord(down));
-    }
-
-    @Override
-    public void onConflict(ClientReadingListRecord up, ReadingListResponse response) {
-      ExtendedJSONObject body;
-      try {
-        body = response.jsonObjectBody();
-        String conflicting = body.getString("id");
-        Logger.warn(LOG_TAG, "Conflict detected: remote ID is " + conflicting);
-
-        // TODO: When an operation implies that a server record is a replacement
-        // of what we uploaded, we should ensure that we have a local copy of
-        // that server record!
-      } catch (IllegalStateException | NonObjectJSONException | IOException |
-               ParseException e) {
-        // Oops.
-        // But our workaround is the same either way.
-      }
-
-      // Either the record exists locally, in which case we need to merge,
-      // or it doesn't, and we'll download it shortly.
-      // The simplest thing to do in both cases is to simply delete the local
-      // record we tried to upload. Yes, we might lose some annotations, but
-      // we can leave doing better to a follow-up.
-      // Issues here are so unlikely that we don't do anything sophisticated
-      // (like moving the record to a holding area) -- just delete it ASAP.
-      acc.addDeletion(up);
-    }
-
-    @Override
-    public void onInvalidUpload(ClientReadingListRecord up, ReadingListResponse response) {
-      recordFailed(up);
-    }
-
-    @Override
-    public void onFailure(ClientReadingListRecord up, MozResponse response) {
-      recordFailed(up);
-    }
-
-    @Override
-    public void onFailure(ClientReadingListRecord up, Exception ex) {
-      recordFailed(up);
-    }
-
-    @Override
-    public void onBadRequest(ClientReadingListRecord up, MozResponse response) {
-      recordFailed(up);
-    }
-
-    private void recordFailed(ClientReadingListRecord up) {
-      ++failures;
-    }
-
-    @Override
-    public void onBatchDone() {
-      // We mark uploaded records as synced when we apply the server record with the
-      // GUID -- we don't know the GUID yet!
-      if (failures == 0) {
-        try {
-          next.next();
-        } catch (Exception e) {
-          next.fail(e);
-        }
-        return;
-      }
-      next.fail();
-    }
-  }
-
-  private static class DeletionUploadDelegate implements ReadingListDeleteDelegate {
-    private final ReadingListChangeAccumulator acc;
-    private final StageDelegate next;
-
-    DeletionUploadDelegate(ReadingListChangeAccumulator acc, StageDelegate next) {
-      this.acc = acc;
-      this.next = next;
-    }
-
-    @Override
-    public void onBatchDone() {
-      try {
-        acc.finish();
-      } catch (Exception e) {
-        next.fail(e);
-        return;
-      }
-
-      next.next();
-    }
-
-    @Override
-    public void onSuccess(ReadingListRecordResponse response,
-                          ReadingListRecord record) {
-      Logger.debug(LOG_TAG, "Tracking uploaded deletion " + record.getGUID());
-      acc.addDeletion(record.getGUID());
-    }
-
-    @Override
-    public void onPreconditionFailed(String guid, MozResponse response) {
-      // Should never happen.
-    }
-
-    @Override
-    public void onRecordMissingOrDeleted(String guid, MozResponse response) {
-      // Great!
-      Logger.debug(LOG_TAG, "Tracking redundant deletion " + guid);
-      acc.addDeletion(guid);
-    }
-
-    @Override
-    public void onFailure(Exception e) {
-      // Ignore.
-    }
-
-    @Override
-    public void onFailure(MozResponse response) {
-      // Ignore.
-    }
-  }
-
-
-  private Queue<String> collectDeletedIDsFromCursor(Cursor cursor) {
-    try {
-      final Queue<String> toDelete = new LinkedList<>();
-
-      final int columnGUID = cursor.getColumnIndexOrThrow(ReadingListItems.GUID);
-
-      while (cursor.moveToNext()) {
-        final String guid = cursor.getString(columnGUID);
-        if (guid == null) {
-          // Nothing we can do here.
-          continue;
-        }
-
-        toDelete.add(guid);
-      }
-
-      return toDelete;
-    } finally {
-      cursor.close();
-    }
-  }
-
-  private static class StatusUploadDelegate implements ReadingListRecordUploadDelegate {
-    private final ReadingListChangeAccumulator acc;
-
-    public volatile int failures = 0;
-    private final StageDelegate next;
-
-    StatusUploadDelegate(ReadingListChangeAccumulator acc, StageDelegate next) {
-      this.acc = acc;
-      this.next = next;
-    }
-
-    @Override
-    public void onInvalidUpload(ClientReadingListRecord up,
-                                ReadingListResponse response) {
-      recordFailed(up);
-    }
-
-    @Override
-    public void onConflict(ClientReadingListRecord up,
-                           ReadingListResponse response) {
-      // This should never happen for a status-only change.
-      // TODO: mark this record as requiring a full upload or download.
-      failures++;
-    }
-
-    @Override
-    public void onSuccess(ClientReadingListRecord up,
-                          ReadingListRecordResponse response,
-                          ServerReadingListRecord down) {
-      if (!TextUtils.equals(up.getGUID(), down.getGUID())) {
-        // Uh oh!
-        // This should never occur. We should get an onConflict instead,
-        // so this would imply a server bug, or something like a truncated
-        // over-long GUID string.
-        //
-        // Should we wish to recover from this case, probably the right approach
-        // is to ensure that the GUID is overwritten locally (given that we know
-        // the numeric ID).
-      }
-
-      acc.addChangedRecord(up.givenServerRecord(down));
-    }
-
-    @Override
-    public void onBadRequest(ClientReadingListRecord up, MozResponse response) {
-      recordFailed(up);
-    }
-
-    @Override
-    public void onFailure(ClientReadingListRecord up, Exception ex) {
-      recordFailed(up);
-    }
-
-    @Override
-    public void onFailure(ClientReadingListRecord up, MozResponse response) {
-      recordFailed(up);
-    }
-
-    private void recordFailed(ClientReadingListRecord up) {
-      ++failures;
-    }
-
-    @Override
-    public void onBatchDone() {
-      try {
-        acc.finish();
-      } catch (Exception e) {
-        next.fail(e);
-        return;
-      }
-
-      if (failures == 0) {
-        next.next();
-        return;
-      }
-
-      next.fail();
-    }
-  }
-
-  private Queue<ClientReadingListRecord> collectStatusChangesFromCursor(final Cursor cursor) {
-    try {
-      final Queue<ClientReadingListRecord> toUpload = new LinkedList<>();
-
-      // The columns should come in this order, FWIW.
-      final int columnGUID = cursor.getColumnIndexOrThrow(ReadingListItems.GUID);
-      final int columnIsUnread = cursor.getColumnIndexOrThrow(ReadingListItems.IS_UNREAD);
-      final int columnIsFavorite = cursor.getColumnIndexOrThrow(ReadingListItems.IS_FAVORITE);
-      final int columnMarkedReadBy = cursor.getColumnIndexOrThrow(ReadingListItems.MARKED_READ_BY);
-      final int columnMarkedReadOn = cursor.getColumnIndexOrThrow(ReadingListItems.MARKED_READ_ON);
-      final int columnChangeFlags = cursor.getColumnIndexOrThrow(ReadingListItems.SYNC_CHANGE_FLAGS);
-
-      while (cursor.moveToNext()) {
-        final String guid = cursor.getString(columnGUID);
-        if (guid == null) {
-          // Nothing we can do here.
-          continue;
-        }
-
-        final ExtendedJSONObject o = new ExtendedJSONObject();
-        o.put("id", guid);
-
-        final int changeFlags = cursor.getInt(columnChangeFlags);
-        if ((changeFlags & ReadingListItems.SYNC_CHANGE_FAVORITE_CHANGED) > 0) {
-          o.put("favorite", cursor.getInt(columnIsFavorite) == 1);
-        }
-
-        if ((changeFlags & ReadingListItems.SYNC_CHANGE_UNREAD_CHANGED) > 0) {
-          final boolean isUnread = cursor.getInt(columnIsUnread) == 1;
-          o.put("unread", isUnread);
-          if (!isUnread) {
-            o.put("marked_read_by", cursor.getString(columnMarkedReadBy));
-            o.put("marked_read_on", cursor.getLong(columnMarkedReadOn));
-          }
-        }
-
-        final ClientMetadata cm = null;
-        final ServerMetadata sm = new ServerMetadata(guid, -1L);
-        final ClientReadingListRecord record = new ClientReadingListRecord(sm, cm, o);
-        toUpload.add(record);
-      }
-
-      return toUpload;
-    } finally {
-      cursor.close();
-    }
-  }
-
-  private static class ModifiedUploadDelegate implements ReadingListRecordUploadDelegate {
-    private final ReadingListChangeAccumulator acc;
-
-    public volatile int failures = 0;
-    private final StageDelegate next;
-
-    ModifiedUploadDelegate(ReadingListChangeAccumulator acc, StageDelegate next) {
-      this.acc = acc;
-      this.next = next;
-    }
-
-    @Override
-    public void onInvalidUpload(ClientReadingListRecord up,
-                                ReadingListResponse response) {
-      recordFailed(up);
-    }
-
-    @Override
-    public void onConflict(ClientReadingListRecord up,
-                           ReadingListResponse response) {
-      // This can happen for a material change.
-      failures++;
-    }
-
-    @Override
-    public void onSuccess(ClientReadingListRecord up,
-                          ReadingListRecordResponse response,
-                          ServerReadingListRecord down) {
-      if (!TextUtils.equals(up.getGUID(), down.getGUID())) {
-        // Uh oh!
-        // This should never occur. We should get an onConflict instead,
-        // so this would imply a server bug, or something like a truncated
-        // over-long GUID string.
-        //
-        // Should we wish to recover from this case, probably the right approach
-        // is to ensure that the GUID is overwritten locally (given that we know
-        // the numeric ID).
-      }
-
-      // We could upload our material changes but get back additional status
-      // changes from the server.  Apply them.
-      acc.addChangedRecord(up.givenServerRecord(down));
-    }
-
-    @Override
-    public void onBadRequest(ClientReadingListRecord up, MozResponse response) {
-      recordFailed(up);
-    }
-
-    @Override
-    public void onFailure(ClientReadingListRecord up, Exception ex) {
-      recordFailed(up);
-    }
-
-    @Override
-    public void onFailure(ClientReadingListRecord up, MozResponse response) {
-      // Since we download and apply remote changes before uploading local changes, the conflict
-      // window is very small.  We should essentially never see true conflicts here.
-      if (response.getStatusCode() == 404) {
-        // We shouldn't see a 404; we should see a record with deleted=true when
-        // we fetch remote changes.
-        Logger.warn(LOG_TAG, "Ignoring 404 response patching record with guid: " + up.getGUID());
-      } else if (response.getStatusCode() == 409) {
-        // A 409 indicates that resolved_url has collided with an existing
-        // record. Not much to be done here.
-        Logger.info(LOG_TAG, "409 response seen; deleting record with guid: " + up.getGUID());
-        acc.addDeletion(up);
-      } else {
-        // We should never see a 412 since we race to upload our changes (and
-        // accept whatever the server gives us back).
-        recordFailed(up);
-      }
-    }
-
-    private void recordFailed(ClientReadingListRecord up) {
-      ++failures;
-    }
-
-    @Override
-    public void onBatchDone() {
-      try {
-        acc.finish();
-      } catch (Exception e) {
-        next.fail(e);
-        return;
-      }
-
-      if (failures == 0) {
-        next.next();
-        return;
-      }
-
-      next.fail();
-    }
-  }
-
-  private Queue<ClientReadingListRecord> collectModifiedFromCursor(final Cursor cursor) {
-    try {
-      final Queue<ClientReadingListRecord> toUpload = new LinkedList<>();
-
-      final int columnGUID = cursor.getColumnIndexOrThrow(ReadingListItems.GUID);
-      final int columnExcerpt = cursor.getColumnIndexOrThrow(ReadingListItems.EXCERPT);
-      final int columnResolvedURL = cursor.getColumnIndexOrThrow(ReadingListItems.RESOLVED_URL);
-      final int columnResolvedTitle = cursor.getColumnIndexOrThrow(ReadingListItems.RESOLVED_TITLE);
-      // TODO: final int columnIsArticle = cursor.getColumnIndexOrThrow(ReadingListItems.IS_ARTICLE);
-      // TODO: final int columnWordCount = cursor.getColumnIndexOrThrow(ReadingListItems.WORD_COUNT);
-
-      while (cursor.moveToNext()) {
-        final String guid = cursor.getString(columnGUID);
-        if (guid == null) {
-          // Nothing we can do here, but this should never happen: we should
-          // have uploaded this record as new before trying to upload a
-          // material modification!
-          continue;
-        }
-
-        final ExtendedJSONObject o = new ExtendedJSONObject();
-        o.put("id", guid);
-        final String excerpt = cursor.getString(columnExcerpt); // Can be NULL.
-        final String resolvedURL = cursor.getString(columnResolvedURL); // Can be NULL.
-        final String resolvedTitle = cursor.getString(columnResolvedTitle); // Can be NULL.
-        if (excerpt == null && resolvedURL == null && resolvedTitle == null) {
-          // Nothing material to upload, so skip this record.
-          continue;
-        }
-        o.put("excerpt", excerpt);
-        o.put("resolved_url", resolvedURL);
-        o.put("resolved_title", resolvedTitle);
-        // TODO: o.put("is_article", cursor.getInt(columnIsArticle) == 1);
-        // TODO: o.put("word_count", cursor.getInt(columnWordCount));
-
-        final ClientMetadata cm = null;
-        final ServerMetadata sm = new ServerMetadata(guid, -1L);
-        final ClientReadingListRecord record = new ClientReadingListRecord(sm, cm, o);
-        toUpload.add(record);
-      }
-
-      return toUpload;
-    } finally {
-      cursor.close();
-    }
-  }
-
-  private Queue<ClientReadingListRecord> accumulateNewItems(Cursor cursor) {
-    try {
-      final Queue<ClientReadingListRecord> toUpload = new LinkedList<>();
-      final ReadingListClientRecordFactory factory = new ReadingListClientRecordFactory(cursor);
-
-      ClientReadingListRecord record;
-      while ((record = factory.getNext()) != null) {
-        toUpload.add(record);
-      }
-      return toUpload;
-    } finally {
-      cursor.close();
-    }
-  }
-
-  protected void uploadDeletions(final StageDelegate delegate) {
-    try {
-      final Cursor cursor = local.getDeletedItems();
-
-      if (cursor == null) {
-        delegate.fail(new RuntimeException("Unable to get unread item cursor."));
-        return;
-      }
-
-      final Queue<String> toDelete = collectDeletedIDsFromCursor(cursor);
-
-      // Nothing to do.
-      if (toDelete.isEmpty()) {
-        Logger.debug(LOG_TAG, "No new deletions to upload. Skipping.");
-        delegate.next();
-        return;
-      } else {
-        Logger.debug(LOG_TAG, "Deleting " + toDelete.size() + " records from the server.");
-      }
-
-      final ReadingListChangeAccumulator acc = this.local.getChangeAccumulator();
-      final DeletionUploadDelegate deleteDelegate = new DeletionUploadDelegate(acc, delegate);
-
-      // Don't send I-U-S; we're happy for the client to win, because this is a one-way state change.
-      this.remote.delete(toDelete, executor, deleteDelegate);
-    } catch (Exception e) {
-      delegate.fail(e);
-    }
-  }
-
-  // N.B., status changes for items that haven't been uploaded yet are dealt with in
-  // uploadNewItems.
-  protected void uploadUnreadChanges(final StageDelegate delegate) {
-    try {
-      final Cursor cursor = local.getStatusChanges();
-
-      if (cursor == null) {
-        delegate.fail(new RuntimeException("Unable to get unread item cursor."));
-        return;
-      }
-
-      final Queue<ClientReadingListRecord> toUpload = collectStatusChangesFromCursor(cursor);
-
-      // Nothing to do.
-      if (toUpload.isEmpty()) {
-        Logger.debug(LOG_TAG, "No new unread changes to upload. Skipping.");
-        delegate.next();
-        return;
-      } else {
-        Logger.debug(LOG_TAG, "Uploading " + toUpload.size() + " new unread changes.");
-      }
-
-      // Upload each record. This looks like batching, but it's really chained serial requests.
-      final ReadingListChangeAccumulator acc = this.local.getChangeAccumulator();
-      final StatusUploadDelegate uploadDelegate = new StatusUploadDelegate(acc, delegate);
-
-      // Don't send I-U-S; in the case of favorites we're
-      // happy to overwrite the server value, and in the case of unread status
-      // the server will reconcile for us.
-      this.remote.patch(toUpload, executor, uploadDelegate);
-    } catch (Exception e) {
-      delegate.fail(e);
-    }
-  }
-
-  protected void uploadNewItems(final StageDelegate delegate) {
-    try {
-      final Cursor cursor = this.local.getNew();
-
-      if (cursor == null) {
-        delegate.fail(new RuntimeException("Unable to get new item cursor."));
-        return;
-      }
-
-      Queue<ClientReadingListRecord> toUpload = accumulateNewItems(cursor);
-
-      // Nothing to do.
-      if (toUpload.isEmpty()) {
-        Logger.debug(LOG_TAG, "No new items to upload. Skipping.");
-        delegate.next();
-        return;
-      } else {
-        Logger.debug(LOG_TAG, "Uploading " + toUpload.size() + " new items.");
-      }
-
-      final ReadingListChangeAccumulator acc = this.local.getChangeAccumulator();
-      final NewItemUploadDelegate uploadDelegate = new NewItemUploadDelegate(acc, new StageDelegate() {
-        private boolean tryFlushChanges() {
-          Logger.debug(LOG_TAG, "Flushing post-upload changes.");
-          try {
-            acc.finish();
-            return true;
-          } catch (Exception e) {
-            Logger.warn(LOG_TAG, "Flushing changes failed! This sync went wrong.", e);
-            delegate.fail(e);
-            return false;
-          }
-        }
-
-        @Override
-        public void next() {
-          Logger.debug(LOG_TAG, "New items uploaded successfully.");
-
-          if (tryFlushChanges()) {
-            delegate.next();
-          }
-        }
-
-        @Override
-        public void fail() {
-          Logger.warn(LOG_TAG, "Couldn't upload new items.");
-          if (tryFlushChanges()) {
-            delegate.fail();
-          }
-        }
-
-        @Override
-        public void fail(Exception e) {
-          Logger.warn(LOG_TAG, "Couldn't upload new items.", e);
-          if (tryFlushChanges()) {
-            delegate.fail(e);
-          }
-        }
-      });
-
-      // Handle 201 for success, 400 for invalid, 303 for redirect.
-      // TODO: 200 == "was already on the server, we didn't touch it, here it is."
-      // ... we need to apply it locally.
-      this.remote.add(toUpload, executor, uploadDelegate);
-    } catch (Exception e) {
-      delegate.fail(e);
-      return;
-    }
-  }
-
-  protected void uploadModified(final StageDelegate delegate) {
-    try {
-      // This looks strange because modified includes material changes and
-      // status changes, but this is called after status changes have been
-      // uploaded and removed from local storage. So what's left should be
-      // material changes.  Even so, it should be safe to upload status changes
-      // here.
-      final Cursor cursor = this.local.getModified();
-
-      if (cursor == null) {
-        delegate.fail(new RuntimeException("Unable to get modified item cursor."));
-        return;
-      }
-
-      final Queue<ClientReadingListRecord> toUpload = collectModifiedFromCursor(cursor);
-
-      // Nothing to do.
-      if (toUpload.isEmpty()) {
-        Logger.debug(LOG_TAG, "No modified items to upload. Skipping.");
-        delegate.next();
-        return;
-      } else {
-        Logger.debug(LOG_TAG, "Uploading " + toUpload.size() + " modified items.");
-      }
-
-      final ReadingListChangeAccumulator acc = this.local.getChangeAccumulator();
-      final ModifiedUploadDelegate uploadDelegate = new ModifiedUploadDelegate(acc, new StageDelegate() {
-        private boolean tryFlushChanges() {
-          Logger.debug(LOG_TAG, "Flushing post-upload changes.");
-          try {
-            acc.finish();
-            return true;
-          } catch (Exception e) {
-            Logger.warn(LOG_TAG, "Flushing changes failed! This sync went wrong.", e);
-            delegate.fail(e);
-            return false;
-          }
-        }
-
-        @Override
-        public void next() {
-          Logger.debug(LOG_TAG, "Modified items uploaded successfully.");
-
-          if (tryFlushChanges()) {
-            delegate.next();
-          }
-        }
-
-        @Override
-        public void fail() {
-          Logger.warn(LOG_TAG, "Couldn't upload modified items.");
-          if (tryFlushChanges()) {
-            delegate.fail();
-          }
-        }
-
-        @Override
-        public void fail(Exception e) {
-          Logger.warn(LOG_TAG, "Couldn't upload modified items.", e);
-          if (tryFlushChanges()) {
-            delegate.fail(e);
-          }
-        }
-      });
-
-      // Handle 201 for success, 400 for invalid, 303 for redirect.
-      // TODO: 200 == "was already on the server, we didn't touch it, here it is."
-      // ... we need to apply it locally.
-      this.remote.patch(toUpload, executor, uploadDelegate);
-    } catch (Exception e) {
-      delegate.fail(e);
-      return;
-    }
-  }
-
-  private void downloadIncoming(final long since, final StageDelegate delegate) {
-    final ReadingListChangeAccumulator postDownload = this.local.getChangeAccumulator();
-
-    final FetchSpec spec = new FetchSpec.Builder().setSince(since).build();
-
-    // TODO: should we flush the accumulator if we get a failure?
-    ReadingListRecordDelegate recordDelegate = new ReadingListRecordDelegate() {
-      @Override
-      public void onRecordReceived(ServerReadingListRecord record) {
-        postDownload.addDownloadedRecord(record);
-      }
-
-      @Override
-      public void onRecordMissingOrDeleted(String guid, ReadingListResponse resp) {
-        // Should never occur. Deleted records will be processed by onRecordReceived.
-      }
-
-      @Override
-      public void onFailure(Exception error) {
-        Logger.error(LOG_TAG, "Download failed. since = " + since + ".", error);
-        delegate.fail(error);
-      }
-
-      @Override
-      public void onFailure(MozResponse response) {
-        final int statusCode = response.getStatusCode();
-        Logger.error(LOG_TAG, "Download failed. since = " + since + ". Response: " + statusCode);
-        response.logResponseBody(LOG_TAG);
-        if (response.isInvalidAuthentication()) {
-          delegate.fail(new ReadingListInvalidAuthenticationException(response));
-        } else {
-          delegate.fail();
-        }
-      }
-
-      @Override
-      public void onComplete(ReadingListResponse response) {
-        long lastModified = response.getLastModified();
-        Logger.info(LOG_TAG, "Server last modified: " + lastModified);
-        try {
-          postDownload.finish();
-
-          // Yay. We do this here so that if writing changes fails, we don't advance.
-          advanceLastModified(lastModified);
-          delegate.next();
-        } catch (Exception e) {
-          delegate.fail(e);
-        }
-      }
-    };
-
-    try {
-      remote.getAll(spec, recordDelegate, since);
-    } catch (URISyntaxException e) {
-      delegate.fail(e);
-    }
-  }
-
-  /**
-   * Upload deletions and unread changes, then upload new items, then call `done`.
-   * Substantially modified records are uploaded last.
-   *
-   * @param syncDelegate only used for status callbacks.
-   */
-  private void syncUp(final ReadingListSynchronizerDelegate syncDelegate, final StageDelegate done) {
-    // Third.
-    final StageDelegate onNewItemsUploaded = new NextDelegate(executor) {
-      @Override
-      public void doNext() {
-        syncDelegate.onNewItemUploadComplete(null, null);
-        done.next();
-      }
-
-      @Override
-      public void doFail(Exception e) {
-        done.fail(e);
-      }
-    };
-
-    // Second.
-    final StageDelegate onUnreadChangesUploaded = new NextDelegate(executor) {
-      @Override
-      public void doNext() {
-        syncDelegate.onStatusUploadComplete(null, null);
-        uploadNewItems(onNewItemsUploaded);
-      }
-
-      @Override
-      public void doFail(Exception e) {
-        Logger.warn(LOG_TAG, "Uploading unread changes failed.", e);
-        done.fail(e);
-      }
-    };
-
-    // First.
-    final StageDelegate onDeletionsUploaded = new NextDelegate(executor) {
-      @Override
-      public void doNext() {
-        syncDelegate.onDeletionsUploadComplete();
-        uploadUnreadChanges(onUnreadChangesUploaded);
-      }
-
-      @Override
-      public void doFail(Exception e) {
-        Logger.warn(LOG_TAG, "Uploading deletions failed.", e);
-        done.fail(e);
-      }
-    };
-
-    try {
-      uploadDeletions(onDeletionsUploaded);
-    } catch (Exception ee) {
-      done.fail(ee);
-    }
-  }
-
-
-  /**
-   * Do an upload-only sync.
-   * By "upload-only" we mean status-only changes and new items.
-   * To upload modifications, use syncAll.
-   */
-  /*
-   // Not yet used
-  public void syncUp(final ReadingListSynchronizerDelegate syncDelegate) {
-    final StageDelegate onUploadCompleted = new StageDelegate() {
-      @Override
-      public void next() {
-        // TODO
-        syncDelegate.onNewItemUploadComplete(null, null);
-      }
-
-      @Override
-      public void fail(Exception e) {
-        syncDelegate.onUnableToSync(e);
-      }
-    };
-
-    executor.execute(new Runnable() {
-      @Override
-      public void run() {
-        try {
-          syncUp(onUploadCompleted);
-        } catch (Exception e) {
-          syncDelegate.onUnableToSync(e);
-          return;
-        }
-      }
-    });
-  }
-*/
-
-  /**
-   * Do a bidirectional sync.
-   */
-  public void syncAll(final ReadingListSynchronizerDelegate syncDelegate) {
-    syncAll(getLastModified(), syncDelegate);
-  }
-
-  public void syncAll(final long since, final ReadingListSynchronizerDelegate syncDelegate) {
-    // Fourth: call back to the synchronizer delegate.
-    final StageDelegate onModifiedUploadComplete = new NextDelegate(executor) {
-      @Override
-      public void doNext() {
-        syncDelegate.onModifiedUploadComplete();
-        syncDelegate.onComplete();
-      }
-
-      @Override
-      public void doFail(Exception e) {
-        syncDelegate.onUnableToSync(e);
-      }
-    };
-
-    // Third: upload modified records.
-    final StageDelegate onDownloadCompleted = new NextDelegate(executor) {     // TODO: since.
-      @Override
-      public void doNext() {
-        // TODO: save prefs.
-        syncDelegate.onDownloadComplete();
-        uploadModified(onModifiedUploadComplete);
-      }
-
-      @Override
-      public void doFail(Exception e) {
-        Logger.warn(LOG_TAG, "Download failed.", e);
-        syncDelegate.onUnableToSync(e);
-      }
-    };
-
-    // Second: download incoming changes.
-    final StageDelegate onUploadCompleted = new NextDelegate(executor) {
-      @Override
-      public void doNext() {
-        // N.B., we apply the downloaded versions of all uploaded records.
-        // That means the DB server timestamp matches the server's current
-        // timestamp when we do a fetch; we skip records in this way.
-        // We can also optimize by keeping the (guid, server timestamp) pair
-        // in memory, but of course this runs into invalidation issues if
-        // concurrent writes are occurring.
-        downloadIncoming(since, onDownloadCompleted);
-      }
-
-      @Override
-      public void doFail(Exception e) {
-        Logger.warn(LOG_TAG, "Upload failed.", e);
-        syncDelegate.onUnableToSync(e);
-      }
-    };
-
-    // First: upload changes and new items.
-    executor.execute(new Runnable() {
-      @Override
-      public void run() {
-        try {
-          syncUp(syncDelegate, onUploadCompleted);
-        } catch (Exception e) {
-          syncDelegate.onUnableToSync(e);
-          return;
-        }
-      }
-    });
-
-    // TODO: ensure that records we identified as conflicts have been downloaded.
-  }
-
-  protected long getLastModified() {
-    return prefs.getLong(PREF_LAST_MODIFIED, -1L);
-  }
-
-  protected void advanceLastModified(final long lastModified) {
-    if (getLastModified() > lastModified) {
-      return;
-    }
-    prefs.edit().putLong(PREF_LAST_MODIFIED, lastModified).apply();
-  }
-}
deleted file mode 100644
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/reading/ReadingListSynchronizerDelegate.java
+++ /dev/null
@@ -1,23 +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.reading;
-
-import java.util.Collection;
-
-public interface ReadingListSynchronizerDelegate {
-  // Called on failure.
-  void onUnableToSync(Exception e);
-
-  // These are called sequentially, or not at all
-  // if a failure occurs.
-  void onDeletionsUploadComplete();
-  void onStatusUploadComplete(Collection<String> uploaded, Collection<String> failed);
-  void onNewItemUploadComplete(Collection<String> uploaded, Collection<String> failed);
-  void onDownloadComplete();
-  void onModifiedUploadComplete();
-
-  // If no failure occurred, called at the end.
-  void onComplete();
-}
deleted file mode 100644
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/reading/ReadingListWipeDelegate.java
+++ /dev/null
@@ -1,14 +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.reading;
-
-import org.mozilla.gecko.sync.net.MozResponse;
-
-public interface ReadingListWipeDelegate {
-  void onSuccess(ReadingListStorageResponse response);
-  void onPreconditionFailed(MozResponse response);
-  void onFailure(Exception e);
-  void onFailure(MozResponse response);
-}
deleted file mode 100644
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/reading/ServerReadingListRecord.java
+++ /dev/null
@@ -1,35 +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.reading;
-
-import org.mozilla.gecko.sync.ExtendedJSONObject;
-
-public class ServerReadingListRecord extends ReadingListRecord {
-  final ExtendedJSONObject fields;
-
-  public ServerReadingListRecord(ExtendedJSONObject obj) {
-    super(new ServerMetadata(obj));
-    this.fields = obj.deepCopy();
-  }
-
-  @Override
-  public String getURL() {
-    return this.fields.getString("url");    // TODO: resolved_url
-  }
-
-  @Override
-  public String getTitle() {
-    return this.fields.getString("title");  // TODO: resolved_title
-  }
-
-  @Override
-  public String getAddedBy() {
-    return this.fields.getString("added_by");
-  }
-
-  public String getExcerpt() {
-    return this.fields.getString("excerpt");
-  }
-}