Bug 1237880 - Remove code for syncing Reading List. r=rnewman
authorNick Alexander <nalexander@mozilla.com>
Tue, 19 Jan 2016 15:06:28 -0800
changeset 303133 31202b9037c8aac5e5b172c9a00337a0605880cf
parent 303132 ed222e27eb642c27d4d24dd437121f1047c97744
child 303134 9300568c634b2f76eb5893b3630e1efe76c7f49d
push id8978
push userraliiev@mozilla.com
push dateMon, 25 Jan 2016 14:05:32 +0000
treeherdermozilla-aurora@b9a803752a2c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersrnewman
bugs1237880
milestone46.0a1
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");
-  }
-}