[X2Go-Commits] [pale-moon] 273/294: Remove Firefox Accounts service and tie-ins.

git-admin at x2go.org git-admin at x2go.org
Sat Apr 27 08:58:33 CEST 2019


This is an automated email from the git hooks/post-receive script.

x2go pushed a commit to branch upstream/28.5.0
in repository pale-moon.

commit de75f133a7ec654d2a7c5bf628b3aee9fc109730
Author: wolfbeast <mcwerewolf at wolfbeast.com>
Date:   Fri Apr 19 02:02:56 2019 +0200

    Remove Firefox Accounts service and tie-ins.
    
    See previous commit for removal of browser identity module.
---
 mobile/android/app/mobile.js                       |   12 -
 mobile/android/base/android-services.mozbuild      |   61 -
 .../java/org/mozilla/gecko/push/PushService.java   |   24 +-
 .../android/base/resources/values-v21/themes.xml   |    5 -
 mobile/android/chrome/content/aboutAccounts.js     |  351 ----
 mobile/android/chrome/content/aboutAccounts.xhtml  |   83 -
 mobile/android/chrome/content/browser.js           |   14 -
 mobile/android/chrome/jar.mn                       |    2 -
 mobile/android/components/FxAccountsPush.js        |  164 --
 .../android/components/MobileComponents.manifest   |    5 -
 mobile/android/components/moz.build                |    1 -
 mobile/android/config/proguard/proguard.cfg        |    2 -
 mobile/android/confvars.sh                         |    2 +-
 mobile/android/installer/package-manifest.in       |    1 -
 mobile/android/modules/Accounts.jsm                |  105 +-
 mobile/android/modules/FxAccountsWebChannel.jsm    |  394 -----
 mobile/android/modules/moz.build                   |    1 -
 mobile/android/services/README.txt                 |    1 -
 .../FxAccountAndroidManifest_activities.xml.in     |   63 -
 .../FxAccountAndroidManifest_permissions.xml.in    |   18 -
 .../FxAccountAndroidManifest_services.xml.in       |   34 -
 .../gecko/background/ReadingListConstants.java     |   23 -
 .../gecko/background/common/EditorBranch.java      |   82 -
 .../gecko/background/common/GlobalConstants.java   |   90 -
 .../gecko/background/common/PrefsBranch.java       |   83 -
 .../gecko/background/common/log/Logger.java        |  232 ---
 .../log/writers/AndroidLevelCachingLogWriter.java  |  132 --
 .../common/log/writers/AndroidLogWriter.java       |   46 -
 .../log/writers/LevelFilteringLogWriter.java       |   67 -
 .../background/common/log/writers/LogWriter.java   |   29 -
 .../common/log/writers/PrintLogWriter.java         |   77 -
 .../common/log/writers/SimpleTagLogWriter.java     |   21 -
 .../common/log/writers/StringLogWriter.java        |   57 -
 .../common/log/writers/TagLogWriter.java           |   55 -
 .../log/writers/ThreadLocalTagLogWriter.java       |   25 -
 .../common/telemetry/TelemetryWrapper.java         |   56 -
 .../mozilla/gecko/background/db/CursorDumper.java  |   99 --
 .../java/org/mozilla/gecko/background/db/Tab.java  |   86 -
 .../background/fxa/FxAccount20CreateDelegate.java  |   52 -
 .../background/fxa/FxAccount20LoginDelegate.java   |   36 -
 .../gecko/background/fxa/FxAccountClient.java      |   24 -
 .../gecko/background/fxa/FxAccountClient20.java    |  914 -----------
 .../background/fxa/FxAccountClientException.java   |  133 --
 .../gecko/background/fxa/FxAccountRemoteError.java |   33 -
 .../gecko/background/fxa/FxAccountUtils.java       |  217 ---
 .../gecko/background/fxa/PasswordStretcher.java    |   12 -
 .../background/fxa/QuickPasswordStretcher.java     |   35 -
 .../mozilla/gecko/background/fxa/SkewHandler.java  |  111 --
 .../fxa/oauth/FxAccountAbstractClient.java         |  224 ---
 .../oauth/FxAccountAbstractClientException.java    |   68 -
 .../fxa/oauth/FxAccountOAuthClient10.java          |  129 --
 .../fxa/oauth/FxAccountOAuthRemoteError.java       |   19 -
 .../fxa/profile/FxAccountProfileClient10.java      |   59 -
 .../gecko/background/nativecode/NativeCrypto.java  |   60 -
 .../background/preferences/PreferenceFragment.java |  326 ----
 .../preferences/PreferenceManagerCompat.java       |  226 ---
 .../java/org/mozilla/gecko/browserid/ASNUtils.java |   82 -
 .../mozilla/gecko/browserid/BrowserIDKeyPair.java  |   35 -
 .../gecko/browserid/DSACryptoImplementation.java   |  255 ---
 .../mozilla/gecko/browserid/JSONWebTokenUtils.java |  245 ---
 .../gecko/browserid/MockMyIDTokenFactory.java      |  128 --
 .../gecko/browserid/RSACryptoImplementation.java   |  182 --
 .../mozilla/gecko/browserid/SigningPrivateKey.java |   41 -
 .../gecko/browserid/VerifyingPublicKey.java        |   34 -
 .../AbstractBrowserIDRemoteVerifierClient.java     |   95 --
 .../verifier/BrowserIDRemoteVerifierClient10.java  |   62 -
 .../verifier/BrowserIDRemoteVerifierClient20.java  |   58 -
 .../verifier/BrowserIDVerifierClient.java          |    9 -
 .../verifier/BrowserIDVerifierDelegate.java        |   13 -
 .../verifier/BrowserIDVerifierException.java       |   41 -
 .../java/org/mozilla/gecko/fxa/AccountLoader.java  |  227 ---
 .../org/mozilla/gecko/fxa/FirefoxAccounts.java     |  222 ---
 .../org/mozilla/gecko/fxa/FxAccountConstants.java  |   75 -
 .../org/mozilla/gecko/fxa/FxAccountDevice.java     |   81 -
 .../gecko/fxa/FxAccountDeviceRegistrator.java      |  282 ----
 .../mozilla/gecko/fxa/FxAccountPushHandler.java    |   95 --
 .../org/mozilla/gecko/fxa/SyncStatusListener.java  |   31 -
 .../fxa/activities/CustomColorPreference.java      |   52 -
 .../fxa/activities/FxAccountAbstractActivity.java  |   80 -
 .../FxAccountConfirmAccountActivityWeb.java        |   11 -
 .../FxAccountFinishMigratingActivityWeb.java       |   11 -
 .../activities/FxAccountGetStartedActivityWeb.java |   11 -
 .../fxa/activities/FxAccountStatusActivity.java    |  228 ---
 .../fxa/activities/FxAccountStatusFragment.java    |  949 -----------
 .../FxAccountUpdateCredentialsActivityWeb.java     |   11 -
 .../fxa/activities/FxAccountWebFlowActivity.java   |   91 -
 .../activities/PicassoPreferenceIconTarget.java    |   63 -
 .../gecko/fxa/authenticator/AccountPickler.java    |  362 ----
 .../gecko/fxa/authenticator/AndroidFxAccount.java  |  929 -----------
 .../FxADefaultLoginStateMachineDelegate.java       |   84 -
 .../fxa/authenticator/FxAccountAuthenticator.java  |  385 -----
 .../FxAccountAuthenticatorService.java             |   55 -
 .../fxa/authenticator/FxAccountLoginDelegate.java  |   26 -
 .../fxa/authenticator/FxAccountLoginException.java |   33 -
 .../gecko/fxa/login/BaseRequestDelegate.java       |   49 -
 .../org/mozilla/gecko/fxa/login/Cohabiting.java    |   50 -
 .../java/org/mozilla/gecko/fxa/login/Doghouse.java |   25 -
 .../java/org/mozilla/gecko/fxa/login/Engaged.java  |   91 -
 .../fxa/login/FxAccountLoginStateMachine.java      |   84 -
 .../gecko/fxa/login/FxAccountLoginTransition.java  |   68 -
 .../java/org/mozilla/gecko/fxa/login/Married.java  |  117 --
 .../gecko/fxa/login/MigratedFromSync11.java        |   28 -
 .../org/mozilla/gecko/fxa/login/Separated.java     |   25 -
 .../java/org/mozilla/gecko/fxa/login/State.java    |   72 -
 .../org/mozilla/gecko/fxa/login/StateFactory.java  |  206 ---
 .../gecko/fxa/login/TokensAndKeysState.java        |   45 -
 .../fxa/receivers/FxAccountDeletedService.java     |  154 --
 .../fxa/receivers/FxAccountUpgradeReceiver.java    |  133 --
 .../fxa/sync/FxAccountNotificationManager.java     |  114 --
 .../gecko/fxa/sync/FxAccountProfileService.java    |  107 --
 .../gecko/fxa/sync/FxAccountSchedulePolicy.java    |  178 --
 .../gecko/fxa/sync/FxAccountSyncAdapter.java       |  568 -------
 .../gecko/fxa/sync/FxAccountSyncDelegate.java      |  110 --
 .../gecko/fxa/sync/FxAccountSyncService.java       |   28 -
 .../gecko/fxa/sync/FxAccountSyncStatusHelper.java  |  113 --
 .../org/mozilla/gecko/fxa/sync/SchedulePolicy.java |   43 -
 .../gecko/push/RegisterUserAgentResponse.java      |   19 -
 .../gecko/push/SubscribeChannelResponse.java       |   19 -
 .../gecko/push/autopush/AutopushClient.java        |  410 -----
 .../push/autopush/AutopushClientException.java     |   81 -
 .../gecko/sync/AlreadySyncingException.java        |   22 -
 .../org/mozilla/gecko/sync/BackoffHandler.java     |   34 -
 .../gecko/sync/BadRequiredFieldJSONException.java  |    5 -
 .../org/mozilla/gecko/sync/CollectionKeys.java     |  199 ---
 .../org/mozilla/gecko/sync/CommandProcessor.java   |  261 ---
 .../java/org/mozilla/gecko/sync/CommandRunner.java |   22 -
 .../mozilla/gecko/sync/CredentialException.java    |   56 -
 .../java/org/mozilla/gecko/sync/CryptoRecord.java  |  255 ---
 .../org/mozilla/gecko/sync/DelayedWorkTracker.java |   69 -
 .../org/mozilla/gecko/sync/EngineSettings.java     |   31 -
 .../org/mozilla/gecko/sync/ExtendedJSONObject.java |  426 -----
 .../java/org/mozilla/gecko/sync/GlobalSession.java | 1167 -------------
 .../mozilla/gecko/sync/HTTPFailureException.java   |   47 -
 .../org/mozilla/gecko/sync/InfoCollections.java    |  103 --
 .../org/mozilla/gecko/sync/InfoConfiguration.java  |   93 --
 .../java/org/mozilla/gecko/sync/InfoCounts.java    |   67 -
 .../org/mozilla/gecko/sync/JSONRecordFetcher.java  |  145 --
 .../org/mozilla/gecko/sync/KeyBundleProvider.java  |   11 -
 .../java/org/mozilla/gecko/sync/MetaGlobal.java    |  372 -----
 .../mozilla/gecko/sync/MetaGlobalException.java    |   45 -
 .../sync/MetaGlobalMissingEnginesException.java    |    9 -
 .../gecko/sync/MetaGlobalNotSetException.java      |    9 -
 .../gecko/sync/NoCollectionKeysSetException.java   |   16 -
 .../gecko/sync/NodeAuthenticationException.java    |   16 -
 .../mozilla/gecko/sync/NonArrayJSONException.java  |   17 -
 .../mozilla/gecko/sync/NonObjectJSONException.java |   17 -
 .../gecko/sync/NullClusterURLException.java        |   16 -
 .../mozilla/gecko/sync/PersistedMetaGlobal.java    |   86 -
 .../mozilla/gecko/sync/PrefsBackoffHandler.java    |   59 -
 .../main/java/org/mozilla/gecko/sync/README.txt    |    1 -
 .../sync/Server11PreviousPostFailedException.java  |   12 -
 .../sync/Server11RecordPostFailedException.java    |   12 -
 .../sync/SharedPreferencesClientsDataDelegate.java |  121 --
 .../mozilla/gecko/sync/Sync11Configuration.java    |   84 -
 .../org/mozilla/gecko/sync/SyncConfiguration.java  |  480 ------
 .../gecko/sync/SyncConfigurationException.java     |   16 -
 .../java/org/mozilla/gecko/sync/SyncConstants.java |   20 -
 .../java/org/mozilla/gecko/sync/SyncException.java |   34 -
 .../gecko/sync/SynchronizerConfiguration.java      |   68 -
 .../java/org/mozilla/gecko/sync/ThreadPool.java    |   15 -
 .../gecko/sync/UnexpectedJSONException.java        |   25 -
 ...nSynchronizerConfigurationVersionException.java |   16 -
 .../main/java/org/mozilla/gecko/sync/Utils.java    |  575 -------
 .../mozilla/gecko/sync/crypto/CryptoException.java |   19 -
 .../org/mozilla/gecko/sync/crypto/CryptoInfo.java  |  232 ---
 .../java/org/mozilla/gecko/sync/crypto/HKDF.java   |  128 --
 .../sync/crypto/HMACVerificationException.java     |   12 -
 .../org/mozilla/gecko/sync/crypto/KeyBundle.java   |  135 --
 .../sync/crypto/MissingCryptoInputException.java   |    9 -
 .../gecko/sync/crypto/NoKeyBundleException.java    |    9 -
 .../java/org/mozilla/gecko/sync/crypto/PBKDF2.java |   78 -
 .../gecko/sync/crypto/PersistedCrypto5Keys.java    |  103 --
 .../gecko/sync/delegates/ClientsDataDelegate.java  |   28 -
 .../gecko/sync/delegates/FreshStartDelegate.java   |   10 -
 .../sync/delegates/GlobalSessionCallback.java      |   49 -
 .../sync/delegates/JSONRecordFetchDelegate.java    |   19 -
 .../gecko/sync/delegates/KeyUploadDelegate.java    |   21 -
 .../gecko/sync/delegates/MetaGlobalDelegate.java   |   15 -
 .../gecko/sync/delegates/WipeServerDelegate.java   |   10 -
 .../middleware/Crypto5MiddlewareRepository.java    |   76 -
 .../Crypto5MiddlewareRepositorySession.java        |  172 --
 .../sync/middleware/MiddlewareRepository.java      |   22 -
 .../middleware/MiddlewareRepositorySession.java    |  185 ---
 .../net/AbstractBearerTokenAuthHeaderProvider.java |   34 -
 .../mozilla/gecko/sync/net/AuthHeaderProvider.java |   30 -
 .../org/mozilla/gecko/sync/net/BaseResource.java   |  565 -------
 .../gecko/sync/net/BaseResourceDelegate.java       |   44 -
 .../gecko/sync/net/BasicAuthHeaderProvider.java    |   51 -
 .../gecko/sync/net/BearerAuthHeaderProvider.java   |   22 -
 .../sync/net/BrowserIDAuthHeaderProvider.java      |   23 -
 .../gecko/sync/net/ConnectionMonitorThread.java    |   44 -
 .../sync/net/GzipNonChunkedCompressingEntity.java  |   92 --
 .../gecko/sync/net/HMACAuthHeaderProvider.java     |  257 ---
 .../gecko/sync/net/HandleProgressException.java    |   15 -
 .../gecko/sync/net/HawkAuthHeaderProvider.java     |  403 -----
 .../gecko/sync/net/HttpResponseObserver.java       |   20 -
 .../org/mozilla/gecko/sync/net/MozResponse.java    |  225 ---
 .../java/org/mozilla/gecko/sync/net/Resource.java  |   20 -
 .../mozilla/gecko/sync/net/ResourceDelegate.java   |   55 -
 .../org/mozilla/gecko/sync/net/SRPConstants.java   |  174 --
 .../org/mozilla/gecko/sync/net/SyncResponse.java   |  157 --
 .../sync/net/SyncStorageCollectionRequest.java     |  145 --
 .../net/SyncStorageCollectionRequestDelegate.java  |    9 -
 .../gecko/sync/net/SyncStorageRecordRequest.java   |   95 --
 .../mozilla/gecko/sync/net/SyncStorageRequest.java |  204 ---
 .../gecko/sync/net/SyncStorageRequestDelegate.java |   38 -
 .../net/SyncStorageRequestIncrementalDelegate.java |    9 -
 .../gecko/sync/net/SyncStorageResponse.java        |   85 -
 .../mozilla/gecko/sync/net/TLSSocketFactory.java   |   62 -
 .../sync/net/WBOCollectionRequestDelegate.java     |   35 -
 .../mozilla/gecko/sync/net/WBORequestDelegate.java |   14 -
 .../BookmarkNeedsReparentingException.java         |   17 -
 .../sync/repositories/BookmarksRepository.java     |   16 -
 .../ConstrainedServer11Repository.java             |   51 -
 .../sync/repositories/FetchFailedException.java    |   11 -
 .../sync/repositories/HashSetStoreTracker.java     |   61 -
 .../gecko/sync/repositories/HistoryRepository.java |   16 -
 .../sync/repositories/IdentityRecordFactory.java   |   15 -
 .../repositories/InactiveSessionException.java     |   17 -
 .../repositories/InvalidBookmarkTypeException.java |   17 -
 .../sync/repositories/InvalidRequestException.java |   16 -
 .../InvalidSessionTransitionException.java         |   17 -
 .../MultipleRecordsForGuidException.java           |   16 -
 .../repositories/NoContentProviderException.java   |   25 -
 .../sync/repositories/NoGuidForIdException.java    |   16 -
 .../repositories/NoStoreDelegateException.java     |   11 -
 .../sync/repositories/NullCursorException.java     |   17 -
 .../sync/repositories/ParentNotFoundException.java |   17 -
 .../repositories/ProfileDatabaseException.java     |   17 -
 .../gecko/sync/repositories/RecordFactory.java     |   13 -
 .../gecko/sync/repositories/RecordFilter.java      |   11 -
 .../gecko/sync/repositories/Repository.java        |   18 -
 .../gecko/sync/repositories/RepositorySession.java |  384 -----
 .../sync/repositories/RepositorySessionBundle.java |   55 -
 .../sync/repositories/Server11Repository.java      |  144 --
 .../repositories/Server11RepositorySession.java    |  104 --
 .../sync/repositories/StoreFailedException.java    |   11 -
 .../gecko/sync/repositories/StoreTracker.java      |   82 -
 .../StoreTrackingRepositorySession.java            |  102 --
 .../AndroidBrowserBookmarksDataAccessor.java       |  326 ----
 .../android/AndroidBrowserBookmarksRepository.java |   25 -
 .../AndroidBrowserBookmarksRepositorySession.java  | 1107 -------------
 .../android/AndroidBrowserHistoryDataAccessor.java |  188 ---
 .../android/AndroidBrowserHistoryRepository.java   |   25 -
 .../AndroidBrowserHistoryRepositorySession.java    |  208 ---
 .../android/AndroidBrowserRepository.java          |   74 -
 .../AndroidBrowserRepositoryDataAccessor.java      |  232 ---
 .../android/AndroidBrowserRepositorySession.java   |  792 ---------
 .../android/BookmarksDeletionManager.java          |  239 ---
 .../android/BookmarksInsertionManager.java         |  298 ----
 .../android/BrowserContractHelpers.java            |  154 --
 .../android/CachedSQLiteOpenHelper.java            |   62 -
 .../sync/repositories/android/ClientsDatabase.java |  252 ---
 .../android/ClientsDatabaseAccessor.java           |  178 --
 .../repositories/android/FennecTabsRepository.java |  383 -----
 .../android/FormHistoryRepositorySession.java      |  723 --------
 .../android/PasswordsRepositorySession.java        |  725 --------
 .../gecko/sync/repositories/android/RepoUtils.java |  290 ----
 .../sync/repositories/android/VisitsHelper.java    |  130 --
 ...eferrableRepositorySessionCreationDelegate.java |   41 -
 .../DeferredRepositorySessionBeginDelegate.java    |   46 -
 ...erredRepositorySessionFetchRecordsDelegate.java |   56 -
 .../DeferredRepositorySessionFinishDelegate.java   |   51 -
 .../DeferredRepositorySessionStoreDelegate.java    |   57 -
 .../delegates/RepositorySessionBeginDelegate.java  |   23 -
 .../delegates/RepositorySessionCleanDelegate.java  |   12 -
 .../RepositorySessionCreationDelegate.java         |   15 -
 .../RepositorySessionFetchRecordsDelegate.java     |   27 -
 .../delegates/RepositorySessionFinishDelegate.java |   16 -
 .../RepositorySessionGuidsSinceDelegate.java       |   10 -
 .../delegates/RepositorySessionStoreDelegate.java  |   23 -
 .../delegates/RepositorySessionWipeDelegate.java   |   13 -
 .../sync/repositories/domain/BookmarkRecord.java   |  488 ------
 .../repositories/domain/BookmarkRecordFactory.java |   25 -
 .../sync/repositories/domain/ClientRecord.java     |  231 ---
 .../repositories/domain/ClientRecordFactory.java   |   17 -
 .../repositories/domain/FormHistoryRecord.java     |  139 --
 .../sync/repositories/domain/HistoryRecord.java    |  217 ---
 .../repositories/domain/HistoryRecordFactory.java  |   25 -
 .../sync/repositories/domain/PasswordRecord.java   |  205 ---
 .../repositories/domain/PasswordRecordFactory.java |   19 -
 .../gecko/sync/repositories/domain/Record.java     |  308 ----
 .../repositories/domain/RecordParseException.java  |   14 -
 .../gecko/sync/repositories/domain/TabsRecord.java |  153 --
 .../repositories/domain/TabsRecordFactory.java     |   17 -
 .../sync/repositories/domain/VersionConstants.java |   14 -
 .../downloaders/BatchingDownloader.java            |  310 ----
 .../downloaders/BatchingDownloaderDelegate.java    |   91 -
 .../sync/repositories/uploaders/BatchMeta.java     |  165 --
 .../repositories/uploaders/BatchingUploader.java   |  344 ----
 .../repositories/uploaders/BufferSizeTracker.java  |  103 --
 .../repositories/uploaders/MayUploadProvider.java  |    9 -
 .../gecko/sync/repositories/uploaders/Payload.java |   66 -
 .../uploaders/PayloadUploadDelegate.java           |  185 ---
 .../uploaders/RecordUploadRunnable.java            |  176 --
 .../org/mozilla/gecko/sync/setup/Constants.java    |   29 -
 .../gecko/sync/setup/InvalidSyncKeyException.java  |    9 -
 .../gecko/sync/setup/activities/ActivityUtils.java |   34 -
 .../gecko/sync/setup/activities/WebURLFinder.java  |  161 --
 .../sync/stage/AbstractNonRepositorySyncStage.java |   26 -
 .../stage/AbstractSessionManagingSyncStage.java    |   43 -
 .../AndroidBrowserBookmarksServerSyncStage.java    |   80 -
 .../AndroidBrowserHistoryServerSyncStage.java      |   74 -
 .../gecko/sync/stage/CheckPreconditionsStage.java  |   13 -
 .../mozilla/gecko/sync/stage/CompletedStage.java   |   16 -
 .../gecko/sync/stage/EnsureCrypto5KeysStage.java   |  192 ---
 .../sync/stage/FennecTabsServerSyncStage.java      |   40 -
 .../sync/stage/FetchInfoCollectionsStage.java      |   44 -
 .../sync/stage/FetchInfoConfigurationStage.java    |   59 -
 .../gecko/sync/stage/FetchMetaGlobalStage.java     |   79 -
 .../sync/stage/FormHistoryServerSyncStage.java     |   76 -
 .../mozilla/gecko/sync/stage/GlobalSyncStage.java  |   93 --
 .../gecko/sync/stage/NoSuchStageException.java     |   13 -
 .../gecko/sync/stage/PasswordsServerSyncStage.java |   38 -
 .../stage/SafeConstrainedServer11Repository.java   |  110 --
 .../mozilla/gecko/sync/stage/ServerSyncStage.java  |  627 -------
 .../gecko/sync/stage/SyncClientsEngineStage.java   |  691 --------
 .../gecko/sync/stage/UploadMetaGlobalStage.java    |   18 -
 .../synchronizer/ConcurrentRecordConsumer.java     |  122 --
 .../gecko/sync/synchronizer/RecordConsumer.java    |   26 -
 .../gecko/sync/synchronizer/RecordsChannel.java    |  292 ----
 .../sync/synchronizer/RecordsChannelDelegate.java  |   13 -
 .../sync/synchronizer/RecordsConsumerDelegate.java |   23 -
 .../sync/synchronizer/SerialRecordConsumer.java    |  131 --
 .../sync/synchronizer/ServerLocalSynchronizer.java |   18 -
 .../ServerLocalSynchronizerSession.java            |   78 -
 .../synchronizer/SessionNotBegunException.java     |   19 -
 .../gecko/sync/synchronizer/Synchronizer.java      |  105 --
 .../sync/synchronizer/SynchronizerDelegate.java    |   10 -
 .../sync/synchronizer/SynchronizerSession.java     |  425 -----
 .../synchronizer/SynchronizerSessionDelegate.java  |   13 -
 .../gecko/sync/synchronizer/UnbundleError.java     |   19 -
 .../synchronizer/UnexpectedSessionException.java   |   26 -
 .../gecko/sync/telemetry/TelemetryContract.java    |   56 -
 .../gecko/tokenserver/TokenServerClient.java       |  330 ----
 .../tokenserver/TokenServerClientDelegate.java     |   19 -
 .../gecko/tokenserver/TokenServerException.java    |   89 -
 .../gecko/tokenserver/TokenServerToken.java        |   19 -
 .../java/org/mozilla/gecko/util/PRNGFixes.java     |  339 ----
 .../res/drawable-hdpi/fxaccount_sync_error.png     |  Bin 543 -> 0 bytes
 .../main/res/drawable-hdpi/sync_avatar_default.png |  Bin 5146 -> 0 bytes
 .../src/main/res/drawable-hdpi/sync_desktop.png    |  Bin 196 -> 0 bytes
 .../res/drawable-hdpi/sync_desktop_inactive.png    |  Bin 211 -> 0 bytes
 .../src/main/res/drawable-hdpi/sync_mobile.png     |  Bin 163 -> 0 bytes
 .../res/drawable-hdpi/sync_mobile_inactive.png     |  Bin 165 -> 0 bytes
 .../src/main/res/drawable-hdpi/sync_promo.png      |  Bin 994 -> 0 bytes
 .../res/drawable-xhdpi/fxaccount_sync_error.png    |  Bin 716 -> 0 bytes
 .../src/main/res/drawable-xhdpi/sync_desktop.png   |  Bin 229 -> 0 bytes
 .../res/drawable-xhdpi/sync_desktop_inactive.png   |  Bin 244 -> 0 bytes
 .../src/main/res/drawable-xhdpi/sync_mobile.png    |  Bin 210 -> 0 bytes
 .../res/drawable-xhdpi/sync_mobile_inactive.png    |  Bin 215 -> 0 bytes
 .../src/main/res/drawable-xhdpi/sync_promo.png     |  Bin 1236 -> 0 bytes
 .../res/drawable-xxhdpi/fxaccount_sync_error.png   |  Bin 1070 -> 0 bytes
 .../res/drawable-xxhdpi/sync_avatar_default.png    |  Bin 11124 -> 0 bytes
 .../src/main/res/drawable-xxhdpi/sync_desktop.png  |  Bin 339 -> 0 bytes
 .../res/drawable-xxhdpi/sync_desktop_inactive.png  |  Bin 363 -> 0 bytes
 .../src/main/res/drawable-xxhdpi/sync_mobile.png   |  Bin 246 -> 0 bytes
 .../res/drawable-xxhdpi/sync_mobile_inactive.png   |  Bin 249 -> 0 bytes
 .../layout/fxaccount_preference_list_fragment.xml  |   40 -
 .../layout/fxaccount_status_error_preference.xml   |   66 -
 .../src/main/res/layout/homescreen_prompt.xml      |   92 --
 .../src/main/res/layout/simple_helper_ui.xml       |   61 -
 .../src/main/res/menu/fxaccount_status_menu.xml    |    8 -
 .../src/main/res/values-v11/fxaccount_styles.xml   |   21 -
 .../src/main/res/values/fxaccount_colors.xml       |    9 -
 .../src/main/res/values/fxaccount_dimens.xml       |   18 -
 .../src/main/res/values/fxaccount_styles.xml       |   27 -
 .../src/main/res/xml/fxaccount_authenticator.xml   |   11 -
 .../src/main/res/xml/fxaccount_options.xml         |   18 -
 .../main/res/xml/fxaccount_status_prefscreen.xml   |  142 --
 .../src/main/res/xml/fxaccount_syncadapter.xml     |   12 -
 mobile/android/services/strings.xml.in             |   86 -
 modules/libpref/init/all.js                        |    3 -
 services/fxaccounts/Credentials.jsm                |  136 --
 services/fxaccounts/FxAccounts.jsm                 | 1735 --------------------
 services/fxaccounts/FxAccountsClient.jsm           |  623 -------
 services/fxaccounts/FxAccountsCommon.js            |  368 -----
 services/fxaccounts/FxAccountsComponents.manifest  |    4 -
 services/fxaccounts/FxAccountsConfig.jsm           |  179 --
 services/fxaccounts/FxAccountsOAuthClient.jsm      |  269 ---
 services/fxaccounts/FxAccountsOAuthGrantClient.jsm |  241 ---
 services/fxaccounts/FxAccountsProfile.jsm          |  191 ---
 services/fxaccounts/FxAccountsProfileClient.jsm    |  260 ---
 services/fxaccounts/FxAccountsPush.js              |  240 ---
 services/fxaccounts/FxAccountsStorage.jsm          |  606 -------
 services/fxaccounts/FxAccountsWebChannel.jsm       |  474 ------
 services/fxaccounts/interfaces/moz.build           |   11 -
 .../fxaccounts/interfaces/nsIFxAccountsUIGlue.idl  |   15 -
 services/fxaccounts/moz.build                      |   32 -
 services/fxaccounts/tests/mochitest/chrome.ini     |    7 -
 .../tests/mochitest/file_invalidEmailCase.sjs      |   80 -
 .../tests/mochitest/test_invalidEmailCase.html     |  131 --
 services/fxaccounts/tests/xpcshell/head.js         |   18 -
 .../fxaccounts/tests/xpcshell/test_accounts.js     | 1531 -----------------
 .../xpcshell/test_accounts_device_registration.js  |  526 ------
 services/fxaccounts/tests/xpcshell/test_client.js  |  917 -----------
 .../fxaccounts/tests/xpcshell/test_credentials.js  |  110 --
 .../tests/xpcshell/test_loginmgr_storage.js        |  214 ---
 .../fxaccounts/tests/xpcshell/test_oauth_client.js |   55 -
 .../tests/xpcshell/test_oauth_grant_client.js      |  292 ----
 .../xpcshell/test_oauth_grant_client_server.js     |   73 -
 .../tests/xpcshell/test_oauth_token_storage.js     |  165 --
 .../fxaccounts/tests/xpcshell/test_oauth_tokens.js |  251 ---
 services/fxaccounts/tests/xpcshell/test_profile.js |  409 -----
 .../tests/xpcshell/test_profile_client.js          |  411 -----
 .../fxaccounts/tests/xpcshell/test_push_service.js |  236 ---
 .../tests/xpcshell/test_storage_manager.js         |  477 ------
 .../fxaccounts/tests/xpcshell/test_web_channel.js  |  499 ------
 services/fxaccounts/tests/xpcshell/xpcshell.ini    |   23 -
 services/moz.build                                 |    3 -
 services/sync/modules/util.js                      |   40 -
 services/sync/tests/unit/test_errorhandler.js      |    4 -
 services/sync/tps/extensions/tps/resource/tps.jsm  |    4 +-
 testing/profiles/prefs_general.js                  |   15 -
 .../mochitest-browser-chrome-e10s.runtimes.json    |    1 -
 .../mochitest-browser-chrome.runtimes.json         |    1 -
 testing/talos/talos/config.py                      |    2 -
 toolkit/identity/tests/unit/head_identity.js       |    8 -
 .../lib/rules/import-browserjs-globals.js          |    3 +-
 tools/lint/eslint/modules.json                     |    2 +-
 420 files changed, 9 insertions(+), 51958 deletions(-)

diff --git a/mobile/android/app/mobile.js b/mobile/android/app/mobile.js
index ef4764d..abd30a8 100644
--- a/mobile/android/app/mobile.js
+++ b/mobile/android/app/mobile.js
@@ -901,18 +901,6 @@ pref("dom.push.maxRecentMessageIDsPerSubscription", 0);
 pref("dom.push.enabled", false);
 #endif
 
-// The remote content URL where FxAccountsWebChannel messages originate.  Must use HTTPS.
-pref("identity.fxaccounts.remote.webchannel.uri", "https://accounts.firefox.com");
-
-// The remote URL of the Firefox Account profile server.
-pref("identity.fxaccounts.remote.profile.uri", "https://profile.accounts.firefox.com/v1");
-
-// The remote URL of the Firefox Account oauth server.
-pref("identity.fxaccounts.remote.oauth.uri", "https://oauth.accounts.firefox.com/v1");
-
-// Token server used by Firefox Account-authenticated Sync.
-pref("identity.sync.tokenserver.uri", "https://token.services.mozilla.com/1.0/sync/1.5");
-
 // Enable Presentation API
 pref("dom.presentation.enabled", false);
 pref("dom.presentation.discovery.enabled", true);
diff --git a/mobile/android/base/android-services.mozbuild b/mobile/android/base/android-services.mozbuild
index 118a0c4..ca26654 100644
--- a/mobile/android/base/android-services.mozbuild
+++ b/mobile/android/base/android-services.mozbuild
@@ -780,21 +780,6 @@ sync_java_files = [TOPSRCDIR + '/mobile/android/services/src/main/java/org/mozil
     'background/common/telemetry/TelemetryWrapper.java',
     'background/db/CursorDumper.java',
     'background/db/Tab.java',
-    'background/fxa/FxAccount20CreateDelegate.java',
-    'background/fxa/FxAccount20LoginDelegate.java',
-    'background/fxa/FxAccountClient.java',
-    'background/fxa/FxAccountClient20.java',
-    'background/fxa/FxAccountClientException.java',
-    'background/fxa/FxAccountRemoteError.java',
-    'background/fxa/FxAccountUtils.java',
-    'background/fxa/oauth/FxAccountAbstractClient.java',
-    'background/fxa/oauth/FxAccountAbstractClientException.java',
-    'background/fxa/oauth/FxAccountOAuthClient10.java',
-    'background/fxa/oauth/FxAccountOAuthRemoteError.java',
-    'background/fxa/PasswordStretcher.java',
-    'background/fxa/profile/FxAccountProfileClient10.java',
-    'background/fxa/QuickPasswordStretcher.java',
-    'background/fxa/SkewHandler.java',
     'background/nativecode/NativeCrypto.java',
     'background/preferences/PreferenceFragment.java',
     'background/preferences/PreferenceManagerCompat.java',
@@ -813,52 +798,6 @@ sync_java_files = [TOPSRCDIR + '/mobile/android/services/src/main/java/org/mozil
     'browserid/verifier/BrowserIDVerifierDelegate.java',
     'browserid/verifier/BrowserIDVerifierException.java',
     'browserid/VerifyingPublicKey.java',
-    'fxa/AccountLoader.java',
-    'fxa/activities/CustomColorPreference.java',
-    'fxa/activities/FxAccountAbstractActivity.java',
-    'fxa/activities/FxAccountConfirmAccountActivityWeb.java',
-    'fxa/activities/FxAccountFinishMigratingActivityWeb.java',
-    'fxa/activities/FxAccountGetStartedActivityWeb.java',
-    'fxa/activities/FxAccountStatusActivity.java',
-    'fxa/activities/FxAccountStatusFragment.java',
-    'fxa/activities/FxAccountUpdateCredentialsActivityWeb.java',
-    'fxa/activities/FxAccountWebFlowActivity.java',
-    'fxa/activities/PicassoPreferenceIconTarget.java',
-    'fxa/authenticator/AccountPickler.java',
-    'fxa/authenticator/AndroidFxAccount.java',
-    'fxa/authenticator/FxAccountAuthenticator.java',
-    'fxa/authenticator/FxAccountAuthenticatorService.java',
-    'fxa/authenticator/FxAccountLoginDelegate.java',
-    'fxa/authenticator/FxAccountLoginException.java',
-    'fxa/authenticator/FxADefaultLoginStateMachineDelegate.java',
-    'fxa/FirefoxAccounts.java',
-    'fxa/FxAccountConstants.java',
-    'fxa/FxAccountDevice.java',
-    'fxa/FxAccountDeviceRegistrator.java',
-    'fxa/FxAccountPushHandler.java',
-    'fxa/login/BaseRequestDelegate.java',
-    'fxa/login/Cohabiting.java',
-    'fxa/login/Doghouse.java',
-    'fxa/login/Engaged.java',
-    'fxa/login/FxAccountLoginStateMachine.java',
-    'fxa/login/FxAccountLoginTransition.java',
-    'fxa/login/Married.java',
-    'fxa/login/MigratedFromSync11.java',
-    'fxa/login/Separated.java',
-    'fxa/login/State.java',
-    'fxa/login/StateFactory.java',
-    'fxa/login/TokensAndKeysState.java',
-    'fxa/receivers/FxAccountDeletedService.java',
-    'fxa/receivers/FxAccountUpgradeReceiver.java',
-    'fxa/sync/FxAccountNotificationManager.java',
-    'fxa/sync/FxAccountProfileService.java',
-    'fxa/sync/FxAccountSchedulePolicy.java',
-    'fxa/sync/FxAccountSyncAdapter.java',
-    'fxa/sync/FxAccountSyncDelegate.java',
-    'fxa/sync/FxAccountSyncService.java',
-    'fxa/sync/FxAccountSyncStatusHelper.java',
-    'fxa/sync/SchedulePolicy.java',
-    'fxa/SyncStatusListener.java',
     'push/autopush/AutopushClient.java',
     'push/autopush/AutopushClientException.java',
     'push/RegisterUserAgentResponse.java',
diff --git a/mobile/android/base/java/org/mozilla/gecko/push/PushService.java b/mobile/android/base/java/org/mozilla/gecko/push/PushService.java
index 8d3a92e..7c3a943 100644
--- a/mobile/android/base/java/org/mozilla/gecko/push/PushService.java
+++ b/mobile/android/base/java/org/mozilla/gecko/push/PushService.java
@@ -22,7 +22,6 @@ import org.mozilla.gecko.Telemetry;
 import org.mozilla.gecko.TelemetryContract;
 import org.mozilla.gecko.annotation.ReflectionTarget;
 import org.mozilla.gecko.db.BrowserDB;
-import org.mozilla.gecko.fxa.FxAccountPushHandler;
 import org.mozilla.gecko.gcm.GcmTokenClient;
 import org.mozilla.gecko.push.autopush.AutopushClientException;
 import org.mozilla.gecko.util.BundleEventListener;
@@ -66,13 +65,10 @@ public class PushService implements BundleEventListener {
             "PushServiceAndroidGCM:UnregisterUserAgent",
             "PushServiceAndroidGCM:SubscribeChannel",
             "PushServiceAndroidGCM:UnsubscribeChannel",
-            "FxAccountsPush:Initialized",
-            "FxAccountsPush:ReceivedPushMessageToDecode:Response",
             "History:GetPrePathLastVisitedTimeMilliseconds",
     };
 
     private enum GeckoComponent {
-        FxAccountsPush,
         PushServiceAndroidGCM
     }
 
@@ -98,7 +94,6 @@ public class PushService implements BundleEventListener {
 
     // NB: These are not thread-safe, we're depending on these being access from the same background thread.
     private boolean isReadyPushServiceAndroidGCM = false;
-    private boolean isReadyFxAccountsPush = false;
     private final List<JSONObject> pendingPushMessages;
 
     public PushService(Context context) {
@@ -238,9 +233,6 @@ public class PushService implements BundleEventListener {
 
     protected static void sendMessageToDecodeToGeckoService(final @NonNull JSONObject message) {
         Log.i(LOG_TAG, "Delivering dom/push message to decode to Gecko!");
-        GeckoAppShell.notifyObservers("FxAccountsPush:ReceivedPushMessageToDecode",
-                                      message.toString(),
-                                      GeckoThread.State.PROFILE_READY);
     }
 
     protected void registerGeckoEventListener() {
@@ -279,11 +271,6 @@ public class PushService implements BundleEventListener {
                 callback.sendSuccess(null);
                 return;
             }
-            if ("FxAccountsPush:Initialized".equals(event)) {
-                processComponentState(GeckoComponent.FxAccountsPush, true);
-                callback.sendSuccess(null);
-                return;
-            }
             if ("PushServiceAndroidGCM:Configure".equals(event)) {
                 final String endpoint = message.getString("endpoint");
                 if (endpoint == null) {
@@ -390,10 +377,6 @@ public class PushService implements BundleEventListener {
                 callback.sendError("Could not unsubscribe from channel: " + channelID);
                 return;
             }
-            if ("FxAccountsPush:ReceivedPushMessageToDecode:Response".equals(event)) {
-                FxAccountPushHandler.handleFxAPushMessage(context, message);
-                return;
-            }
             if ("History:GetPrePathLastVisitedTimeMilliseconds".equals(event)) {
                 if (callback == null) {
                     Log.e(LOG_TAG, "callback must not be null in " + event);
@@ -420,10 +403,7 @@ public class PushService implements BundleEventListener {
     }
 
     private void processComponentState(@NonNull GeckoComponent component, boolean isReady) {
-        if (component == GeckoComponent.FxAccountsPush) {
-            isReadyFxAccountsPush = isReady;
-
-        } else if (component == GeckoComponent.PushServiceAndroidGCM) {
+        if (component == GeckoComponent.PushServiceAndroidGCM) {
             isReadyPushServiceAndroidGCM = isReady;
         }
 
@@ -435,7 +415,7 @@ public class PushService implements BundleEventListener {
     }
 
     private boolean canSendPushMessagesToGecko() {
-        return isReadyFxAccountsPush && isReadyPushServiceAndroidGCM;
+        return isReadyPushServiceAndroidGCM;
     }
 
     private static void sendPushMessagesToGecko(@NonNull List<JSONObject> messages) {
diff --git a/mobile/android/base/resources/values-v21/themes.xml b/mobile/android/base/resources/values-v21/themes.xml
index ddb08d0..140f066 100644
--- a/mobile/android/base/resources/values-v21/themes.xml
+++ b/mobile/android/base/resources/values-v21/themes.xml
@@ -21,11 +21,6 @@
         <item name="android:colorAccent">@color/fennec_ui_orange</item>
     </style>
 
-    <style name="ActionBar.FxAccountStatusActivity" parent="@android:style/Widget.Material.ActionBar.Solid">
-        <item name="android:displayOptions">homeAsUp|showTitle</item>
-        <item name="android:titleTextStyle">@style/ActionBarTitleTextStyle</item>
-    </style>
-
     <style name="GeckoAppBase" parent="Gecko">
         <item name="android:actionButtonStyle">@style/GeckoActionBar.Button</item>
         <item name="android:listViewStyle">@style/Widget.ListView</item>
diff --git a/mobile/android/chrome/content/aboutAccounts.js b/mobile/android/chrome/content/aboutAccounts.js
deleted file mode 100644
index 4801a76..0000000
--- a/mobile/android/chrome/content/aboutAccounts.js
+++ /dev/null
@@ -1,351 +0,0 @@
-// -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
-/* 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/. */
-
-/**
- * Wrap a remote fxa-content-server.
- *
- * An about:accounts tab loads and displays an fxa-content-server page,
- * depending on the current Android Account status and an optional 'action'
- * parameter.
- *
- * We show a spinner while the remote iframe is loading.  We expect the
- * WebChannel message listening to the fxa-content-server to send this tab's
- * <browser>'s messageManager a LOADED message when the remote iframe provides
- * the WebChannel LOADED message.  See the messageManager registration and the
- * |loadedDeferred| promise.  This loosely couples the WebChannel implementation
- * and about:accounts!  (We need this coupling in order to distinguish
- * WebChannel LOADED messages produced by multiple about:accounts tabs.)
- *
- * We capture error conditions by accessing the inner nsIWebNavigation of the
- * iframe directly.
- */
-
-"use strict";
-
-var {classes: Cc, interfaces: Ci, utils: Cu} = Components; /*global Components */
-
-Cu.import("resource://gre/modules/Accounts.jsm"); /*global Accounts */
-Cu.import("resource://gre/modules/PromiseUtils.jsm"); /*global PromiseUtils */
-Cu.import("resource://gre/modules/Services.jsm"); /*global Services */
-Cu.import("resource://gre/modules/XPCOMUtils.jsm"); /*global XPCOMUtils */
-
-const ACTION_URL_PARAM = "action";
-
-const COMMAND_LOADED = "fxaccounts:loaded";
-
-const log = Cu.import("resource://gre/modules/AndroidLog.jsm", {}).AndroidLog.bind("FxAccounts");
-
-XPCOMUtils.defineLazyServiceGetter(this, "ParentalControls",
-  "@mozilla.org/parental-controls-service;1", "nsIParentalControlsService");
-
-// Shows the toplevel element with |id| to be shown - all other top-level
-// elements are hidden.
-// If |id| is 'spinner', then 'remote' is also shown, with opacity 0.
-function show(id) {
-  let allTop = document.querySelectorAll(".toplevel");
-  for (let elt of allTop) {
-    if (elt.getAttribute("id") == id) {
-      elt.style.display = 'block';
-    } else {
-      elt.style.display = 'none';
-    }
-  }
-  if (id == 'spinner') {
-    document.getElementById('remote').style.display = 'block';
-    document.getElementById('remote').style.opacity = 0;
-  }
-}
-
-// Each time we try to load the remote <iframe>, loadedDeferred is replaced.  It
-// is resolved by a LOADED message, and rejected by a failure to load.
-var loadedDeferred = null;
-
-// We have a new load starting.  Replace the existing promise with a new one,
-// and queue up the transition to remote content.
-function deferTransitionToRemoteAfterLoaded() {
-  log.d('Waiting for LOADED message.');
-
-  loadedDeferred = PromiseUtils.defer();
-  loadedDeferred.promise.then(() => {
-    log.d('Got LOADED message!');
-    document.getElementById("remote").style.opacity = 0;
-    show("remote");
-    document.getElementById("remote").style.opacity = 1;
-  })
-  .catch((e) => {
-    log.w('Did not get LOADED message: ' + e.toString());
-  });
-}
-
-function handleLoadedMessage(message) {
-  loadedDeferred.resolve();
-};
-
-var wrapper = {
-  iframe: null,
-
-  url: null,
-
-  init: function (url) {
-    this.url = url;
-    deferTransitionToRemoteAfterLoaded();
-
-    let iframe = document.getElementById("remote");
-    this.iframe = iframe;
-    this.iframe.QueryInterface(Ci.nsIFrameLoaderOwner);
-    let docShell = this.iframe.frameLoader.docShell;
-    docShell.QueryInterface(Ci.nsIWebProgress);
-    docShell.addProgressListener(this.iframeListener, Ci.nsIWebProgress.NOTIFY_STATE_DOCUMENT);
-
-    // Set the iframe's location with loadURI/LOAD_FLAGS_BYPASS_HISTORY to
-    // avoid having a new history entry being added.
-    let webNav = iframe.frameLoader.docShell.QueryInterface(Ci.nsIWebNavigation);
-    webNav.loadURI(url, Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_HISTORY, null, null, null);
-  },
-
-  retry: function () {
-    deferTransitionToRemoteAfterLoaded();
-
-    let webNav = this.iframe.frameLoader.docShell.QueryInterface(Ci.nsIWebNavigation);
-    webNav.loadURI(this.url, Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_HISTORY, null, null, null);
-  },
-
-  iframeListener: {
-    QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener,
-                                         Ci.nsISupportsWeakReference,
-                                         Ci.nsISupports]),
-
-    onStateChange: function(aWebProgress, aRequest, aState, aStatus) {
-      let failure = false;
-
-      // Captive portals sometimes redirect users
-      if ((aState & Ci.nsIWebProgressListener.STATE_REDIRECTING)) {
-        failure = true;
-      } else if ((aState & Ci.nsIWebProgressListener.STATE_STOP)) {
-        if (aRequest instanceof Ci.nsIHttpChannel) {
-          try {
-            failure = aRequest.responseStatus != 200;
-          } catch (e) {
-            failure = aStatus != Components.results.NS_OK;
-          }
-        }
-      }
-
-      // Calling cancel() will raise some OnStateChange notifications by itself,
-      // so avoid doing that more than once
-      if (failure && aStatus != Components.results.NS_BINDING_ABORTED) {
-        aRequest.cancel(Components.results.NS_BINDING_ABORTED);
-        // Since after a promise is fulfilled, subsequent fulfillments are
-        // treated as no-ops, we don't care that we might see multiple failures
-        // due to multiple listener callbacks.  (It's not easy to extract this
-        // from the Promises spec, but it is widely quoted.  Start with
-        // http://stackoverflow.com/a/18218542.)
-        loadedDeferred.reject(new Error("Failed in onStateChange!"));
-        show("networkError");
-      }
-    },
-
-    onLocationChange: function(aWebProgress, aRequest, aLocation, aFlags) {
-      if (aRequest && aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_ERROR_PAGE) {
-        aRequest.cancel(Components.results.NS_BINDING_ABORTED);
-        // As above, we're not concerned by multiple listener callbacks.
-        loadedDeferred.reject(new Error("Failed in onLocationChange!"));
-        show("networkError");
-      }
-    },
-
-    onProgressChange: function() {},
-    onStatusChange: function() {},
-    onSecurityChange: function() {},
-  },
-};
-
-
-function retry() {
-  log.i("Retrying.");
-  show("spinner");
-  wrapper.retry();
-}
-
-function openPrefs() {
-  log.i("Opening Sync preferences.");
-  // If an Android Account exists, this will open the Status Activity.
-  // Otherwise, it will begin the Get Started flow.  This should only be shown
-  // when an Account actually exists.
-  Accounts.launchSetup();
-}
-
-function getURLForAction(action, urlParams) {
-  let url = Services.urlFormatter.formatURLPref("identity.fxaccounts.remote.webchannel.uri");
-  url = url + (url.endsWith("/") ? "" : "/") + action;
-  const CONTEXT = "fx_fennec_v1";
-  // The only service managed by Fennec, to date, is Firefox Sync.
-  const SERVICE = "sync";
-  urlParams = urlParams || new URLSearchParams("");
-  urlParams.set('service', SERVICE);
-  urlParams.set('context', CONTEXT);
-  // Ideally we'd just merge urlParams with new URL(url).searchParams, but our
-  // URLSearchParams implementation doesn't support iteration (bug 1085284).
-  let urlParamStr = urlParams.toString();
-  if (urlParamStr) {
-    url += (url.includes("?") ? "&" : "?") + urlParamStr;
-  }
-  return url;
-}
-
-function updateDisplayedEmail(user) {
-  let emailDiv = document.getElementById("email");
-  if (emailDiv && user) {
-    emailDiv.textContent = user.email;
-  }
-}
-
-function init() {
-  // Test for restrictions before getFirefoxAccount(), since that will fail if
-  // we are restricted.
-  if (!ParentalControls.isAllowed(ParentalControls.MODIFY_ACCOUNTS)) {
-    // It's better to log and show an error message than to invite user
-    // confusion by removing about:accounts entirely.  That is, if the user is
-    // restricted, this way they'll discover as much and may be able to get
-    // out of their restricted profile.  If we remove about:accounts entirely,
-    // it will look like Fennec is buggy, and the user will be very confused.
-    log.e("This profile cannot connect to Firefox Accounts: showing restricted error.");
-    show("restrictedError");
-    return;
-  }
-
-  Accounts.getFirefoxAccount().then(user => {
-    // It's possible for the window to start closing before getting the user
-    // completes.  Tests in particular can cause this.
-    if (window.closed) {
-      return;
-    }
-
-    updateDisplayedEmail(user);
-
-    // Ideally we'd use new URL(document.URL).searchParams, but for about: URIs,
-    // searchParams is empty.
-    let urlParams = new URLSearchParams(document.URL.split("?")[1] || "");
-    let action = urlParams.get(ACTION_URL_PARAM);
-    urlParams.delete(ACTION_URL_PARAM);
-
-    switch (action) {
-    case "signup":
-      if (user) {
-        // Asking to sign-up when already signed in just shows prefs.
-        show("prefs");
-      } else {
-        show("spinner");
-        wrapper.init(getURLForAction("signup", urlParams));
-      }
-      break;
-    case "signin":
-      if (user) {
-        // Asking to sign-in when already signed in just shows prefs.
-        show("prefs");
-      } else {
-        show("spinner");
-        wrapper.init(getURLForAction("signin", urlParams));
-      }
-      break;
-    case "force_auth":
-      if (user) {
-        show("spinner");
-        urlParams.set("email", user.email); // In future, pin using the UID.
-        wrapper.init(getURLForAction("force_auth", urlParams));
-      } else {
-        show("spinner");
-        wrapper.init(getURLForAction("signup", urlParams));
-      }
-      break;
-    case "manage":
-      if (user) {
-        show("spinner");
-        urlParams.set("email", user.email); // In future, pin using the UID.
-        wrapper.init(getURLForAction("settings", urlParams));
-      } else {
-        show("spinner");
-        wrapper.init(getURLForAction("signup", urlParams));
-      }
-      break;
-    case "avatar":
-      if (user) {
-        show("spinner");
-        urlParams.set("email", user.email); // In future, pin using the UID.
-        wrapper.init(getURLForAction("settings/avatar/change", urlParams));
-      } else {
-        show("spinner");
-        wrapper.init(getURLForAction("signup", urlParams));
-      }
-      break;
-    default:
-      // Unrecognized or no action specified.
-      if (action) {
-        log.w("Ignoring unrecognized action: " + action);
-      }
-      if (user) {
-        show("prefs");
-      } else {
-        show("spinner");
-        wrapper.init(getURLForAction("signup", urlParams));
-      }
-      break;
-    }
-  }).catch(e => {
-    log.e("Failed to get the signed in user: " + e.toString());
-  });
-}
-
-document.addEventListener("DOMContentLoaded", function onload() {
-  document.removeEventListener("DOMContentLoaded", onload, true);
-  init();
-  var buttonRetry = document.getElementById('buttonRetry');
-  buttonRetry.addEventListener('click', retry);
-
-  var buttonOpenPrefs = document.getElementById('buttonOpenPrefs');
-  buttonOpenPrefs.addEventListener('click', openPrefs);
-}, true);
-
-// This window is contained in a XUL <browser> element.  Return the
-// messageManager of that <browser> element, or null.
-function getBrowserMessageManager() {
-  let browser = window
-        .QueryInterface(Ci.nsIInterfaceRequestor)
-        .getInterface(Ci.nsIWebNavigation)
-        .QueryInterface(Ci.nsIDocShellTreeItem)
-        .rootTreeItem
-        .QueryInterface(Ci.nsIInterfaceRequestor)
-        .getInterface(Ci.nsIDOMWindow)
-        .QueryInterface(Ci.nsIDOMChromeWindow)
-        .BrowserApp
-        .getBrowserForDocument(document);
-  if (browser) {
-    return browser.messageManager;
-  }
-  return null;
-}
-
-// Add a single listener for 'loaded' messages from the iframe in this
-// <browser>.  These 'loaded' messages are ferried from the WebChannel to just
-// this <browser>.
-var mm = getBrowserMessageManager();
-if (mm) {
-  mm.addMessageListener(COMMAND_LOADED, handleLoadedMessage);
-} else {
-  log.e('No messageManager, not listening for LOADED message!');
-}
-
-window.addEventListener("unload", function(event) {
-  try {
-    let mm = getBrowserMessageManager();
-    if (mm) {
-      mm.removeMessageListener(COMMAND_LOADED, handleLoadedMessage);
-    }
-  } catch (e) {
-    // This could fail if the page is being torn down, the tab is being
-    // destroyed, etc.
-    log.w('Not removing listener for LOADED message: ' + e.toString());
-  }
-});
diff --git a/mobile/android/chrome/content/aboutAccounts.xhtml b/mobile/android/chrome/content/aboutAccounts.xhtml
deleted file mode 100644
index b988741..0000000
--- a/mobile/android/chrome/content/aboutAccounts.xhtml
+++ /dev/null
@@ -1,83 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 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/. -->
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
-  "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd" [
-<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd" >
-%brandDTD;
-<!ENTITY % globalDTD SYSTEM "chrome://global/locale/global.dtd" >
-%globalDTD;
-<!ENTITY % aboutDTD SYSTEM "chrome://browser/locale/aboutAccounts.dtd">
-%aboutDTD;
-]>
-
-<html xmlns="http://www.w3.org/1999/xhtml" dir="&locale.dir;">
-  <head>
-    <title>Firefox Sync</title>
-    <meta name="viewport" content="width=device-width; user-scalable=0" />
-    <link rel="icon" type="image/png" sizes="64x64" href="chrome://branding/content/favicon64.png" />
-    <link rel="stylesheet" href="chrome://browser/skin/spinner.css" type="text/css"/>
-    <link rel="stylesheet" href="chrome://browser/skin/aboutBase.css" type="text/css"/>
-    <link rel="stylesheet" href="chrome://browser/skin/aboutAccounts.css" type="text/css"/>
-  </head>
-  <body>
-    <div id="spinner" class="toplevel">
-      <div class="container flex-column">
-        <!-- Empty text-container for spacing. -->
-        <div class="text-container flex-column" />
-
-          <div class="mui-refresh-main">
-            <div class="mui-refresh-wrapper">
-              <div class="mui-spinner-wrapper">
-                <div class="mui-spinner-main">
-                  <div class="mui-spinner-left">
-                    <div class="mui-half-circle-left" />
-                  </div>
-                  <div class="mui-spinner-right">
-                    <div class="mui-half-circle-right" />
-                  </div>
-                </div>
-              </div>
-            </div>
-          </div>
-
-      </div>
-    </div>
-
-    <iframe mozframetype="content" id="remote" class="toplevel" />
-
-    <div id="prefs" class="toplevel">
-      <div class="container flex-column">
-        <div class="text-container flex-column">
-          <div class="text">&aboutAccounts.connected.title;</div>
-          <div class="hint">&aboutAccounts.connected.description;</div>
-          <div id="email" class="hint"></div>
-        </div>
-        <a id="buttonOpenPrefs" tabindex="0" href="#">&aboutAccounts.syncPreferences.label;</a>
-      </div>
-    </div>
-
-    <div id="networkError" class="toplevel">
-      <div class="container flex-column">
-        <div class="text-container flex-column">
-          <div class="text">&aboutAccounts.noConnection.title;</div>
-        </div>
-        <div class="button-row">
-          <button id="buttonRetry" class="button" tabindex="1">&aboutAccounts.retry.label;</button>
-        </div>
-      </div>
-    </div>
-
-    <div id="restrictedError" class="toplevel">
-      <div class="container flex-column">
-        <div class="text-container flex-column">
-          <div class="text">&aboutAccounts.restrictedError.title;</div>
-          <div class="hint">&aboutAccounts.restrictedError.description;</div>
-        </div>
-      </div>
-    </div>
-
-    <script type="application/javascript;version=1.8" src="chrome://browser/content/aboutAccounts.js"></script>
-  </body>
-</html>
diff --git a/mobile/android/chrome/content/browser.js b/mobile/android/chrome/content/browser.js
index e2706f4..93eb2ad 100644
--- a/mobile/android/chrome/content/browser.js
+++ b/mobile/android/chrome/content/browser.js
@@ -489,20 +489,6 @@ var BrowserApp = {
     let mm = window.getGroupMessageManager("browsers");
     mm.loadFrameScript("chrome://browser/content/content.js", true);
 
-    // We can't delay registering WebChannel listeners: if the first page is
-    // about:accounts, which can happen when starting the Firefox Account flow
-    // from the first run experience, or via the Firefox Account Status
-    // Activity, we can and do miss messages from the fxa-content-server.
-    // However, we never allow suitably restricted profiles from listening to
-    // fxa-content-server messages.
-    if (ParentalControls.isAllowed(ParentalControls.MODIFY_ACCOUNTS)) {
-      console.log("browser.js: loading Firefox Accounts WebChannel");
-      Cu.import("resource://gre/modules/FxAccountsWebChannel.jsm");
-      EnsureFxAccountsWebChannel();
-    } else {
-      console.log("browser.js: not loading Firefox Accounts WebChannel; this profile cannot connect to Firefox Accounts.");
-    }
-
     // Notify Java that Gecko has loaded.
     Messaging.sendRequest({ type: "Gecko:Ready" });
 
diff --git a/mobile/android/chrome/jar.mn b/mobile/android/chrome/jar.mn
index 538a025..183defe 100644
--- a/mobile/android/chrome/jar.mn
+++ b/mobile/android/chrome/jar.mn
@@ -56,8 +56,6 @@ chrome.jar:
   content/aboutHealthReport.xhtml      (content/aboutHealthReport.xhtml)
   content/aboutHealthReport.js         (content/aboutHealthReport.js)
 #endif
-  content/aboutAccounts.xhtml          (content/aboutAccounts.xhtml)
-  content/aboutAccounts.js             (content/aboutAccounts.js)
   content/aboutLogins.xhtml            (content/aboutLogins.xhtml)
   content/aboutLogins.js               (content/aboutLogins.js)
 #ifndef RELEASE_OR_BETA
diff --git a/mobile/android/components/FxAccountsPush.js b/mobile/android/components/FxAccountsPush.js
deleted file mode 100644
index e6054a2..0000000
--- a/mobile/android/components/FxAccountsPush.js
+++ /dev/null
@@ -1,164 +0,0 @@
-/* jshint moz: true, esnext: true */
-/* 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/. */
-
-const Cc = Components.classes;
-const Ci = Components.interfaces;
-const Cu = Components.utils;
-
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://gre/modules/Messaging.jsm");
-const {
-  PushCrypto,
-  getCryptoParams,
-} = Cu.import("resource://gre/modules/PushCrypto.jsm");
-
-XPCOMUtils.defineLazyServiceGetter(this, "PushService",
-  "@mozilla.org/push/Service;1", "nsIPushService");
-XPCOMUtils.defineLazyGetter(this, "_decoder", () => new TextDecoder());
-
-const FXA_PUSH_SCOPE = "chrome://fxa-push";
-const Log = Cu.import("resource://gre/modules/AndroidLog.jsm", {}).AndroidLog.bind("FxAccountsPush");
-
-function FxAccountsPush() {
-  Services.obs.addObserver(this, "FxAccountsPush:ReceivedPushMessageToDecode", false);
-
-  Messaging.sendRequestForResult({
-    type: "FxAccountsPush:Initialized"
-  });
-}
-
-FxAccountsPush.prototype = {
-  observe: function (subject, topic, data) {
-    switch (topic) {
-      case "android-push-service":
-        if (data === "android-fxa-subscribe") {
-          this._subscribe();
-        } else if (data === "android-fxa-unsubscribe") {
-          this._unsubscribe();
-        }
-        break;
-      case "FxAccountsPush:ReceivedPushMessageToDecode":
-        this._decodePushMessage(data);
-        break;
-    }
-  },
-
-  _subscribe() {
-    Log.i("FxAccountsPush _subscribe");
-    return new Promise((resolve, reject) => {
-      PushService.subscribe(FXA_PUSH_SCOPE,
-        Services.scriptSecurityManager.getSystemPrincipal(),
-        (result, subscription) => {
-          if (Components.isSuccessCode(result)) {
-            Log.d("FxAccountsPush got subscription");
-            resolve(subscription);
-          } else {
-            Log.w("FxAccountsPush failed to subscribe", result);
-            reject(new Error("FxAccountsPush failed to subscribe"));
-          }
-        });
-    })
-    .then(subscription => {
-      Messaging.sendRequest({
-        type: "FxAccountsPush:Subscribe:Response",
-        subscription: {
-          pushCallback: subscription.endpoint,
-          pushPublicKey: urlsafeBase64Encode(subscription.getKey('p256dh')),
-          pushAuthKey: urlsafeBase64Encode(subscription.getKey('auth'))
-        }
-      });
-    })
-    .catch(err => {
-      Log.i("Error when registering FxA push endpoint " + err);
-    });
-  },
-
-  _unsubscribe() {
-    Log.i("FxAccountsPush _unsubscribe");
-    return new Promise((resolve) => {
-      PushService.unsubscribe(FXA_PUSH_SCOPE,
-        Services.scriptSecurityManager.getSystemPrincipal(),
-        (result, ok) => {
-          if (Components.isSuccessCode(result)) {
-            if (ok === true) {
-              Log.d("FxAccountsPush unsubscribed");
-            } else {
-              Log.d("FxAccountsPush had no subscription to unsubscribe");
-            }
-          } else {
-            Log.w("FxAccountsPush failed to unsubscribe", result);
-          }
-          return resolve(ok);
-        });
-    }).catch(err => {
-      Log.e("Error during unsubscribe", err);
-    });
-  },
-
-  _decodePushMessage(data) {
-    Log.i("FxAccountsPush _decodePushMessage");
-    data = JSON.parse(data);
-    let { headers, message } = this._messageAndHeaders(data);
-    return new Promise((resolve, reject) => {
-      PushService.getSubscription(FXA_PUSH_SCOPE,
-        Services.scriptSecurityManager.getSystemPrincipal(),
-        (result, subscription) => {
-          if (!subscription) {
-            return reject(new Error("No subscription found"));
-          }
-          return resolve(subscription);
-        });
-    }).then(subscription => {
-      return PushCrypto.decrypt(subscription.p256dhPrivateKey,
-                                new Uint8Array(subscription.getKey("p256dh")),
-                                new Uint8Array(subscription.getKey("auth")),
-                                headers, message);
-    })
-    .then(plaintext => {
-      let decryptedMessage = plaintext ? _decoder.decode(plaintext) : "";
-      Messaging.sendRequestForResult({
-        type: "FxAccountsPush:ReceivedPushMessageToDecode:Response",
-        message: decryptedMessage
-      });
-    })
-    .catch(err => {
-      Log.d("Error while decoding incoming message : " + err);
-    });
-  },
-
-  // Copied from PushServiceAndroidGCM
-  _messageAndHeaders(data) {
-    // Default is no data (and no encryption).
-    let message = null;
-    let headers = null;
-
-    if (data.message && data.enc && (data.enckey || data.cryptokey)) {
-      headers = {
-        encryption_key: data.enckey,
-        crypto_key: data.cryptokey,
-        encryption: data.enc,
-        encoding: data.con,
-      };
-      // Ciphertext is (urlsafe) Base 64 encoded.
-      message = ChromeUtils.base64URLDecode(data.message, {
-        // The Push server may append padding.
-        padding: "ignore",
-      });
-    }
-    return { headers, message };
-  },
-
-  QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]),
-
-  classID: Components.ID("{d1bbb0fd-1d47-4134-9c12-d7b1be20b721}")
-};
-
-function urlsafeBase64Encode(key) {
-  return ChromeUtils.base64URLEncode(new Uint8Array(key), { pad: false });
-}
-
-var components = [ FxAccountsPush ];
-this.NSGetFactory = XPCOMUtils.generateNSGetFactory(components);
diff --git a/mobile/android/components/MobileComponents.manifest b/mobile/android/components/MobileComponents.manifest
index 5194de4..fe5deb9 100644
--- a/mobile/android/components/MobileComponents.manifest
+++ b/mobile/android/components/MobileComponents.manifest
@@ -100,11 +100,6 @@ contract @mozilla.org/dom/site-specific-user-agent;1 {d5234c9d-0ee2-4b3c-9da3-18
 component {18a4e042-7c7c-424b-a583-354e68553a7f} FilePicker.js
 contract @mozilla.org/filepicker;1 {18a4e042-7c7c-424b-a583-354e68553a7f}
 
-# FxAccountsPush.js
-component {d1bbb0fd-1d47-4134-9c12-d7b1be20b721} FxAccountsPush.js
-contract @mozilla.org/fxa-push;1 {d1bbb0fd-1d47-4134-9c12-d7b1be20b721}
-category android-push-service FxAccountsPush @mozilla.org/fxa-push;1
-
 #ifndef RELEASE_OR_BETA
 # TabSource.js
 component {5850c76e-b916-4218-b99a-31f004e0a7e7} TabSource.js
diff --git a/mobile/android/components/moz.build b/mobile/android/components/moz.build
index cac34b6..9e66836 100644
--- a/mobile/android/components/moz.build
+++ b/mobile/android/components/moz.build
@@ -20,7 +20,6 @@ EXTRA_COMPONENTS += [
     'ContentPermissionPrompt.js',
     'DirectoryProvider.js',
     'FilePicker.js',
-    'FxAccountsPush.js',
     'HelperAppDialog.js',
     'ImageBlockingPolicy.js',
     'LoginManagerPrompter.js',
diff --git a/mobile/android/config/proguard/proguard.cfg b/mobile/android/config/proguard/proguard.cfg
index f44730e..0348556 100644
--- a/mobile/android/config/proguard/proguard.cfg
+++ b/mobile/android/config/proguard/proguard.cfg
@@ -16,8 +16,6 @@
 -keep public class * extends android.content.BroadcastReceiver
 -keep public class * extends android.content.ContentProvider
 -keep public class * extends android.preference.Preference
--keep public class * extends org.mozilla.gecko.fxa.sync.FxAccountSyncAdapter
--keep class org.mozilla.gecko.fxa.sync.FxAccountSyncAdapter
 
 -keep public class * extends android.support.v4.app.Fragment
 
diff --git a/mobile/android/confvars.sh b/mobile/android/confvars.sh
index 869d033..d1bfd1d 100644
--- a/mobile/android/confvars.sh
+++ b/mobile/android/confvars.sh
@@ -57,4 +57,4 @@ MOZ_WEBGL_CONFORMANT=1
 export JS_GC_SMALL_CHUNK_SIZE=1
 
 # Enable checking that add-ons are signed by the trusted root
-MOZ_ADDON_SIGNING=1
+MOZ_ADDON_SIGNING=
diff --git a/mobile/android/installer/package-manifest.in b/mobile/android/installer/package-manifest.in
index af4a155..55fcacd 100644
--- a/mobile/android/installer/package-manifest.in
+++ b/mobile/android/installer/package-manifest.in
@@ -503,7 +503,6 @@
 @BINPATH@/components/ImageBlockingPolicy.js
 @BINPATH@/components/DirectoryProvider.js
 @BINPATH@/components/FilePicker.js
- at BINPATH@/components/FxAccountsPush.js
 @BINPATH@/components/HelperAppDialog.js
 @BINPATH@/components/LoginManagerPrompter.js
 @BINPATH@/components/MobileComponents.manifest
diff --git a/mobile/android/modules/Accounts.jsm b/mobile/android/modules/Accounts.jsm
index a611f3c..ee0fc0c 100644
--- a/mobile/android/modules/Accounts.jsm
+++ b/mobile/android/modules/Accounts.jsm
@@ -40,14 +40,8 @@ var Accounts = Object.freeze({
     }).then(data => data.exists);
   },
 
-  firefoxAccountsExist: function () {
-    return this._accountsExist("fxa");
-  },
-
   syncAccountsExist: function () {
-    Deprecated.warning("The legacy Sync account type has been removed from Firefox for Android. " +
-                       "Please use `firefoxAccountsExist` instead.",
-                       "https://developer.mozilla.org/en-US/Add-ons/Firefox_for_Android/API/Accounts.jsm");
+    Deprecated.warning("The legacy Sync account type has been removed from Firefox for Android.");
     return Promise.resolve(false);
   },
 
@@ -73,106 +67,13 @@ var Accounts = Object.freeze({
   },
 
   _addDefaultEndpoints: function (json) {
+    // Empty without FxA
     let newData = Cu.cloneInto(json, {}, { cloneFunctions: false });
-    let associations = {
-      authServerEndpoint: 'identity.fxaccounts.auth.uri',
-      profileServerEndpoint: 'identity.fxaccounts.remote.profile.uri',
-      tokenServerEndpoint: 'identity.sync.tokenserver.uri'
-    };
-    for (let key in associations) {
-      newData[key] = newData[key] || Services.urlFormatter.formatURLPref(associations[key]);
-    }
     return newData;
   },
 
-  /**
-   * Create a new Android Account corresponding to the given
-   * fxa-content-server "login" JSON datum.  The new account will be
-   * in the "Engaged" state, and will start syncing immediately.
-   *
-   * It is an error if an Android Account already exists.
-   *
-   * Returns a Promise that resolves to a boolean indicating success.
-   */
-  createFirefoxAccountFromJSON: function (json) {
-    return Messaging.sendRequestForResult({
-      type: "Accounts:CreateFirefoxAccountFromJSON",
-      json: this._addDefaultEndpoints(json)
-    });
-  },
-
-  /**
-   * Move an existing Android Account to the "Engaged" state with the given
-   * fxa-content-server "login" JSON datum.  The account will (re)start
-   * syncing immediately, unless the user has manually configured the account
-   * to not Sync.
-   *
-   * It is an error if no Android Account exists.
-   *
-   * Returns a Promise that resolves to a boolean indicating success.
-   */
-  updateFirefoxAccountFromJSON: function (json) {
-    return Messaging.sendRequestForResult({
-      type: "Accounts:UpdateFirefoxAccountFromJSON",
-      json: this._addDefaultEndpoints(json)
-    });
-  },
-
-  /**
-   * Notify that profile for Android Account has updated.
-   * The account will re-fetch the profile image.
-   *
-   * It is an error if no Android Account exists.
-   *
-   * There is no return value from this method.
-   */
-  notifyFirefoxAccountProfileChanged: function () {
-    Messaging.sendRequest({
-      type: "Accounts:ProfileUpdated",
-    });
-  },
-
-  /**
-   * Fetch information about an existing Android Firefox Account.
-   *
-   * Returns a Promise that resolves to null if no Android Firefox Account
-   * exists, or an object including at least a string-valued 'email' key.
-   */
-  getFirefoxAccount: function () {
-    return Messaging.sendRequestForResult({
-      type: "Accounts:Exist",
-      kind: "fxa",
-    }).then(data => {
-      if (!data || !data.exists) {
-        return null;
-      }
-      delete data.exists;
-      return data;
-    });
-  },
-
-  /**
-   * Delete an existing Android Firefox Account.
-   *
-   * It is an error if no Android Account exists.
-   *
-   * Returns a Promise that resolves to a boolean indicating success.
-   */
-  deleteFirefoxAccount: function () {
-    return Messaging.sendRequestForResult({
-      type: "Accounts:DeleteFirefoxAccount",
-    });
-  },
-
   showSyncPreferences: function () {
     // Only show Sync preferences of an existing Android Account.
-    return Accounts.getFirefoxAccount().then(account => {
-      if (!account) {
-        throw new Error("Can't show Sync preferences of non-existent Firefox Account!");
-      }
-      return Messaging.sendRequestForResult({
-        type: "Accounts:ShowSyncPreferences"
-      });
-    });
+    throw new Error("Can't show Sync preferences without accounts!");
   }
 });
diff --git a/mobile/android/modules/FxAccountsWebChannel.jsm b/mobile/android/modules/FxAccountsWebChannel.jsm
deleted file mode 100644
index 6ee8fd0..0000000
--- a/mobile/android/modules/FxAccountsWebChannel.jsm
+++ /dev/null
@@ -1,394 +0,0 @@
-// -*- Mode: js2; tab-width: 2; indent-tabs-mode: nil; js2-basic-offset: 2; js2-skip-preprocessor-directives: t; -*-
-/* 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/. */
-
-/**
- * Firefox Accounts Web Channel.
- *
- * Use the WebChannel component to receive messages about account
- * state changes.
- */
-this.EXPORTED_SYMBOLS = ["EnsureFxAccountsWebChannel"];
-
-const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components; /*global Components */
-
-Cu.import("resource://gre/modules/Accounts.jsm"); /*global Accounts */
-Cu.import("resource://gre/modules/Services.jsm"); /*global Services */
-Cu.import("resource://gre/modules/WebChannel.jsm"); /*global WebChannel */
-Cu.import("resource://gre/modules/XPCOMUtils.jsm"); /*global XPCOMUtils */
-
-const log = Cu.import("resource://gre/modules/AndroidLog.jsm", {}).AndroidLog.bind("FxAccounts");
-
-const WEBCHANNEL_ID = "account_updates";
-
-const COMMAND_LOADED               = "fxaccounts:loaded";
-const COMMAND_CAN_LINK_ACCOUNT     = "fxaccounts:can_link_account";
-const COMMAND_LOGIN                = "fxaccounts:login";
-const COMMAND_CHANGE_PASSWORD      = "fxaccounts:change_password";
-const COMMAND_DELETE_ACCOUNT       = "fxaccounts:delete_account";
-const COMMAND_PROFILE_CHANGE       = "profile:change";
-const COMMAND_SYNC_PREFERENCES     = "fxaccounts:sync_preferences";
-
-const PREF_LAST_FXA_USER           = "identity.fxaccounts.lastSignedInUserHash";
-
-XPCOMUtils.defineLazyGetter(this, "strings",
-                            () => Services.strings.createBundle("chrome://browser/locale/aboutAccounts.properties")); /*global strings */
-
-XPCOMUtils.defineLazyModuleGetter(this, "Snackbars", "resource://gre/modules/Snackbars.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "Prompt", "resource://gre/modules/Prompt.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "UITelemetry", "resource://gre/modules/UITelemetry.jsm");
-
-this.FxAccountsWebChannelHelpers = function() {
-};
-
-this.FxAccountsWebChannelHelpers.prototype = {
-  /**
-   * Get the hash of account name of the previously signed in account.
-   */
-  getPreviousAccountNameHashPref() {
-    try {
-      return Services.prefs.getComplexValue(PREF_LAST_FXA_USER, Ci.nsISupportsString).data;
-    } catch (_) {
-      return "";
-    }
-  },
-
-  /**
-   * Given an account name, set the hash of the previously signed in account.
-   *
-   * @param acctName the account name of the user's account.
-   */
-  setPreviousAccountNameHashPref(acctName) {
-    let string = Cc["@mozilla.org/supports-string;1"]
-                 .createInstance(Ci.nsISupportsString);
-    string.data = this.sha256(acctName);
-    Services.prefs.setComplexValue(PREF_LAST_FXA_USER, Ci.nsISupportsString, string);
-  },
-
-  /**
-   * Given a string, returns the SHA265 hash in base64.
-   */
-  sha256(str) {
-    let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
-                      .createInstance(Ci.nsIScriptableUnicodeConverter);
-    converter.charset = "UTF-8";
-    // Data is an array of bytes.
-    let data = converter.convertToByteArray(str, {});
-    let hasher = Cc["@mozilla.org/security/hash;1"]
-                   .createInstance(Ci.nsICryptoHash);
-    hasher.init(hasher.SHA256);
-    hasher.update(data, data.length);
-
-    return hasher.finish(true);
-  },
-};
-
-/**
- * Create a new FxAccountsWebChannel to listen for account updates.
- *
- * @param {Object} options Options
- *   @param {Object} options
- *     @param {String} options.content_uri
- *     The FxA Content server uri
- *     @param {String} options.channel_id
- *     The ID of the WebChannel
- *     @param {String} options.helpers
- *     Helpers functions. Should only be passed in for testing.
- * @constructor
- */
-this.FxAccountsWebChannel = function(options) {
-  if (!options) {
-    throw new Error("Missing configuration options");
-  }
-  if (!options["content_uri"]) {
-    throw new Error("Missing 'content_uri' option");
-  }
-  this._contentUri = options.content_uri;
-
-  if (!options["channel_id"]) {
-    throw new Error("Missing 'channel_id' option");
-  }
-  this._webChannelId = options.channel_id;
-
-  // options.helpers is only specified by tests.
-  this._helpers = options.helpers || new FxAccountsWebChannelHelpers(options);
-
-  this._setupChannel();
-};
-
-this.FxAccountsWebChannel.prototype = {
-  /**
-   * WebChannel that is used to communicate with content page
-   */
-  _channel: null,
-
-  /**
-   * WebChannel ID.
-   */
-  _webChannelId: null,
-  /**
-   * WebChannel origin, used to validate origin of messages
-   */
-  _webChannelOrigin: null,
-
-  /**
-   * Release all resources that are in use.
-   */
-  tearDown() {
-    this._channel.stopListening();
-    this._channel = null;
-    this._channelCallback = null;
-  },
-
-  /**
-   * Configures and registers a new WebChannel
-   *
-   * @private
-   */
-  _setupChannel() {
-    // if this.contentUri is present but not a valid URI, then this will throw an error.
-    try {
-      this._webChannelOrigin = Services.io.newURI(this._contentUri, null, null);
-      this._registerChannel();
-    } catch (e) {
-      log.e(e.toString());
-      throw e;
-    }
-  },
-
-  /**
-   * Create a new channel with the WebChannelBroker, setup a callback listener
-   * @private
-   */
-  _registerChannel() {
-    /**
-     * Processes messages that are called back from the FxAccountsChannel
-     *
-     * @param webChannelId {String}
-     *        Command webChannelId
-     * @param message {Object}
-     *        Command message
-     * @param sendingContext {Object}
-     *        Message sending context.
-     *        @param sendingContext.browser {browser}
-     *               The <browser> object that captured the
-     *               WebChannelMessageToChrome.
-     *        @param sendingContext.eventTarget {EventTarget}
-     *               The <EventTarget> where the message was sent.
-     *        @param sendingContext.principal {Principal}
-     *               The <Principal> of the EventTarget where the message was sent.
-     * @private
-     *
-     */
-    let listener = (webChannelId, message, sendingContext) => {
-      if (message) {
-        let command = message.command;
-        let data = message.data;
-        log.d("FxAccountsWebChannel message received, command: " + command);
-
-        // Respond to the message with true or false.
-        let respond = (data) => {
-          let response = {
-            command: command,
-            messageId: message.messageId,
-            data: data
-          };
-          log.d("Sending response to command: " + command);
-          this._channel.send(response, sendingContext);
-        };
-
-        switch (command) {
-          case COMMAND_LOADED:
-            let mm = sendingContext.browser.docShell
-              .QueryInterface(Ci.nsIInterfaceRequestor)
-              .getInterface(Ci.nsIContentFrameMessageManager);
-            mm.sendAsyncMessage(COMMAND_LOADED);
-            break;
-
-          case COMMAND_CAN_LINK_ACCOUNT:
-            Accounts.getFirefoxAccount().then(account => {
-              if (account) {
-                // If we /have/ an Android Account, we never allow the user to
-                // login to a different account.  They need to manually delete
-                // the first Android Account and then create a new one.
-                if (account.email == data.email) {
-                  // In future, we should use a UID for this comparison.
-                  log.d("Relinking existing Android Account: email addresses agree.");
-                  respond({ok: true});
-                } else {
-                  log.w("Not relinking existing Android Account: email addresses disagree!");
-                  let message = strings.GetStringFromName("relinkDenied.message");
-                  let buttonLabel = strings.GetStringFromName("relinkDenied.openPrefs");
-                  Snackbars.show(message, Snackbars.LENGTH_LONG, {
-                    action: {
-                      label: buttonLabel,
-                      callback: () => {
-                        // We have an account, so this opens Sync native preferences.
-                        Accounts.launchSetup();
-                      },
-                    }
-                  });
-                  respond({ok: false});
-                }
-              } else {
-                // If we /don't have/ an Android Account, we warn if we're
-                // connecting to a new Account.  This is to minimize surprise;
-                // we never did this when changing accounts via the native UI.
-                let prevAcctHash = this._helpers.getPreviousAccountNameHashPref();
-                let shouldShowWarning = prevAcctHash && (prevAcctHash != this._helpers.sha256(data.email));
-
-                if (shouldShowWarning) {
-                  log.w("Warning about creating a new Android Account: previously linked to different email address!");
-                  let message = strings.formatStringFromName("relinkVerify.message", [data.email], 1);
-                  new Prompt({
-                    title: strings.GetStringFromName("relinkVerify.title"),
-                    message: message,
-                    buttons: [
-                      // This puts Cancel on the right.
-                      strings.GetStringFromName("relinkVerify.cancel"),
-                      strings.GetStringFromName("relinkVerify.continue"),
-                    ],
-                  }).show(result => respond({ok: result && result.button == 1}));
-                } else {
-                  log.d("Not warning about creating a new Android Account: no previously linked email address.");
-                  respond({ok: true});
-                }
-              }
-            }).catch(e => {
-              log.e(e.toString());
-              respond({ok: false});
-            });
-            break;
-
-          case COMMAND_LOGIN:
-            // Either create a new Android Account or re-connect an existing
-            // Android Account here.  There's not much to be done if we don't
-            // succeed or get an error.
-            Accounts.getFirefoxAccount().then(account => {
-              if (!account) {
-                return Accounts.createFirefoxAccountFromJSON(data).then(success => {
-                  if (!success) {
-                    throw new Error("Could not create Firefox Account!");
-                  }
-                  UITelemetry.addEvent("action.1", "content", null, "fxaccount-create");
-                  return success;
-                });
-              } else {
-                return Accounts.updateFirefoxAccountFromJSON(data).then(success => {
-                  if (!success) {
-                    throw new Error("Could not update Firefox Account!");
-                  }
-                  UITelemetry.addEvent("action.1", "content", null, "fxaccount-login");
-                  return success;
-                });
-              }
-            })
-            .then(success => {
-              if (!success) {
-                throw new Error("Could not create or update Firefox Account!");
-              }
-
-              // Remember who it is so we can show a relink warning when appropriate.
-              this._helpers.setPreviousAccountNameHashPref(data.email);
-
-              log.i("Created or updated Firefox Account.");
-            })
-            .catch(e => {
-              log.e(e.toString());
-            });
-            break;
-
-          case COMMAND_CHANGE_PASSWORD:
-            // Only update an existing Android Account.
-            Accounts.getFirefoxAccount().then(account => {
-              if (!account) {
-                throw new Error("Can't change password of non-existent Firefox Account!");
-              }
-              return Accounts.updateFirefoxAccountFromJSON(data);
-            })
-            .then(success => {
-              if (!success) {
-                throw new Error("Could not change Firefox Account password!");
-              }
-              UITelemetry.addEvent("action.1", "content", null, "fxaccount-changepassword");
-              log.i("Changed Firefox Account password.");
-            })
-            .catch(e => {
-              log.e(e.toString());
-            });
-            break;
-
-          case COMMAND_DELETE_ACCOUNT:
-            // The fxa-content-server has already confirmed the user's intent.
-            // Bombs away.  There's no recovery from failure, and not even a
-            // real need to check an account exists (although we do, for error
-            // messaging only).
-            Accounts.getFirefoxAccount().then(account => {
-              if (!account) {
-                throw new Error("Can't delete non-existent Firefox Account!");
-              }
-              return Accounts.deleteFirefoxAccount().then(success => {
-                if (!success) {
-                  throw new Error("Could not delete Firefox Account!");
-                }
-                UITelemetry.addEvent("action.1", "content", null, "fxaccount-delete");
-                log.i("Firefox Account deleted.");
-              });
-            }).catch(e => {
-              log.e(e.toString());
-            });
-            break;
-
-          case COMMAND_PROFILE_CHANGE:
-            // Only update an existing Android Account.
-            Accounts.getFirefoxAccount().then(account => {
-              if (!account) {
-                throw new Error("Can't change profile of non-existent Firefox Account!");
-              }
-              UITelemetry.addEvent("action.1", "content", null, "fxaccount-changeprofile");
-              return Accounts.notifyFirefoxAccountProfileChanged();
-            })
-            .catch(e => {
-              log.e(e.toString());
-            });
-            break;
-
-          case COMMAND_SYNC_PREFERENCES:
-            UITelemetry.addEvent("action.1", "content", null, "fxaccount-syncprefs");
-            Accounts.showSyncPreferences()
-            .catch(e => {
-              log.e(e.toString());
-            });
-            break;
-
-          default:
-            log.w("Ignoring unrecognized FxAccountsWebChannel command: " + JSON.stringify(command));
-            break;
-        }
-      }
-    };
-
-    this._channelCallback = listener;
-    this._channel = new WebChannel(this._webChannelId, this._webChannelOrigin);
-    this._channel.listen(listener);
-
-    log.d("FxAccountsWebChannel registered: " + this._webChannelId + " with origin " + this._webChannelOrigin.prePath);
-  }
-};
-
-var singleton;
-// The entry-point for this module, which ensures only one of our channels is
-// ever created - we require this because the WebChannel is global in scope and
-// allowing multiple channels would cause such notifications to be sent multiple
-// times.
-this.EnsureFxAccountsWebChannel = function() {
-  if (!singleton) {
-    let contentUri = Services.urlFormatter.formatURLPref("identity.fxaccounts.remote.webchannel.uri");
-    // The FxAccountsWebChannel listens for events and updates the Java layer.
-    singleton = new this.FxAccountsWebChannel({
-      content_uri: contentUri,
-      channel_id: WEBCHANNEL_ID,
-    });
-  }
-};
diff --git a/mobile/android/modules/moz.build b/mobile/android/modules/moz.build
index 479ff1f..dd62e48 100644
--- a/mobile/android/modules/moz.build
+++ b/mobile/android/modules/moz.build
@@ -10,7 +10,6 @@ EXTRA_JS_MODULES += [
     'dbg-browser-actors.js',
     'DelayedInit.jsm',
     'DownloadNotifications.jsm',
-    'FxAccountsWebChannel.jsm',
     'HelperApps.jsm',
     'Home.jsm',
     'HomeProvider.jsm',
diff --git a/mobile/android/services/README.txt b/mobile/android/services/README.txt
deleted file mode 100644
index cf4624c..0000000
--- a/mobile/android/services/README.txt
+++ /dev/null
@@ -1 +0,0 @@
-These files are managed in the android-sync repo. Do not modify directly, or your changes will be lost.
diff --git a/mobile/android/services/manifests/FxAccountAndroidManifest_activities.xml.in b/mobile/android/services/manifests/FxAccountAndroidManifest_activities.xml.in
deleted file mode 100644
index ad9542a..0000000
--- a/mobile/android/services/manifests/FxAccountAndroidManifest_activities.xml.in
+++ /dev/null
@@ -1,63 +0,0 @@
-        <activity
-            android:theme="@style/FxAccountTheme.FxAccountStatusActivity"
-            android:label="@string/fxaccount_status_activity_label"
-            android:clearTaskOnLaunch="true"
-            android:taskAffinity="@ANDROID_PACKAGE_NAME at .FXA"
-            android:name="org.mozilla.gecko.fxa.activities.FxAccountStatusActivity"
-            android:configChanges="locale|layoutDirection"
-            android:windowSoftInputMode="adjustResize">
-            <!-- Adding a launcher will make this activity appear on the
-                 Apps screen, which we only want when testing. -->
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <!-- <category android:name="android.intent.category.LAUNCHER" /> -->
-            </intent-filter>
-            <intent-filter>
-                <action android:name="@ANDROID_PACKAGE_NAME at .ACTION_FXA_STATUS"/>
-                <category android:name="android.intent.category.DEFAULT"/>
-            </intent-filter>
-        </activity>
-
-        <receiver
-            android:name="org.mozilla.gecko.fxa.receivers.FxAccountUpgradeReceiver">
-            <intent-filter>
-                <action android:name="android.intent.action.PACKAGE_REPLACED" />
-                <data android:scheme="package"/>
-            </intent-filter>
-        </receiver>
-
-        <activity
-            android:exported="false"
-            android:name="org.mozilla.gecko.fxa.activities.FxAccountGetStartedActivityWeb">
-            <intent-filter>
-                <action android:name="@ANDROID_PACKAGE_NAME at .ACTION_FXA_GET_STARTED"/>
-                <category android:name="android.intent.category.DEFAULT"/>
-            </intent-filter>
-        </activity>
-
-        <activity
-            android:exported="false"
-            android:name="org.mozilla.gecko.fxa.activities.FxAccountUpdateCredentialsActivityWeb">
-            <intent-filter>
-                <action android:name="@ANDROID_PACKAGE_NAME at .ACTION_FXA_UPDATE_CREDENTIALS"/>
-                <category android:name="android.intent.category.DEFAULT"/>
-            </intent-filter>
-        </activity>
-
-        <activity
-            android:exported="false"
-            android:name="org.mozilla.gecko.fxa.activities.FxAccountFinishMigratingActivityWeb">
-            <intent-filter>
-                <action android:name="@ANDROID_PACKAGE_NAME at .ACTION_FXA_FINISH_MIGRATING"/>
-                <category android:name="android.intent.category.DEFAULT"/>
-            </intent-filter>
-        </activity>
-
-        <activity
-            android:exported="false"
-            android:name="org.mozilla.gecko.fxa.activities.FxAccountConfirmAccountActivityWeb">
-            <intent-filter>
-                <action android:name="@ANDROID_PACKAGE_NAME at .ACTION_FXA_CONFIRM_ACCOUNT"/>
-                <category android:name="android.intent.category.DEFAULT"/>
-            </intent-filter>
-        </activity>
diff --git a/mobile/android/services/manifests/FxAccountAndroidManifest_permissions.xml.in b/mobile/android/services/manifests/FxAccountAndroidManifest_permissions.xml.in
deleted file mode 100644
index d5c7e3e..0000000
--- a/mobile/android/services/manifests/FxAccountAndroidManifest_permissions.xml.in
+++ /dev/null
@@ -1,18 +0,0 @@
-    <uses-permission android:name="android.permission.GET_ACCOUNTS" />
-    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
-    <uses-permission android:name="android.permission.MANAGE_ACCOUNTS" />
-    <uses-permission android:name="android.permission.USE_CREDENTIALS" />
-    <uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS" />
-    <uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS" />
-    <uses-permission android:name="android.permission.WRITE_SETTINGS" />
-    <uses-permission android:name="android.permission.READ_SYNC_STATS" />
-    <uses-permission android:name="android.permission.READ_SYNC_SETTINGS" />
-
-    <!-- A signature level permission granted only to the Firefox
-         channels sharing an Android Account type. -->
-    <permission
-        android:name="@ANDROID_PACKAGE_NAME at _fxaccount.permission.PER_ACCOUNT_TYPE"
-        android:protectionLevel="signature">
-    </permission>
-
-    <uses-permission android:name="@ANDROID_PACKAGE_NAME at _fxaccount.permission.PER_ACCOUNT_TYPE" />
diff --git a/mobile/android/services/manifests/FxAccountAndroidManifest_services.xml.in b/mobile/android/services/manifests/FxAccountAndroidManifest_services.xml.in
deleted file mode 100644
index a109d1b..0000000
--- a/mobile/android/services/manifests/FxAccountAndroidManifest_services.xml.in
+++ /dev/null
@@ -1,34 +0,0 @@
-        <service
-            android:exported="true"
-            android:name="org.mozilla.gecko.fxa.authenticator.FxAccountAuthenticatorService" >
-            <intent-filter >
-                <action android:name="android.accounts.AccountAuthenticator" />
-            </intent-filter>
-
-            <meta-data
-                android:name="android.accounts.AccountAuthenticator"
-                android:resource="@xml/fxaccount_authenticator" />
-        </service>
-
-        <service
-            android:exported="false"
-            android:name="org.mozilla.gecko.fxa.receivers.FxAccountDeletedService" >
-        </service>
-
-        <service
-            android:exported="false"
-            android:name="org.mozilla.gecko.fxa.sync.FxAccountProfileService" >
-        </service>
-
-        <!-- Firefox Sync. -->
-        <service
-            android:exported="false"
-            android:name="org.mozilla.gecko.fxa.sync.FxAccountSyncService" >
-            <intent-filter >
-                <action android:name="android.content.SyncAdapter" />
-            </intent-filter>
-
-            <meta-data
-                android:name="android.content.SyncAdapter"
-                android:resource="@xml/fxaccount_syncadapter" />
-        </service>
\ No newline at end of file
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/background/ReadingListConstants.java b/mobile/android/services/src/main/java/org/mozilla/gecko/background/ReadingListConstants.java
deleted file mode 100644
index df603a5..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/background/ReadingListConstants.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.background;
-
-import org.mozilla.gecko.AppConstants;
-
-/**
- * This is in 'background' not 'reading' so that it's still usable even when the
- * Reading List feature is build-time disabled.
- */
-public class ReadingListConstants {
-  public static final String GLOBAL_LOG_TAG = "FxReadingList";
-  public static final String USER_AGENT = "Firefox-Android-FxReader/" + AppConstants.MOZ_APP_VERSION + " (" + AppConstants.MOZ_APP_UA_NAME + ")";
-  public static final String DEFAULT_DEV_ENDPOINT = "https://readinglist.dev.mozaws.net/v1/";
-  public static final String DEFAULT_PROD_ENDPOINT = "https://readinglist.services.mozilla.com/v1/";
-
-  public static final String OAUTH_SCOPE_READINGLIST = "readinglist";
-  public static final String AUTH_TOKEN_TYPE = "oauth::" + OAUTH_SCOPE_READINGLIST;
-
-  public static boolean DEBUG = false;
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/background/common/EditorBranch.java b/mobile/android/services/src/main/java/org/mozilla/gecko/background/common/EditorBranch.java
deleted file mode 100644
index 1ead09a..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/background/common/EditorBranch.java
+++ /dev/null
@@ -1,82 +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.background.common;
-
-import java.util.Set;
-
-import android.content.SharedPreferences;
-import android.content.SharedPreferences.Editor;
-
-public class EditorBranch implements Editor {
-
-  private final String prefix;
-  private Editor editor;
-
-  public EditorBranch(final SharedPreferences prefs, final String prefix) {
-    if (!prefix.endsWith(".")) {
-      throw new IllegalArgumentException("No trailing period in prefix.");
-    }
-    this.prefix = prefix;
-    this.editor = prefs.edit();
-  }
-
-  @Override
-  public void apply() {
-    this.editor.apply();
-  }
-
-  @Override
-  public Editor clear() {
-    this.editor = this.editor.clear();
-    return this;
-  }
-
-  @Override
-  public boolean commit() {
-    return this.editor.commit();
-  }
-
-  @Override
-  public Editor putBoolean(String key, boolean value) {
-    this.editor = this.editor.putBoolean(prefix + key, value);
-    return this;
-  }
-
-  @Override
-  public Editor putFloat(String key, float value) {
-    this.editor = this.editor.putFloat(prefix + key, value);
-    return this;
-  }
-
-  @Override
-  public Editor putInt(String key, int value) {
-    this.editor = this.editor.putInt(prefix + key, value);
-    return this;
-  }
-
-  @Override
-  public Editor putLong(String key, long value) {
-    this.editor = this.editor.putLong(prefix + key, value);
-    return this;
-  }
-
-  @Override
-  public Editor putString(String key, String value) {
-    this.editor = this.editor.putString(prefix + key, value);
-    return this;
-  }
-
-  // Not marking as Override, because Android <= 10 doesn't have
-  // putStringSet. Neither can we implement it.
-  public Editor putStringSet(String key, Set<String> value) {
-    throw new RuntimeException("putStringSet not available.");
-  }
-
-  @Override
-  public Editor remove(String key) {
-    this.editor = this.editor.remove(prefix + key);
-    return this;
-  }
-}
\ No newline at end of file
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/background/common/GlobalConstants.java b/mobile/android/services/src/main/java/org/mozilla/gecko/background/common/GlobalConstants.java
deleted file mode 100644
index d661e62..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/background/common/GlobalConstants.java
+++ /dev/null
@@ -1,90 +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.background.common;
-
-import org.mozilla.gecko.AppConstants;
-import org.mozilla.gecko.AppConstants.Versions;
-
-/**
- * Constant values common to all Android services.
- */
-public class GlobalConstants {
-  public static final String BROWSER_INTENT_PACKAGE = AppConstants.ANDROID_PACKAGE_NAME;
-  public static final String BROWSER_INTENT_CLASS = AppConstants.MOZ_ANDROID_BROWSER_INTENT_CLASS;
-
-  public static final int SHARED_PREFERENCES_MODE = 0;
-
-  // Common time values.
-  public static final long MILLISECONDS_PER_DAY = 24 * 60 * 60 * 1000;
-  public static final long MILLISECONDS_PER_SIX_MONTHS = 180 * MILLISECONDS_PER_DAY;
-
-  // Acceptable cipher suites.
-  /**
-   * We support only a very limited range of strong cipher suites and protocols:
-   * no SSLv3 or TLSv1.0 (if we can), no DHE ciphers that might be vulnerable to Logjam
-   * (https://weakdh.org/), no RC4.
-   *
-   * Backstory: Bug 717691 (we no longer support Android 2.2, so the name
-   * workaround is unnecessary), Bug 1081953, Bug 1061273, Bug 1166839.
-   *
-   * See <http://developer.android.com/reference/javax/net/ssl/SSLSocket.html> for
-   * supported Android versions for each set of protocols and cipher suites.
-   *
-   * Note that currently we need to support connections to Sync 1.1 on Mozilla-hosted infra,
-   * as well as connections to FxA and Sync 1.5 on AWS.
-   *
-   * ELB cipher suites:
-   * <http://docs.aws.amazon.com/ElasticLoadBalancing/latest/DeveloperGuide/elb-security-policy-table.html>
-   */
-  public static final String[] DEFAULT_CIPHER_SUITES;
-  public static final String[] DEFAULT_PROTOCOLS;
-
-  static {
-    // Prioritize 128 over 256 as a tradeoff between device CPU/battery and the minor
-    // increase in strength.
-    if (Versions.feature20Plus) {
-      DEFAULT_CIPHER_SUITES = new String[]
-          {
-           "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",   // 20+
-           "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",     // 20+
-           "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256",     // 20+
-           "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA",        // 11+
-           "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",     // 20+
-           "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384",     // 20+
-           "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA",        // 11+
-
-           // For Sync 1.1.
-           "TLS_DHE_RSA_WITH_AES_128_CBC_SHA",  // 9+
-           "TLS_RSA_WITH_AES_128_CBC_SHA",      // 9+
-          };
-    } else {
-      DEFAULT_CIPHER_SUITES = new String[]
-          {
-           "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA",        // 11+
-           "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA",      // 11+
-           "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA",        // 11+
-
-           // For Sync 1.1.
-           "TLS_DHE_RSA_WITH_AES_128_CBC_SHA",  // 9+
-           "TLS_RSA_WITH_AES_128_CBC_SHA",      // 9+
-          };
-    }
-
-    if (Versions.feature16Plus) {
-      DEFAULT_PROTOCOLS = new String[]
-          {
-           "TLSv1.2",
-           "TLSv1.1",
-           "TLSv1",             // We would like to remove this, and will do so when we can.
-          };
-    } else {
-      // Fall back to TLSv1 if there's nothing better.
-      DEFAULT_PROTOCOLS = new String[]
-          {
-           "TLSv1",
-          };
-    }
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/background/common/PrefsBranch.java b/mobile/android/services/src/main/java/org/mozilla/gecko/background/common/PrefsBranch.java
deleted file mode 100644
index 78d5f61..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/background/common/PrefsBranch.java
+++ /dev/null
@@ -1,83 +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.background.common;
-
-import java.util.Map;
-import java.util.Set;
-
-import android.content.SharedPreferences;
-
-/**
- * A wrapper around a portion of the SharedPreferences space.
- */
-public class PrefsBranch implements SharedPreferences {
-  private final SharedPreferences prefs;
-  private final String prefix;                // Including trailing period.
-
-  public PrefsBranch(SharedPreferences prefs, String prefix) {
-    if (!prefix.endsWith(".")) {
-      throw new IllegalArgumentException("No trailing period in prefix.");
-    }
-    this.prefs = prefs;
-    this.prefix = prefix;
-  }
-
-  @Override
-  public boolean contains(String key) {
-    return prefs.contains(prefix + key);
-  }
-
-  @Override
-  public Editor edit() {
-    return new EditorBranch(prefs, prefix);
-  }
-
-  @Override
-  public Map<String, ?> getAll() {
-    // Not implemented. TODO
-    return null;
-  }
-
-  @Override
-  public boolean getBoolean(String key, boolean defValue) {
-    return prefs.getBoolean(prefix + key, defValue);
-  }
-
-  @Override
-  public float getFloat(String key, float defValue) {
-    return prefs.getFloat(prefix + key, defValue);
-  }
-
-  @Override
-  public int getInt(String key, int defValue) {
-    return prefs.getInt(prefix + key, defValue);
-  }
-
-  @Override
-  public long getLong(String key, long defValue) {
-    return prefs.getLong(prefix + key, defValue);
-  }
-
-  @Override
-  public String getString(String key, String defValue) {
-    return prefs.getString(prefix + key, defValue);
-  }
-
-  // Not marking as Override, because Android <= 10 doesn't have
-  // getStringSet. Neither can we implement it.
-  public Set<String> getStringSet(String key, Set<String> defValue) {
-    throw new RuntimeException("getStringSet not available.");
-  }
-
-  @Override
-  public void registerOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) {
-    prefs.registerOnSharedPreferenceChangeListener(listener);
-  }
-
-  @Override
-  public void unregisterOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) {
-    prefs.unregisterOnSharedPreferenceChangeListener(listener);
-  }
-}
\ No newline at end of file
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/background/common/log/Logger.java b/mobile/android/services/src/main/java/org/mozilla/gecko/background/common/log/Logger.java
deleted file mode 100644
index 2575717..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/background/common/log/Logger.java
+++ /dev/null
@@ -1,232 +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.background.common.log;
-
-import java.io.PrintWriter;
-import java.util.Iterator;
-import java.util.LinkedHashSet;
-import java.util.Set;
-
-import org.mozilla.gecko.background.common.GlobalConstants;
-import org.mozilla.gecko.background.common.log.writers.AndroidLevelCachingLogWriter;
-import org.mozilla.gecko.background.common.log.writers.AndroidLogWriter;
-import org.mozilla.gecko.background.common.log.writers.LogWriter;
-import org.mozilla.gecko.background.common.log.writers.PrintLogWriter;
-import org.mozilla.gecko.background.common.log.writers.SimpleTagLogWriter;
-import org.mozilla.gecko.background.common.log.writers.ThreadLocalTagLogWriter;
-
-import android.util.Log;
-
-/**
- * Logging helper class. Serializes all log operations (by synchronizing).
- */
-public class Logger {
-  public static final String LOGGER_TAG = "Logger";
-  public static final String DEFAULT_LOG_TAG = "GeckoLogger";
-
-  // For extra debugging.
-  public static boolean LOG_PERSONAL_INFORMATION = false;
-
-  /**
-   * Allow each thread to use its own global log tag. This allows
-   * independent services to log as different sources.
-   *
-   * When your thread sets up logging, it should do something like the following:
-   *
-   *   Logger.setThreadLogTag("MyTag");
-   *
-   * The value is inheritable, so worker threads and such do not need to
-   * set the same log tag as their parent.
-   */
-  private static final InheritableThreadLocal<String> logTag = new InheritableThreadLocal<String>() {
-    @Override
-    protected String initialValue() {
-      return DEFAULT_LOG_TAG;
-    }
-  };
-
-  public static void setThreadLogTag(final String logTag) {
-    Logger.logTag.set(logTag);
-  }
-  public static String getThreadLogTag() {
-    return Logger.logTag.get();
-  }
-
-  /**
-   * Current set of writers to which we will log.
-   * <p>
-   * We want logging to be available while running tests, so we initialize
-   * this set statically.
-   */
-  protected final static Set<LogWriter> logWriters;
-  static {
-    final Set<LogWriter> defaultWriters = Logger.defaultLogWriters();
-    logWriters = new LinkedHashSet<LogWriter>(defaultWriters);
-  }
-
-  /**
-   * Default set of log writers to log to.
-   */
-  public final static Set<LogWriter> defaultLogWriters() {
-    final String processedPackage = GlobalConstants.BROWSER_INTENT_PACKAGE.replace("org.mozilla.", "");
-
-    final Set<LogWriter> defaultLogWriters = new LinkedHashSet<LogWriter>();
-
-    final LogWriter log = new AndroidLogWriter();
-    final LogWriter cache = new AndroidLevelCachingLogWriter(log);
-
-    final LogWriter single = new SimpleTagLogWriter(processedPackage, new ThreadLocalTagLogWriter(Logger.logTag, cache));
-
-    defaultLogWriters.add(single);
-    return defaultLogWriters;
-  }
-
-  public static synchronized void startLoggingTo(LogWriter logWriter) {
-    logWriters.add(logWriter);
-  }
-
-  public static synchronized void startLoggingToWriters(Set<LogWriter> writers) {
-    logWriters.addAll(writers);
-  }
-
-  public static synchronized void stopLoggingTo(LogWriter logWriter) {
-    try {
-      logWriter.close();
-    } catch (Exception e) {
-      Log.e(LOGGER_TAG, "Got exception closing and removing LogWriter " + logWriter + ".", e);
-    }
-    logWriters.remove(logWriter);
-  }
-
-  public static synchronized void stopLoggingToAll() {
-    for (LogWriter logWriter : logWriters) {
-      try {
-        logWriter.close();
-      } catch (Exception e) {
-        Log.e(LOGGER_TAG, "Got exception closing and removing LogWriter " + logWriter + ".", e);
-      }
-    }
-    logWriters.clear();
-  }
-
-  /**
-   * Write to only the default log writers.
-   */
-  public static synchronized void resetLogging() {
-    stopLoggingToAll();
-    logWriters.addAll(Logger.defaultLogWriters());
-  }
-
-  /**
-   * Start writing log output to stdout.
-   * <p>
-   * Use <code>resetLogging</code> to stop logging to stdout.
-   */
-  public static synchronized void startLoggingToConsole() {
-    setThreadLogTag("Test");
-    startLoggingTo(new PrintLogWriter(new PrintWriter(System.out, true)));
-  }
-
-  // Synchronized version for other classes to use.
-  public static synchronized boolean shouldLogVerbose(String logTag) {
-    for (LogWriter logWriter : logWriters) {
-      if (logWriter.shouldLogVerbose(logTag)) {
-        return true;
-      }
-    }
-    return false;
-  }
-
-  public static void error(String tag, String message) {
-    Logger.error(tag, message, null);
-  }
-
-  public static void warn(String tag, String message) {
-    Logger.warn(tag, message, null);
-  }
-
-  public static void info(String tag, String message) {
-    Logger.info(tag, message, null);
-  }
-
-  public static void debug(String tag, String message) {
-    Logger.debug(tag, message, null);
-  }
-
-  public static void trace(String tag, String message) {
-    Logger.trace(tag, message, null);
-  }
-
-  public static void pii(String tag, String message) {
-    if (LOG_PERSONAL_INFORMATION) {
-      Logger.debug(tag, "$$PII$$: " + message);
-    }
-  }
-
-  public static synchronized void error(String tag, String message, Throwable error) {
-    Iterator<LogWriter> it = logWriters.iterator();
-    while (it.hasNext()) {
-      LogWriter writer = it.next();
-      try {
-        writer.error(tag, message, error);
-      } catch (Exception e) {
-        Log.e(LOGGER_TAG, "Got exception logging; removing LogWriter " + writer + ".", e);
-        it.remove();
-      }
-    }
-  }
-
-  public static synchronized void warn(String tag, String message, Throwable error) {
-    Iterator<LogWriter> it = logWriters.iterator();
-    while (it.hasNext()) {
-      LogWriter writer = it.next();
-      try {
-        writer.warn(tag, message, error);
-      } catch (Exception e) {
-        Log.e(LOGGER_TAG, "Got exception logging; removing LogWriter " + writer + ".", e);
-        it.remove();
-      }
-    }
-  }
-
-  public static synchronized void info(String tag, String message, Throwable error) {
-    Iterator<LogWriter> it = logWriters.iterator();
-    while (it.hasNext()) {
-      LogWriter writer = it.next();
-      try {
-        writer.info(tag, message, error);
-      } catch (Exception e) {
-        Log.e(LOGGER_TAG, "Got exception logging; removing LogWriter " + writer + ".", e);
-        it.remove();
-      }
-    }
-  }
-
-  public static synchronized void debug(String tag, String message, Throwable error) {
-    Iterator<LogWriter> it = logWriters.iterator();
-    while (it.hasNext()) {
-      LogWriter writer = it.next();
-      try {
-        writer.debug(tag, message, error);
-      } catch (Exception e) {
-        Log.e(LOGGER_TAG, "Got exception logging; removing LogWriter " + writer + ".", e);
-        it.remove();
-      }
-    }
-  }
-
-  public static synchronized void trace(String tag, String message, Throwable error) {
-    Iterator<LogWriter> it = logWriters.iterator();
-    while (it.hasNext()) {
-      LogWriter writer = it.next();
-      try {
-        writer.trace(tag, message, error);
-      } catch (Exception e) {
-        Log.e(LOGGER_TAG, "Got exception logging; removing LogWriter " + writer + ".", e);
-        it.remove();
-      }
-    }
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/background/common/log/writers/AndroidLevelCachingLogWriter.java b/mobile/android/services/src/main/java/org/mozilla/gecko/background/common/log/writers/AndroidLevelCachingLogWriter.java
deleted file mode 100644
index ac4250a..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/background/common/log/writers/AndroidLevelCachingLogWriter.java
+++ /dev/null
@@ -1,132 +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.background.common.log.writers;
-
-import java.util.IdentityHashMap;
-import java.util.Map;
-
-import android.util.Log;
-
-/**
- * Make a <code>LogWriter</code> only log when the Android log system says to.
- */
-public class AndroidLevelCachingLogWriter extends LogWriter {
-  protected final LogWriter inner;
-
-  public AndroidLevelCachingLogWriter(LogWriter inner) {
-    this.inner = inner;
-  }
-
-  // I can't believe we have to implement this ourselves.
-  // These aren't synchronized (and neither are the setters) because
-  // the logging calls themselves are synchronized.
-  private Map<String, Boolean> isErrorLoggable   = new IdentityHashMap<String, Boolean>();
-  private Map<String, Boolean> isWarnLoggable    = new IdentityHashMap<String, Boolean>();
-  private Map<String, Boolean> isInfoLoggable    = new IdentityHashMap<String, Boolean>();
-  private Map<String, Boolean> isDebugLoggable   = new IdentityHashMap<String, Boolean>();
-  private Map<String, Boolean> isVerboseLoggable = new IdentityHashMap<String, Boolean>();
-
-  /**
-   * Empty the caches of log levels.
-   */
-  public void refreshLogLevels() {
-    isErrorLoggable   = new IdentityHashMap<String, Boolean>();
-    isWarnLoggable    = new IdentityHashMap<String, Boolean>();
-    isInfoLoggable    = new IdentityHashMap<String, Boolean>();
-    isDebugLoggable   = new IdentityHashMap<String, Boolean>();
-    isVerboseLoggable = new IdentityHashMap<String, Boolean>();
-  }
-
-  private boolean shouldLogError(String logTag) {
-    Boolean out = isErrorLoggable.get(logTag);
-    if (out != null) {
-      return out;
-    }
-    out = Log.isLoggable(logTag, Log.ERROR);
-    isErrorLoggable.put(logTag, out);
-    return out;
-  }
-
-  private boolean shouldLogWarn(String logTag) {
-    Boolean out = isWarnLoggable.get(logTag);
-    if (out != null) {
-      return out;
-    }
-    out = Log.isLoggable(logTag, Log.WARN);
-    isWarnLoggable.put(logTag, out);
-    return out;
-  }
-
-  private boolean shouldLogInfo(String logTag) {
-    Boolean out = isInfoLoggable.get(logTag);
-    if (out != null) {
-      return out;
-    }
-    out = Log.isLoggable(logTag, Log.INFO);
-    isInfoLoggable.put(logTag, out);
-    return out;
-  }
-
-  private boolean shouldLogDebug(String logTag) {
-    Boolean out = isDebugLoggable.get(logTag);
-    if (out != null) {
-      return out;
-    }
-    out = Log.isLoggable(logTag, Log.DEBUG);
-    isDebugLoggable.put(logTag, out);
-    return out;
-  }
-
-  @Override
-  public boolean shouldLogVerbose(String logTag) {
-    Boolean out = isVerboseLoggable.get(logTag);
-    if (out != null) {
-      return out;
-    }
-    out = Log.isLoggable(logTag, Log.VERBOSE);
-    isVerboseLoggable.put(logTag, out);
-    return out;
-  }
-
-  @Override
-  public void error(String tag, String message, Throwable error) {
-    if (shouldLogError(tag)) {
-      inner.error(tag, message, error);
-    }
-  }
-
-  @Override
-  public void warn(String tag, String message, Throwable error) {
-    if (shouldLogWarn(tag)) {
-      inner.warn(tag, message, error);
-    }
-  }
-
-  @Override
-  public void info(String tag, String message, Throwable error) {
-    if (shouldLogInfo(tag)) {
-      inner.info(tag, message, error);
-    }
-  }
-
-  @Override
-  public void debug(String tag, String message, Throwable error) {
-    if (shouldLogDebug(tag)) {
-      inner.debug(tag, message, error);
-    }
-  }
-
-  @Override
-  public void trace(String tag, String message, Throwable error) {
-    if (shouldLogVerbose(tag)) {
-      inner.trace(tag, message, error);
-    }
-  }
-
-  @Override
-  public void close() {
-    inner.close();
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/background/common/log/writers/AndroidLogWriter.java b/mobile/android/services/src/main/java/org/mozilla/gecko/background/common/log/writers/AndroidLogWriter.java
deleted file mode 100644
index 9d30984..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/background/common/log/writers/AndroidLogWriter.java
+++ /dev/null
@@ -1,46 +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.background.common.log.writers;
-
-import android.util.Log;
-
-/**
- * Log to the Android log.
- */
-public class AndroidLogWriter extends LogWriter {
-  @Override
-  public boolean shouldLogVerbose(String logTag) {
-    return true;
-  }
-
-  @Override
-  public void error(String tag, String message, Throwable error) {
-    Log.e(tag, message, error);
-  }
-
-  @Override
-  public void warn(String tag, String message, Throwable error) {
-    Log.w(tag, message, error);
-  }
-
-  @Override
-  public void info(String tag, String message, Throwable error) {
-    Log.i(tag, message, error);
-  }
-
-  @Override
-  public void debug(String tag, String message, Throwable error) {
-    Log.d(tag, message, error);
-  }
-
-  @Override
-  public void trace(String tag, String message, Throwable error) {
-    Log.v(tag, message, error);
-  }
-
-  @Override
-  public void close() {
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/background/common/log/writers/LevelFilteringLogWriter.java b/mobile/android/services/src/main/java/org/mozilla/gecko/background/common/log/writers/LevelFilteringLogWriter.java
deleted file mode 100644
index 74c3608..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/background/common/log/writers/LevelFilteringLogWriter.java
+++ /dev/null
@@ -1,67 +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.background.common.log.writers;
-
-import android.util.Log;
-
-/**
- * A LogWriter that logs only if the message is as important as the specified
- * level. For example, if the specified level is <code>Log.WARN</code>, only
- * <code>warn</code> and <code>error</code> will log.
- */
-public class LevelFilteringLogWriter extends LogWriter {
-  protected final LogWriter inner;
-  protected final int logLevel;
-
-  public LevelFilteringLogWriter(int logLevel, LogWriter inner) {
-    this.inner = inner;
-    this.logLevel = logLevel;
-  }
-
-  @Override
-  public void close() {
-    inner.close();
-  }
-
-  @Override
-  public void error(String tag, String message, Throwable error) {
-    if (logLevel <= Log.ERROR) {
-      inner.error(tag, message, error);
-    }
-  }
-
-  @Override
-  public void warn(String tag, String message, Throwable error) {
-    if (logLevel <= Log.WARN) {
-      inner.warn(tag, message, error);
-    }
-  }
-
-  @Override
-  public void info(String tag, String message, Throwable error) {
-    if (logLevel <= Log.INFO) {
-      inner.info(tag, message, error);
-    }
-  }
-
-  @Override
-  public void debug(String tag, String message, Throwable error) {
-    if (logLevel <= Log.DEBUG) {
-      inner.debug(tag, message, error);
-    }
-  }
-
-  @Override
-  public void trace(String tag, String message, Throwable error) {
-    if (logLevel <= Log.VERBOSE) {
-      inner.trace(tag, message, error);
-    }
-  }
-
-  @Override
-  public boolean shouldLogVerbose(String tag) {
-    return logLevel <= Log.VERBOSE;
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/background/common/log/writers/LogWriter.java b/mobile/android/services/src/main/java/org/mozilla/gecko/background/common/log/writers/LogWriter.java
deleted file mode 100644
index acfb099..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/background/common/log/writers/LogWriter.java
+++ /dev/null
@@ -1,29 +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.background.common.log.writers;
-
-/**
- * An abstract object that logs information in some way.
- * <p>
- * Intended to be composed with other log writers, for example a log
- * writer could make all log entries have the same single log tag, or
- * could ignore certain log levels, before delegating to an inner log
- * writer.
- */
-public abstract class LogWriter {
-  public abstract void error(String tag, String message, Throwable error);
-  public abstract void warn(String tag, String message, Throwable error);
-  public abstract void info(String tag, String message, Throwable error);
-  public abstract void debug(String tag, String message, Throwable error);
-  public abstract void trace(String tag, String message, Throwable error);
-
-  /**
-   * We expect <code>close</code> to be called only by static
-   * synchronized methods in class <code>Logger</code>.
-   */
-  public abstract void close();
-
-  public abstract boolean shouldLogVerbose(String tag);
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/background/common/log/writers/PrintLogWriter.java b/mobile/android/services/src/main/java/org/mozilla/gecko/background/common/log/writers/PrintLogWriter.java
deleted file mode 100644
index 6e1f63d..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/background/common/log/writers/PrintLogWriter.java
+++ /dev/null
@@ -1,77 +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.background.common.log.writers;
-
-import java.io.PrintWriter;
-
-/**
- * Log to a <code>PrintWriter</code>.
- */
-public class PrintLogWriter extends LogWriter {
-  protected final PrintWriter pw;
-  protected boolean closed = false;
-
-  public static final String ERROR   = " :: E :: ";
-  public static final String WARN    = " :: W :: ";
-  public static final String INFO    = " :: I :: ";
-  public static final String DEBUG   = " :: D :: ";
-  public static final String VERBOSE = " :: V :: ";
-
-  public PrintLogWriter(PrintWriter pw) {
-    this.pw = pw;
-  }
-
-  protected void log(String tag, String message, Throwable error) {
-    if (closed) {
-      return;
-    }
-
-    pw.println(tag + message);
-    if (error != null) {
-      error.printStackTrace(pw);
-    }
-  }
-
-  @Override
-  public void error(String tag, String message, Throwable error) {
-    log(tag, ERROR + message, error);
-  }
-
-  @Override
-  public void warn(String tag, String message, Throwable error) {
-    log(tag, WARN + message, error);
-  }
-
-  @Override
-  public void info(String tag, String message, Throwable error) {
-    log(tag, INFO + message, error);
-  }
-
-  @Override
-  public void debug(String tag, String message, Throwable error) {
-    log(tag, DEBUG + message, error);
-  }
-
-  @Override
-  public void trace(String tag, String message, Throwable error) {
-    log(tag, VERBOSE + message, error);
-  }
-
-  @Override
-  public boolean shouldLogVerbose(String tag) {
-    return true;
-  }
-
-  @Override
-  public void close() {
-    if (closed) {
-      return;
-    }
-    if (pw != null) {
-      pw.close();
-    }
-    closed = true;
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/background/common/log/writers/SimpleTagLogWriter.java b/mobile/android/services/src/main/java/org/mozilla/gecko/background/common/log/writers/SimpleTagLogWriter.java
deleted file mode 100644
index a176543..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/background/common/log/writers/SimpleTagLogWriter.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.background.common.log.writers;
-
-/**
- * Make a <code>LogWriter</code> only log with a single string tag.
- */
-public class SimpleTagLogWriter extends TagLogWriter {
-  final String tag;
-  public SimpleTagLogWriter(String tag, LogWriter inner) {
-    super(inner);
-    this.tag = tag;
-  }
-
-  @Override
-  protected String getMainTag() {
-    return tag;
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/background/common/log/writers/StringLogWriter.java b/mobile/android/services/src/main/java/org/mozilla/gecko/background/common/log/writers/StringLogWriter.java
deleted file mode 100644
index d6a9f5e..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/background/common/log/writers/StringLogWriter.java
+++ /dev/null
@@ -1,57 +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.background.common.log.writers;
-
-import java.io.PrintWriter;
-import java.io.StringWriter;
-
-public class StringLogWriter extends LogWriter {
-  protected final StringWriter sw;
-  protected final PrintLogWriter inner;
-
-  public StringLogWriter() {
-    sw = new StringWriter();
-    inner = new PrintLogWriter(new PrintWriter(sw));
-  }
-
-  public String toString() {
-    return sw.toString();
-  }
-
-  @Override
-  public boolean shouldLogVerbose(String tag) {
-    return true;
-  }
-
-  @Override
-  public void error(String tag, String message, Throwable error) {
-    inner.error(tag, message, error);
-  }
-
-  @Override
-  public void warn(String tag, String message, Throwable error) {
-    inner.warn(tag, message, error);
-  }
-
-  @Override
-  public void info(String tag, String message, Throwable error) {
-    inner.info(tag, message, error);
-  }
-
-  @Override
-  public void debug(String tag, String message, Throwable error) {
-    inner.debug(tag, message, error);
-  }
-
-  @Override
-  public void trace(String tag, String message, Throwable error) {
-    inner.trace(tag, message, error);
-  }
-
-  @Override
-  public void close() {
-    inner.close();
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/background/common/log/writers/TagLogWriter.java b/mobile/android/services/src/main/java/org/mozilla/gecko/background/common/log/writers/TagLogWriter.java
deleted file mode 100644
index fbcd94a..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/background/common/log/writers/TagLogWriter.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.background.common.log.writers;
-
-/**
- * A @link{LogWriter} that logs each message under a parent tag.
- */
-public abstract class TagLogWriter extends LogWriter {
-
-  protected final LogWriter inner;
-
-  public TagLogWriter(final LogWriter inner) {
-    super();
-    this.inner = inner;
-  }
-
-  protected abstract String getMainTag();
-
-  @Override
-  public void error(String tag, String message, Throwable error) {
-    inner.error(this.getMainTag(), tag + " :: " + message, error);
-  }
-
-  @Override
-  public void warn(String tag, String message, Throwable error) {
-    inner.warn(this.getMainTag(), tag + " :: " + message, error);
-  }
-
-  @Override
-  public void info(String tag, String message, Throwable error) {
-    inner.info(this.getMainTag(), tag + " :: " + message, error);
-  }
-
-  @Override
-  public void debug(String tag, String message, Throwable error) {
-    inner.debug(this.getMainTag(), tag + " :: " + message, error);
-  }
-
-  @Override
-  public void trace(String tag, String message, Throwable error) {
-    inner.trace(this.getMainTag(), tag + " :: " + message, error);
-  }
-
-  @Override
-  public boolean shouldLogVerbose(String tag) {
-    return inner.shouldLogVerbose(this.getMainTag());
-  }
-
-  @Override
-  public void close() {
-    inner.close();
-  }
-}
\ No newline at end of file
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/background/common/log/writers/ThreadLocalTagLogWriter.java b/mobile/android/services/src/main/java/org/mozilla/gecko/background/common/log/writers/ThreadLocalTagLogWriter.java
deleted file mode 100644
index 0c83504..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/background/common/log/writers/ThreadLocalTagLogWriter.java
+++ /dev/null
@@ -1,25 +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.background.common.log.writers;
-
-/**
- * Log with a single global tag… but that tag can be different for each thread.
- *
- * Takes a @link{ThreadLocal} as a constructor parameter.
- */
-public class ThreadLocalTagLogWriter extends TagLogWriter {
-
-  private final ThreadLocal<String> tag;
-
-  public ThreadLocalTagLogWriter(ThreadLocal<String> tag, LogWriter inner) {
-    super(inner);
-    this.tag = tag;
-  }
-
-  @Override
-  protected String getMainTag() {
-    return this.tag.get();
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/background/common/telemetry/TelemetryWrapper.java b/mobile/android/services/src/main/java/org/mozilla/gecko/background/common/telemetry/TelemetryWrapper.java
deleted file mode 100644
index 6639b81..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/background/common/telemetry/TelemetryWrapper.java
+++ /dev/null
@@ -1,56 +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.background.common.telemetry;
-
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-
-import org.mozilla.gecko.background.common.log.Logger;
-
-/**
- * Android Background Services are normally built into Fennec, but can also be
- * built as a stand-alone APK for rapid local development. The current Telemetry
- * implementation is coupled to Gecko, and Background Services should not
- * interact with Gecko directly. To maintain this independence, Background
- * Services lazily introspects the relevant Telemetry class from the enclosing
- * package, warning but otherwise ignoring failures during introspection or
- * invocation.
- * <p>
- * It is possible that Background Services will introspect and invoke the
- * Telemetry implementation while Gecko is not running. In this case, the Fennec
- * process itself buffers Telemetry events until such time as they can be
- * flushed to disk and uploaded. <b>There is no guarantee that all Telemetry
- * events will be uploaded!</b> Depending on the volume of data and the
- * application lifecycle, Telemetry events may be dropped.
- */
-public class TelemetryWrapper {
-  private static final String LOG_TAG = TelemetryWrapper.class.getSimpleName();
-
-  // Marking this volatile maintains thread safety cheaply.
-  private static volatile Method mAddToHistogram;
-
-  public static void addToHistogram(String key, int value) {
-    if (mAddToHistogram == null) {
-      try {
-        final Class<?> telemetry = Class.forName("org.mozilla.gecko.Telemetry");
-        mAddToHistogram = telemetry.getMethod("addToHistogram", String.class, int.class);
-      } catch (ClassNotFoundException e) {
-        Logger.warn(LOG_TAG, "org.mozilla.gecko.Telemetry class found!");
-        return;
-      } catch (NoSuchMethodException e) {
-        Logger.warn(LOG_TAG, "org.mozilla.gecko.Telemetry.addToHistogram(String, int) method not found!");
-        return;
-      }
-    }
-
-    if (mAddToHistogram != null) {
-      try {
-        mAddToHistogram.invoke(null, key, value);
-      } catch (IllegalArgumentException | InvocationTargetException | IllegalAccessException e) {
-        Logger.warn(LOG_TAG, "Got exception invoking telemetry!");
-      }
-    }
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/background/db/CursorDumper.java b/mobile/android/services/src/main/java/org/mozilla/gecko/background/db/CursorDumper.java
deleted file mode 100644
index bce968b..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/background/db/CursorDumper.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.background.db;
-
-import android.database.Cursor;
-
-/**
- * A utility for dumping a cursor the debug log.
- * <p>
- * <b>For debugging only!</p>
- */
-public class CursorDumper {
-  protected static String fixedWidth(int width, String s) {
-    if (s == null) {
-      return spaces(width);
-    }
-    int length = s.length();
-    if (width == length) {
-      return s;
-    }
-    if (width > length) {
-      return s + spaces(width - length);
-    }
-    return s.substring(0, width);
-  }
-
-  protected static String spaces(int i) {
-    return "                                     ".substring(0, i);
-  }
-
-  protected static String dashes(int i) {
-    return "-------------------------------------".substring(0, i);
-  }
-
-  /**
-   * Dump a cursor to the debug log, ignoring any log level settings.
-   * <p>
-   * The position in the cursor is maintained. Caller is responsible for opening
-   * and closing cursor.
-   *
-   * @param cursor
-   *          to dump.
-   */
-  public static void dumpCursor(Cursor cursor) {
-    dumpCursor(cursor, 18, "records");
-  }
-
-  /**
-   * Dump a cursor to the debug log, ignoring any log level settings.
-   * <p>
-   * The position in the cursor is maintained. Caller is responsible for opening
-   * and closing cursor.
-   *
-   * @param cursor
-   *          to dump.
-   * @param columnWidth
-   *          how many characters per cursor column.
-   * @param tags
-   *          a descriptor, printed like "(10 tags)", in the header row.
-   */
-  protected static void dumpCursor(Cursor cursor, int columnWidth, String tags) {
-    int originalPosition = cursor.getPosition();
-    try {
-      String[] columnNames = cursor.getColumnNames();
-      int columnCount      = cursor.getColumnCount();
-
-      for (int i = 0; i < columnCount; ++i) {
-        System.out.print(fixedWidth(columnWidth, columnNames[i]) + " | ");
-      }
-      System.out.println("(" + cursor.getCount() + " " + tags + ")");
-      for (int i = 0; i < columnCount; ++i) {
-        System.out.print(dashes(columnWidth) + " | ");
-      }
-      System.out.println("");
-      if (!cursor.moveToFirst()) {
-        System.out.println("EMPTY");
-        return;
-      }
-
-      cursor.moveToFirst();
-      while (!cursor.isAfterLast()) {
-        for (int i = 0; i < columnCount; ++i) {
-          System.out.print(fixedWidth(columnWidth, cursor.getString(i)) + " | ");
-        }
-        System.out.println("");
-        cursor.moveToNext();
-      }
-      for (int i = 0; i < columnCount-1; ++i) {
-        System.out.print(dashes(columnWidth + 3));
-      }
-      System.out.print(dashes(columnWidth + 3 - 1));
-      System.out.println("");
-    } finally {
-      cursor.moveToPosition(originalPosition);
-    }
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/background/db/Tab.java b/mobile/android/services/src/main/java/org/mozilla/gecko/background/db/Tab.java
deleted file mode 100644
index f38cfdf..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/background/db/Tab.java
+++ /dev/null
@@ -1,86 +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.background.db;
-
-import org.json.simple.JSONArray;
-import org.mozilla.gecko.db.BrowserContract;
-import org.mozilla.gecko.db.BrowserContract.Tabs;
-import org.mozilla.gecko.sync.Utils;
-import org.mozilla.gecko.sync.repositories.android.RepoUtils;
-
-import android.content.ContentValues;
-import android.database.Cursor;
-
-// Immutable.
-public class Tab {
-  public final String    title;
-  public final String    icon;
-  public final JSONArray history;
-  public final long      lastUsed;
-
-  public Tab(String title, String icon, JSONArray history, long lastUsed) {
-    this.title    = title;
-    this.icon     = icon;
-    this.history  = history;
-    this.lastUsed = lastUsed;
-  }
-
-  public ContentValues toContentValues(String clientGUID, int position) {
-    ContentValues out = new ContentValues();
-    out.put(BrowserContract.Tabs.POSITION,    position);
-    out.put(BrowserContract.Tabs.CLIENT_GUID, clientGUID);
-
-    out.put(BrowserContract.Tabs.FAVICON,   this.icon);
-    out.put(BrowserContract.Tabs.LAST_USED, this.lastUsed);
-    out.put(BrowserContract.Tabs.TITLE,     this.title);
-    out.put(BrowserContract.Tabs.URL,       (String) this.history.get(0));
-    out.put(BrowserContract.Tabs.HISTORY,   this.history.toJSONString());
-    return out;
-  }
-
-  @Override
-  public boolean equals(Object o) {
-    if (!(o instanceof Tab)) {
-      return false;
-    }
-    final Tab other = (Tab) o;
-
-    if (!RepoUtils.stringsEqual(this.title, other.title)) {
-      return false;
-    }
-    if (!RepoUtils.stringsEqual(this.icon, other.icon)) {
-      return false;
-    }
-
-    if (!(this.lastUsed == other.lastUsed)) {
-      return false;
-    }
-
-    return Utils.sameArrays(this.history, other.history);
-  }
-
-  @Override
-  public int hashCode() {
-    return super.hashCode();
-  }
-
-  /**
-   * Extract a <code>Tab</code> from a cursor row.
-   * <p>
-   * Caller is responsible for creating, positioning, and closing the cursor.
-   *
-   * @param cursor
-   *          to inspect.
-   * @return <code>Tab</code> instance.
-   */
-  public static Tab fromCursor(final Cursor cursor) {
-    final String title = RepoUtils.getStringFromCursor(cursor, Tabs.TITLE);
-    final String icon = RepoUtils.getStringFromCursor(cursor, Tabs.FAVICON);
-    final JSONArray history = RepoUtils.getJSONArrayFromCursor(cursor, Tabs.HISTORY);
-    final long lastUsed = RepoUtils.getLongFromCursor(cursor, Tabs.LAST_USED);
-
-    return new Tab(title, icon, history, lastUsed);
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/background/fxa/FxAccount20CreateDelegate.java b/mobile/android/services/src/main/java/org/mozilla/gecko/background/fxa/FxAccount20CreateDelegate.java
deleted file mode 100644
index 9880913..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/background/fxa/FxAccount20CreateDelegate.java
+++ /dev/null
@@ -1,52 +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.background.fxa;
-
-import org.mozilla.gecko.sync.ExtendedJSONObject;
-import org.mozilla.gecko.sync.Utils;
-
-import java.io.UnsupportedEncodingException;
-import java.security.GeneralSecurityException;
-
-public class FxAccount20CreateDelegate {
-  protected final byte[] emailUTF8;
-  protected final byte[] authPW;
-  protected final boolean preVerified;
-
-  /**
-   * Make a new "create account" delegate.
-   *
-   * @param emailUTF8
-   *          email as UTF-8 bytes.
-   * @param quickStretchedPW
-   *          quick stretched password as bytes.
-   * @param preVerified
-   *          true if account should be marked already verified; only effective
-   *          for non-production auth servers.
-   * @throws UnsupportedEncodingException
-   * @throws GeneralSecurityException
-   */
-  public FxAccount20CreateDelegate(byte[] emailUTF8, byte[] quickStretchedPW, boolean preVerified) throws UnsupportedEncodingException, GeneralSecurityException {
-    this.emailUTF8 = emailUTF8;
-    this.authPW = FxAccountUtils.generateAuthPW(quickStretchedPW);
-    this.preVerified = preVerified;
-  }
-
-  public ExtendedJSONObject getCreateBody() throws FxAccountClientException {
-    final ExtendedJSONObject body = new ExtendedJSONObject();
-    try {
-      body.put("email", new String(emailUTF8, "UTF-8"));
-      body.put("authPW", Utils.byte2Hex(authPW));
-      if (preVerified) {
-        // Production endpoints do not allow preVerified; this assumes we only
-        // set it when it's okay to send it.
-        body.put("preVerified", preVerified);
-      }
-      return body;
-    } catch (UnsupportedEncodingException e) {
-      throw new FxAccountClientException(e);
-    }
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/background/fxa/FxAccount20LoginDelegate.java b/mobile/android/services/src/main/java/org/mozilla/gecko/background/fxa/FxAccount20LoginDelegate.java
deleted file mode 100644
index 0266a6e..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/background/fxa/FxAccount20LoginDelegate.java
+++ /dev/null
@@ -1,36 +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.background.fxa;
-
-import org.mozilla.gecko.sync.ExtendedJSONObject;
-import org.mozilla.gecko.sync.Utils;
-
-import java.io.UnsupportedEncodingException;
-import java.security.GeneralSecurityException;
-
-/**
- * An abstraction around providing an email and authorization token to the auth
- * server.
- */
-public class FxAccount20LoginDelegate {
-  protected final byte[] emailUTF8;
-  protected final byte[] authPW;
-
-  public FxAccount20LoginDelegate(byte[] emailUTF8, byte[] quickStretchedPW) throws UnsupportedEncodingException, GeneralSecurityException {
-    this.emailUTF8 = emailUTF8;
-    this.authPW = FxAccountUtils.generateAuthPW(quickStretchedPW);
-  }
-
-  public ExtendedJSONObject getCreateBody() throws FxAccountClientException {
-    final ExtendedJSONObject body = new ExtendedJSONObject();
-    try {
-      body.put("email", new String(emailUTF8, "UTF-8"));
-      body.put("authPW", Utils.byte2Hex(authPW));
-      return body;
-    } catch (UnsupportedEncodingException e) {
-      throw new FxAccountClientException(e);
-    }
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/background/fxa/FxAccountClient.java b/mobile/android/services/src/main/java/org/mozilla/gecko/background/fxa/FxAccountClient.java
deleted file mode 100644
index ed959ff..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/background/fxa/FxAccountClient.java
+++ /dev/null
@@ -1,24 +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.background.fxa;
-
-import org.mozilla.gecko.background.fxa.FxAccountClient20.AccountStatusResponse;
-import org.mozilla.gecko.background.fxa.FxAccountClient20.RecoveryEmailStatusResponse;
-import org.mozilla.gecko.background.fxa.FxAccountClient20.RequestDelegate;
-import org.mozilla.gecko.background.fxa.FxAccountClient20.TwoKeys;
-import org.mozilla.gecko.fxa.FxAccountDevice;
-import org.mozilla.gecko.sync.ExtendedJSONObject;
-
-import java.util.List;
-
-public interface FxAccountClient {
-  public void accountStatus(String uid, RequestDelegate<AccountStatusResponse> requestDelegate);
-  public void recoveryEmailStatus(byte[] sessionToken, RequestDelegate<RecoveryEmailStatusResponse> requestDelegate);
-  public void keys(byte[] keyFetchToken, RequestDelegate<TwoKeys> requestDelegate);
-  public void sign(byte[] sessionToken, ExtendedJSONObject publicKey, long certificateDurationInMilliseconds, RequestDelegate<String> requestDelegate);
-  public void registerOrUpdateDevice(byte[] sessionToken, FxAccountDevice device, RequestDelegate<FxAccountDevice> requestDelegate);
-  public void deviceList(byte[] sessionToken, RequestDelegate<FxAccountDevice[]> requestDelegate);
-  public void notifyDevices(byte[] sessionToken, List<String> deviceIds, ExtendedJSONObject payload, Long TTL, RequestDelegate<ExtendedJSONObject> requestDelegate);
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/background/fxa/FxAccountClient20.java b/mobile/android/services/src/main/java/org/mozilla/gecko/background/fxa/FxAccountClient20.java
deleted file mode 100644
index 596f452..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/background/fxa/FxAccountClient20.java
+++ /dev/null
@@ -1,914 +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.background.fxa;
-
-import android.support.annotation.NonNull;
-
-import org.json.simple.JSONArray;
-import org.json.simple.JSONObject;
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.background.fxa.FxAccountClientException.FxAccountClientMalformedResponseException;
-import org.mozilla.gecko.background.fxa.FxAccountClientException.FxAccountClientRemoteException;
-import org.mozilla.gecko.fxa.FxAccountConstants;
-import org.mozilla.gecko.Locales;
-import org.mozilla.gecko.fxa.FxAccountDevice;
-import org.mozilla.gecko.sync.ExtendedJSONObject;
-import org.mozilla.gecko.sync.Utils;
-import org.mozilla.gecko.sync.crypto.HKDF;
-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.HawkAuthHeaderProvider;
-import org.mozilla.gecko.sync.net.Resource;
-import org.mozilla.gecko.sync.net.SyncResponse;
-import org.mozilla.gecko.sync.net.SyncStorageResponse;
-
-import java.io.IOException;
-import java.io.UnsupportedEncodingException;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.net.URLEncoder;
-import java.security.GeneralSecurityException;
-import java.security.InvalidKeyException;
-import java.security.NoSuchAlgorithmException;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.concurrent.Executor;
-
-import javax.crypto.Mac;
-
-import ch.boye.httpclientandroidlib.HttpEntity;
-import ch.boye.httpclientandroidlib.HttpHeaders;
-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;
-
-/**
- * An HTTP client for talking to an FxAccount server.
- * <p>
- * <p>
- * The delegate structure used is a little different from the rest of the code
- * base. We add a <code>RequestDelegate</code> layer that processes a typed
- * value extracted from the body of a successful response.
- */
-public class FxAccountClient20 implements FxAccountClient {
-  protected static final String LOG_TAG = FxAccountClient20.class.getSimpleName();
-
-  protected static final String ACCEPT_HEADER = "application/json;charset=utf-8";
-
-  public static final String JSON_KEY_EMAIL = "email";
-  public static final String JSON_KEY_KEYFETCHTOKEN = "keyFetchToken";
-  public static final String JSON_KEY_SESSIONTOKEN = "sessionToken";
-  public static final String JSON_KEY_UID = "uid";
-  public static final String JSON_KEY_VERIFIED = "verified";
-  public static final String JSON_KEY_ERROR = "error";
-  public static final String JSON_KEY_MESSAGE = "message";
-  public static final String JSON_KEY_INFO = "info";
-  public static final String JSON_KEY_CODE = "code";
-  public static final String JSON_KEY_ERRNO = "errno";
-  public static final String JSON_KEY_EXISTS = "exists";
-
-  protected static final String[] requiredErrorStringFields = { JSON_KEY_ERROR, JSON_KEY_MESSAGE, JSON_KEY_INFO };
-  protected static final String[] requiredErrorLongFields = { JSON_KEY_CODE, JSON_KEY_ERRNO };
-
-  /**
-   * The server's URI.
-   * <p>
-   * We assume throughout that this ends with a trailing slash (and guarantee as
-   * much in the constructor).
-   */
-  protected final String serverURI;
-
-  protected final Executor executor;
-
-  public FxAccountClient20(String serverURI, Executor executor) {
-    if (serverURI == null) {
-      throw new IllegalArgumentException("Must provide a server URI.");
-    }
-    if (executor == null) {
-      throw new IllegalArgumentException("Must provide a non-null executor.");
-    }
-    this.serverURI = serverURI.endsWith("/") ? serverURI : serverURI + "/";
-    if (!this.serverURI.endsWith("/")) {
-      throw new IllegalArgumentException("Constructed serverURI must end with a trailing slash: " + this.serverURI);
-    }
-    this.executor = executor;
-  }
-
-  protected BaseResource getBaseResource(String path, Map<String, String> queryParameters) throws UnsupportedEncodingException, URISyntaxException {
-    if (queryParameters == null || queryParameters.isEmpty()) {
-      return getBaseResource(path);
-    }
-    final String[] array = new String[2 * queryParameters.size()];
-    int i = 0;
-    for (Entry<String, String> entry : queryParameters.entrySet()) {
-      array[i++] = entry.getKey();
-      array[i++] = entry.getValue();
-    }
-    return getBaseResource(path, array);
-  }
-
-  /**
-   * Create <code>BaseResource</code>, encoding query parameters carefully.
-   * <p>
-   * This is equivalent to <code>android.net.Uri.Builder</code>, which is not
-   * present in our JUnit 4 tests.
-   *
-   * @param path fragment.
-   * @param queryParameters list of key/value query parameter pairs.  Must be even length!
-   * @return <code>BaseResource<instance>
-   * @throws URISyntaxException
-   * @throws UnsupportedEncodingException
-   */
-  protected BaseResource getBaseResource(String path, String... queryParameters) throws URISyntaxException, UnsupportedEncodingException {
-    final StringBuilder sb = new StringBuilder(serverURI);
-    sb.append(path);
-    if (queryParameters != null) {
-      int i = 0;
-      while (i < queryParameters.length) {
-        sb.append(i > 0 ? "&" : "?");
-        final String key = queryParameters[i++];
-        final String val = queryParameters[i++];
-        sb.append(URLEncoder.encode(key, "UTF-8"));
-        sb.append("=");
-        sb.append(URLEncoder.encode(val, "UTF-8"));
-      }
-    }
-    return new BaseResource(new URI(sb.toString()));
-  }
-
-  /**
-   * Process a typed value extracted from a successful response (in an
-   * endpoint-dependent way).
-   */
-  public interface RequestDelegate<T> {
-    public void handleError(Exception e);
-    public void handleFailure(FxAccountClientRemoteException e);
-    public void handleSuccess(T result);
-  }
-
-  /**
-   * Thin container for two cryptographic keys.
-   */
-  public static class TwoKeys {
-    public final byte[] kA;
-    public final byte[] wrapkB;
-    public TwoKeys(byte[] kA, byte[] wrapkB) {
-      this.kA = kA;
-      this.wrapkB = wrapkB;
-    }
-  }
-
-  protected <T> void invokeHandleError(final RequestDelegate<T> delegate, final Exception e) {
-    executor.execute(new Runnable() {
-      @Override
-      public void run() {
-        delegate.handleError(e);
-      }
-    });
-  }
-
-  enum ResponseType {
-    JSON_ARRAY,
-    JSON_OBJECT
-  }
-
-  /**
-   * Translate resource callbacks into request callbacks invoked on the provided
-   * executor.
-   * <p>
-   * Override <code>handleSuccess</code> to parse the body of the resource
-   * request and call the request callback. <code>handleSuccess</code> is
-   * invoked via the executor, so you don't need to delegate further.
-   */
-  protected abstract class ResourceDelegate<T> extends BaseResourceDelegate {
-
-    protected void handleSuccess(final int status, HttpResponse response, final ExtendedJSONObject body) throws Exception {
-      throw new UnsupportedOperationException();
-    }
-
-    protected void handleSuccess(final int status, HttpResponse response, final JSONArray body) throws Exception {
-      throw new UnsupportedOperationException();
-    }
-
-    protected final RequestDelegate<T> delegate;
-
-    protected final byte[] tokenId;
-    protected final byte[] reqHMACKey;
-    protected final SkewHandler skewHandler;
-    protected final ResponseType responseType;
-
-    /**
-     * Create a delegate for an un-authenticated resource.
-     */
-    public ResourceDelegate(final Resource resource, final RequestDelegate<T> delegate, ResponseType responseType) {
-      this(resource, delegate, responseType, null, null);
-    }
-
-    /**
-     * Create a delegate for a Hawk-authenticated resource.
-     * <p>
-     * Every Hawk request that encloses an entity (PATCH, POST, and PUT) will
-     * include the payload verification hash.
-     */
-    public ResourceDelegate(final Resource resource, final RequestDelegate<T> delegate, ResponseType responseType, final byte[] tokenId, final byte[] reqHMACKey) {
-      super(resource);
-      this.delegate = delegate;
-      this.reqHMACKey = reqHMACKey;
-      this.tokenId = tokenId;
-      this.skewHandler = SkewHandler.getSkewHandlerForResource(resource);
-      this.responseType = responseType;
-    }
-
-    @Override
-    public AuthHeaderProvider getAuthHeaderProvider() {
-      if (tokenId != null && reqHMACKey != null) {
-        // We always include the payload verification hash for FxA Hawk-authenticated requests.
-        final boolean includePayloadVerificationHash = true;
-        return new HawkAuthHeaderProvider(Utils.byte2Hex(tokenId), reqHMACKey, includePayloadVerificationHash, skewHandler.getSkewInSeconds());
-      }
-      return super.getAuthHeaderProvider();
-    }
-
-    @Override
-    public String getUserAgent() {
-      return FxAccountConstants.USER_AGENT;
-    }
-
-    @Override
-    public void handleHttpResponse(HttpResponse response) {
-      try {
-        final int status = validateResponse(response);
-        skewHandler.updateSkew(response, now());
-        invokeHandleSuccess(status, response);
-      } catch (FxAccountClientRemoteException e) {
-        if (!skewHandler.updateSkew(response, now())) {
-          // If we couldn't update skew, but we got a failure, let's try clearing the skew.
-          skewHandler.resetSkew();
-        }
-        invokeHandleFailure(e);
-      }
-    }
-
-    protected void invokeHandleFailure(final FxAccountClientRemoteException e) {
-      executor.execute(new Runnable() {
-        @Override
-        public void run() {
-          delegate.handleFailure(e);
-        }
-      });
-    }
-
-    protected void invokeHandleSuccess(final int status, final HttpResponse response) {
-      executor.execute(new Runnable() {
-        @Override
-        public void run() {
-          try {
-            SyncResponse syncResponse = new SyncResponse(response);
-            if (responseType == ResponseType.JSON_ARRAY) {
-              JSONArray body = syncResponse.jsonArrayBody();
-              ResourceDelegate.this.handleSuccess(status, response, body);
-            } else {
-              ExtendedJSONObject body = syncResponse.jsonObjectBody();
-              ResourceDelegate.this.handleSuccess(status, response, body);
-            }
-          } catch (Exception e) {
-            delegate.handleError(e);
-          }
-        }
-      });
-    }
-
-    @Override
-    public void handleHttpProtocolException(final ClientProtocolException e) {
-      invokeHandleError(delegate, e);
-    }
-
-    @Override
-    public void handleHttpIOException(IOException e) {
-      invokeHandleError(delegate, e);
-    }
-
-    @Override
-    public void handleTransportException(GeneralSecurityException e) {
-      invokeHandleError(delegate, e);
-    }
-
-    @Override
-    public void addHeaders(HttpRequestBase request, DefaultHttpClient client) {
-      super.addHeaders(request, client);
-
-      // The basics.
-      final Locale locale = Locale.getDefault();
-      request.addHeader(HttpHeaders.ACCEPT_LANGUAGE, Locales.getLanguageTag(locale));
-      request.addHeader(HttpHeaders.ACCEPT, ACCEPT_HEADER);
-    }
-  }
-
-  protected <T> void post(BaseResource resource, final ExtendedJSONObject requestBody) {
-    if (requestBody == null) {
-      resource.post((HttpEntity) null);
-    } else {
-      resource.post(requestBody);
-    }
-  }
-
-  @SuppressWarnings("static-method")
-  public long now() {
-    return System.currentTimeMillis();
-  }
-
-  /**
-   * Intepret a response from the auth server.
-   * <p>
-   * Throw an appropriate exception on errors; otherwise, return the response's
-   * status code.
-   *
-   * @return response's HTTP status code.
-   * @throws FxAccountClientException
-   */
-  public static int validateResponse(HttpResponse response) throws FxAccountClientRemoteException {
-    final int status = response.getStatusLine().getStatusCode();
-    if (status == 200) {
-      return status;
-    }
-    int code;
-    int errno;
-    String error;
-    String message;
-    String info;
-    ExtendedJSONObject body;
-    try {
-      body = new SyncStorageResponse(response).jsonObjectBody();
-      body.throwIfFieldsMissingOrMisTyped(requiredErrorStringFields, String.class);
-      body.throwIfFieldsMissingOrMisTyped(requiredErrorLongFields, Long.class);
-      code = body.getLong(JSON_KEY_CODE).intValue();
-      errno = body.getLong(JSON_KEY_ERRNO).intValue();
-      error = body.getString(JSON_KEY_ERROR);
-      message = body.getString(JSON_KEY_MESSAGE);
-      info = body.getString(JSON_KEY_INFO);
-    } catch (Exception e) {
-      throw new FxAccountClientMalformedResponseException(response);
-    }
-    throw new FxAccountClientRemoteException(response, code, errno, error, message, info, body);
-  }
-
-  /**
-   * Don't call this directly. Use <code>unbundleBody</code> instead.
-   */
-  protected void unbundleBytes(byte[] bundleBytes, byte[] respHMACKey, byte[] respXORKey, byte[]... rest)
-      throws InvalidKeyException, NoSuchAlgorithmException, FxAccountClientException {
-    if (bundleBytes.length < 32) {
-      throw new IllegalArgumentException("input bundle must include HMAC");
-    }
-    int len = respXORKey.length;
-    if (bundleBytes.length != len + 32) {
-      throw new IllegalArgumentException("input bundle and XOR key with HMAC have different lengths");
-    }
-    int left = len;
-    for (byte[] array : rest) {
-      left -= array.length;
-    }
-    if (left != 0) {
-      throw new IllegalArgumentException("XOR key and total output arrays have different lengths");
-    }
-
-    byte[] ciphertext = new byte[len];
-    byte[] HMAC = new byte[32];
-    System.arraycopy(bundleBytes, 0, ciphertext, 0, len);
-    System.arraycopy(bundleBytes, len, HMAC, 0, 32);
-
-    Mac hmacHasher = HKDF.makeHMACHasher(respHMACKey);
-    byte[] computedHMAC = hmacHasher.doFinal(ciphertext);
-    if (!Arrays.equals(computedHMAC, HMAC)) {
-      throw new FxAccountClientException("Bad message HMAC");
-    }
-
-    int offset = 0;
-    for (byte[] array : rest) {
-      for (int i = 0; i < array.length; i++) {
-        array[i] = (byte) (respXORKey[offset + i] ^ ciphertext[offset + i]);
-      }
-      offset += array.length;
-    }
-  }
-
-  protected void unbundleBody(ExtendedJSONObject body, byte[] requestKey, byte[] ctxInfo, byte[]... rest) throws Exception {
-    int length = 0;
-    for (byte[] array : rest) {
-      length += array.length;
-    }
-
-    if (body == null) {
-      throw new FxAccountClientException("body must be non-null");
-    }
-    String bundle = body.getString("bundle");
-    if (bundle == null) {
-      throw new FxAccountClientException("bundle must be a non-null string");
-    }
-    byte[] bundleBytes = Utils.hex2Byte(bundle);
-
-    final byte[] respHMACKey = new byte[32];
-    final byte[] respXORKey = new byte[length];
-    HKDF.deriveMany(requestKey, new byte[0], ctxInfo, respHMACKey, respXORKey);
-    unbundleBytes(bundleBytes, respHMACKey, respXORKey, rest);
-  }
-
-  public void keys(byte[] keyFetchToken, final RequestDelegate<TwoKeys> delegate) {
-    final byte[] tokenId = new byte[32];
-    final byte[] reqHMACKey = new byte[32];
-    final byte[] requestKey = new byte[32];
-    try {
-      HKDF.deriveMany(keyFetchToken, new byte[0], FxAccountUtils.KW("keyFetchToken"), tokenId, reqHMACKey, requestKey);
-    } catch (Exception e) {
-      invokeHandleError(delegate, e);
-      return;
-    }
-
-    BaseResource resource;
-    try {
-      resource = getBaseResource("account/keys");
-    } catch (URISyntaxException | UnsupportedEncodingException e) {
-      invokeHandleError(delegate, e);
-      return;
-    }
-
-    resource.delegate = new ResourceDelegate<TwoKeys>(resource, delegate, ResponseType.JSON_OBJECT, tokenId, reqHMACKey) {
-      @Override
-      public void handleSuccess(int status, HttpResponse response, ExtendedJSONObject body) throws Exception {
-        byte[] kA = new byte[FxAccountUtils.CRYPTO_KEY_LENGTH_BYTES];
-        byte[] wrapkB = new byte[FxAccountUtils.CRYPTO_KEY_LENGTH_BYTES];
-        unbundleBody(body, requestKey, FxAccountUtils.KW("account/keys"), kA, wrapkB);
-        delegate.handleSuccess(new TwoKeys(kA, wrapkB));
-      }
-    };
-    resource.get();
-  }
-
-  /**
-   * Thin container for account status response.
-   */
-  public static class AccountStatusResponse {
-    public final boolean exists;
-    public AccountStatusResponse(boolean exists) {
-      this.exists = exists;
-    }
-  }
-
-  /**
-   * Query the account status of an account given a uid.
-   *
-   * @param uid to query.
-   * @param delegate to invoke callbacks.
-   */
-  public void accountStatus(String uid, final RequestDelegate<AccountStatusResponse> delegate) {
-    final BaseResource resource;
-    try {
-      final Map<String, String> params = new HashMap<>(1);
-      params.put("uid", uid);
-      resource = getBaseResource("account/status", params);
-    } catch (URISyntaxException | UnsupportedEncodingException e) {
-      invokeHandleError(delegate, e);
-      return;
-    }
-
-    resource.delegate = new ResourceDelegate<AccountStatusResponse>(resource, delegate, ResponseType.JSON_OBJECT) {
-      @Override
-      public void handleSuccess(int status, HttpResponse response, ExtendedJSONObject body) throws Exception {
-        boolean exists = body.getBoolean(JSON_KEY_EXISTS);
-        delegate.handleSuccess(new AccountStatusResponse(exists));
-      }
-    };
-    resource.get();
-  }
-
-  /**
-   * Thin container for recovery email status response.
-   */
-  public static class RecoveryEmailStatusResponse {
-    public final String email;
-    public final boolean verified;
-    public RecoveryEmailStatusResponse(String email, boolean verified) {
-      this.email = email;
-      this.verified = verified;
-    }
-  }
-
-  /**
-   * Query the recovery email status of an account given a valid session token.
-   * <p>
-   * This API is a little odd: the auth server returns the email and
-   * verification state of the account that corresponds to the (opaque) session
-   * token. It might fail if the session token is unknown (or invalid, or
-   * revoked).
-   *
-   * @param sessionToken
-   *          to query.
-   * @param delegate
-   *          to invoke callbacks.
-   */
-  public void recoveryEmailStatus(byte[] sessionToken, final RequestDelegate<RecoveryEmailStatusResponse> delegate) {
-    final byte[] tokenId = new byte[32];
-    final byte[] reqHMACKey = new byte[32];
-    final byte[] requestKey = new byte[32];
-    try {
-      HKDF.deriveMany(sessionToken, new byte[0], FxAccountUtils.KW("sessionToken"), tokenId, reqHMACKey, requestKey);
-    } catch (Exception e) {
-      invokeHandleError(delegate, e);
-      return;
-    }
-
-    BaseResource resource;
-    try {
-      resource = getBaseResource("recovery_email/status");
-    } catch (URISyntaxException | UnsupportedEncodingException e) {
-      invokeHandleError(delegate, e);
-      return;
-    }
-
-    resource.delegate = new ResourceDelegate<RecoveryEmailStatusResponse>(resource, delegate, ResponseType.JSON_OBJECT, tokenId, reqHMACKey) {
-      @Override
-      public void handleSuccess(int status, HttpResponse response, ExtendedJSONObject body) throws Exception {
-        String[] requiredStringFields = new String[] { JSON_KEY_EMAIL };
-        body.throwIfFieldsMissingOrMisTyped(requiredStringFields, String.class);
-        String email = body.getString(JSON_KEY_EMAIL);
-        Boolean verified = body.getBoolean(JSON_KEY_VERIFIED);
-        delegate.handleSuccess(new RecoveryEmailStatusResponse(email, verified));
-      }
-    };
-    resource.get();
-  }
-
-  @SuppressWarnings("unchecked")
-  public void sign(final byte[] sessionToken, final ExtendedJSONObject publicKey, long durationInMilliseconds, final RequestDelegate<String> delegate) {
-    final ExtendedJSONObject body = new ExtendedJSONObject();
-    body.put("publicKey", publicKey);
-    body.put("duration", durationInMilliseconds);
-
-    final byte[] tokenId = new byte[32];
-    final byte[] reqHMACKey = new byte[32];
-    try {
-      HKDF.deriveMany(sessionToken, new byte[0], FxAccountUtils.KW("sessionToken"), tokenId, reqHMACKey);
-    } catch (Exception e) {
-      invokeHandleError(delegate, e);
-      return;
-    }
-
-    BaseResource resource;
-    try {
-      resource = getBaseResource("certificate/sign");
-    } catch (URISyntaxException | UnsupportedEncodingException e) {
-      invokeHandleError(delegate, e);
-      return;
-    }
-
-    resource.delegate = new ResourceDelegate<String>(resource, delegate, ResponseType.JSON_OBJECT, tokenId, reqHMACKey) {
-      @Override
-      public void handleSuccess(int status, HttpResponse response, ExtendedJSONObject body) throws Exception {
-        String cert = body.getString("cert");
-        if (cert == null) {
-          delegate.handleError(new FxAccountClientException("cert must be a non-null string"));
-          return;
-        }
-        delegate.handleSuccess(cert);
-      }
-    };
-    post(resource, body);
-  }
-
-  protected static final String[] LOGIN_RESPONSE_REQUIRED_STRING_FIELDS = new String[] { JSON_KEY_UID, JSON_KEY_SESSIONTOKEN };
-  protected static final String[] LOGIN_RESPONSE_REQUIRED_STRING_FIELDS_KEYS = new String[] { JSON_KEY_UID, JSON_KEY_SESSIONTOKEN, JSON_KEY_KEYFETCHTOKEN, };
-  protected static final String[] LOGIN_RESPONSE_REQUIRED_BOOLEAN_FIELDS = new String[] { JSON_KEY_VERIFIED };
-
-  /**
-   * Thin container for login response.
-   * <p>
-   * The <code>remoteEmail</code> field is the email address as normalized by the
-   * server, and is <b>not necessarily</b> the email address delivered to the
-   * <code>login</code> or <code>create</code> call.
-   */
-  public static class LoginResponse {
-    public final String remoteEmail;
-    public final String uid;
-    public final byte[] sessionToken;
-    public final boolean verified;
-    public final byte[] keyFetchToken;
-
-    public LoginResponse(String remoteEmail, String uid, boolean verified, byte[] sessionToken, byte[] keyFetchToken) {
-      this.remoteEmail = remoteEmail;
-      this.uid = uid;
-      this.verified = verified;
-      this.sessionToken = sessionToken;
-      this.keyFetchToken = keyFetchToken;
-    }
-  }
-
-  // Public for testing only; prefer login and loginAndGetKeys (without boolean parameter).
-  public void login(final byte[] emailUTF8, final byte[] quickStretchedPW, final boolean getKeys,
-                    final Map<String, String> queryParameters,
-                    final RequestDelegate<LoginResponse> delegate) {
-    final BaseResource resource;
-    final ExtendedJSONObject body;
-    try {
-      final String path = "account/login";
-      final Map<String, String> modifiedParameters = new HashMap<>();
-      if (queryParameters != null) {
-        modifiedParameters.putAll(queryParameters);
-      }
-      if (getKeys) {
-        modifiedParameters.put("keys", "true");
-      }
-      resource = getBaseResource(path, modifiedParameters);
-      body = new FxAccount20LoginDelegate(emailUTF8, quickStretchedPW).getCreateBody();
-    } catch (Exception e) {
-      invokeHandleError(delegate, e);
-      return;
-    }
-
-    resource.delegate = new ResourceDelegate<LoginResponse>(resource, delegate, ResponseType.JSON_OBJECT) {
-      @Override
-      public void handleSuccess(int status, HttpResponse response,  ExtendedJSONObject body) throws Exception {
-        final String[] requiredStringFields = getKeys ? LOGIN_RESPONSE_REQUIRED_STRING_FIELDS_KEYS : LOGIN_RESPONSE_REQUIRED_STRING_FIELDS;
-        body.throwIfFieldsMissingOrMisTyped(requiredStringFields, String.class);
-
-        final String[] requiredBooleanFields = LOGIN_RESPONSE_REQUIRED_BOOLEAN_FIELDS;
-        body.throwIfFieldsMissingOrMisTyped(requiredBooleanFields, Boolean.class);
-
-        String uid = body.getString(JSON_KEY_UID);
-        boolean verified = body.getBoolean(JSON_KEY_VERIFIED);
-        byte[] sessionToken = Utils.hex2Byte(body.getString(JSON_KEY_SESSIONTOKEN));
-        byte[] keyFetchToken = null;
-        if (getKeys) {
-          keyFetchToken = Utils.hex2Byte(body.getString(JSON_KEY_KEYFETCHTOKEN));
-        }
-        LoginResponse loginResponse = new LoginResponse(new String(emailUTF8, "UTF-8"), uid, verified, sessionToken, keyFetchToken);
-
-        delegate.handleSuccess(loginResponse);
-      }
-    };
-
-    post(resource, body);
-  }
-
-  public void createAccount(final byte[] emailUTF8, final byte[] quickStretchedPW,
-                            final boolean getKeys,
-                            final boolean preVerified,
-                            final Map<String, String> queryParameters,
-                            final RequestDelegate<LoginResponse> delegate) {
-    final BaseResource resource;
-    final ExtendedJSONObject body;
-    try {
-      final String path = "account/create";
-      final Map<String, String> modifiedParameters = new HashMap<>();
-      if (queryParameters != null) {
-        modifiedParameters.putAll(queryParameters);
-      }
-      if (getKeys) {
-        modifiedParameters.put("keys", "true");
-      }
-      resource = getBaseResource(path, modifiedParameters);
-      body = new FxAccount20CreateDelegate(emailUTF8, quickStretchedPW, preVerified).getCreateBody();
-    } catch (Exception e) {
-      invokeHandleError(delegate, e);
-      return;
-    }
-
-    // This is very similar to login, except verified is not required.
-    resource.delegate = new ResourceDelegate<LoginResponse>(resource, delegate, ResponseType.JSON_OBJECT) {
-      @Override
-      public void handleSuccess(int status, HttpResponse response, ExtendedJSONObject body) throws Exception {
-        final String[] requiredStringFields = getKeys ? LOGIN_RESPONSE_REQUIRED_STRING_FIELDS_KEYS : LOGIN_RESPONSE_REQUIRED_STRING_FIELDS;
-        body.throwIfFieldsMissingOrMisTyped(requiredStringFields, String.class);
-
-        String uid = body.getString(JSON_KEY_UID);
-        boolean verified = false; // In production, we're definitely not verified immediately upon creation.
-        Boolean tempVerified = body.getBoolean(JSON_KEY_VERIFIED);
-        if (tempVerified != null) {
-          verified = tempVerified;
-        }
-        byte[] sessionToken = Utils.hex2Byte(body.getString(JSON_KEY_SESSIONTOKEN));
-        byte[] keyFetchToken = null;
-        if (getKeys) {
-          keyFetchToken = Utils.hex2Byte(body.getString(JSON_KEY_KEYFETCHTOKEN));
-        }
-        LoginResponse loginResponse = new LoginResponse(new String(emailUTF8, "UTF-8"), uid, verified, sessionToken, keyFetchToken);
-
-        delegate.handleSuccess(loginResponse);
-      }
-    };
-
-    post(resource, body);
-  }
-
-  /**
-   * We want users to be able to enter their email address case-insensitively.
-   * We stretch the password locally using the email address as a salt, to make
-   * dictionary attacks more expensive. This means that a client with a
-   * case-differing email address is unable to produce the correct
-   * authorization, even though it knows the password. In this case, the server
-   * returns the email that the account was created with, so that the client can
-   * re-stretch the password locally with the correct email salt. This version
-   * of <code>login</code> retries at most one time with a server provided email
-   * address.
-   * <p>
-   * Be aware that consumers will not see the initial error response from the
-   * server providing an alternate email (if there is one).
-   *
-   * @param emailUTF8
-   *          user entered email address.
-   * @param stretcher
-   *          delegate to stretch and re-stretch password.
-   * @param getKeys
-   *          true if a <code>keyFetchToken</code> should be returned (in
-   *          addition to the standard <code>sessionToken</code>).
-   * @param queryParameters
-   * @param delegate
-   *          to invoke callbacks.
-   */
-  public void login(final byte[] emailUTF8, final PasswordStretcher stretcher, final boolean getKeys,
-                    final Map<String, String> queryParameters,
-                    final RequestDelegate<LoginResponse> delegate) {
-    byte[] quickStretchedPW;
-    try {
-      FxAccountUtils.pii(LOG_TAG, "Trying user provided email: '" + new String(emailUTF8, "UTF-8") + "'" );
-      quickStretchedPW = stretcher.getQuickStretchedPW(emailUTF8);
-    } catch (Exception e) {
-      delegate.handleError(e);
-      return;
-    }
-
-    this.login(emailUTF8, quickStretchedPW, getKeys, queryParameters, new RequestDelegate<LoginResponse>() {
-      @Override
-      public void handleSuccess(LoginResponse result) {
-        delegate.handleSuccess(result);
-      }
-
-      @Override
-      public void handleError(Exception e) {
-        delegate.handleError(e);
-      }
-
-      @Override
-      public void handleFailure(FxAccountClientRemoteException e) {
-        String alternateEmail = e.body.getString(JSON_KEY_EMAIL);
-        if (!e.isBadEmailCase() || alternateEmail == null) {
-          delegate.handleFailure(e);
-          return;
-        };
-
-        Logger.info(LOG_TAG, "Server returned alternate email; retrying login with provided email.");
-        FxAccountUtils.pii(LOG_TAG, "Trying server provided email: '" + alternateEmail + "'" );
-
-        try {
-          // Nota bene: this is not recursive, since we call the fixed password
-          // signature here, which invokes a non-retrying version.
-          byte[] alternateEmailUTF8 = alternateEmail.getBytes("UTF-8");
-          byte[] alternateQuickStretchedPW = stretcher.getQuickStretchedPW(alternateEmailUTF8);
-          login(alternateEmailUTF8, alternateQuickStretchedPW, getKeys, queryParameters, delegate);
-        } catch (Exception innerException) {
-          delegate.handleError(innerException);
-          return;
-        }
-      }
-    });
-  }
-
-  /**
-   * Registers a device given a valid session token.
-   *
-   * @param sessionToken to query.
-   * @param delegate to invoke callbacks.
-   */
-  @Override
-  public void registerOrUpdateDevice(byte[] sessionToken, FxAccountDevice device, RequestDelegate<FxAccountDevice> delegate) {
-    final byte[] tokenId = new byte[32];
-    final byte[] reqHMACKey = new byte[32];
-    final byte[] requestKey = new byte[32];
-    try {
-      HKDF.deriveMany(sessionToken, new byte[0], FxAccountUtils.KW("sessionToken"), tokenId, reqHMACKey, requestKey);
-    } catch (Exception e) {
-      invokeHandleError(delegate, e);
-      return;
-    }
-
-    final BaseResource resource;
-    final ExtendedJSONObject body;
-    try {
-      resource = getBaseResource("account/device");
-      body = device.toJson();
-    } catch (URISyntaxException | UnsupportedEncodingException e) {
-      invokeHandleError(delegate, e);
-      return;
-    }
-
-    resource.delegate = new ResourceDelegate<FxAccountDevice>(resource, delegate, ResponseType.JSON_OBJECT, tokenId, reqHMACKey) {
-      @Override
-      public void handleSuccess(int status, HttpResponse response, ExtendedJSONObject body) {
-        try {
-          delegate.handleSuccess(FxAccountDevice.fromJson(body));
-        } catch (Exception e) {
-          delegate.handleError(e);
-        }
-      }
-    };
-
-    post(resource, body);
-  }
-
-  @Override
-  public void deviceList(byte[] sessionToken, RequestDelegate<FxAccountDevice[]> delegate) {
-    final byte[] tokenId = new byte[32];
-    final byte[] reqHMACKey = new byte[32];
-    final byte[] requestKey = new byte[32];
-    try {
-      HKDF.deriveMany(sessionToken, new byte[0], FxAccountUtils.KW("sessionToken"), tokenId, reqHMACKey, requestKey);
-    } catch (Exception e) {
-      invokeHandleError(delegate, e);
-      return;
-    }
-
-    final BaseResource resource;
-    try {
-      resource = getBaseResource("account/devices");
-    } catch (URISyntaxException | UnsupportedEncodingException e) {
-      invokeHandleError(delegate, e);
-      return;
-    }
-
-    resource.delegate = new ResourceDelegate<FxAccountDevice[]>(resource, delegate, ResponseType.JSON_ARRAY, tokenId, reqHMACKey) {
-      @Override
-      public void handleSuccess(int status, HttpResponse response, JSONArray devicesJson) {
-        try {
-          FxAccountDevice[] devices = new FxAccountDevice[devicesJson.size()];
-          for (int i = 0; i < devices.length; i++) {
-            ExtendedJSONObject deviceJson = new ExtendedJSONObject((JSONObject) devicesJson.get(i));
-            devices[i] = FxAccountDevice.fromJson(deviceJson);
-          }
-          delegate.handleSuccess(devices);
-        } catch (Exception e) {
-          delegate.handleError(e);
-        }
-      }
-    };
-
-    resource.get();
-  }
-
-  @Override
-  public void notifyDevices(@NonNull byte[] sessionToken, @NonNull List<String> deviceIds, ExtendedJSONObject payload, Long TTL, RequestDelegate<ExtendedJSONObject> delegate) {
-    final byte[] tokenId = new byte[32];
-    final byte[] reqHMACKey = new byte[32];
-    final byte[] requestKey = new byte[32];
-    try {
-      HKDF.deriveMany(sessionToken, new byte[0], FxAccountUtils.KW("sessionToken"), tokenId, reqHMACKey, requestKey);
-    } catch (Exception e) {
-      invokeHandleError(delegate, e);
-      return;
-    }
-
-    final BaseResource resource;
-    final ExtendedJSONObject body = createNotifyDevicesBody(deviceIds, payload, TTL);
-    try {
-      resource = getBaseResource("account/devices/notify");
-    } catch (URISyntaxException | UnsupportedEncodingException e) {
-      invokeHandleError(delegate, e);
-      return;
-    }
-
-    resource.delegate = new ResourceDelegate<ExtendedJSONObject>(resource, delegate, ResponseType.JSON_OBJECT, tokenId, reqHMACKey) {
-      @Override
-      public void handleSuccess(int status, HttpResponse response, ExtendedJSONObject body) {
-        try {
-          delegate.handleSuccess(body);
-        } catch (Exception e) {
-          delegate.handleError(e);
-        }
-      }
-    };
-
-    post(resource, body);
-  }
-
-  @NonNull
-  @SuppressWarnings("unchecked")
-  private ExtendedJSONObject createNotifyDevicesBody(@NonNull List<String> deviceIds, ExtendedJSONObject payload, Long TTL) {
-    final ExtendedJSONObject body = new ExtendedJSONObject();
-    final JSONArray to = new JSONArray();
-    to.addAll(deviceIds);
-    body.put("to", to);
-    if (payload != null) {
-      body.put("payload", payload);
-    }
-    if (TTL != null) {
-      body.put("TTL", TTL);
-    }
-    return body;
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/background/fxa/FxAccountClientException.java b/mobile/android/services/src/main/java/org/mozilla/gecko/background/fxa/FxAccountClientException.java
deleted file mode 100644
index 28ee563..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/background/fxa/FxAccountClientException.java
+++ /dev/null
@@ -1,133 +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.background.fxa;
-
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.sync.ExtendedJSONObject;
-import org.mozilla.gecko.sync.HTTPFailureException;
-import org.mozilla.gecko.sync.net.SyncStorageResponse;
-
-import ch.boye.httpclientandroidlib.HttpResponse;
-import ch.boye.httpclientandroidlib.HttpStatus;
-
-/**
- * From <a href="https://github.com/mozilla/fxa-auth-server/blob/master/docs/api.md">https://github.com/mozilla/fxa-auth-server/blob/master/docs/api.md</a>.
- */
-public class FxAccountClientException extends Exception {
-  private static final long serialVersionUID = 7953459541558266597L;
-
-  public FxAccountClientException(String detailMessage) {
-    super(detailMessage);
-  }
-
-  public FxAccountClientException(Exception e) {
-    super(e);
-  }
-
-  public static class FxAccountClientRemoteException extends FxAccountClientException {
-    private static final long serialVersionUID = 2209313149952001097L;
-
-    public final HttpResponse response;
-    public final long httpStatusCode;
-    public final long apiErrorNumber;
-    public final String error;
-    public final String message;
-    public final String info;
-    public final ExtendedJSONObject body;
-
-    public FxAccountClientRemoteException(HttpResponse response, long httpStatusCode, long apiErrorNumber, String error, String message, String info, ExtendedJSONObject body) {
-      super(new HTTPFailureException(new SyncStorageResponse(response)));
-      if (body == null) {
-        throw new IllegalArgumentException("body must not be null");
-      }
-      this.response = response;
-      this.httpStatusCode = httpStatusCode;
-      this.apiErrorNumber = apiErrorNumber;
-      this.error = error;
-      this.message = message;
-      this.info = info;
-      this.body = body;
-    }
-
-    @Override
-    public String toString() {
-      return "<FxAccountClientRemoteException " + this.httpStatusCode + " [" + this.apiErrorNumber + "]: " + this.message + ">";
-    }
-
-    public boolean isInvalidAuthentication() {
-      return httpStatusCode == HttpStatus.SC_UNAUTHORIZED;
-    }
-
-    public boolean isAccountAlreadyExists() {
-      return apiErrorNumber == FxAccountRemoteError.ATTEMPT_TO_CREATE_AN_ACCOUNT_THAT_ALREADY_EXISTS;
-    }
-
-    public boolean isAccountDoesNotExist() {
-      return apiErrorNumber == FxAccountRemoteError.ATTEMPT_TO_ACCESS_AN_ACCOUNT_THAT_DOES_NOT_EXIST;
-    }
-
-    public boolean isBadPassword() {
-      return apiErrorNumber == FxAccountRemoteError.INCORRECT_PASSWORD;
-    }
-
-    public boolean isUnverified() {
-      return apiErrorNumber == FxAccountRemoteError.ATTEMPT_TO_OPERATE_ON_AN_UNVERIFIED_ACCOUNT;
-    }
-
-    public boolean isUpgradeRequired() {
-      return
-          apiErrorNumber == FxAccountRemoteError.ENDPOINT_IS_NO_LONGER_SUPPORTED ||
-          apiErrorNumber == FxAccountRemoteError.INCORRECT_LOGIN_METHOD_FOR_THIS_ACCOUNT ||
-          apiErrorNumber == FxAccountRemoteError.INCORRECT_KEY_RETRIEVAL_METHOD_FOR_THIS_ACCOUNT ||
-          apiErrorNumber == FxAccountRemoteError.INCORRECT_API_VERSION_FOR_THIS_ACCOUNT;
-    }
-
-    public boolean isTooManyRequests() {
-      return apiErrorNumber == FxAccountRemoteError.CLIENT_HAS_SENT_TOO_MANY_REQUESTS;
-    }
-
-    public boolean isServerUnavailable() {
-      return apiErrorNumber == FxAccountRemoteError.SERVICE_TEMPORARILY_UNAVAILABLE_DUE_TO_HIGH_LOAD;
-    }
-
-    public boolean isBadEmailCase() {
-      return apiErrorNumber == FxAccountRemoteError.INCORRECT_EMAIL_CASE;
-    }
-
-    public boolean isAccountLocked() {
-      return apiErrorNumber == FxAccountRemoteError.ACCOUNT_LOCKED;
-    }
-
-    public int getErrorMessageStringResource() {
-      if (isUpgradeRequired()) {
-        return R.string.fxaccount_remote_error_UPGRADE_REQUIRED;
-      } else if (isAccountAlreadyExists()) {
-        return R.string.fxaccount_remote_error_ATTEMPT_TO_CREATE_AN_ACCOUNT_THAT_ALREADY_EXISTS;
-      } else if (isAccountDoesNotExist()) {
-        return R.string.fxaccount_remote_error_ATTEMPT_TO_ACCESS_AN_ACCOUNT_THAT_DOES_NOT_EXIST;
-      } else if (isBadPassword()) {
-        return R.string.fxaccount_remote_error_INCORRECT_PASSWORD;
-      } else if (isUnverified()) {
-        return R.string.fxaccount_remote_error_ATTEMPT_TO_OPERATE_ON_AN_UNVERIFIED_ACCOUNT;
-      } else if (isTooManyRequests()) {
-        return R.string.fxaccount_remote_error_CLIENT_HAS_SENT_TOO_MANY_REQUESTS;
-      } else if (isServerUnavailable()) {
-        return R.string.fxaccount_remote_error_SERVICE_TEMPORARILY_UNAVAILABLE_TO_DUE_HIGH_LOAD;
-      } else if (isAccountLocked()) {
-        return R.string.fxaccount_remote_error_ACCOUNT_LOCKED;
-      } else {
-        return R.string.fxaccount_remote_error_UNKNOWN_ERROR;
-      }
-    }
-  }
-
-  public static class FxAccountClientMalformedResponseException extends FxAccountClientRemoteException {
-    private static final long serialVersionUID = 2209313149952001098L;
-
-    public FxAccountClientMalformedResponseException(HttpResponse response) {
-      super(response, 0, FxAccountRemoteError.UNKNOWN_ERROR, "Response malformed", "Response malformed", "Response malformed", new ExtendedJSONObject());
-    }
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/background/fxa/FxAccountRemoteError.java b/mobile/android/services/src/main/java/org/mozilla/gecko/background/fxa/FxAccountRemoteError.java
deleted file mode 100644
index 5a89561..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/background/fxa/FxAccountRemoteError.java
+++ /dev/null
@@ -1,33 +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.background.fxa;
-
-public interface FxAccountRemoteError {
-  public static final int ATTEMPT_TO_CREATE_AN_ACCOUNT_THAT_ALREADY_EXISTS = 101;
-  public static final int ATTEMPT_TO_ACCESS_AN_ACCOUNT_THAT_DOES_NOT_EXIST = 102;
-  public static final int INCORRECT_PASSWORD = 103;
-  public static final int ATTEMPT_TO_OPERATE_ON_AN_UNVERIFIED_ACCOUNT = 104;
-  public static final int INVALID_VERIFICATION_CODE = 105;
-  public static final int REQUEST_BODY_WAS_NOT_VALID_JSON = 106;
-  public static final int REQUEST_BODY_CONTAINS_INVALID_PARAMETERS = 107;
-  public static final int REQUEST_BODY_MISSING_REQUIRED_PARAMETERS = 108;
-  public static final int INVALID_REQUEST_SIGNATURE = 109;
-  public static final int INVALID_AUTHENTICATION_TOKEN = 110;
-  public static final int INVALID_AUTHENTICATION_TIMESTAMP = 111;
-  public static final int CONTENT_LENGTH_HEADER_WAS_NOT_PROVIDED = 112;
-  public static final int REQUEST_BODY_TOO_LARGE = 113;
-  public static final int CLIENT_HAS_SENT_TOO_MANY_REQUESTS = 114;
-  public static final int INVALID_NONCE_IN_REQUEST_SIGNATURE = 115;
-  public static final int ENDPOINT_IS_NO_LONGER_SUPPORTED = 116;
-  public static final int INCORRECT_LOGIN_METHOD_FOR_THIS_ACCOUNT = 117;
-  public static final int INCORRECT_KEY_RETRIEVAL_METHOD_FOR_THIS_ACCOUNT = 118;
-  public static final int INCORRECT_API_VERSION_FOR_THIS_ACCOUNT = 119;
-  public static final int INCORRECT_EMAIL_CASE = 120;
-  public static final int ACCOUNT_LOCKED = 121;
-  public static final int UNKNOWN_DEVICE = 123;
-  public static final int DEVICE_SESSION_CONFLICT = 124;
-  public static final int SERVICE_TEMPORARILY_UNAVAILABLE_DUE_TO_HIGH_LOAD = 201;
-  public static final int UNKNOWN_ERROR = 999;
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/background/fxa/FxAccountUtils.java b/mobile/android/services/src/main/java/org/mozilla/gecko/background/fxa/FxAccountUtils.java
deleted file mode 100644
index 2d29725..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/background/fxa/FxAccountUtils.java
+++ /dev/null
@@ -1,217 +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.background.fxa;
-
-import java.io.UnsupportedEncodingException;
-import java.math.BigInteger;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.security.GeneralSecurityException;
-import java.security.InvalidKeyException;
-import java.security.NoSuchAlgorithmException;
-
-import org.mozilla.gecko.AppConstants;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.background.nativecode.NativeCrypto;
-import org.mozilla.gecko.sync.Utils;
-import org.mozilla.gecko.sync.crypto.HKDF;
-import org.mozilla.gecko.sync.crypto.KeyBundle;
-import org.mozilla.gecko.sync.crypto.PBKDF2;
-
-import android.content.Context;
-
-public class FxAccountUtils {
-  private static final String LOG_TAG = FxAccountUtils.class.getSimpleName();
-
-  public static final int SALT_LENGTH_BYTES = 32;
-  public static final int SALT_LENGTH_HEX = 2 * SALT_LENGTH_BYTES;
-
-  public static final int HASH_LENGTH_BYTES = 16;
-  public static final int HASH_LENGTH_HEX = 2 * HASH_LENGTH_BYTES;
-
-  public static final int CRYPTO_KEY_LENGTH_BYTES = 32;
-  public static final int CRYPTO_KEY_LENGTH_HEX = 2 * CRYPTO_KEY_LENGTH_BYTES;
-
-  public static final String KW_VERSION_STRING = "identity.mozilla.com/picl/v1/";
-
-  public static final int NUMBER_OF_QUICK_STRETCH_ROUNDS = 1000;
-
-  // For extra debugging.  Not final so it can be changed from Fennec, or from
-  // an add-on.
-  public static boolean LOG_PERSONAL_INFORMATION = false;
-
-  public static void pii(String tag, String message) {
-    if (FxAccountUtils.LOG_PERSONAL_INFORMATION) {
-      Logger.info(tag, "$$FxA PII$$: " + message);
-    }
-  }
-
-  public static String bytes(String string) throws UnsupportedEncodingException {
-    return Utils.byte2Hex(string.getBytes("UTF-8"));
-  }
-
-  public static byte[] KW(String name) throws UnsupportedEncodingException {
-    return Utils.concatAll(
-        KW_VERSION_STRING.getBytes("UTF-8"),
-        name.getBytes("UTF-8"));
-  }
-
-  public static byte[] KWE(String name, byte[] emailUTF8) throws UnsupportedEncodingException {
-    return Utils.concatAll(
-        KW_VERSION_STRING.getBytes("UTF-8"),
-        name.getBytes("UTF-8"),
-        ":".getBytes("UTF-8"),
-        emailUTF8);
-  }
-
-  /**
-   * Calculate the SRP verifier <tt>x</tt> value.
-   */
-  public static BigInteger srpVerifierLowercaseX(byte[] emailUTF8, byte[] srpPWBytes, byte[] srpSaltBytes)
-      throws NoSuchAlgorithmException, UnsupportedEncodingException {
-    byte[] inner = Utils.sha256(Utils.concatAll(emailUTF8, ":".getBytes("UTF-8"), srpPWBytes));
-    byte[] outer = Utils.sha256(Utils.concatAll(srpSaltBytes, inner));
-    return new BigInteger(1, outer);
-  }
-
-  /**
-   * Calculate the SRP verifier <tt>v</tt> value.
-   */
-  public static BigInteger srpVerifierLowercaseV(byte[] emailUTF8, byte[] srpPWBytes, byte[] srpSaltBytes, BigInteger g, BigInteger N)
-      throws NoSuchAlgorithmException, UnsupportedEncodingException {
-    BigInteger x = srpVerifierLowercaseX(emailUTF8, srpPWBytes, srpSaltBytes);
-    BigInteger v = g.modPow(x, N);
-    return v;
-  }
-
-  /**
-   * Format x modulo N in hexadecimal, using as many characters as N takes (in hexadecimal).
-   * @param x to format.
-   * @param N modulus.
-   * @return x modulo N in hexadecimal.
-   */
-  public static String hexModN(BigInteger x, BigInteger N) {
-    int byteLength = (N.bitLength() + 7) / 8;
-    int hexLength = 2 * byteLength;
-    return Utils.byte2Hex(Utils.hex2Byte((x.mod(N)).toString(16), byteLength), hexLength);
-  }
-
-  /**
-   * The first engineering milestone of PICL (Profile-in-the-Cloud) was
-   * comprised of Sync 1.1 fronted by a Firefox Account. The sync key was
-   * generated from the Firefox Account password-derived kB value using this
-   * method.
-   */
-  public static KeyBundle generateSyncKeyBundle(final byte[] kB) throws InvalidKeyException, NoSuchAlgorithmException, UnsupportedEncodingException {
-    byte[] encryptionKey = new byte[32];
-    byte[] hmacKey = new byte[32];
-    byte[] derived = HKDF.derive(kB, new byte[0], FxAccountUtils.KW("oldsync"), 2*32);
-    System.arraycopy(derived, 0*32, encryptionKey, 0, 1*32);
-    System.arraycopy(derived, 1*32, hmacKey, 0, 1*32);
-    return new KeyBundle(encryptionKey, hmacKey);
-  }
-
-  /**
-   * Firefox Accounts are password authenticated, but clients should not store
-   * the plain-text password for any amount of time. Equivalent, but slightly
-   * more secure, is the quickly client-side stretched password.
-   * <p>
-   * We separate this since multiple login-time operations want it, and the
-   * PBKDF2 operation is computationally expensive.
-   */
-  public static byte[] generateQuickStretchedPW(byte[] emailUTF8, byte[] passwordUTF8) throws GeneralSecurityException, UnsupportedEncodingException {
-    byte[] S = FxAccountUtils.KWE("quickStretch", emailUTF8);
-    try {
-      return NativeCrypto.pbkdf2SHA256(passwordUTF8, S, NUMBER_OF_QUICK_STRETCH_ROUNDS, 32);
-    } catch (final LinkageError e) {
-      // This will throw UnsatisfiedLinkError (missing mozglue) the first time it is called, and
-      // ClassNotDefFoundError, for the uninitialized NativeCrypto class, each subsequent time this
-      // is called; LinkageError is their common ancestor.
-      Logger.warn(LOG_TAG, "Got throwable stretching password using native pbkdf2SHA256 " +
-          "implementation; ignoring and using Java implementation.", e);
-      return PBKDF2.pbkdf2SHA256(passwordUTF8, S, NUMBER_OF_QUICK_STRETCH_ROUNDS, 32);
-    }
-  }
-
-  /**
-   * The password-derived credential used to authenticate to the Firefox Account
-   * auth server.
-   */
-  public static byte[] generateAuthPW(byte[] quickStretchedPW) throws GeneralSecurityException, UnsupportedEncodingException {
-    return HKDF.derive(quickStretchedPW, new byte[0], FxAccountUtils.KW("authPW"), 32);
-  }
-
-  /**
-   * The password-derived credential used to unwrap keys managed by the Firefox
-   * Account auth server.
-   */
-  public static byte[] generateUnwrapBKey(byte[] quickStretchedPW) throws GeneralSecurityException, UnsupportedEncodingException {
-    return HKDF.derive(quickStretchedPW, new byte[0], FxAccountUtils.KW("unwrapBkey"), 32);
-  }
-
-  public static byte[] unwrapkB(byte[] unwrapkB, byte[] wrapkB) {
-    if (unwrapkB == null) {
-      throw new IllegalArgumentException("unwrapkB must not be null");
-    }
-    if (wrapkB == null) {
-      throw new IllegalArgumentException("wrapkB must not be null");
-    }
-    if (unwrapkB.length != CRYPTO_KEY_LENGTH_BYTES || wrapkB.length != CRYPTO_KEY_LENGTH_BYTES) {
-      throw new IllegalArgumentException("unwrapkB and wrapkB must be " + CRYPTO_KEY_LENGTH_BYTES + " bytes long");
-    }
-    byte[] kB = new byte[CRYPTO_KEY_LENGTH_BYTES];
-    for (int i = 0; i < wrapkB.length; i++) {
-      kB[i] = (byte) (wrapkB[i] ^ unwrapkB[i]);
-    }
-    return kB;
-  }
-
-  /**
-   * The token server accepts an X-Client-State header, which is the
-   * lowercase-hex-encoded first 16 bytes of the SHA-256 hash of the
-   * bytes of kB.
-   * @param kB a byte array, expected to be 32 bytes long.
-   * @return a 32-character string.
-   * @throws NoSuchAlgorithmException
-   */
-  public static String computeClientState(byte[] kB) throws NoSuchAlgorithmException {
-    if (kB == null ||
-        kB.length != 32) {
-      throw new IllegalArgumentException("Unexpected kB.");
-    }
-    byte[] sha256 = Utils.sha256(kB);
-    byte[] truncated = new byte[16];
-    System.arraycopy(sha256, 0, truncated, 0, 16);
-    return Utils.byte2Hex(truncated);    // This is automatically lowercase.
-  }
-
-  /**
-   * Given an endpoint, calculate the corresponding BrowserID audience.
-   * <p>
-   * This is the domain, in web parlance.
-   *
-   * @param serverURI endpoint.
-   * @return BrowserID audience.
-   * @throws URISyntaxException
-   */
-  public static String getAudienceForURL(String serverURI) throws URISyntaxException {
-    URI uri = new URI(serverURI);
-    return new URI(uri.getScheme(), null, uri.getHost(), uri.getPort(), null, null, null).toString();
-  }
-
-  public static String defaultClientName(Context context) {
-    String name = AppConstants.MOZ_APP_DISPLAYNAME; // The display name is never translated.
-    // Change "Firefox Aurora" or similar into "Aurora".
-    if (name.contains("Aurora")) {
-        name = "Aurora";
-    } else if (name.contains("Beta")) {
-        name = "Beta";
-    } else if (name.contains("Nightly")) {
-        name = "Nightly";
-    }
-    return context.getResources().getString(R.string.sync_default_client_name, name, android.os.Build.MODEL);
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/background/fxa/PasswordStretcher.java b/mobile/android/services/src/main/java/org/mozilla/gecko/background/fxa/PasswordStretcher.java
deleted file mode 100644
index 2debf3c..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/background/fxa/PasswordStretcher.java
+++ /dev/null
@@ -1,12 +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.background.fxa;
-
-import java.io.UnsupportedEncodingException;
-import java.security.GeneralSecurityException;
-
-public interface PasswordStretcher {
-  public byte[] getQuickStretchedPW(byte[] emailUTF8) throws UnsupportedEncodingException, GeneralSecurityException;
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/background/fxa/QuickPasswordStretcher.java b/mobile/android/services/src/main/java/org/mozilla/gecko/background/fxa/QuickPasswordStretcher.java
deleted file mode 100644
index bf4b1bc..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/background/fxa/QuickPasswordStretcher.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.background.fxa;
-
-import java.io.UnsupportedEncodingException;
-import java.security.GeneralSecurityException;
-import java.util.HashMap;
-import java.util.Map;
-
-import org.mozilla.gecko.sync.Utils;
-
-public class QuickPasswordStretcher implements PasswordStretcher {
-  protected final String password;
-  protected final Map<String, String> cache = new HashMap<String, String>();
-
-  public QuickPasswordStretcher(String password) {
-    this.password = password;
-  }
-
-  @Override
-  public synchronized byte[] getQuickStretchedPW(byte[] emailUTF8) throws UnsupportedEncodingException, GeneralSecurityException {
-    if (emailUTF8 == null) {
-      throw new IllegalArgumentException("emailUTF8 must not be null");
-    }
-    String key = Utils.byte2Hex(emailUTF8);
-    if (!cache.containsKey(key)) {
-      byte[] value = FxAccountUtils.generateQuickStretchedPW(emailUTF8, password.getBytes("UTF-8"));
-      cache.put(key, Utils.byte2Hex(value));
-      return value;
-    }
-    return Utils.hex2Byte(cache.get(key));
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/background/fxa/SkewHandler.java b/mobile/android/services/src/main/java/org/mozilla/gecko/background/fxa/SkewHandler.java
deleted file mode 100644
index 9d0ad5e..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/background/fxa/SkewHandler.java
+++ /dev/null
@@ -1,111 +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.background.fxa;
-
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.util.HashMap;
-
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.sync.net.Resource;
-
-import ch.boye.httpclientandroidlib.Header;
-import ch.boye.httpclientandroidlib.HttpHeaders;
-import ch.boye.httpclientandroidlib.HttpResponse;
-import ch.boye.httpclientandroidlib.impl.cookie.DateParseException;
-import ch.boye.httpclientandroidlib.impl.cookie.DateUtils;
-
-public class SkewHandler {
-  private static final String LOG_TAG = "SkewHandler";
-  protected volatile long skewMillis = 0L;
-  protected final String hostname;
-
-  private static final HashMap<String, SkewHandler> skewHandlers = new HashMap<String, SkewHandler>();
-
-  public static SkewHandler getSkewHandlerForResource(final Resource resource) {
-    return getSkewHandlerForHostname(resource.getHostname());
-  }
-
-  public static SkewHandler getSkewHandlerFromEndpointString(final String url) throws URISyntaxException {
-    if (url == null) {
-      throw new IllegalArgumentException("url must not be null.");
-    }
-    URI u = new URI(url);
-    return getSkewHandlerForHostname(u.getHost());
-  }
-
-  public static synchronized SkewHandler getSkewHandlerForHostname(final String hostname) {
-    SkewHandler handler = skewHandlers.get(hostname);
-    if (handler == null) {
-      handler = new SkewHandler(hostname);
-      skewHandlers.put(hostname, handler);
-    }
-    return handler;
-  }
-
-  public static synchronized void clearSkewHandlers() {
-    skewHandlers.clear();
-  }
-
-  public SkewHandler(final String hostname) {
-    this.hostname = hostname;
-  }
-
-  public boolean updateSkewFromServerMillis(long millis, long now) {
-    skewMillis = millis - now;
-    Logger.debug(LOG_TAG, "Updated skew: " + skewMillis + "ms for hostname " + this.hostname);
-    return true;
-  }
-
-  public boolean updateSkewFromHTTPDateString(String date, long now) {
-    try {
-      final long millis = DateUtils.parseDate(date).getTime();
-      return updateSkewFromServerMillis(millis, now);
-    } catch (DateParseException e) {
-      Logger.warn(LOG_TAG, "Unexpected: invalid Date header from " + this.hostname);
-      return false;
-    }
-  }
-
-  public boolean updateSkewFromDateHeader(Header header, long now) {
-    String date = header.getValue();
-    if (null == date) {
-      Logger.warn(LOG_TAG, "Unexpected: null Date header from " + this.hostname);
-      return false;
-    }
-    return updateSkewFromHTTPDateString(date, now);
-  }
-
-  /**
-   * Update our tracked skew value to account for the local clock differing from
-   * the server's.
-   * 
-   * @param response
-   *          the received HTTP response.
-   * @param now
-   *          the current time in milliseconds.
-   * @return true if the skew value was updated, false otherwise.
-   */
-  public boolean updateSkew(HttpResponse response, long now) {
-    Header header = response.getFirstHeader(HttpHeaders.DATE);
-    if (null == header) {
-      Logger.warn(LOG_TAG, "Unexpected: missing Date header from " + this.hostname);
-      return false;
-    }
-    return updateSkewFromDateHeader(header, now);
-  }
-
-  public long getSkewInMillis() {
-    return skewMillis;
-  }
-
-  public long getSkewInSeconds() {
-    return skewMillis / 1000;
-  }
-
-  public void resetSkew() {
-    skewMillis = 0L;
-  }
-}
\ No newline at end of file
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/background/fxa/oauth/FxAccountAbstractClient.java b/mobile/android/services/src/main/java/org/mozilla/gecko/background/fxa/oauth/FxAccountAbstractClient.java
deleted file mode 100644
index 4bdaa66..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/background/fxa/oauth/FxAccountAbstractClient.java
+++ /dev/null
@@ -1,224 +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.background.fxa.oauth;
-
-import java.io.IOException;
-import java.security.GeneralSecurityException;
-import java.util.Locale;
-import java.util.concurrent.Executor;
-
-import org.mozilla.gecko.background.fxa.FxAccountClientException;
-import org.mozilla.gecko.background.fxa.oauth.FxAccountAbstractClientException.FxAccountAbstractClientMalformedResponseException;
-import org.mozilla.gecko.background.fxa.oauth.FxAccountAbstractClientException.FxAccountAbstractClientRemoteException;
-import org.mozilla.gecko.fxa.FxAccountConstants;
-import org.mozilla.gecko.Locales;
-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.Resource;
-import org.mozilla.gecko.sync.net.SyncResponse;
-import org.mozilla.gecko.sync.net.SyncStorageResponse;
-
-import ch.boye.httpclientandroidlib.HttpEntity;
-import ch.boye.httpclientandroidlib.HttpHeaders;
-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;
-
-public abstract class FxAccountAbstractClient {
-  protected static final String LOG_TAG = FxAccountAbstractClient.class.getSimpleName();
-
-  protected static final String ACCEPT_HEADER = "application/json;charset=utf-8";
-  protected static final String AUTHORIZATION_RESPONSE_TYPE = "token";
-
-  public static final String JSON_KEY_ERROR = "error";
-  public static final String JSON_KEY_MESSAGE = "message";
-  public static final String JSON_KEY_CODE = "code";
-  public static final String JSON_KEY_ERRNO = "errno";
-
-  protected static final String[] requiredErrorStringFields = { JSON_KEY_ERROR, JSON_KEY_MESSAGE };
-  protected static final String[] requiredErrorLongFields = { JSON_KEY_CODE, JSON_KEY_ERRNO };
-
-  /**
-   * The server's URI.
-   * <p>
-   * We assume throughout that this ends with a trailing slash (and guarantee as
-   * much in the constructor).
-   */
-  protected final String serverURI;
-
-  protected final Executor executor;
-
-  public FxAccountAbstractClient(String serverURI, Executor executor) {
-    if (serverURI == null) {
-      throw new IllegalArgumentException("Must provide a server URI.");
-    }
-    if (executor == null) {
-      throw new IllegalArgumentException("Must provide a non-null executor.");
-    }
-    this.serverURI = serverURI.endsWith("/") ? serverURI : serverURI + "/";
-    if (!this.serverURI.endsWith("/")) {
-      throw new IllegalArgumentException("Constructed serverURI must end with a trailing slash: " + this.serverURI);
-    }
-    this.executor = executor;
-  }
-
-  /**
-   * Process a typed value extracted from a successful response (in an
-   * endpoint-dependent way).
-   */
-  public interface RequestDelegate<T> {
-    public void handleError(Exception e);
-    public void handleFailure(FxAccountAbstractClientRemoteException e);
-    public void handleSuccess(T result);
-  }
-
-  /**
-   * Intepret a response from the auth server.
-   * <p>
-   * Throw an appropriate exception on errors; otherwise, return the response's
-   * status code.
-   *
-   * @return response's HTTP status code.
-   * @throws FxAccountClientException
-   */
-  public static int validateResponse(HttpResponse response) throws FxAccountAbstractClientRemoteException {
-    final int status = response.getStatusLine().getStatusCode();
-    if (status == 200) {
-      return status;
-    }
-    int code;
-    int errno;
-    String error;
-    String message;
-    ExtendedJSONObject body;
-    try {
-      body = new SyncStorageResponse(response).jsonObjectBody();
-      body.throwIfFieldsMissingOrMisTyped(requiredErrorStringFields, String.class);
-      body.throwIfFieldsMissingOrMisTyped(requiredErrorLongFields, Long.class);
-      code = body.getLong(JSON_KEY_CODE).intValue();
-      errno = body.getLong(JSON_KEY_ERRNO).intValue();
-      error = body.getString(JSON_KEY_ERROR);
-      message = body.getString(JSON_KEY_MESSAGE);
-    } catch (Exception e) {
-      throw new FxAccountAbstractClientMalformedResponseException(response);
-    }
-    throw new FxAccountAbstractClientRemoteException(response, code, errno, error, message, body);
-  }
-
-  protected <T> void invokeHandleError(final RequestDelegate<T> delegate, final Exception e) {
-    executor.execute(new Runnable() {
-      @Override
-      public void run() {
-        delegate.handleError(e);
-      }
-    });
-  }
-
-  protected <T> void post(BaseResource resource, final ExtendedJSONObject requestBody, final RequestDelegate<T> delegate) {
-    try {
-      if (requestBody == null) {
-        resource.post((HttpEntity) null);
-      } else {
-        resource.post(requestBody);
-      }
-    } catch (Exception e) {
-      invokeHandleError(delegate, e);
-      return;
-    }
-  }
-
-  /**
-   * Translate resource callbacks into request callbacks invoked on the provided
-   * executor.
-   * <p>
-   * Override <code>handleSuccess</code> to parse the body of the resource
-   * request and call the request callback. <code>handleSuccess</code> is
-   * invoked via the executor, so you don't need to delegate further.
-   */
-  protected abstract class ResourceDelegate<T> extends BaseResourceDelegate {
-    protected abstract void handleSuccess(final int status, HttpResponse response, final ExtendedJSONObject body);
-
-    protected final RequestDelegate<T> delegate;
-
-    /**
-     * Create a delegate for an un-authenticated resource.
-     */
-    public ResourceDelegate(final Resource resource, final RequestDelegate<T> delegate) {
-      super(resource);
-      this.delegate = delegate;
-    }
-
-    @Override
-    public AuthHeaderProvider getAuthHeaderProvider() {
-      return super.getAuthHeaderProvider();
-    }
-
-    @Override
-    public String getUserAgent() {
-      return FxAccountConstants.USER_AGENT;
-    }
-
-    @Override
-    public void handleHttpResponse(HttpResponse response) {
-      try {
-        final int status = validateResponse(response);
-        invokeHandleSuccess(status, response);
-      } catch (FxAccountAbstractClientRemoteException e) {
-        invokeHandleFailure(e);
-      }
-    }
-
-    protected void invokeHandleFailure(final FxAccountAbstractClientRemoteException e) {
-      executor.execute(new Runnable() {
-        @Override
-        public void run() {
-          delegate.handleFailure(e);
-        }
-      });
-    }
-
-    protected void invokeHandleSuccess(final int status, final HttpResponse response) {
-      executor.execute(new Runnable() {
-        @Override
-        public void run() {
-          try {
-            ExtendedJSONObject body = new SyncResponse(response).jsonObjectBody();
-            ResourceDelegate.this.handleSuccess(status, response, body);
-          } catch (Exception e) {
-            delegate.handleError(e);
-          }
-        }
-      });
-    }
-
-    @Override
-    public void handleHttpProtocolException(final ClientProtocolException e) {
-      invokeHandleError(delegate, e);
-    }
-
-    @Override
-    public void handleHttpIOException(IOException e) {
-      invokeHandleError(delegate, e);
-    }
-
-    @Override
-    public void handleTransportException(GeneralSecurityException e) {
-      invokeHandleError(delegate, e);
-    }
-
-    @Override
-    public void addHeaders(HttpRequestBase request, DefaultHttpClient client) {
-      super.addHeaders(request, client);
-
-      // The basics.
-      final Locale locale = Locale.getDefault();
-      request.addHeader(HttpHeaders.ACCEPT_LANGUAGE, Locales.getLanguageTag(locale));
-      request.addHeader(HttpHeaders.ACCEPT, ACCEPT_HEADER);
-    }
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/background/fxa/oauth/FxAccountAbstractClientException.java b/mobile/android/services/src/main/java/org/mozilla/gecko/background/fxa/oauth/FxAccountAbstractClientException.java
deleted file mode 100644
index 21025af..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/background/fxa/oauth/FxAccountAbstractClientException.java
+++ /dev/null
@@ -1,68 +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.background.fxa.oauth;
-
-import org.mozilla.gecko.sync.ExtendedJSONObject;
-import org.mozilla.gecko.sync.HTTPFailureException;
-import org.mozilla.gecko.sync.net.SyncStorageResponse;
-
-import ch.boye.httpclientandroidlib.HttpResponse;
-import ch.boye.httpclientandroidlib.HttpStatus;
-
-/**
- * From <a href="https://github.com/mozilla/fxa-auth-server/blob/master/docs/api.md">https://github.com/mozilla/fxa-auth-server/blob/master/docs/api.md</a>.
- */
-public class FxAccountAbstractClientException extends Exception {
-  private static final long serialVersionUID = 1953459541558266597L;
-
-  public FxAccountAbstractClientException(String detailMessage) {
-    super(detailMessage);
-  }
-
-  public FxAccountAbstractClientException(Exception e) {
-    super(e);
-  }
-
-  public static class FxAccountAbstractClientRemoteException extends FxAccountAbstractClientException {
-    private static final long serialVersionUID = 1209313149952001097L;
-
-    public final HttpResponse response;
-    public final long httpStatusCode;
-    public final long apiErrorNumber;
-    public final String error;
-    public final String message;
-    public final ExtendedJSONObject body;
-
-    public FxAccountAbstractClientRemoteException(HttpResponse response, long httpStatusCode, long apiErrorNumber, String error, String message, ExtendedJSONObject body) {
-      super(new HTTPFailureException(new SyncStorageResponse(response)));
-      if (body == null) {
-        throw new IllegalArgumentException("body must not be null");
-      }
-      this.response = response;
-      this.httpStatusCode = httpStatusCode;
-      this.apiErrorNumber = apiErrorNumber;
-      this.error = error;
-      this.message = message;
-      this.body = body;
-    }
-
-    @Override
-    public String toString() {
-      return "<FxAccountAbstractClientRemoteException " + this.httpStatusCode + " [" + this.apiErrorNumber + "]: " + this.message + ">";
-    }
-
-    public boolean isInvalidAuthentication() {
-      return this.httpStatusCode == HttpStatus.SC_UNAUTHORIZED;
-    }
-  }
-
-  public static class FxAccountAbstractClientMalformedResponseException extends FxAccountAbstractClientRemoteException {
-    private static final long serialVersionUID = 1209313149952001098L;
-
-    public FxAccountAbstractClientMalformedResponseException(HttpResponse response) {
-      super(response, 0, FxAccountOAuthRemoteError.UNKNOWN_ERROR, "Response malformed", "Response malformed", new ExtendedJSONObject());
-    }
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/background/fxa/oauth/FxAccountOAuthClient10.java b/mobile/android/services/src/main/java/org/mozilla/gecko/background/fxa/oauth/FxAccountOAuthClient10.java
deleted file mode 100644
index 4f23369..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/background/fxa/oauth/FxAccountOAuthClient10.java
+++ /dev/null
@@ -1,129 +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.background.fxa.oauth;
-
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.util.concurrent.Executor;
-
-import org.mozilla.gecko.sync.ExtendedJSONObject;
-import org.mozilla.gecko.sync.net.BaseResource;
-
-import ch.boye.httpclientandroidlib.HttpResponse;
-
-/**
- * Talk to an fxa-oauth-server to get "implicitly granted" OAuth tokens.
- * <p>
- * To use this client, you will need a pre-allocated fxa-oauth-server
- * "client_id" with special "implicit grant" permissions.
- * <p>
- * This client was written against the API documented at <a href="https://github.com/mozilla/fxa-oauth-server/blob/41538990df9e91158558ae5a8115194383ac3b05/docs/api.md">https://github.com/mozilla/fxa-oauth-server/blob/41538990df9e91158558ae5a8115194383ac3b05/docs/api.md</a>.
- */
-public class FxAccountOAuthClient10 extends FxAccountAbstractClient {
-  protected static final String LOG_TAG = FxAccountOAuthClient10.class.getSimpleName();
-
-  protected static final String AUTHORIZATION_RESPONSE_TYPE = "token";
-
-  protected static final String JSON_KEY_ACCESS_TOKEN = "access_token";
-  protected static final String JSON_KEY_ASSERTION = "assertion";
-  protected static final String JSON_KEY_CLIENT_ID = "client_id";
-  protected static final String JSON_KEY_RESPONSE_TYPE = "response_type";
-  protected static final String JSON_KEY_SCOPE = "scope";
-  protected static final String JSON_KEY_STATE = "state";
-  protected static final String JSON_KEY_TOKEN = "token";
-  protected static final String JSON_KEY_TOKEN_TYPE = "token_type";
-
-  // access_token: A string that can be used for authorized requests to service providers.
-  // scope: A string of space-separated permissions that this token has. May differ from requested scopes, since user can deny permissions.
-  // token_type: A string representing the token type. Currently will always be "bearer".
-  protected static final String[] AUTHORIZATION_RESPONSE_REQUIRED_STRING_FIELDS = new String[] { JSON_KEY_ACCESS_TOKEN, JSON_KEY_SCOPE, JSON_KEY_TOKEN_TYPE };
-
-  public FxAccountOAuthClient10(String serverURI, Executor executor) {
-    super(serverURI, executor);
-  }
-
-  /**
-   * Thin container for an authorization response.
-   */
-  public static class AuthorizationResponse {
-    public final String access_token;
-    public final String token_type;
-    public final String scope;
-
-    public AuthorizationResponse(String access_token, String token_type, String scope) {
-      this.access_token = access_token;
-      this.token_type = token_type;
-      this.scope = scope;
-    }
-  }
-
-  public void authorization(String client_id, String assertion, String state, String scope,
-                            RequestDelegate<AuthorizationResponse> delegate) {
-    final BaseResource resource;
-    try {
-      resource = new BaseResource(new URI(serverURI + "authorization"));
-    } catch (URISyntaxException e) {
-      invokeHandleError(delegate, e);
-      return;
-    }
-
-    resource.delegate = new ResourceDelegate<AuthorizationResponse>(resource, delegate) {
-      @Override
-      public void handleSuccess(int status, HttpResponse response, ExtendedJSONObject body) {
-        try {
-          body.throwIfFieldsMissingOrMisTyped(AUTHORIZATION_RESPONSE_REQUIRED_STRING_FIELDS, String.class);
-          String access_token = body.getString(JSON_KEY_ACCESS_TOKEN);
-          String token_type = body.getString(JSON_KEY_TOKEN_TYPE);
-          String scope = body.getString(JSON_KEY_SCOPE);
-          delegate.handleSuccess(new AuthorizationResponse(access_token, token_type, scope));
-          return;
-        } catch (Exception e) {
-          delegate.handleError(e);
-          return;
-        }
-      }
-    };
-
-    final ExtendedJSONObject requestBody = new ExtendedJSONObject();
-    requestBody.put(JSON_KEY_RESPONSE_TYPE, AUTHORIZATION_RESPONSE_TYPE);
-    requestBody.put(JSON_KEY_CLIENT_ID, client_id);
-    requestBody.put(JSON_KEY_ASSERTION, assertion);
-    if (scope != null) {
-      requestBody.put(JSON_KEY_SCOPE, scope);
-    }
-    if (state != null) {
-      requestBody.put(JSON_KEY_STATE, state);
-    }
-
-    post(resource, requestBody, delegate);
-  }
-
-  public void deleteToken(final String token, final RequestDelegate<Void> delegate) {
-    final BaseResource resource;
-    try {
-      resource = new BaseResource(new URI(serverURI + "destroy"));
-    } catch (URISyntaxException e) {
-      invokeHandleError(delegate, e);
-      return;
-    }
-
-    resource.delegate = new ResourceDelegate<Void>(resource, delegate) {
-      @Override
-      public void handleSuccess(int status, HttpResponse response, ExtendedJSONObject body) {
-        try {
-          delegate.handleSuccess(null);
-          return;
-        } catch (Exception e) {
-          delegate.handleError(e);
-          return;
-        }
-      }
-    };
-
-    final ExtendedJSONObject requestBody = new ExtendedJSONObject();
-    requestBody.put(JSON_KEY_TOKEN, token);
-    post(resource, requestBody, delegate);
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/background/fxa/oauth/FxAccountOAuthRemoteError.java b/mobile/android/services/src/main/java/org/mozilla/gecko/background/fxa/oauth/FxAccountOAuthRemoteError.java
deleted file mode 100644
index d949d31..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/background/fxa/oauth/FxAccountOAuthRemoteError.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.background.fxa.oauth;
-
-public interface FxAccountOAuthRemoteError {
-  public static final int ATTEMPT_TO_CREATE_AN_ACCOUNT_THAT_ALREADY_EXISTS = 101;
-  public static final int UNKNOWN_CLIENT_ID = 101;
-  public static final int INCORRECT_CLIENT_SECRET = 102;
-  public static final int REDIRECT_URI_DOES_NOT_MATCH_REGISTERED_VALUE = 103;
-  public static final int INVALID_FXA_ASSERTION = 104;
-  public static final int UNKNOWN_CODE = 105;
-  public static final int INCORRECT_CODE = 106;
-  public static final int EXPIRED_CODE = 107;
-  public static final int INVALID_TOKEN = 108;
-  public static final int INVALID_REQUEST_PARAMETER = 109;
-  public static final int UNKNOWN_ERROR = 999;
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/background/fxa/profile/FxAccountProfileClient10.java b/mobile/android/services/src/main/java/org/mozilla/gecko/background/fxa/profile/FxAccountProfileClient10.java
deleted file mode 100644
index cb851a8..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/background/fxa/profile/FxAccountProfileClient10.java
+++ /dev/null
@@ -1,59 +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.background.fxa.profile;
-
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.util.concurrent.Executor;
-
-import org.mozilla.gecko.background.fxa.oauth.FxAccountAbstractClient;
-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.BearerAuthHeaderProvider;
-
-import ch.boye.httpclientandroidlib.HttpResponse;
-
-
-/**
- * Talk to an fxa-profile-server to get profile information like name, age, gender, and avatar image.
- * <p>
- * This client was written against the API documented at <a href="https://github.com/mozilla/fxa-profile-server/blob/0c065619f5a2e867f813a343b4c67da3fe2c82a4/docs/API.md">https://github.com/mozilla/fxa-profile-server/blob/0c065619f5a2e867f813a343b4c67da3fe2c82a4/docs/API.md</a>.
- */
-public class FxAccountProfileClient10 extends FxAccountAbstractClient {
-  public FxAccountProfileClient10(String serverURI, Executor executor) {
-    super(serverURI, executor);
-  }
-
-  public void profile(final String token, RequestDelegate<ExtendedJSONObject> delegate) {
-    BaseResource resource;
-    try {
-      resource = new BaseResource(new URI(serverURI + "profile"));
-    } catch (URISyntaxException e) {
-      invokeHandleError(delegate, e);
-      return;
-    }
-
-    resource.delegate = new ResourceDelegate<ExtendedJSONObject>(resource, delegate) {
-      @Override
-      public AuthHeaderProvider getAuthHeaderProvider() {
-        return new BearerAuthHeaderProvider(token);
-      }
-
-      @Override
-      public void handleSuccess(int status, HttpResponse response, ExtendedJSONObject body) {
-        try {
-          delegate.handleSuccess(body);
-          return;
-        } catch (Exception e) {
-          delegate.handleError(e);
-          return;
-        }
-      }
-    };
-
-    resource.get();
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/background/nativecode/NativeCrypto.java b/mobile/android/services/src/main/java/org/mozilla/gecko/background/nativecode/NativeCrypto.java
deleted file mode 100644
index 25f0f84..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/background/nativecode/NativeCrypto.java
+++ /dev/null
@@ -1,60 +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.background.nativecode;
-
-import java.security.GeneralSecurityException;
-
-import org.mozilla.gecko.annotation.RobocopTarget;
-import org.mozilla.gecko.AppConstants;
-
-import android.util.Log;
-
- at RobocopTarget
-public class NativeCrypto {
-  static {
-    try {
-      System.loadLibrary("mozglue");
-    } catch (UnsatisfiedLinkError e) {
-      Log.wtf("NativeCrypto", "Couldn't load mozglue. Trying /data/app-lib path.");
-      try {
-        System.load("/data/app-lib/" + AppConstants.ANDROID_PACKAGE_NAME + "/libmozglue.so");
-      } catch (Throwable ee) {
-          try {
-            Log.wtf("NativeCrypto", "Couldn't load mozglue: " + ee + ". Trying /data/data path.");
-            System.load("/data/data/" + AppConstants.ANDROID_PACKAGE_NAME + "/lib/libmozglue.so");
-          } catch (UnsatisfiedLinkError eee) {
-              Log.wtf("NativeCrypto", "Failed every attempt to load mozglue. Giving up.");
-              throw new RuntimeException("Unable to load mozglue", eee);
-          }
-      }
-    }
-  }
-
-  /**
-   * Wrapper to perform PBKDF2-HMAC-SHA-256 in native code.
-   */
-  public native static byte[] pbkdf2SHA256(byte[] password, byte[] salt, int c, int dkLen)
-      throws GeneralSecurityException;
-
-  /**
-   * Wrapper to perform SHA-1 in native code.
-   */
-  public native static byte[] sha1(byte[] str);
-
-  /**
-   * Wrapper to perform SHA-256 init in native code. Returns a SHA-256 context.
-   */
-  public native static byte[] sha256init();
-
-  /**
-   * Wrapper to update a SHA-256 context in native code.
-   */
-  public native static void sha256update(byte[] ctx, byte[] str, int len);
-
-  /**
-   * Wrapper to finalize a SHA-256 context in native code. Returns digest.
-   */
-  public native static byte[] sha256finalize(byte[] ctx);
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/background/preferences/PreferenceFragment.java b/mobile/android/services/src/main/java/org/mozilla/gecko/background/preferences/PreferenceFragment.java
deleted file mode 100644
index 5bc5422..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/background/preferences/PreferenceFragment.java
+++ /dev/null
@@ -1,326 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.mozilla.gecko.background.preferences;
-
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.util.WeakReferenceHandler;
-
-import android.content.Intent;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Message;
-import android.preference.Preference;
-import android.preference.PreferenceGroup;
-import android.preference.PreferenceManager;
-import android.preference.PreferenceScreen;
-import android.support.v4.app.Fragment;
-import android.view.KeyEvent;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.View.OnKeyListener;
-import android.view.ViewGroup;
-import android.widget.ListView;
-
-public abstract class PreferenceFragment extends Fragment implements PreferenceManagerCompat.OnPreferenceTreeClickListener {
-  private static final String PREFERENCES_TAG = "android:preferences";
-
-  private PreferenceManager mPreferenceManager;
-  private ListView mList;
-  private boolean mHavePrefs;
-  private boolean mInitDone;
-
-  /**
-   * The starting request code given out to preference framework.
-   */
-  private static final int FIRST_REQUEST_CODE = 100;
-
-  private static final int MSG_BIND_PREFERENCES = 1;
-
-  private static class PreferenceFragmentHandler extends WeakReferenceHandler<PreferenceFragment> {
-    public PreferenceFragmentHandler(final PreferenceFragment that) {
-      super(that);
-    }
-
-    @Override
-    public void handleMessage(Message msg) {
-      final PreferenceFragment that = mTarget.get();
-      if (that == null) {
-        return;
-      }
-
-      switch (msg.what) {
-
-      case MSG_BIND_PREFERENCES:
-        that.bindPreferences();
-        break;
-      }
-    }
-  }
-
-  private final Handler mHandler = new PreferenceFragmentHandler(this);
-
-  final private Runnable mRequestFocus = new Runnable() {
-    @Override
-    public void run() {
-      mList.focusableViewAvailable(mList);
-    }
-  };
-
-  /**
-   * Interface that PreferenceFragment's containing activity should
-   * implement to be able to process preference items that wish to
-   * switch to a new fragment.
-   */
-  public interface OnPreferenceStartFragmentCallback {
-    /**
-     * Called when the user has clicked on a Preference that has
-     * a fragment class name associated with it.  The implementation
-     * to should instantiate and switch to an instance of the given
-     * fragment.
-     */
-    boolean onPreferenceStartFragment(PreferenceFragment caller, Preference pref);
-  }
-
-  @Override
-  public void onCreate(Bundle paramBundle) {
-    super.onCreate(paramBundle);
-    mPreferenceManager = PreferenceManagerCompat.newInstance(getActivity(), FIRST_REQUEST_CODE);
-    PreferenceManagerCompat.setFragment(mPreferenceManager, this);
-  }
-
-  @Override
-  public View onCreateView(LayoutInflater paramLayoutInflater, ViewGroup paramViewGroup, Bundle paramBundle) {
-    return paramLayoutInflater.inflate(R.layout.fxaccount_preference_list_fragment, paramViewGroup,
-        false);
-  }
-
-  @Override
-  public void onActivityCreated(Bundle savedInstanceState) {
-    super.onActivityCreated(savedInstanceState);
-
-    if (mHavePrefs) {
-      bindPreferences();
-    }
-
-    mInitDone = true;
-
-    if (savedInstanceState != null) {
-      Bundle container = savedInstanceState.getBundle(PREFERENCES_TAG);
-      if (container != null) {
-        final PreferenceScreen preferenceScreen = getPreferenceScreen();
-        if (preferenceScreen != null) {
-          preferenceScreen.restoreHierarchyState(container);
-        }
-      }
-    }
-  }
-
-  @Override
-  public void onStart() {
-    super.onStart();
-    PreferenceManagerCompat.setOnPreferenceTreeClickListener(mPreferenceManager, this);
-  }
-
-  @Override
-  public void onStop() {
-    super.onStop();
-    PreferenceManagerCompat.dispatchActivityStop(mPreferenceManager);
-    PreferenceManagerCompat.setOnPreferenceTreeClickListener(mPreferenceManager, null);
-  }
-
-  @Override
-  public void onDestroyView() {
-    mList = null;
-    mHandler.removeCallbacks(mRequestFocus);
-    mHandler.removeMessages(MSG_BIND_PREFERENCES);
-    super.onDestroyView();
-  }
-
-  @Override
-  public void onDestroy() {
-    super.onDestroy();
-    PreferenceManagerCompat.dispatchActivityDestroy(mPreferenceManager);
-  }
-
-  @Override
-  public void onSaveInstanceState(Bundle outState) {
-    super.onSaveInstanceState(outState);
-
-    final PreferenceScreen preferenceScreen = getPreferenceScreen();
-    if (preferenceScreen != null) {
-      Bundle container = new Bundle();
-      preferenceScreen.saveHierarchyState(container);
-      outState.putBundle(PREFERENCES_TAG, container);
-    }
-  }
-
-  @Override
-  public void onActivityResult(int requestCode, int resultCode, Intent data) {
-    super.onActivityResult(requestCode, resultCode, data);
-
-    PreferenceManagerCompat.dispatchActivityResult(mPreferenceManager, requestCode, resultCode, data);
-  }
-
-  /**
-   * Returns the {@link PreferenceManager} used by this fragment.
-   * @return The {@link PreferenceManager}.
-   */
-  public PreferenceManager getPreferenceManager() {
-    return mPreferenceManager;
-  }
-
-  /**
-   * Sets the root of the preference hierarchy that this fragment is showing.
-   *
-   * @param preferenceScreen The root {@link PreferenceScreen} of the preference hierarchy.
-   */
-  public void setPreferenceScreen(PreferenceScreen preferenceScreen) {
-    if (PreferenceManagerCompat.setPreferences(mPreferenceManager, preferenceScreen) && preferenceScreen != null) {
-      mHavePrefs = true;
-      if (mInitDone) {
-        postBindPreferences();
-      }
-    }
-  }
-
-  /**
-   * Gets the root of the preference hierarchy that this fragment is showing.
-   *
-   * @return The {@link PreferenceScreen} that is the root of the preference
-   *         hierarchy.
-   */
-  public PreferenceScreen getPreferenceScreen() {
-    return PreferenceManagerCompat.getPreferenceScreen(mPreferenceManager);
-  }
-
-  /**
-   * Adds preferences from activities that match the given {@link Intent}.
-   *
-   * @param intent The {@link Intent} to query activities.
-   */
-  public void addPreferencesFromIntent(Intent intent) {
-    requirePreferenceManager();
-
-    setPreferenceScreen(PreferenceManagerCompat.inflateFromIntent(mPreferenceManager, intent, getPreferenceScreen()));
-  }
-
-  /**
-   * Inflates the given XML resource and adds the preference hierarchy to the current
-   * preference hierarchy.
-   *
-   * @param preferencesResId The XML resource ID to inflate.
-   */
-  public void addPreferencesFromResource(int preferencesResId) {
-    requirePreferenceManager();
-
-    setPreferenceScreen(PreferenceManagerCompat.inflateFromResource(mPreferenceManager, getActivity(),
-        preferencesResId, getPreferenceScreen()));
-  }
-
-  /**
-   * {@inheritDoc}
-   */
-  @Override
-  public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen,
-      Preference preference) {
-    //if (preference.getFragment() != null &&
-    if (
-        getActivity() instanceof OnPreferenceStartFragmentCallback) {
-      return ((OnPreferenceStartFragmentCallback)getActivity()).onPreferenceStartFragment(
-          this, preference);
-    }
-    return false;
-  }
-
-  /**
-   * Finds a {@link Preference} based on its key.
-   *
-   * @param key The key of the preference to retrieve.
-   * @return The {@link Preference} with the key, or null.
-   * @see PreferenceGroup#findPreference(CharSequence)
-   */
-  public Preference findPreference(CharSequence key) {
-    if (mPreferenceManager == null) {
-      return null;
-    }
-    return mPreferenceManager.findPreference(key);
-  }
-
-  private void requirePreferenceManager() {
-    if (mPreferenceManager == null) {
-      throw new RuntimeException("This should be called after super.onCreate.");
-    }
-  }
-
-  private void postBindPreferences() {
-    if (mHandler.hasMessages(MSG_BIND_PREFERENCES)) return;
-    mHandler.obtainMessage(MSG_BIND_PREFERENCES).sendToTarget();
-  }
-
-  private void bindPreferences() {
-    final PreferenceScreen preferenceScreen = getPreferenceScreen();
-    if (preferenceScreen != null) {
-      preferenceScreen.bind(getListView());
-    }
-  }
-
-  public ListView getListView() {
-    ensureList();
-    return mList;
-  }
-
-  private void ensureList() {
-    if (mList != null) {
-      return;
-    }
-    View root = getView();
-    if (root == null) {
-      throw new IllegalStateException("Content view not yet created");
-    }
-    View rawListView = root.findViewById(android.R.id.list);
-    if (!(rawListView instanceof ListView)) {
-      throw new RuntimeException(
-          "Content has view with id attribute 'android.R.id.list' "
-              + "that is not a ListView class");
-    }
-    mList = (ListView)rawListView;
-    if (mList == null) {
-      throw new RuntimeException(
-          "Your content must have a ListView whose id attribute is " +
-          "'android.R.id.list'");
-    }
-    mList.setOnKeyListener(mListOnKeyListener);
-    mHandler.post(mRequestFocus);
-  }
-
-  private final OnKeyListener mListOnKeyListener = new OnKeyListener() {
-
-    @Override
-    public boolean onKey(View v, int keyCode, KeyEvent event) {
-      Object selectedItem = mList.getSelectedItem();
-      if (selectedItem instanceof Preference) {
-        @SuppressWarnings("unused")
-        View selectedView = mList.getSelectedView();
-        //return ((Preference)selectedItem).onKey(
-        //        selectedView, keyCode, event);
-        return false;
-      }
-      return false;
-    }
-
-  };
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/background/preferences/PreferenceManagerCompat.java b/mobile/android/services/src/main/java/org/mozilla/gecko/background/preferences/PreferenceManagerCompat.java
deleted file mode 100644
index 22c62e4..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/background/preferences/PreferenceManagerCompat.java
+++ /dev/null
@@ -1,226 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.mozilla.gecko.background.preferences;
-
-import java.lang.reflect.Constructor;
-import java.lang.reflect.Field;
-import java.lang.reflect.InvocationHandler;
-import java.lang.reflect.Method;
-import java.lang.reflect.Proxy;
-
-import android.app.Activity;
-import android.content.Context;
-import android.content.Intent;
-import android.preference.Preference;
-import android.preference.PreferenceManager;
-import android.preference.PreferenceScreen;
-import android.util.Log;
-
-public class PreferenceManagerCompat {
-
-  private static final String TAG = PreferenceManagerCompat.class.getSimpleName();
-
-  /**
-   * Interface definition for a callback to be invoked when a {@link Preference} in the hierarchy
-   * rooted at this {@link PreferenceScreen} is clicked.
-   */
-  interface OnPreferenceTreeClickListener {
-    /**
-     * Called when a preference in the tree rooted at this {@link PreferenceScreen} has been
-     * clicked.
-     *
-     * @param preferenceScreen The {@link PreferenceScreen} that the preference is located in.
-     * @param preference       The preference that was clicked.
-     *
-     * @return Whether the click was handled.
-     */
-    boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference);
-  }
-
-  static PreferenceManager newInstance(Activity activity, int firstRequestCode) {
-    try {
-      Constructor<PreferenceManager> c = PreferenceManager.class.getDeclaredConstructor(Activity.class, int.class);
-      c.setAccessible(true);
-      return c.newInstance(activity, firstRequestCode);
-    } catch (Exception e) {
-      Log.w(TAG, "Couldn't call constructor PreferenceManager by reflection", e);
-    }
-    return null;
-  }
-
-  /**
-   * Sets the owning preference fragment
-   */
-  static void setFragment(PreferenceManager manager, PreferenceFragment fragment) {
-    // stub
-  }
-
-  /**
-   * Sets the callback to be invoked when a {@link Preference} in the hierarchy rooted at this
-   * {@link PreferenceManager} is clicked.
-   *
-   * @param listener The callback to be invoked.
-   */
-  static void setOnPreferenceTreeClickListener(PreferenceManager manager, final OnPreferenceTreeClickListener listener) {
-    try {
-      Field onPreferenceTreeClickListener = PreferenceManager.class.getDeclaredField("mOnPreferenceTreeClickListener");
-      onPreferenceTreeClickListener.setAccessible(true);
-      if (listener != null) {
-        Object proxy = Proxy.newProxyInstance(
-          onPreferenceTreeClickListener.getType().getClassLoader(),
-          new Class<?>[] { onPreferenceTreeClickListener.getType() },
-          new InvocationHandler() {
-            @Override
-            public Object invoke(Object proxy, Method method, Object[] args) {
-              if (method.getName().equals("onPreferenceTreeClick")) {
-                return listener.onPreferenceTreeClick((PreferenceScreen) args[0], (Preference) args[1]);
-              } else {
-                return null;
-              }
-            }
-          });
-        onPreferenceTreeClickListener.set(manager, proxy);
-      } else {
-        onPreferenceTreeClickListener.set(manager, null);
-      }
-    } catch (Exception e) {
-      Log.w(TAG, "Couldn't set PreferenceManager.mOnPreferenceTreeClickListener by reflection", e);
-    }
-  }
-
-  /**
-   * Inflates a preference hierarchy from the preference hierarchies of {@link Activity Activities}
-   * that match the given {@link Intent}. An {@link Activity} defines its preference hierarchy with
-   * meta-data using the {@link #METADATA_KEY_PREFERENCES} key.
-   * <p/>
-   * If a preference hierarchy is given, the new preference hierarchies will be merged in.
-   *
-   * @param queryIntent     The intent to match activities.
-   * @param rootPreferences Optional existing hierarchy to merge the new hierarchies into.
-   *
-   * @return The root hierarchy (if one was not provided, the new hierarchy's root).
-   */
-  static PreferenceScreen inflateFromIntent(PreferenceManager manager, Intent intent, PreferenceScreen screen) {
-    try {
-      Method m = PreferenceManager.class.getDeclaredMethod("inflateFromIntent", Intent.class, PreferenceScreen.class);
-      m.setAccessible(true);
-      PreferenceScreen prefScreen = (PreferenceScreen) m.invoke(manager, intent, screen);
-      return prefScreen;
-    } catch (Exception e) {
-      Log.w(TAG, "Couldn't call PreferenceManager.inflateFromIntent by reflection", e);
-    }
-    return null;
-  }
-
-  /**
-   * Inflates a preference hierarchy from XML. If a preference hierarchy is given, the new
-   * preference hierarchies will be merged in.
-   *
-   * @param context         The context of the resource.
-   * @param resId           The resource ID of the XML to inflate.
-   * @param rootPreferences Optional existing hierarchy to merge the new hierarchies into.
-   *
-   * @return The root hierarchy (if one was not provided, the new hierarchy's root).
-   *
-   * @hide
-   */
-  static PreferenceScreen inflateFromResource(PreferenceManager manager, Activity activity, int resId, PreferenceScreen screen) {
-    try {
-      Method m = PreferenceManager.class.getDeclaredMethod("inflateFromResource", Context.class, int.class, PreferenceScreen.class);
-      m.setAccessible(true);
-      PreferenceScreen prefScreen = (PreferenceScreen) m.invoke(manager, activity, resId, screen);
-      return prefScreen;
-    } catch (Exception e) {
-      Log.w(TAG, "Couldn't call PreferenceManager.inflateFromResource by reflection", e);
-    }
-    return null;
-  }
-
-  /**
-   * Returns the root of the preference hierarchy managed by this class.
-   *
-   * @return The {@link PreferenceScreen} object that is at the root of the hierarchy.
-   */
-  static PreferenceScreen getPreferenceScreen(PreferenceManager manager) {
-    try {
-      Method m = PreferenceManager.class.getDeclaredMethod("getPreferenceScreen");
-      m.setAccessible(true);
-      return (PreferenceScreen) m.invoke(manager);
-    } catch (Exception e) {
-      Log.w(TAG, "Couldn't call PreferenceManager.getPreferenceScreen by reflection", e);
-    }
-    return null;
-  }
-
-  /**
-   * Called by the {@link PreferenceManager} to dispatch a subactivity result.
-   */
-  static void dispatchActivityResult(PreferenceManager manager, int requestCode, int resultCode, Intent data) {
-    try {
-      Method m = PreferenceManager.class.getDeclaredMethod("dispatchActivityResult", int.class, int.class, Intent.class);
-      m.setAccessible(true);
-      m.invoke(manager, requestCode, resultCode, data);
-    } catch (Exception e) {
-      Log.w(TAG, "Couldn't call PreferenceManager.dispatchActivityResult by reflection", e);
-    }
-  }
-
-  /**
-   * Called by the {@link PreferenceManager} to dispatch the activity stop event.
-   */
-  static void dispatchActivityStop(PreferenceManager manager) {
-    try {
-      Method m = PreferenceManager.class.getDeclaredMethod("dispatchActivityStop");
-      m.setAccessible(true);
-      m.invoke(manager);
-    } catch (Exception e) {
-      Log.w(TAG, "Couldn't call PreferenceManager.dispatchActivityStop by reflection", e);
-    }
-  }
-
-  /**
-   * Called by the {@link PreferenceManager} to dispatch the activity destroy event.
-   */
-  static void dispatchActivityDestroy(PreferenceManager manager) {
-    try {
-      Method m = PreferenceManager.class.getDeclaredMethod("dispatchActivityDestroy");
-      m.setAccessible(true);
-      m.invoke(manager);
-    } catch (Exception e) {
-      Log.w(TAG, "Couldn't call PreferenceManager.dispatchActivityDestroy by reflection", e);
-    }
-  }
-
-  /**
-   * Sets the root of the preference hierarchy.
-   *
-   * @param preferenceScreen The root {@link PreferenceScreen} of the preference hierarchy.
-   *
-   * @return Whether the {@link PreferenceScreen} given is different than the previous.
-   */
-  static boolean setPreferences(PreferenceManager manager, PreferenceScreen screen) {
-    try {
-      Method m = PreferenceManager.class.getDeclaredMethod("setPreferences", PreferenceScreen.class);
-      m.setAccessible(true);
-      return ((Boolean) m.invoke(manager, screen));
-    } catch (Exception e) {
-      Log.w(TAG, "Couldn't call PreferenceManager.setPreferences by reflection", e);
-    }
-    return false;
-  }
-
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/browserid/ASNUtils.java b/mobile/android/services/src/main/java/org/mozilla/gecko/browserid/ASNUtils.java
deleted file mode 100644
index b032067..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/browserid/ASNUtils.java
+++ /dev/null
@@ -1,82 +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.browserid;
-
-
-/**
- * Java produces signature in ASN.1 format. Here's some hard-coded encoding and decoding
- * code, courtesy of a comment in
- * <a href="http://stackoverflow.com/questions/10921733/how-sign-method-of-the-digital-signature-combines-the-r-s-values-in-to-array">http://stackoverflow.com/questions/10921733/how-sign-method-of-the-digital-signature-combines-the-r-s-values-in-to-array</a>.
- */
-public class ASNUtils {
-  /**
-   * Decode two short arrays from ASN.1 bytes.
-   * @param input to extract.
-   * @return length 2 array of byte arrays.
-   */
-  public static byte[][] decodeTwoArraysFromASN1(byte[] input) throws IllegalArgumentException {
-    if (input == null) {
-      throw new IllegalArgumentException("input must not be null");
-    }
-    if (input.length <= 3)
-      throw new IllegalArgumentException("bad length");
-    if (input[0] != 0x30)
-      throw new IllegalArgumentException("bad encoding");
-    if ((input[1] & ((byte) 0x80)) != 0)
-      throw new IllegalArgumentException("bad length encoding");
-    if (input[2] != 0x02)
-      throw new IllegalArgumentException("bad encoding");
-    if ((input[3] & ((byte) 0x80)) != 0)
-      throw new IllegalArgumentException("bad length encoding");
-    byte rLength = input[3];
-    if (input.length <= 5 + rLength)
-      throw new IllegalArgumentException("bad length");
-    if (input[4 + rLength] != 0x02)
-      throw new IllegalArgumentException("bad encoding");
-    if ((input[5 + rLength] & (byte) 0x80) !=0)
-      throw new IllegalArgumentException("bad length encoding");
-    byte sLength = input[5 + rLength];
-    if (input.length != 6 + sLength + rLength)
-      throw new IllegalArgumentException("bad length");
-    byte[] rArr = new byte[rLength];
-    byte[] sArr = new byte[sLength];
-    System.arraycopy(input, 4, rArr, 0, rLength);
-    System.arraycopy(input, 6 + rLength, sArr, 0, sLength);
-    return new byte[][] { rArr, sArr };
-  }
-
-  /**
-   * Encode two short arrays into ASN.1 bytes.
-   * @param first array to encode.
-   * @param second array to encode.
-   * @return array.
-   */
-  public static byte[] encodeTwoArraysToASN1(byte[] first, byte[] second) throws IllegalArgumentException {
-    if (first == null) {
-      throw new IllegalArgumentException("first must not be null");
-    }
-    if (second == null) {
-      throw new IllegalArgumentException("second must not be null");
-    }
-    byte[] output = new byte[6 + first.length + second.length];
-    output[0] = 0x30;
-    if (4 + first.length + second.length > 255)
-      throw new IllegalArgumentException("bad length");
-    output[1] = (byte) (4 + first.length + second.length);
-    if ((output[1] & ((byte) 0x80)) != 0)
-      throw new IllegalArgumentException("bad length encoding");
-    output[2] = 0x02;
-    output[3] = (byte) first.length;
-    if ((output[3] & ((byte) 0x80)) != 0)
-      throw new IllegalArgumentException("bad length encoding");
-    System.arraycopy(first, 0, output, 4, first.length);
-    output[4 + first.length] = 0x02;
-    output[5 + first.length] = (byte) second.length;
-    if ((output[5 + first.length] & ((byte) 0x80)) != 0)
-      throw new IllegalArgumentException("bad length encoding");
-    System.arraycopy(second, 0, output, 6 + first.length, second.length);
-    return output;
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/browserid/BrowserIDKeyPair.java b/mobile/android/services/src/main/java/org/mozilla/gecko/browserid/BrowserIDKeyPair.java
deleted file mode 100644
index 7283a02..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/browserid/BrowserIDKeyPair.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.browserid;
-
-import org.mozilla.gecko.sync.ExtendedJSONObject;
-
-public class BrowserIDKeyPair {
-  public static final String JSON_KEY_PRIVATEKEY = "privateKey";
-  public static final String JSON_KEY_PUBLICKEY = "publicKey";
-
-  protected final SigningPrivateKey privateKey;
-  protected final VerifyingPublicKey publicKey;
-
-  public BrowserIDKeyPair(SigningPrivateKey privateKey, VerifyingPublicKey publicKey) {
-    this.privateKey = privateKey;
-    this.publicKey = publicKey;
-  }
-
-  public SigningPrivateKey getPrivate() {
-    return this.privateKey;
-  }
-
-  public VerifyingPublicKey getPublic() {
-    return this.publicKey;
-  }
-
-  public ExtendedJSONObject toJSONObject() {
-    ExtendedJSONObject o = new ExtendedJSONObject();
-    o.put(JSON_KEY_PRIVATEKEY, privateKey.toJSONObject());
-    o.put(JSON_KEY_PUBLICKEY, publicKey.toJSONObject());
-    return o;
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/browserid/DSACryptoImplementation.java b/mobile/android/services/src/main/java/org/mozilla/gecko/browserid/DSACryptoImplementation.java
deleted file mode 100644
index a04a89c..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/browserid/DSACryptoImplementation.java
+++ /dev/null
@@ -1,255 +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.browserid;
-
-import android.annotation.SuppressLint;
-
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.sync.ExtendedJSONObject;
-import org.mozilla.gecko.sync.NonObjectJSONException;
-import org.mozilla.gecko.sync.Utils;
-import org.mozilla.gecko.util.PRNGFixes;
-
-import java.math.BigInteger;
-import java.security.GeneralSecurityException;
-import java.security.KeyFactory;
-import java.security.KeyPair;
-import java.security.KeyPairGenerator;
-import java.security.NoSuchAlgorithmException;
-import java.security.Signature;
-import java.security.interfaces.DSAParams;
-import java.security.interfaces.DSAPrivateKey;
-import java.security.interfaces.DSAPublicKey;
-import java.security.spec.DSAPrivateKeySpec;
-import java.security.spec.DSAPublicKeySpec;
-import java.security.spec.InvalidKeySpecException;
-import java.security.spec.KeySpec;
-
-public class DSACryptoImplementation {
-  private static final String LOG_TAG = DSACryptoImplementation.class.getSimpleName();
-
-  public static final String SIGNATURE_ALGORITHM = "SHA1withDSA";
-  public static final int SIGNATURE_LENGTH_BYTES = 40; // DSA signatures are always 40 bytes long.
-
-  /**
-   * Parameters are serialized as hex strings. Hex-versus-decimal was
-   * reverse-engineered from what the Persona public verifier accepted. We
-   * expect to follow the JOSE/JWT spec as it solidifies, and that will probably
-   * mean unifying this base.
-   */
-  protected static final int SERIALIZATION_BASE = 16;
-
-  protected static class DSAVerifyingPublicKey implements VerifyingPublicKey {
-    protected final DSAPublicKey publicKey;
-
-    public DSAVerifyingPublicKey(DSAPublicKey publicKey) {
-      this.publicKey = publicKey;
-    }
-
-    /**
-     * Serialize to a JSON object.
-     * <p>
-     * Parameters are serialized as hex strings. Hex-versus-decimal was
-     * reverse-engineered from what the Persona public verifier accepted.
-     */
-    @Override
-    public ExtendedJSONObject toJSONObject() {
-      DSAParams params = publicKey.getParams();
-      ExtendedJSONObject o = new ExtendedJSONObject();
-      o.put("algorithm", "DS");
-      o.put("y", publicKey.getY().toString(SERIALIZATION_BASE));
-      o.put("g", params.getG().toString(SERIALIZATION_BASE));
-      o.put("p", params.getP().toString(SERIALIZATION_BASE));
-      o.put("q", params.getQ().toString(SERIALIZATION_BASE));
-      return o;
-    }
-
-    @Override
-    public boolean verifyMessage(byte[] bytes, byte[] signature)
-        throws GeneralSecurityException {
-      if (bytes == null) {
-        throw new IllegalArgumentException("bytes must not be null");
-      }
-      if (signature == null) {
-        throw new IllegalArgumentException("signature must not be null");
-      }
-      if (signature.length != SIGNATURE_LENGTH_BYTES) {
-        return false;
-      }
-      byte[] first = new byte[signature.length / 2];
-      byte[] second = new byte[signature.length / 2];
-      System.arraycopy(signature, 0, first, 0, first.length);
-      System.arraycopy(signature, first.length, second, 0, second.length);
-      BigInteger r = new BigInteger(Utils.byte2Hex(first), 16);
-      BigInteger s = new BigInteger(Utils.byte2Hex(second), 16);
-      // This is awful, but encoding an extra 0 byte works better on devices.
-      byte[] encoded = ASNUtils.encodeTwoArraysToASN1(
-          Utils.hex2Byte(r.toString(16), 1 + SIGNATURE_LENGTH_BYTES / 2),
-          Utils.hex2Byte(s.toString(16), 1 + SIGNATURE_LENGTH_BYTES / 2));
-
-      final Signature signer = Signature.getInstance(SIGNATURE_ALGORITHM);
-      signer.initVerify(publicKey);
-      signer.update(bytes);
-      return signer.verify(encoded);
-    }
-  }
-
-  protected static class DSASigningPrivateKey implements SigningPrivateKey {
-    protected final DSAPrivateKey privateKey;
-
-    public DSASigningPrivateKey(DSAPrivateKey privateKey) {
-      this.privateKey = privateKey;
-    }
-
-    @Override
-    public String getAlgorithm() {
-      return "DS" + (privateKey.getParams().getP().bitLength() + 7)/8;
-    }
-
-    /**
-     * Serialize to a JSON object.
-     * <p>
-     * Parameters are serialized as decimal strings. Hex-versus-decimal was
-     * reverse-engineered from what the Persona public verifier accepted.
-     */
-    @Override
-    public ExtendedJSONObject toJSONObject() {
-      DSAParams params = privateKey.getParams();
-      ExtendedJSONObject o = new ExtendedJSONObject();
-      o.put("algorithm", "DS");
-      o.put("x", privateKey.getX().toString(SERIALIZATION_BASE));
-      o.put("g", params.getG().toString(SERIALIZATION_BASE));
-      o.put("p", params.getP().toString(SERIALIZATION_BASE));
-      o.put("q", params.getQ().toString(SERIALIZATION_BASE));
-      return o;
-    }
-
-    @SuppressLint("TrulyRandom")
-    @Override
-    public byte[] signMessage(byte[] bytes)
-        throws GeneralSecurityException {
-      if (bytes == null) {
-        throw new IllegalArgumentException("bytes must not be null");
-      }
-
-      try {
-        PRNGFixes.apply();
-      } catch (Exception e) {
-        // Not much to be done here: it was weak before, and we couldn't patch it, so it's weak now.  Not worth aborting.
-        Logger.error(LOG_TAG, "Got exception applying PRNGFixes!  Cryptographic data produced on this device may be weak.  Ignoring.", e);
-      }
-
-      final Signature signer = Signature.getInstance(SIGNATURE_ALGORITHM);
-      signer.initSign(privateKey);
-      signer.update(bytes);
-      final byte[] signature = signer.sign();
-
-      final byte[][] arrays = ASNUtils.decodeTwoArraysFromASN1(signature);
-      BigInteger r = new BigInteger(arrays[0]);
-      BigInteger s = new BigInteger(arrays[1]);
-      // This is awful, but signatures are always 40 bytes long.
-      byte[] decoded = Utils.concatAll(
-          Utils.hex2Byte(r.toString(16), SIGNATURE_LENGTH_BYTES / 2),
-          Utils.hex2Byte(s.toString(16), SIGNATURE_LENGTH_BYTES / 2));
-      return decoded;
-    }
-  }
-
-  public static BrowserIDKeyPair generateKeyPair(int keysize)
-      throws NoSuchAlgorithmException {
-    final KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("DSA");
-    keyPairGenerator.initialize(keysize);
-    final KeyPair keyPair = keyPairGenerator.generateKeyPair();
-    DSAPrivateKey privateKey = (DSAPrivateKey) keyPair.getPrivate();
-    DSAPublicKey publicKey = (DSAPublicKey) keyPair.getPublic();
-    return new BrowserIDKeyPair(new DSASigningPrivateKey(privateKey), new DSAVerifyingPublicKey(publicKey));
-  }
-
-  public static SigningPrivateKey createPrivateKey(BigInteger x, BigInteger p, BigInteger q, BigInteger g) throws NoSuchAlgorithmException, InvalidKeySpecException {
-    if (x == null) {
-      throw new IllegalArgumentException("x must not be null");
-    }
-    if (p == null) {
-      throw new IllegalArgumentException("p must not be null");
-    }
-    if (q == null) {
-      throw new IllegalArgumentException("q must not be null");
-    }
-    if (g == null) {
-      throw new IllegalArgumentException("g must not be null");
-    }
-    KeySpec keySpec = new DSAPrivateKeySpec(x, p, q, g);
-    KeyFactory keyFactory = KeyFactory.getInstance("DSA");
-    DSAPrivateKey privateKey = (DSAPrivateKey) keyFactory.generatePrivate(keySpec);
-    return new DSASigningPrivateKey(privateKey);
-  }
-
-  public static VerifyingPublicKey createPublicKey(BigInteger y, BigInteger p, BigInteger q, BigInteger g) throws NoSuchAlgorithmException, InvalidKeySpecException {
-    if (y == null) {
-      throw new IllegalArgumentException("n must not be null");
-    }
-    if (p == null) {
-      throw new IllegalArgumentException("p must not be null");
-    }
-    if (q == null) {
-      throw new IllegalArgumentException("q must not be null");
-    }
-    if (g == null) {
-      throw new IllegalArgumentException("g must not be null");
-    }
-    KeySpec keySpec = new DSAPublicKeySpec(y, p, q, g);
-    KeyFactory keyFactory = KeyFactory.getInstance("DSA");
-    DSAPublicKey publicKey = (DSAPublicKey) keyFactory.generatePublic(keySpec);
-    return new DSAVerifyingPublicKey(publicKey);
-  }
-
-  public static SigningPrivateKey createPrivateKey(ExtendedJSONObject o) throws InvalidKeySpecException, NoSuchAlgorithmException {
-    String algorithm = o.getString("algorithm");
-    if (!"DS".equals(algorithm)) {
-      throw new InvalidKeySpecException("algorithm must equal DS, was " + algorithm);
-    }
-    try {
-      BigInteger x = new BigInteger(o.getString("x"), SERIALIZATION_BASE);
-      BigInteger p = new BigInteger(o.getString("p"), SERIALIZATION_BASE);
-      BigInteger q = new BigInteger(o.getString("q"), SERIALIZATION_BASE);
-      BigInteger g = new BigInteger(o.getString("g"), SERIALIZATION_BASE);
-      return createPrivateKey(x, p, q, g);
-    } catch (NullPointerException | NumberFormatException e) {
-      throw new InvalidKeySpecException("x, p, q, and g must be integers encoded as strings, base " + SERIALIZATION_BASE);
-    }
-  }
-
-  public static VerifyingPublicKey createPublicKey(ExtendedJSONObject o) throws InvalidKeySpecException, NoSuchAlgorithmException {
-    String algorithm = o.getString("algorithm");
-    if (!"DS".equals(algorithm)) {
-      throw new InvalidKeySpecException("algorithm must equal DS, was " + algorithm);
-    }
-    try {
-      BigInteger y = new BigInteger(o.getString("y"), SERIALIZATION_BASE);
-      BigInteger p = new BigInteger(o.getString("p"), SERIALIZATION_BASE);
-      BigInteger q = new BigInteger(o.getString("q"), SERIALIZATION_BASE);
-      BigInteger g = new BigInteger(o.getString("g"), SERIALIZATION_BASE);
-      return createPublicKey(y, p, q, g);
-    } catch (NullPointerException | NumberFormatException e) {
-      throw new InvalidKeySpecException("y, p, q, and g must be integers encoded as strings, base " + SERIALIZATION_BASE);
-    }
-  }
-
-  public static BrowserIDKeyPair fromJSONObject(ExtendedJSONObject o) throws InvalidKeySpecException, NoSuchAlgorithmException {
-    try {
-      ExtendedJSONObject privateKey = o.getObject(BrowserIDKeyPair.JSON_KEY_PRIVATEKEY);
-      ExtendedJSONObject publicKey = o.getObject(BrowserIDKeyPair.JSON_KEY_PUBLICKEY);
-      if (privateKey == null) {
-        throw new InvalidKeySpecException("privateKey must not be null");
-      }
-      if (publicKey == null) {
-        throw new InvalidKeySpecException("publicKey must not be null");
-      }
-      return new BrowserIDKeyPair(createPrivateKey(privateKey), createPublicKey(publicKey));
-    } catch (NonObjectJSONException e) {
-      throw new InvalidKeySpecException("privateKey and publicKey must be JSON objects");
-    }
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/browserid/JSONWebTokenUtils.java b/mobile/android/services/src/main/java/org/mozilla/gecko/browserid/JSONWebTokenUtils.java
deleted file mode 100644
index 207accc..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/browserid/JSONWebTokenUtils.java
+++ /dev/null
@@ -1,245 +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.browserid;
-
-import org.json.simple.JSONObject;
-import org.mozilla.apache.commons.codec.binary.Base64;
-import org.mozilla.apache.commons.codec.binary.StringUtils;
-import org.mozilla.gecko.sync.ExtendedJSONObject;
-import org.mozilla.gecko.sync.NonObjectJSONException;
-import org.mozilla.gecko.sync.Utils;
-
-import java.io.IOException;
-import java.io.UnsupportedEncodingException;
-import java.security.GeneralSecurityException;
-import java.util.ArrayList;
-import java.util.TreeMap;
-
-/**
- * Encode and decode JSON Web Tokens.
- * <p>
- * Reverse-engineered from the Node.js jwcrypto library at
- * <a href="https://github.com/mozilla/jwcrypto">https://github.com/mozilla/jwcrypto</a>
- * and informed by the informal draft standard "JSON Web Token (JWT)" at
- * <a href="http://self-issued.info/docs/draft-ietf-oauth-json-web-token.html">http://self-issued.info/docs/draft-ietf-oauth-json-web-token.html</a>.
- */
-public class JSONWebTokenUtils {
-  public static final long DEFAULT_CERTIFICATE_DURATION_IN_MILLISECONDS = 60 * 60 * 1000;
-  public static final long DEFAULT_ASSERTION_DURATION_IN_MILLISECONDS = 60 * 60 * 1000;
-  public static final long DEFAULT_FUTURE_EXPIRES_AT_IN_MILLISECONDS = 9999999999999L;
-  public static final String DEFAULT_CERTIFICATE_ISSUER = "127.0.0.1";
-  public static final String DEFAULT_ASSERTION_ISSUER = "127.0.0.1";
-
-  public static String encode(String payload, SigningPrivateKey privateKey) throws UnsupportedEncodingException, GeneralSecurityException  {
-    final ExtendedJSONObject header = new ExtendedJSONObject();
-    header.put("alg", privateKey.getAlgorithm());
-    String encodedHeader  = Base64.encodeBase64URLSafeString(header.toJSONString().getBytes("UTF-8"));
-    String encodedPayload = Base64.encodeBase64URLSafeString(payload.getBytes("UTF-8"));
-    ArrayList<String> segments = new ArrayList<String>();
-    segments.add(encodedHeader);
-    segments.add(encodedPayload);
-    byte[] message = Utils.toDelimitedString(".", segments).getBytes("UTF-8");
-    byte[] signature = privateKey.signMessage(message);
-    segments.add(Base64.encodeBase64URLSafeString(signature));
-    return Utils.toDelimitedString(".", segments);
-  }
-
-  public static String decode(String token, VerifyingPublicKey publicKey) throws GeneralSecurityException, UnsupportedEncodingException  {
-    if (token == null) {
-      throw new IllegalArgumentException("token must not be null");
-    }
-    String[] segments = token.split("\\.");
-    if (segments == null || segments.length != 3) {
-      throw new GeneralSecurityException("malformed token");
-    }
-    byte[] message = (segments[0] + "." + segments[1]).getBytes("UTF-8");
-    byte[] signature = Base64.decodeBase64(segments[2]);
-    boolean verifies = publicKey.verifyMessage(message, signature);
-    if (!verifies) {
-      throw new GeneralSecurityException("bad signature");
-    }
-    String payload = StringUtils.newStringUtf8(Base64.decodeBase64(segments[1]));
-    return payload;
-  }
-
-  /**
-   * Public for testing.
-   */
-  @SuppressWarnings("unchecked")
-  public static String getPayloadString(String payloadString, String audience, String issuer,
-      Long issuedAt, long expiresAt) throws NonObjectJSONException, IOException {
-    ExtendedJSONObject payload;
-    if (payloadString != null) {
-      payload = new ExtendedJSONObject(payloadString);
-    } else {
-      payload = new ExtendedJSONObject();
-    }
-    if (audience != null) {
-      payload.put("aud", audience);
-    }
-    payload.put("iss", issuer);
-    if (issuedAt != null) {
-      payload.put("iat", issuedAt);
-    }
-    payload.put("exp", expiresAt);
-    // TreeMap so that keys are sorted. A small attempt to keep output stable over time.
-    return JSONObject.toJSONString(new TreeMap<Object, Object>(payload.object));
-  }
-
-  protected static String getCertificatePayloadString(VerifyingPublicKey publicKeyToSign, String email) throws NonObjectJSONException, IOException  {
-    ExtendedJSONObject payload = new ExtendedJSONObject();
-    ExtendedJSONObject principal = new ExtendedJSONObject();
-    principal.put("email", email);
-    payload.put("principal", principal);
-    payload.put("public-key", publicKeyToSign.toJSONObject());
-    return payload.toJSONString();
-  }
-
-  public static String createCertificate(VerifyingPublicKey publicKeyToSign, String email,
-      String issuer, long issuedAt, long expiresAt, SigningPrivateKey privateKey) throws NonObjectJSONException, IOException, GeneralSecurityException  {
-    String certificatePayloadString = getCertificatePayloadString(publicKeyToSign, email);
-    String payloadString = getPayloadString(certificatePayloadString, null, issuer, issuedAt, expiresAt);
-    return JSONWebTokenUtils.encode(payloadString, privateKey);
-  }
-
-  /**
-   * Create a Browser ID assertion.
-   *
-   * @param privateKeyToSignWith
-   *          private key to sign assertion with.
-   * @param certificate
-   *          to include in assertion; no attempt is made to ensure the
-   *          certificate is valid, or corresponds to the private key, or any
-   *          other condition.
-   * @param audience
-   *          to produce assertion for.
-   * @param issuer
-   *          to produce assertion for.
-   * @param issuedAt
-   *          timestamp for assertion, in milliseconds since the epoch; if null,
-   *          no timestamp is included.
-   * @param expiresAt
-   *          expiration timestamp for assertion, in milliseconds since the epoch.
-   * @return assertion.
-   * @throws NonObjectJSONException
-   * @throws IOException
-   * @throws GeneralSecurityException
-   */
-  public static String createAssertion(SigningPrivateKey privateKeyToSignWith, String certificate, String audience,
-      String issuer, Long issuedAt, long expiresAt) throws NonObjectJSONException, IOException, GeneralSecurityException  {
-    String emptyAssertionPayloadString = "{}";
-    String payloadString = getPayloadString(emptyAssertionPayloadString, audience, issuer, issuedAt, expiresAt);
-    String signature = JSONWebTokenUtils.encode(payloadString, privateKeyToSignWith);
-    return certificate + "~" + signature;
-  }
-
-  /**
-   * For debugging only!
-   *
-   * @param input
-   *          certificate to dump.
-   * @return non-null object with keys header, payload, signature if the
-   *         certificate is well-formed.
-   */
-  public static ExtendedJSONObject parseCertificate(String input) {
-    try {
-      String[] parts = input.split("\\.");
-      if (parts.length != 3) {
-        return null;
-      }
-      String cHeader = new String(Base64.decodeBase64(parts[0]));
-      String cPayload = new String(Base64.decodeBase64(parts[1]));
-      String cSignature = Utils.byte2Hex(Base64.decodeBase64(parts[2]));
-      ExtendedJSONObject o = new ExtendedJSONObject();
-      o.put("header", new ExtendedJSONObject(cHeader));
-      o.put("payload", new ExtendedJSONObject(cPayload));
-      o.put("signature", cSignature);
-      return o;
-    } catch (Exception e) {
-      return null;
-    }
-  }
-
-  /**
-   * For debugging only!
-   *
-   * @param input certificate to dump.
-   * @return true if the certificate is well-formed.
-   */
-  public static boolean dumpCertificate(String input) {
-    ExtendedJSONObject c = parseCertificate(input);
-    try {
-      if (c == null) {
-        System.out.println("Malformed certificate -- got exception trying to dump contents.");
-        return false;
-      }
-      System.out.println("certificate header:    " + c.getObject("header").toJSONString());
-      System.out.println("certificate payload:   " + c.getObject("payload").toJSONString());
-      System.out.println("certificate signature: " + c.getString("signature"));
-      return true;
-    } catch (Exception e) {
-      System.out.println("Malformed certificate -- got exception trying to dump contents.");
-      return false;
-    }
-  }
-
-  /**
-   * For debugging only!
-   *
-   * @param input assertion to dump.
-   * @return true if the assertion is well-formed.
-   */
-  public static ExtendedJSONObject parseAssertion(String input) {
-    try {
-      String[] parts = input.split("~");
-      if (parts.length != 2) {
-        return null;
-      }
-      String certificate = parts[0];
-      String assertion = parts[1];
-      parts = assertion.split("\\.");
-      if (parts.length != 3) {
-        return null;
-      }
-      String aHeader = new String(Base64.decodeBase64(parts[0]));
-      String aPayload = new String(Base64.decodeBase64(parts[1]));
-      String aSignature = Utils.byte2Hex(Base64.decodeBase64(parts[2]));
-      // We do all the assertion parsing *before* dumping the certificate in
-      // case there's a malformed assertion.
-      ExtendedJSONObject o = new ExtendedJSONObject();
-      o.put("header", new ExtendedJSONObject(aHeader));
-      o.put("payload", new ExtendedJSONObject(aPayload));
-      o.put("signature", aSignature);
-      o.put("certificate", certificate);
-      return o;
-    } catch (Exception e) {
-      return null;
-    }
-  }
-
-  /**
-   * For debugging only!
-   *
-   * @param input assertion to dump.
-   * @return true if the assertion is well-formed.
-   */
-  public static boolean dumpAssertion(String input) {
-    ExtendedJSONObject a = parseAssertion(input);
-    try {
-      if (a == null) {
-        System.out.println("Malformed assertion -- got exception trying to dump contents.");
-        return false;
-      }
-      dumpCertificate(a.getString("certificate"));
-      System.out.println("assertion   header:    " + a.getObject("header").toJSONString());
-      System.out.println("assertion   payload:   " + a.getObject("payload").toJSONString());
-      System.out.println("assertion   signature: " + a.getString("signature"));
-      return true;
-    } catch (Exception e) {
-      System.out.println("Malformed assertion -- got exception trying to dump contents.");
-      return false;
-    }
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/browserid/MockMyIDTokenFactory.java b/mobile/android/services/src/main/java/org/mozilla/gecko/browserid/MockMyIDTokenFactory.java
deleted file mode 100644
index c807d4c..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/browserid/MockMyIDTokenFactory.java
+++ /dev/null
@@ -1,128 +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.browserid;
-
-import java.math.BigInteger;
-import java.security.NoSuchAlgorithmException;
-import java.security.spec.InvalidKeySpecException;
-
-/**
- * Generate certificates and assertions backed by mockmyid.com's private key.
- * <p>
- * These artifacts are for testing only.
- */
-public class MockMyIDTokenFactory {
-  public static final BigInteger MOCKMYID_x = new BigInteger("385cb3509f086e110c5e24bdd395a84b335a09ae", 16);
-  public static final BigInteger MOCKMYID_y = new BigInteger("738ec929b559b604a232a9b55a5295afc368063bb9c20fac4e53a74970a4db7956d48e4c7ed523405f629b4cc83062f13029c4d615bbacb8b97f5e56f0c7ac9bc1d4e23809889fa061425c984061fca1826040c399715ce7ed385c4dd0d402256912451e03452d3c961614eb458f188e3e8d2782916c43dbe2e571251ce38262", 16);
-  public static final BigInteger MOCKMYID_p = new BigInteger("ff600483db6abfc5b45eab78594b3533d550d9f1bf2a992a7a8daa6dc34f8045ad4e6e0c429d334eeeaaefd7e23d4810be00e4cc1492cba325ba81ff2d5a5b305a8d17eb3bf4a06a349d392e00d329744a5179380344e82a18c47933438f891e22aeef812d69c8f75e326cb70ea000c3f776dfdbd604638c2ef717fc26d02e17", 16);
-  public static final BigInteger MOCKMYID_q = new BigInteger("e21e04f911d1ed7991008ecaab3bf775984309c3", 16);
-  public static final BigInteger MOCKMYID_g = new BigInteger("c52a4a0ff3b7e61fdf1867ce84138369a6154f4afa92966e3c827e25cfa6cf508b90e5de419e1337e07a2e9e2a3cd5dea704d175f8ebf6af397d69e110b96afb17c7a03259329e4829b0d03bbc7896b15b4ade53e130858cc34d96269aa89041f409136c7242a38895c9d5bccad4f389af1d7a4bd1398bd072dffa896233397a", 16);
-
-  // Computed lazily by static <code>getMockMyIDPrivateKey</code>.
-  protected static SigningPrivateKey cachedMockMyIDPrivateKey;
-
-  public static SigningPrivateKey getMockMyIDPrivateKey() throws NoSuchAlgorithmException, InvalidKeySpecException {
-    if (cachedMockMyIDPrivateKey == null) {
-      cachedMockMyIDPrivateKey = DSACryptoImplementation.createPrivateKey(MOCKMYID_x, MOCKMYID_p, MOCKMYID_q, MOCKMYID_g);
-    }
-    return cachedMockMyIDPrivateKey;
-  }
-
-  /**
-   * Sign a public key asserting ownership of username at mockmyid.com with
-   * mockmyid.com's private key.
-   *
-   * @param publicKeyToSign
-   *          public key to sign.
-   * @param username
-   *          sign username at mockmyid.com
-   * @param issuedAt
-   *          timestamp for certificate, in milliseconds since the epoch.
-   * @param expiresAt
-   *          expiration timestamp for certificate, in milliseconds since the epoch.
-   * @return encoded certificate string.
-   * @throws Exception
-   */
-  public String createMockMyIDCertificate(final VerifyingPublicKey publicKeyToSign, String username,
-      final long issuedAt, final long expiresAt)
-          throws Exception {
-    if (!username.endsWith("@mockmyid.com")) {
-      username = username + "@mockmyid.com";
-    }
-    SigningPrivateKey mockMyIdPrivateKey = getMockMyIDPrivateKey();
-    return JSONWebTokenUtils.createCertificate(publicKeyToSign, username, "mockmyid.com", issuedAt, expiresAt, mockMyIdPrivateKey);
-  }
-
-  /**
-   * Sign a public key asserting ownership of username at mockmyid.com with
-   * mockmyid.com's private key.
-   *
-   * @param publicKeyToSign
-   *          public key to sign.
-   * @param username
-   *          sign username at mockmyid.com
-   * @return encoded certificate string.
-   * @throws Exception
-   */
-  public String createMockMyIDCertificate(final VerifyingPublicKey publicKeyToSign, final String username)
-      throws Exception {
-    long ciat = System.currentTimeMillis();
-    long cexp = ciat + JSONWebTokenUtils.DEFAULT_CERTIFICATE_DURATION_IN_MILLISECONDS;
-    return createMockMyIDCertificate(publicKeyToSign, username, ciat, cexp);
-  }
-
-  /**
-   * Generate an assertion asserting ownership of username at mockmyid.com to a
-   * relying party. The underlying certificate is signed by mockymid.com's
-   * private key.
-   *
-   * @param keyPair
-   *          to sign with.
-   * @param username
-   *          sign username at mockmyid.com.
-   * @param certificateIssuedAt
-   *          timestamp for certificate, in milliseconds since the epoch.
-   * @param certificateExpiresAt
-   *          expiration timestamp for certificate, in milliseconds since the epoch.
-   * @param assertionIssuedAt
-   *          timestamp for assertion, in milliseconds since the epoch; if null,
-   *          no timestamp is included.
-   * @param assertionExpiresAt
-   *          expiration timestamp for assertion, in milliseconds since the epoch.
-   * @return encoded assertion string.
-   * @throws Exception
-   */
-  public String createMockMyIDAssertion(BrowserIDKeyPair keyPair, String username, String audience,
-      long certificateIssuedAt, long certificateExpiresAt,
-      Long assertionIssuedAt, long assertionExpiresAt)
-          throws Exception {
-    String certificate = createMockMyIDCertificate(keyPair.getPublic(), username,
-        certificateIssuedAt, certificateExpiresAt);
-    return JSONWebTokenUtils.createAssertion(keyPair.getPrivate(), certificate, audience,
-        JSONWebTokenUtils.DEFAULT_ASSERTION_ISSUER, assertionIssuedAt, assertionExpiresAt);
-  }
-
-  /**
-   * Generate an assertion asserting ownership of username at mockmyid.com to a
-   * relying party. The underlying certificate is signed by mockymid.com's
-   * private key.
-   *
-   * @param keyPair
-   *          to sign with.
-   * @param username
-   *          sign username at mockmyid.com.
-   * @return encoded assertion string.
-   * @throws Exception
-   */
-  public String createMockMyIDAssertion(BrowserIDKeyPair keyPair, String username, String audience)
-      throws Exception {
-    long ciat = System.currentTimeMillis();
-    long cexp = ciat + JSONWebTokenUtils.DEFAULT_CERTIFICATE_DURATION_IN_MILLISECONDS;
-    long aiat = ciat + 1;
-    long aexp = aiat + JSONWebTokenUtils.DEFAULT_ASSERTION_DURATION_IN_MILLISECONDS;
-    return createMockMyIDAssertion(keyPair, username, audience,
-        ciat, cexp, aiat, aexp);
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/browserid/RSACryptoImplementation.java b/mobile/android/services/src/main/java/org/mozilla/gecko/browserid/RSACryptoImplementation.java
deleted file mode 100644
index 902f6fb..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/browserid/RSACryptoImplementation.java
+++ /dev/null
@@ -1,182 +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.browserid;
-
-import java.math.BigInteger;
-import java.security.GeneralSecurityException;
-import java.security.KeyFactory;
-import java.security.KeyPair;
-import java.security.KeyPairGenerator;
-import java.security.NoSuchAlgorithmException;
-import java.security.Signature;
-import java.security.interfaces.RSAPrivateKey;
-import java.security.interfaces.RSAPublicKey;
-import java.security.spec.InvalidKeySpecException;
-import java.security.spec.KeySpec;
-import java.security.spec.RSAPrivateKeySpec;
-import java.security.spec.RSAPublicKeySpec;
-
-import org.mozilla.gecko.sync.ExtendedJSONObject;
-import org.mozilla.gecko.sync.NonObjectJSONException;
-
-public class RSACryptoImplementation {
-  public static final String SIGNATURE_ALGORITHM = "SHA256withRSA";
-
-  /**
-   * Parameters are serialized as decimal strings. Hex-versus-decimal was
-   * reverse-engineered from what the Persona public verifier accepted. We
-   * expect to follow the JOSE/JWT spec as it solidifies, and that will probably
-   * mean unifying this base.
-   */
-  protected static final int SERIALIZATION_BASE = 10;
-
-  protected static class RSAVerifyingPublicKey implements VerifyingPublicKey {
-    protected final RSAPublicKey publicKey;
-
-    public RSAVerifyingPublicKey(RSAPublicKey publicKey) {
-      this.publicKey = publicKey;
-    }
-
-    /**
-     * Serialize to a JSON object.
-     * <p>
-     * Parameters are serialized as decimal strings. Hex-versus-decimal was
-     * reverse-engineered from what the Persona public verifier accepted.
-     */
-    @Override
-    public ExtendedJSONObject toJSONObject() {
-      ExtendedJSONObject o = new ExtendedJSONObject();
-      o.put("algorithm", "RS");
-      o.put("n", publicKey.getModulus().toString(SERIALIZATION_BASE));
-      o.put("e", publicKey.getPublicExponent().toString(SERIALIZATION_BASE));
-      return o;
-    }
-
-    @Override
-    public boolean verifyMessage(byte[] bytes, byte[] signature)
-        throws GeneralSecurityException {
-      final Signature signer = Signature.getInstance(SIGNATURE_ALGORITHM);
-      signer.initVerify(publicKey);
-      signer.update(bytes);
-      return signer.verify(signature);
-    }
-  }
-
-  protected static class RSASigningPrivateKey implements SigningPrivateKey {
-    protected final RSAPrivateKey privateKey;
-
-    public RSASigningPrivateKey(RSAPrivateKey privateKey) {
-      this.privateKey = privateKey;
-    }
-
-    @Override
-    public String getAlgorithm() {
-      return "RS" + (privateKey.getModulus().bitLength() + 7)/8;
-    }
-
-    /**
-     * Serialize to a JSON object.
-     * <p>
-     * Parameters are serialized as decimal strings. Hex-versus-decimal was
-     * reverse-engineered from what the Persona public verifier accepted.
-     */
-    @Override
-    public ExtendedJSONObject toJSONObject() {
-      ExtendedJSONObject o = new ExtendedJSONObject();
-      o.put("algorithm", "RS");
-      o.put("n", privateKey.getModulus().toString(SERIALIZATION_BASE));
-      o.put("d", privateKey.getPrivateExponent().toString(SERIALIZATION_BASE));
-      return o;
-    }
-
-    @Override
-    public byte[] signMessage(byte[] bytes)
-        throws GeneralSecurityException {
-      final Signature signer = Signature.getInstance(SIGNATURE_ALGORITHM);
-      signer.initSign(privateKey);
-      signer.update(bytes);
-      return signer.sign();
-    }
-  }
-
-  public static BrowserIDKeyPair generateKeyPair(final int keysize) throws NoSuchAlgorithmException {
-    final KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
-    keyPairGenerator.initialize(keysize);
-    final KeyPair keyPair = keyPairGenerator.generateKeyPair();
-    RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
-    RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
-    return new BrowserIDKeyPair(new RSASigningPrivateKey(privateKey), new RSAVerifyingPublicKey(publicKey));
-  }
-
-  public static SigningPrivateKey createPrivateKey(BigInteger n, BigInteger d) throws NoSuchAlgorithmException, InvalidKeySpecException {
-    if (n == null) {
-      throw new IllegalArgumentException("n must not be null");
-    }
-    if (d == null) {
-      throw new IllegalArgumentException("d must not be null");
-    }
-    KeyFactory keyFactory = KeyFactory.getInstance("RSA");
-    KeySpec keySpec = new RSAPrivateKeySpec(n, d);
-    RSAPrivateKey privateKey = (RSAPrivateKey) keyFactory.generatePrivate(keySpec);
-    return new RSASigningPrivateKey(privateKey);
-  }
-
-  public static VerifyingPublicKey createPublicKey(BigInteger n, BigInteger e) throws NoSuchAlgorithmException, InvalidKeySpecException {
-    if (n == null) {
-      throw new IllegalArgumentException("n must not be null");
-    }
-    if (e == null) {
-      throw new IllegalArgumentException("e must not be null");
-    }
-    KeyFactory keyFactory = KeyFactory.getInstance("RSA");
-    KeySpec keySpec = new RSAPublicKeySpec(n, e);
-    RSAPublicKey publicKey = (RSAPublicKey) keyFactory.generatePublic(keySpec);
-    return new RSAVerifyingPublicKey(publicKey);
-  }
-
-  public static SigningPrivateKey createPrivateKey(ExtendedJSONObject o) throws InvalidKeySpecException, NoSuchAlgorithmException {
-    String algorithm = o.getString("algorithm");
-    if (!"RS".equals(algorithm)) {
-      throw new InvalidKeySpecException("algorithm must equal RS, was " + algorithm);
-    }
-    try {
-      BigInteger n = new BigInteger(o.getString("n"), SERIALIZATION_BASE);
-      BigInteger d = new BigInteger(o.getString("d"), SERIALIZATION_BASE);
-      return createPrivateKey(n, d);
-    } catch (NullPointerException | NumberFormatException e) {
-      throw new InvalidKeySpecException("n and d must be integers encoded as strings, base " + SERIALIZATION_BASE);
-    }
-  }
-
-  public static VerifyingPublicKey createPublicKey(ExtendedJSONObject o) throws InvalidKeySpecException, NoSuchAlgorithmException {
-    String algorithm = o.getString("algorithm");
-    if (!"RS".equals(algorithm)) {
-      throw new InvalidKeySpecException("algorithm must equal RS, was " + algorithm);
-    }
-    try {
-      BigInteger n = new BigInteger(o.getString("n"), SERIALIZATION_BASE);
-      BigInteger e = new BigInteger(o.getString("e"), SERIALIZATION_BASE);
-      return createPublicKey(n, e);
-    } catch (NullPointerException | NumberFormatException e) {
-      throw new InvalidKeySpecException("n and e must be integers encoded as strings, base " + SERIALIZATION_BASE);
-    }
-  }
-
-  public static BrowserIDKeyPair fromJSONObject(ExtendedJSONObject o) throws InvalidKeySpecException, NoSuchAlgorithmException {
-    try {
-      ExtendedJSONObject privateKey = o.getObject(BrowserIDKeyPair.JSON_KEY_PRIVATEKEY);
-      ExtendedJSONObject publicKey = o.getObject(BrowserIDKeyPair.JSON_KEY_PUBLICKEY);
-      if (privateKey == null) {
-        throw new InvalidKeySpecException("privateKey must not be null");
-      }
-      if (publicKey == null) {
-        throw new InvalidKeySpecException("publicKey must not be null");
-      }
-      return new BrowserIDKeyPair(createPrivateKey(privateKey), createPublicKey(publicKey));
-    } catch (NonObjectJSONException e) {
-      throw new InvalidKeySpecException("privateKey and publicKey must be JSON objects");
-    }
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/browserid/SigningPrivateKey.java b/mobile/android/services/src/main/java/org/mozilla/gecko/browserid/SigningPrivateKey.java
deleted file mode 100644
index 6c388d1..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/browserid/SigningPrivateKey.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.browserid;
-
-import java.security.GeneralSecurityException;
-
-import org.mozilla.gecko.sync.ExtendedJSONObject;
-
-public interface SigningPrivateKey {
-  /**
-   * Return the JSON Web Token "alg" header corresponding to this private key.
-   * <p>
-   * The header is used when formatting web tokens, and generally denotes the
-   * algorithm and an ad-hoc encoding of the key size.
-   *
-   * @return header.
-   */
-  public String getAlgorithm();
-
-  /**
-   * Generate a JSON representation of a private key.
-   * <p>
-   * <b>This should only be used for debugging. No private keys should go over
-   * the wire at any time.</b>
-   *
-   * @param privateKey
-   *          to represent.
-   * @return JSON representation.
-   */
-  public ExtendedJSONObject toJSONObject();
-
-  /**
-   * Sign a message.
-   * @param message to sign.
-   * @return signature.
-   * @throws GeneralSecurityException
-   */
-  public byte[] signMessage(byte[] message) throws GeneralSecurityException;
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/browserid/VerifyingPublicKey.java b/mobile/android/services/src/main/java/org/mozilla/gecko/browserid/VerifyingPublicKey.java
deleted file mode 100644
index 74b534b..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/browserid/VerifyingPublicKey.java
+++ /dev/null
@@ -1,34 +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.browserid;
-
-import java.security.GeneralSecurityException;
-
-import org.mozilla.gecko.sync.ExtendedJSONObject;
-
-
-public interface VerifyingPublicKey {
-  /**
-   * Generate a JSON representation of a public key.
-   *
-   * @param publicKey
-   *          to represent.
-   * @return JSON representation.
-   */
-  public ExtendedJSONObject toJSONObject();
-
-  /**
-   * Verify a signature.
-   *
-   * @param message
-   *          to verify signature of.
-   * @param signature
-   *          to verify.
-   * @return true if signature is a signature of message produced by the private
-   *         key corresponding to this public key.
-   * @throws GeneralSecurityException
-   */
-  public boolean verifyMessage(byte[] message, byte[] signature) throws GeneralSecurityException;
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/browserid/verifier/AbstractBrowserIDRemoteVerifierClient.java b/mobile/android/services/src/main/java/org/mozilla/gecko/browserid/verifier/AbstractBrowserIDRemoteVerifierClient.java
deleted file mode 100644
index aa8db2d..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/browserid/verifier/AbstractBrowserIDRemoteVerifierClient.java
+++ /dev/null
@@ -1,95 +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.browserid.verifier;
-
-import java.io.IOException;
-import java.net.URI;
-import java.security.GeneralSecurityException;
-
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.browserid.verifier.BrowserIDVerifierException.BrowserIDVerifierErrorResponseException;
-import org.mozilla.gecko.browserid.verifier.BrowserIDVerifierException.BrowserIDVerifierMalformedResponseException;
-import org.mozilla.gecko.sync.ExtendedJSONObject;
-import org.mozilla.gecko.sync.net.BaseResourceDelegate;
-import org.mozilla.gecko.sync.net.Resource;
-import org.mozilla.gecko.sync.net.SyncResponse;
-
-import ch.boye.httpclientandroidlib.HttpResponse;
-import ch.boye.httpclientandroidlib.client.ClientProtocolException;
-
-public abstract class AbstractBrowserIDRemoteVerifierClient implements BrowserIDVerifierClient {
-  public static final String LOG_TAG = AbstractBrowserIDRemoteVerifierClient.class.getSimpleName();
-
-  protected static class RemoteVerifierResourceDelegate extends BaseResourceDelegate {
-    private final BrowserIDVerifierDelegate delegate;
-
-    protected RemoteVerifierResourceDelegate(Resource resource, BrowserIDVerifierDelegate delegate) {
-      super(resource);
-      this.delegate = delegate;
-    }
-
-    @Override
-    public String getUserAgent() {
-      return null;
-    }
-
-    @Override
-    public void handleHttpResponse(HttpResponse response) {
-      SyncResponse res = new SyncResponse(response);
-      int statusCode = res.getStatusCode();
-      Logger.debug(LOG_TAG, "Got response with status code " + statusCode + ".");
-
-      if (statusCode != 200) {
-        delegate.handleError(new BrowserIDVerifierErrorResponseException("Expected status code 200."));
-        return;
-      }
-
-      ExtendedJSONObject o = null;
-      try {
-        o = res.jsonObjectBody();
-      } catch (Exception e) {
-        delegate.handleError(new BrowserIDVerifierMalformedResponseException(e));
-        return;
-      }
-
-      String status = o.getString("status");
-      if ("failure".equals(status)) {
-        delegate.handleFailure(o);
-        return;
-      }
-
-      if (!("okay".equals(status))) {
-        delegate.handleError(new BrowserIDVerifierMalformedResponseException("Expected status okay, got '" + status + "'."));
-        return;
-      }
-
-      delegate.handleSuccess(o);
-    }
-
-    @Override
-    public void handleTransportException(GeneralSecurityException e) {
-      Logger.warn(LOG_TAG, "Got transport exception.", e);
-      delegate.handleError(e);
-    }
-
-    @Override
-    public void handleHttpProtocolException(ClientProtocolException e) {
-      Logger.warn(LOG_TAG, "Got protocol exception.", e);
-      delegate.handleError(e);
-    }
-
-    @Override
-    public void handleHttpIOException(IOException e) {
-      Logger.warn(LOG_TAG, "Got IO exception.", e);
-      delegate.handleError(e);
-    }
-  }
-
-  protected final URI verifierUri;
-
-  public AbstractBrowserIDRemoteVerifierClient(URI verifierUri) {
-    this.verifierUri = verifierUri;
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/browserid/verifier/BrowserIDRemoteVerifierClient10.java b/mobile/android/services/src/main/java/org/mozilla/gecko/browserid/verifier/BrowserIDRemoteVerifierClient10.java
deleted file mode 100644
index f61a823..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/browserid/verifier/BrowserIDRemoteVerifierClient10.java
+++ /dev/null
@@ -1,62 +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.browserid.verifier;
-
-import java.io.UnsupportedEncodingException;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.util.Arrays;
-import java.util.List;
-
-import org.mozilla.gecko.sync.net.BaseResource;
-
-import ch.boye.httpclientandroidlib.NameValuePair;
-import ch.boye.httpclientandroidlib.client.entity.UrlEncodedFormEntity;
-import ch.boye.httpclientandroidlib.message.BasicNameValuePair;
-
-/**
- * The verifier protocol changed: version 1 posts form-encoded data; version 2
- * posts JSON data.
- */
-public class BrowserIDRemoteVerifierClient10 extends AbstractBrowserIDRemoteVerifierClient {
-  public static final String LOG_TAG = BrowserIDRemoteVerifierClient10.class.getSimpleName();
-
-  public static final String DEFAULT_VERIFIER_URL = "https://verifier.login.persona.org/verify";
-
-  public BrowserIDRemoteVerifierClient10() throws URISyntaxException {
-    super(new URI(DEFAULT_VERIFIER_URL));
-  }
-
-  public BrowserIDRemoteVerifierClient10(URI verifierUri) {
-    super(verifierUri);
-  }
-
-  @Override
-  public void verify(String audience, String assertion, final BrowserIDVerifierDelegate delegate) {
-    if (audience == null) {
-      throw new IllegalArgumentException("audience cannot be null.");
-    }
-    if (assertion == null) {
-      throw new IllegalArgumentException("assertion cannot be null.");
-    }
-    if (delegate == null) {
-      throw new IllegalArgumentException("delegate cannot be null.");
-    }
-
-    BaseResource r = new BaseResource(verifierUri);
-
-    r.delegate = new RemoteVerifierResourceDelegate(r, delegate);
-
-    List<NameValuePair> nvps = Arrays.asList(new NameValuePair[] {
-        new BasicNameValuePair("audience", audience),
-        new BasicNameValuePair("assertion", assertion) });
-
-    try {
-      r.post(new UrlEncodedFormEntity(nvps, "UTF-8"));
-    } catch (UnsupportedEncodingException e) {
-      delegate.handleError(e);
-    }
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/browserid/verifier/BrowserIDRemoteVerifierClient20.java b/mobile/android/services/src/main/java/org/mozilla/gecko/browserid/verifier/BrowserIDRemoteVerifierClient20.java
deleted file mode 100644
index 0138565..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/browserid/verifier/BrowserIDRemoteVerifierClient20.java
+++ /dev/null
@@ -1,58 +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.browserid.verifier;
-
-import java.net.URI;
-import java.net.URISyntaxException;
-
-import org.mozilla.gecko.sync.ExtendedJSONObject;
-import org.mozilla.gecko.sync.net.BaseResource;
-
-/**
- * The verifier protocol changed: version 1 posts form-encoded data; version 2
- * posts JSON data.
- */
-public class BrowserIDRemoteVerifierClient20 extends AbstractBrowserIDRemoteVerifierClient {
-  public static final String LOG_TAG = BrowserIDRemoteVerifierClient20.class.getSimpleName();
-
-  public static final String DEFAULT_VERIFIER_URL = "https://verifier.accounts.firefox.com/v2";
-
-  protected static final String JSON_KEY_ASSERTION = "assertion";
-  protected static final String JSON_KEY_AUDIENCE = "audience";
-
-  public BrowserIDRemoteVerifierClient20() throws URISyntaxException {
-    super(new URI(DEFAULT_VERIFIER_URL));
-  }
-
-  public BrowserIDRemoteVerifierClient20(URI verifierUri) {
-    super(verifierUri);
-  }
-
-  @Override
-  public void verify(String audience, String assertion, final BrowserIDVerifierDelegate delegate) {
-    if (audience == null) {
-      throw new IllegalArgumentException("audience cannot be null.");
-    }
-    if (assertion == null) {
-      throw new IllegalArgumentException("assertion cannot be null.");
-    }
-    if (delegate == null) {
-      throw new IllegalArgumentException("delegate cannot be null.");
-    }
-
-    BaseResource r = new BaseResource(verifierUri);
-    r.delegate = new RemoteVerifierResourceDelegate(r, delegate);
-
-    final ExtendedJSONObject requestBody = new ExtendedJSONObject();
-    requestBody.put(JSON_KEY_AUDIENCE, audience);
-    requestBody.put(JSON_KEY_ASSERTION, assertion);
-
-    try {
-      r.post(requestBody);
-    } catch (Exception e) {
-      delegate.handleError(e);
-    }
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/browserid/verifier/BrowserIDVerifierClient.java b/mobile/android/services/src/main/java/org/mozilla/gecko/browserid/verifier/BrowserIDVerifierClient.java
deleted file mode 100644
index 67a327f..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/browserid/verifier/BrowserIDVerifierClient.java
+++ /dev/null
@@ -1,9 +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.browserid.verifier;
-
-public interface BrowserIDVerifierClient {
-  public abstract void verify(String audience, String assertion, BrowserIDVerifierDelegate delegate);
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/browserid/verifier/BrowserIDVerifierDelegate.java b/mobile/android/services/src/main/java/org/mozilla/gecko/browserid/verifier/BrowserIDVerifierDelegate.java
deleted file mode 100644
index b58d032..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/browserid/verifier/BrowserIDVerifierDelegate.java
+++ /dev/null
@@ -1,13 +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.browserid.verifier;
-
-import org.mozilla.gecko.sync.ExtendedJSONObject;
-
-public interface BrowserIDVerifierDelegate {
-  void handleSuccess(ExtendedJSONObject response);
-  void handleFailure(ExtendedJSONObject response);
-  void handleError(Exception e);
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/browserid/verifier/BrowserIDVerifierException.java b/mobile/android/services/src/main/java/org/mozilla/gecko/browserid/verifier/BrowserIDVerifierException.java
deleted file mode 100644
index dacaf61..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/browserid/verifier/BrowserIDVerifierException.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.browserid.verifier;
-
-public class BrowserIDVerifierException extends Exception {
-  private static final long serialVersionUID = 2228946910754889975L;
-
-  public BrowserIDVerifierException(String detailMessage) {
-    super(detailMessage);
-  }
-
-  public BrowserIDVerifierException(Throwable throwable) {
-    super(throwable);
-  }
-
-  public static class BrowserIDVerifierMalformedResponseException extends BrowserIDVerifierException {
-    private static final long serialVersionUID = 115377527009652839L;
-
-    public BrowserIDVerifierMalformedResponseException(String detailMessage) {
-      super(detailMessage);
-    }
-
-    public BrowserIDVerifierMalformedResponseException(Throwable throwable) {
-      super(throwable);
-    }
-  }
-
-  public static class BrowserIDVerifierErrorResponseException extends BrowserIDVerifierException {
-    private static final long serialVersionUID = 115377527009652840L;
-
-    public BrowserIDVerifierErrorResponseException(String detailMessage) {
-      super(detailMessage);
-    }
-
-    public BrowserIDVerifierErrorResponseException(Throwable throwable) {
-      super(throwable);
-    }
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/AccountLoader.java b/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/AccountLoader.java
deleted file mode 100644
index 8a31c1c..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/AccountLoader.java
+++ /dev/null
@@ -1,227 +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.fxa;
-
-import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
-
-import android.accounts.Account;
-import android.accounts.AccountManager;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.os.Handler;
-import android.os.Looper;
-import android.content.AsyncTaskLoader;
-import android.support.v4.content.LocalBroadcastManager;
-
-import java.lang.ref.WeakReference;
-
-/**
- * A Loader that queries and updates based on the existence of Firefox and
- * legacy Sync Android Accounts.
- *
- * The loader returns an Android Account (of either Account type) if an account
- * exists, and null to indicate no Account is present.
- *
- * The loader listens for Accounts added and deleted, and also Accounts being
- * updated by Sync or another Activity, via the use of
- * {@link AndroidFxAccount#setState(org.mozilla.gecko.fxa.login.State)}.
- * Be careful of message loops if you update the account state from an activity
- * that uses this loader.
- *
- * This implementation is based on
- * <a href="http://www.androiddesignpatterns.com/2012/08/implementing-loaders.html">http://www.androiddesignpatterns.com/2012/08/implementing-loaders.html</a>.
- */
-public class AccountLoader extends AsyncTaskLoader<Account> {
-  protected Account account = null;
-  protected BroadcastReceiver broadcastReceiver = null;
-
-  // Hold a weak reference to AccountLoader instance in this Runnable to avoid potentially leaking it
-  // after posting to a Handler in the BroadcastReceiver returned from makeNewObserver.
-  private final BroadcastReceiverRunnable broadcastReceiverRunnable = new BroadcastReceiverRunnable(this);
-
-  public AccountLoader(final Context context) {
-    super(context);
-  }
-
-  // Task that performs the asynchronous load.
-  @Override
-  public Account loadInBackground() {
-    return FirefoxAccounts.getFirefoxAccount(getContext());
-  }
-
-  // Deliver the results to the registered listener.
-  @Override
-  public void deliverResult(Account data) {
-    if (isReset()) {
-      // The Loader has been reset; ignore the result and invalidate the data.
-      releaseResources(data);
-      return;
-    }
-
-    // Hold a reference to the old data so it doesn't get garbage collected.
-    // We must protect it until the new data has been delivered.
-    Account oldData = account;
-    account = data;
-
-    if (isStarted()) {
-      // If the Loader is in a started state, deliver the results to the
-      // client. The superclass method does this for us.
-      super.deliverResult(data);
-    }
-
-    // Invalidate the old data as we don't need it any more.
-    if (oldData != null && oldData != data) {
-      releaseResources(oldData);
-    }
-  }
-
-  // The Loader’s state-dependent behavior.
-  @Override
-  protected void onStartLoading() {
-    if (account != null) {
-      // Deliver any previously loaded data immediately.
-      deliverResult(account);
-    }
-
-    // Begin monitoring the underlying data source.
-    if (broadcastReceiver == null) {
-      broadcastReceiver = makeNewObserver();
-      registerLocalObserver(getContext(), broadcastReceiver);
-      registerSystemObserver(getContext(), broadcastReceiver);
-    }
-
-    if (takeContentChanged() || account == null) {
-      // When the observer detects a change, it should call onContentChanged()
-      // on the Loader, which will cause the next call to takeContentChanged()
-      // to return true. If this is ever the case (or if the current data is
-      // null), we force a new load.
-      forceLoad();
-    }
-  }
-
-  @Override
-  protected void onStopLoading() {
-    // The Loader is in a stopped state, so we should attempt to cancel the
-    // current load (if there is one).
-    cancelLoad();
-
-    // Note that we leave the observer as is. Loaders in a stopped state
-    // should still monitor the data source for changes so that the Loader
-    // will know to force a new load if it is ever started again.
-  }
-
-  @Override
-  protected void onReset() {
-    // Ensure the loader has been stopped.  In CursorLoader and the template
-    // this code follows (see the class comment), this is onStopLoading, which
-    // appears to not set the started flag (see Loader itself).
-    stopLoading();
-
-    // At this point we can release the resources associated with 'mData'.
-    if (account != null) {
-      releaseResources(account);
-      account = null;
-    }
-
-    // The Loader is being reset, so we should stop monitoring for changes.
-    if (broadcastReceiver != null) {
-      final BroadcastReceiver observer = broadcastReceiver;
-      broadcastReceiver = null;
-      unregisterObserver(getContext(), observer);
-    }
-  }
-
-  @Override
-  public void onCanceled(final Account data) {
-    // Attempt to cancel the current asynchronous load.
-    super.onCanceled(data);
-
-    // The load has been canceled, so we should release the resources
-    // associated with 'data'.
-    releaseResources(data);
-  }
-
-  // Observer which receives notifications when the data changes.
-  protected BroadcastReceiver makeNewObserver() {
-    return new BroadcastReceiver() {
-      @Override
-      public void onReceive(Context context, Intent intent) {
-        // onContentChanged must be called on the main thread.
-        // If we're already on the main thread, call it directly.
-        if (Looper.myLooper() == Looper.getMainLooper()) {
-          onContentChanged();
-          return;
-        }
-
-        // Otherwise, post a Runnable to a Handler bound to the main thread's message loop.
-        final Handler mainHandler = new Handler(Looper.getMainLooper());
-        mainHandler.post(broadcastReceiverRunnable);
-      }
-    };
-  }
-
-  private static class BroadcastReceiverRunnable implements Runnable {
-    private final WeakReference<AccountLoader> accountLoaderWeakReference;
-
-    public BroadcastReceiverRunnable(final AccountLoader accountLoader) {
-      accountLoaderWeakReference = new WeakReference<>(accountLoader);
-    }
-
-    @Override
-    public void run() {
-      final AccountLoader accountLoader = accountLoaderWeakReference.get();
-      if (accountLoader != null) {
-        accountLoader.onContentChanged();
-      }
-    }
-  }
-
-  private void releaseResources(Account data) {
-    // For a simple List, there is nothing to do. For something like a Cursor, we
-    // would close it in this method. All resources associated with the Loader
-    // should be released here.
-  }
-
-  /**
-   * Register provided observer with the LocalBroadcastManager to listen for internal events.
-   *
-   * @param context <code>Context</code> to use for obtaining LocalBroadcastManager instance.
-   * @param observer <code>BroadcastReceiver</code> which will handle local events.
-   */
-  protected static void registerLocalObserver(final Context context, final BroadcastReceiver observer) {
-    final IntentFilter intentFilter = new IntentFilter();
-    // Firefox Account internal state changed.
-    intentFilter.addAction(FxAccountConstants.ACCOUNT_STATE_CHANGED_ACTION);
-    // Firefox Account profile state changed.
-    intentFilter.addAction(FxAccountConstants.ACCOUNT_PROFILE_JSON_UPDATED_ACTION);
-
-    LocalBroadcastManager.getInstance(context).registerReceiver(observer, intentFilter);
-  }
-
-  /**
-   * Register provided observer for handling system-wide broadcasts.
-   *
-   * @param context <code>Context</code> to use for registering a receiver.
-   * @param observer <code>BroadcastReceiver</code> which will handle system events.
-   */
-  protected static void registerSystemObserver(final Context context, final BroadcastReceiver observer) {
-    context.registerReceiver(observer,
-            // Android Account added or removed.
-            new IntentFilter(AccountManager.LOGIN_ACCOUNTS_CHANGED_ACTION),
-            // No broadcast permissions required.
-            null,
-            // Null handler ensures that broadcasts will be handled on the main thread.
-            null
-    );
-  }
-
-  protected static void unregisterObserver(final Context context, final BroadcastReceiver observer) {
-    LocalBroadcastManager.getInstance(context).unregisterReceiver(observer);
-    context.unregisterReceiver(observer);
-  }
-}
-
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/FirefoxAccounts.java b/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/FirefoxAccounts.java
deleted file mode 100644
index 4184340..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/FirefoxAccounts.java
+++ /dev/null
@@ -1,222 +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.fxa;
-
-import java.io.File;
-import java.util.concurrent.CountDownLatch;
-
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.fxa.authenticator.AccountPickler;
-import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
-import org.mozilla.gecko.fxa.login.State;
-import org.mozilla.gecko.fxa.sync.FxAccountSyncStatusHelper;
-import org.mozilla.gecko.sync.ThreadPool;
-import org.mozilla.gecko.sync.Utils;
-
-import android.accounts.Account;
-import android.accounts.AccountManager;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.os.Bundle;
-
-/**
- * Simple public accessors for Firefox account objects.
- */
-public class FirefoxAccounts {
-  private static final String LOG_TAG = FirefoxAccounts.class.getSimpleName();
-
-  /**
-   * Returns true if a FirefoxAccount exists, false otherwise.
-   *
-   * @param context Android context.
-   * @return true if at least one Firefox account exists.
-   */
-  public static boolean firefoxAccountsExist(final Context context) {
-    return getFirefoxAccounts(context).length > 0;
-  }
-
-  /**
-   * Return Firefox accounts.
-   * <p>
-   * If no accounts exist in the AccountManager, one may be created
-   * via a pickled FirefoxAccount, if available, and that account
-   * will be added to the AccountManager and returned.
-   * <p>
-   * Note that this can be called from any thread.
-   *
-   * @param context Android context.
-   * @return Firefox account objects.
-   */
-  public static Account[] getFirefoxAccounts(final Context context) {
-    final Account[] accounts =
-        AccountManager.get(context).getAccountsByType(FxAccountConstants.ACCOUNT_TYPE);
-    if (accounts.length > 0) {
-      return accounts;
-    }
-
-    final Account pickledAccount = getPickledAccount(context);
-    return (pickledAccount != null) ? new Account[] {pickledAccount} : new Account[0];
-  }
-
-  private static Account getPickledAccount(final Context context) {
-    // To avoid a StrictMode violation for disk access, we call this from a background thread.
-    // We do this every time, so the caller doesn't have to care.
-    final CountDownLatch latch = new CountDownLatch(1);
-    final Account[] accounts = new Account[1];
-    ThreadPool.run(new Runnable() {
-      @Override
-      public void run() {
-        try {
-          final File file = context.getFileStreamPath(FxAccountConstants.ACCOUNT_PICKLE_FILENAME);
-          if (!file.exists()) {
-            accounts[0] = null;
-            return;
-          }
-
-          // There is a small race window here: if the user creates a new Firefox account
-          // between our checks, this could erroneously report that no Firefox accounts
-          // exist.
-          final AndroidFxAccount fxAccount =
-              AccountPickler.unpickle(context, FxAccountConstants.ACCOUNT_PICKLE_FILENAME);
-          accounts[0] = fxAccount != null ? fxAccount.getAndroidAccount() : null;
-        } finally {
-          latch.countDown();
-        }
-      }
-    });
-
-    try {
-      latch.await(); // Wait for the background thread to return.
-    } catch (InterruptedException e) {
-      Logger.warn(LOG_TAG,
-          "Foreground thread unexpectedly interrupted while getting pickled account", e);
-      return null;
-    }
-
-    return accounts[0];
-  }
-
-  /**
-   * @param context Android context.
-   * @return the configured Firefox account if one exists, or null otherwise.
-   */
-  public static Account getFirefoxAccount(final Context context) {
-    Account[] accounts = getFirefoxAccounts(context);
-    if (accounts.length > 0) {
-      return accounts[0];
-    }
-    return null;
-  }
-
-  /**
-   * @return
-   *     the {@link State} instance associated with the current account, or <code>null</code> if
-   *     no accounts exist.
-   */
-  public static State getFirefoxAccountState(final Context context) {
-    final Account account = getFirefoxAccount(context);
-    if (account == null) {
-      return null;
-    }
-
-    final AndroidFxAccount fxAccount = new AndroidFxAccount(context, account);
-    try {
-      return fxAccount.getState();
-    } catch (final Exception ex) {
-      Logger.warn(LOG_TAG, "Could not get FX account state.", ex);
-      return null;
-    }
-  }
-
-  /*
-   * @param context Android context
-   * @return the email address associated with the configured Firefox account if one exists; null otherwise.
-   */
-  public static String getFirefoxAccountEmail(final Context context) {
-    final Account account = getFirefoxAccount(context);
-    if (account == null) {
-      return null;
-    }
-    return account.name;
-  }
-
-  public static void logSyncOptions(Bundle syncOptions) {
-    final boolean scheduleNow = syncOptions.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF, false);
-
-    Logger.info(LOG_TAG, "Sync options -- scheduling now: " + scheduleNow);
-  }
-
-  public static void requestImmediateSync(final Account account, String[] stagesToSync, String[] stagesToSkip) {
-    final Bundle syncOptions = new Bundle();
-    syncOptions.putBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF, true);
-    syncOptions.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true);
-    requestSync(account, syncOptions, stagesToSync, stagesToSkip);
-  }
-
-  public static void requestEventualSync(final Account account, String[] stagesToSync, String[] stagesToSkip) {
-    requestSync(account, Bundle.EMPTY, stagesToSync, stagesToSkip);
-  }
-
-  /**
-   * Request a sync for the given Android Account.
-   * <p>
-   * Any hints are strictly optional: the actual requested sync is scheduled by
-   * the Android sync scheduler, and the sync mechanism may ignore hints as it
-   * sees fit.
-   * <p>
-   * It is safe to call this method from any thread.
-   *
-   * @param account to sync.
-   * @param syncOptions to pass to sync.
-   * @param stagesToSync stage names to sync.
-   * @param stagesToSkip stage names to skip.
-   */
-  protected static void requestSync(final Account account, final Bundle syncOptions, String[] stagesToSync, String[] stagesToSkip) {
-    if (account == null) {
-      throw new IllegalArgumentException("account must not be null");
-    }
-    if (syncOptions == null) {
-      throw new IllegalArgumentException("syncOptions must not be null");
-    }
-
-    Utils.putStageNamesToSync(syncOptions, stagesToSync, stagesToSkip);
-
-    Logger.info(LOG_TAG, "Requesting sync.");
-    logSyncOptions(syncOptions);
-
-    // We get strict mode warnings on some devices, so make the request on a
-    // background thread.
-    ThreadPool.run(new Runnable() {
-      @Override
-      public void run() {
-        for (String authority : AndroidFxAccount.DEFAULT_AUTHORITIES_TO_SYNC_AUTOMATICALLY_MAP.keySet()) {
-          ContentResolver.requestSync(account, authority, syncOptions);
-        }
-      }
-    });
-  }
-
-  /**
-   * Start notifying <code>syncStatusListener</code> of sync status changes.
-   * <p>
-   * Only a weak reference to <code>syncStatusListener</code> is held.
-   *
-   * @param syncStatusListener to start notifying.
-   */
-  public static void addSyncStatusListener(SyncStatusListener syncStatusListener) {
-    // startObserving null-checks its argument.
-    FxAccountSyncStatusHelper.getInstance().startObserving(syncStatusListener);
-  }
-
-  /**
-   * Stop notifying <code>syncStatusListener</code> of sync status changes.
-   *
-   * @param syncStatusListener to stop notifying.
-   */
-  public static void removeSyncStatusListener(SyncStatusListener syncStatusListener) {
-    // stopObserving null-checks its argument.
-    FxAccountSyncStatusHelper.getInstance().stopObserving(syncStatusListener);
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/FxAccountConstants.java b/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/FxAccountConstants.java
deleted file mode 100644
index c6147b3..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/FxAccountConstants.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.fxa;
-
-import org.mozilla.gecko.AppConstants;
-
-public class FxAccountConstants {
-  public static final String GLOBAL_LOG_TAG = "FxAccounts";
-  public static final String ACCOUNT_TYPE = AppConstants.MOZ_ANDROID_SHARED_FXACCOUNT_TYPE;
-
-  // Must be a client ID allocated with "canGrant" privileges!
-  public static final String OAUTH_CLIENT_ID_FENNEC = "3332a18d142636cb";
-
-  public static final String DEFAULT_AUTH_SERVER_ENDPOINT = "https://api.accounts.firefox.com/v1";
-  public static final String DEFAULT_TOKEN_SERVER_ENDPOINT = "https://token.services.mozilla.com/1.0/sync/1.5";
-  public static final String DEFAULT_OAUTH_SERVER_ENDPOINT = "https://oauth.accounts.firefox.com/v1";
-  public static final String DEFAULT_PROFILE_SERVER_ENDPOINT = "https://profile.accounts.firefox.com/v1";
-
-  public static final String STAGE_AUTH_SERVER_ENDPOINT = "https://stable.dev.lcip.org/auth/v1";
-  public static final String STAGE_TOKEN_SERVER_ENDPOINT = "https://stable.dev.lcip.org/syncserver/token/1.0/sync/1.5";
-  public static final String STAGE_OAUTH_SERVER_ENDPOINT = "https://oauth-stable.dev.lcip.org/v1";
-  public static final String STAGE_PROFILE_SERVER_ENDPOINT = "https://latest.dev.lcip.org/profile/v1";
-
-  // Action to update on cached profile information.
-  public static final String ACCOUNT_PROFILE_JSON_UPDATED_ACTION = "org.mozilla.gecko.fxa.profile.JSON.updated";
-
-  // You must be at least 13 years old, on the day of creation, to create a Firefox Account.
-  public static final int MINIMUM_AGE_TO_CREATE_AN_ACCOUNT = 13;
-
-  // Key for avatar URI in profile JSON.
-  public static final String KEY_PROFILE_JSON_AVATAR = "avatar";
-  // Key for username in profile JSON.
-  public static final String KEY_PROFILE_JSON_USERNAME = "displayName";
-
-  // You must wait 15 minutes after failing an age check before trying to create a different account.
-  public static final long MINIMUM_TIME_TO_WAIT_AFTER_AGE_CHECK_FAILED_IN_MILLISECONDS = 15 * 60 * 1000;
-
-  public static final String USER_AGENT = "Firefox-Android-FxAccounts/" + AppConstants.MOZ_APP_VERSION + " (" + AppConstants.MOZ_APP_UA_NAME + ")";
-
-  public static final String ACCOUNT_PICKLE_FILENAME = "fxa.account.json";
-
-
-  /**
-   * Version number of contents of SYNC_ACCOUNT_DELETED_ACTION intent.
-   */
-  public static final long ACCOUNT_DELETED_INTENT_VERSION = 1;
-
-  public static final String ACCOUNT_DELETED_INTENT_VERSION_KEY = "account_deleted_intent_version";
-  public static final String ACCOUNT_DELETED_INTENT_ACCOUNT_KEY = "account_deleted_intent_account";
-  public static final String ACCOUNT_DELETED_INTENT_ACCOUNT_PROFILE = "account_deleted_intent_profile";
-  public static final String ACCOUNT_OAUTH_SERVICE_ENDPOINT_KEY = "account_oauth_service_endpoint";
-  public static final String ACCOUNT_DELETED_INTENT_ACCOUNT_AUTH_TOKENS = "account_deleted_intent_auth_tokens";
-
-  /**
-   * This action is broadcast when an Android Firefox Account's internal state
-   * is changed.
-   * <p>
-   * It is protected by signing-level permission PER_ACCOUNT_TYPE_PERMISSION and
-   * can be received only by Firefox versions sharing the same Android Firefox
-   * Account type.
-   */
-  public static final String ACCOUNT_STATE_CHANGED_ACTION = AppConstants.MOZ_ANDROID_SHARED_FXACCOUNT_TYPE + ".accounts.ACCOUNT_STATE_CHANGED_ACTION";
-
-  public static final String ACTION_FXA_CONFIRM_ACCOUNT            = AppConstants.ANDROID_PACKAGE_NAME + ".ACTION_FXA_CONFIRM_ACCOUNT";
-  public static final String ACTION_FXA_FINISH_MIGRATING           = AppConstants.ANDROID_PACKAGE_NAME + ".ACTION_FXA_FINISH_MIGRATING";
-  public static final String ACTION_FXA_GET_STARTED                = AppConstants.ANDROID_PACKAGE_NAME + ".ACTION_FXA_GET_STARTED";
-  public static final String ACTION_FXA_STATUS                     = AppConstants.ANDROID_PACKAGE_NAME + ".ACTION_FXA_STATUS";
-  public static final String ACTION_FXA_UPDATE_CREDENTIALS         = AppConstants.ANDROID_PACKAGE_NAME + ".ACTION_FXA_UPDATE_CREDENTIALS";
-
-  public static final String ENDPOINT_PREFERENCES  = "preferences";
-  public static final String ENDPOINT_NOTIFICATION = "notification";
-  public static final String ENDPOINT_FIRSTRUN = "firstrun";
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/FxAccountDevice.java b/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/FxAccountDevice.java
deleted file mode 100644
index cd46ae2..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/FxAccountDevice.java
+++ /dev/null
@@ -1,81 +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.fxa;
-
-import org.mozilla.gecko.sync.ExtendedJSONObject;
-
-public class FxAccountDevice {
-
-  public static final String JSON_KEY_NAME = "name";
-  public static final String JSON_KEY_ID = "id";
-  public static final String JSON_KEY_TYPE = "type";
-  public static final String JSON_KEY_ISCURRENTDEVICE = "isCurrentDevice";
-  public static final String JSON_KEY_PUSH_CALLBACK = "pushCallback";
-  public static final String JSON_KEY_PUSH_PUBLICKEY = "pushPublicKey";
-  public static final String JSON_KEY_PUSH_AUTHKEY = "pushAuthKey";
-
-  public final String id;
-  public final String name;
-  public final String type;
-  public final Boolean isCurrentDevice;
-  public final String pushCallback;
-  public final String pushPublicKey;
-  public final String pushAuthKey;
-
-  public FxAccountDevice(String name, String id, String type, Boolean isCurrentDevice,
-                         String pushCallback, String pushPublicKey, String pushAuthKey) {
-    this.name = name;
-    this.id = id;
-    this.type = type;
-    this.isCurrentDevice = isCurrentDevice;
-    this.pushCallback = pushCallback;
-    this.pushPublicKey = pushPublicKey;
-    this.pushAuthKey = pushAuthKey;
-  }
-
-  public static FxAccountDevice forRegister(String name, String type, String pushCallback,
-                                            String pushPublicKey, String pushAuthKey) {
-    return new FxAccountDevice(name, null, type, null, pushCallback, pushPublicKey, pushAuthKey);
-  }
-
-  public static FxAccountDevice forUpdate(String id, String name, String pushCallback,
-                                          String pushPublicKey, String pushAuthKey) {
-    return new FxAccountDevice(name, id, null, null, pushCallback, pushPublicKey, pushAuthKey);
-  }
-
-  public static FxAccountDevice fromJson(ExtendedJSONObject json) {
-    String name = json.getString(JSON_KEY_NAME);
-    String id = json.getString(JSON_KEY_ID);
-    String type = json.getString(JSON_KEY_TYPE);
-    Boolean isCurrentDevice = json.getBoolean(JSON_KEY_ISCURRENTDEVICE);
-    String pushCallback = json.getString(JSON_KEY_PUSH_CALLBACK);
-    String pushPublicKey = json.getString(JSON_KEY_PUSH_PUBLICKEY);
-    String pushAuthKey = json.getString(JSON_KEY_PUSH_AUTHKEY);
-    return new FxAccountDevice(name, id, type, isCurrentDevice, pushCallback, pushPublicKey, pushAuthKey);
-  }
-
-  public ExtendedJSONObject toJson() {
-    final ExtendedJSONObject body = new ExtendedJSONObject();
-    if (this.name != null) {
-      body.put(JSON_KEY_NAME, this.name);
-    }
-    if (this.id != null) {
-      body.put(JSON_KEY_ID, this.id);
-    }
-    if (this.type != null) {
-      body.put(JSON_KEY_TYPE, this.type);
-    }
-    if (this.pushCallback != null) {
-      body.put(JSON_KEY_PUSH_CALLBACK, this.pushCallback);
-    }
-    if (this.pushPublicKey != null) {
-      body.put(JSON_KEY_PUSH_PUBLICKEY, this.pushPublicKey);
-    }
-    if (this.pushAuthKey != null) {
-      body.put(JSON_KEY_PUSH_AUTHKEY, this.pushAuthKey);
-    }
-    return body;
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/FxAccountDeviceRegistrator.java b/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/FxAccountDeviceRegistrator.java
deleted file mode 100644
index 66a8ad8..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/FxAccountDeviceRegistrator.java
+++ /dev/null
@@ -1,282 +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.fxa;
-
-import android.content.Context;
-import android.content.Intent;
-import android.os.Bundle;
-import android.support.annotation.Nullable;
-import android.text.TextUtils;
-import android.util.Log;
-
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.background.fxa.FxAccountClient;
-import org.mozilla.gecko.background.fxa.FxAccountClient20;
-import org.mozilla.gecko.background.fxa.FxAccountClient20.AccountStatusResponse;
-import org.mozilla.gecko.background.fxa.FxAccountClient20.RequestDelegate;
-import org.mozilla.gecko.background.fxa.FxAccountClientException.FxAccountClientRemoteException;
-import org.mozilla.gecko.background.fxa.FxAccountRemoteError;
-import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
-import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount.InvalidFxAState;
-import org.mozilla.gecko.fxa.login.State;
-import org.mozilla.gecko.sync.SharedPreferencesClientsDataDelegate;
-import org.mozilla.gecko.util.BundleEventListener;
-import org.mozilla.gecko.util.EventCallback;
-
-import java.io.UnsupportedEncodingException;
-import java.lang.ref.WeakReference;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.security.GeneralSecurityException;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-
-/* This class provides a way to register the current device against FxA
- * and also stores the registration details in the Android FxAccount.
- * This should be used in a state where we possess a sessionToken, most likely the Married state.
- */
-public class FxAccountDeviceRegistrator implements BundleEventListener {
-  private static final String LOG_TAG = "FxADeviceRegistrator";
-
-  // The current version of the device registration, we use this to re-register
-  // devices after we update what we send on device registration.
-  public static final Integer DEVICE_REGISTRATION_VERSION = 2;
-
-  private static FxAccountDeviceRegistrator instance;
-  private final WeakReference<Context> context;
-
-  private FxAccountDeviceRegistrator(Context appContext) {
-    this.context = new WeakReference<Context>(appContext);
-  }
-
-  private static FxAccountDeviceRegistrator getInstance(Context appContext) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
-    if (instance == null) {
-      FxAccountDeviceRegistrator tempInstance = new FxAccountDeviceRegistrator(appContext);
-      tempInstance.setupListeners(); // Set up listener for FxAccountPush:Subscribe:Response
-      instance = tempInstance;
-    }
-    return instance;
-  }
-
-  public static void register(Context context) {
-    Context appContext = context.getApplicationContext();
-    try {
-      getInstance(appContext).beginRegistration(appContext);
-    } catch (Exception e) {
-      Log.e(LOG_TAG, "Could not start FxA device registration", e);
-    }
-  }
-
-  private void beginRegistration(Context context) {
-    // Fire up gecko and send event
-    // We create the Intent ourselves instead of using GeckoService.getIntentToCreateServices
-    // because we can't import these modules (circular dependency between browser and services)
-    final Intent geckoIntent = new Intent();
-    geckoIntent.setAction("create-services");
-    geckoIntent.setClassName(context, "org.mozilla.gecko.GeckoService");
-    geckoIntent.putExtra("category", "android-push-service");
-    geckoIntent.putExtra("data", "android-fxa-subscribe");
-    final AndroidFxAccount fxAccount = AndroidFxAccount.fromContext(context);
-    geckoIntent.putExtra("org.mozilla.gecko.intent.PROFILE_NAME", fxAccount.getProfile());
-    context.startService(geckoIntent);
-    // -> handleMessage()
-  }
-
-  @Override
-  public void handleMessage(String event, Bundle message, EventCallback callback) {
-    if ("FxAccountsPush:Subscribe:Response".equals(event)) {
-      try {
-        doFxaRegistration(message.getBundle("subscription"));
-      } catch (InvalidFxAState e) {
-        Log.d(LOG_TAG, "Invalid state when trying to register with FxA ", e);
-      }
-    } else {
-      Log.e(LOG_TAG, "No action defined for " + event);
-    }
-  }
-
-  private void doFxaRegistration(Bundle subscription) throws InvalidFxAState {
-    final Context context = this.context.get();
-    if (this.context == null) {
-      throw new IllegalStateException("Application context has been gc'ed");
-    }
-    doFxaRegistration(context, subscription, true);
-  }
-
-  private static void doFxaRegistration(final Context context, final Bundle subscription, final boolean allowRecursion) throws InvalidFxAState {
-    String pushCallback = subscription.getString("pushCallback");
-    String pushPublicKey = subscription.getString("pushPublicKey");
-    String pushAuthKey = subscription.getString("pushAuthKey");
-
-    final AndroidFxAccount fxAccount = AndroidFxAccount.fromContext(context);
-    if (fxAccount == null) {
-      Log.e(LOG_TAG, "AndroidFxAccount is null");
-      return;
-    }
-    final byte[] sessionToken = fxAccount.getSessionToken();
-    final FxAccountDevice device;
-    String deviceId = fxAccount.getDeviceId();
-    String clientName = getClientName(fxAccount, context);
-    if (TextUtils.isEmpty(deviceId)) {
-      Log.i(LOG_TAG, "Attempting registration for a new device");
-      device = FxAccountDevice.forRegister(clientName, "mobile", pushCallback, pushPublicKey, pushAuthKey);
-    } else {
-      Log.i(LOG_TAG, "Attempting registration for an existing device");
-      Logger.pii(LOG_TAG, "Device ID: " + deviceId);
-      device = FxAccountDevice.forUpdate(deviceId, clientName, pushCallback, pushPublicKey, pushAuthKey);
-    }
-
-    ExecutorService executor = Executors.newSingleThreadExecutor(); // Not called often, it's okay to spawn another thread
-    final FxAccountClient20 fxAccountClient =
-            new FxAccountClient20(fxAccount.getAccountServerURI(), executor);
-    fxAccountClient.registerOrUpdateDevice(sessionToken, device, new RequestDelegate<FxAccountDevice>() {
-      @Override
-      public void handleError(Exception e) {
-        Log.e(LOG_TAG, "Error while updating a device registration: ", e);
-      }
-
-      @Override
-      public void handleFailure(FxAccountClientRemoteException error) {
-        Log.e(LOG_TAG, "Error while updating a device registration: ", error);
-        if (error.httpStatusCode == 400) {
-          if (error.apiErrorNumber == FxAccountRemoteError.UNKNOWN_DEVICE) {
-            recoverFromUnknownDevice(fxAccount);
-          } else if (error.apiErrorNumber == FxAccountRemoteError.DEVICE_SESSION_CONFLICT) {
-            recoverFromDeviceSessionConflict(error, fxAccountClient, sessionToken, fxAccount, context,
-                    subscription, allowRecursion);
-          }
-        } else
-        if (error.httpStatusCode == 401
-                && error.apiErrorNumber == FxAccountRemoteError.INVALID_AUTHENTICATION_TOKEN) {
-          handleTokenError(error, fxAccountClient, fxAccount);
-        } else {
-          logErrorAndResetDeviceRegistrationVersion(error, fxAccount);
-        }
-      }
-
-      @Override
-      public void handleSuccess(FxAccountDevice result) {
-        Log.i(LOG_TAG, "Device registration complete");
-        Logger.pii(LOG_TAG, "Registered device ID: " + result.id);
-        fxAccount.setFxAUserData(result.id, DEVICE_REGISTRATION_VERSION);
-      }
-    });
-  }
-
-  private static void logErrorAndResetDeviceRegistrationVersion(
-      final FxAccountClientRemoteException error, final AndroidFxAccount fxAccount) {
-    Log.e(LOG_TAG, "Device registration failed", error);
-    fxAccount.resetDeviceRegistrationVersion();
-  }
-
-  @Nullable
-  private static String getClientName(final AndroidFxAccount fxAccount, final Context context) {
-    try {
-      SharedPreferencesClientsDataDelegate clientsDataDelegate =
-          new SharedPreferencesClientsDataDelegate(fxAccount.getSyncPrefs(), context);
-      return clientsDataDelegate.getClientName();
-    } catch (UnsupportedEncodingException | GeneralSecurityException e) {
-      Log.e(LOG_TAG, "Unable to get client name.", e);
-      return null;
-    }
-  }
-
-  private static void handleTokenError(final FxAccountClientRemoteException error,
-                                       final FxAccountClient fxAccountClient,
-                                       final AndroidFxAccount fxAccount) {
-    Log.i(LOG_TAG, "Recovering from invalid token error: ", error);
-    logErrorAndResetDeviceRegistrationVersion(error, fxAccount);
-    fxAccountClient.accountStatus(fxAccount.getState().uid,
-        new RequestDelegate<AccountStatusResponse>() {
-      @Override
-      public void handleError(Exception e) {
-      }
-
-      @Override
-      public void handleFailure(FxAccountClientRemoteException e) {
-      }
-
-      @Override
-      public void handleSuccess(AccountStatusResponse result) {
-        State doghouseState = fxAccount.getState().makeDoghouseState();
-        if (!result.exists) {
-          Log.i(LOG_TAG, "token invalidated because the account no longer exists");
-          // TODO: Should be in a "I have an Android account, but the FxA is gone." State.
-          // This will do for now..
-          fxAccount.setState(doghouseState);
-          return;
-        }
-        Log.e(LOG_TAG, "sessionToken invalid");
-        fxAccount.setState(doghouseState);
-      }
-    });
-  }
-
-  private static void recoverFromUnknownDevice(final AndroidFxAccount fxAccount) {
-    Log.i(LOG_TAG, "unknown device id, clearing the cached device id");
-    fxAccount.setDeviceId(null);
-  }
-
-  /**
-   * Will call delegate#complete in all cases
-   */
-  private static void recoverFromDeviceSessionConflict(final FxAccountClientRemoteException error,
-                                                       final FxAccountClient fxAccountClient,
-                                                       final byte[] sessionToken,
-                                                       final AndroidFxAccount fxAccount,
-                                                       final Context context,
-                                                       final Bundle subscription,
-                                                       final boolean allowRecursion) {
-    Log.w(LOG_TAG, "device session conflict, attempting to ascertain the correct device id");
-    fxAccountClient.deviceList(sessionToken, new RequestDelegate<FxAccountDevice[]>() {
-      private void onError() {
-        Log.e(LOG_TAG, "failed to recover from device-session conflict");
-        logErrorAndResetDeviceRegistrationVersion(error, fxAccount);
-      }
-
-      @Override
-      public void handleError(Exception e) {
-        onError();
-      }
-
-      @Override
-      public void handleFailure(FxAccountClientRemoteException e) {
-        onError();
-      }
-
-      @Override
-      public void handleSuccess(FxAccountDevice[] devices) {
-        for (FxAccountDevice device : devices) {
-          if (device.isCurrentDevice) {
-            fxAccount.setFxAUserData(device.id, 0); // Reset device registration version
-            if (!allowRecursion) {
-              Log.d(LOG_TAG, "Failure to register a device on the second try");
-              break;
-            }
-            try {
-              doFxaRegistration(context, subscription, false);
-              return;
-            } catch (InvalidFxAState e) {
-              Log.d(LOG_TAG, "Invalid state when trying to recover from a session conflict ", e);
-              break;
-            }
-          }
-        }
-        onError();
-      }
-    });
-  }
-
-  private void setupListeners() throws ClassNotFoundException, NoSuchMethodException,
-          InvocationTargetException, IllegalAccessException {
-    // We have no choice but to use reflection here, sorry :(
-    Class<?> eventDispatcher = Class.forName("org.mozilla.gecko.EventDispatcher");
-    Method getInstance = eventDispatcher.getMethod("getInstance");
-    Object instance = getInstance.invoke(null);
-    Method registerBackgroundThreadListener = eventDispatcher.getMethod("registerBackgroundThreadListener",
-            BundleEventListener.class, String[].class);
-    registerBackgroundThreadListener.invoke(instance, this, new String[] { "FxAccountsPush:Subscribe:Response" });
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/FxAccountPushHandler.java b/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/FxAccountPushHandler.java
deleted file mode 100644
index 0117e63..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/FxAccountPushHandler.java
+++ /dev/null
@@ -1,95 +0,0 @@
-package org.mozilla.gecko.fxa;
-
-import android.accounts.Account;
-import android.accounts.AccountManager;
-import android.content.Context;
-import android.os.Bundle;
-import android.text.TextUtils;
-import android.util.Log;
-
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
-
-public class FxAccountPushHandler {
-    private static final String LOG_TAG = "FxAccountPush";
-
-    private static final String COMMAND_DEVICE_DISCONNECTED = "fxaccounts:device_disconnected";
-    private static final String COMMAND_COLLECTION_CHANGED = "sync:collection_changed";
-
-    private static final String CLIENTS_COLLECTION = "clients";
-
-    // Forbid instantiation
-    private FxAccountPushHandler() {}
-
-    public static void handleFxAPushMessage(Context context, Bundle bundle) {
-        Log.i(LOG_TAG, "Handling FxA Push Message");
-        String rawMessage = bundle.getString("message");
-        JSONObject message = null;
-        if (!TextUtils.isEmpty(rawMessage)) {
-            try {
-                message = new JSONObject(rawMessage);
-            } catch (JSONException e) {
-                Log.e(LOG_TAG, "Could not parse JSON", e);
-                return;
-            }
-        }
-        if (message == null) {
-            // An empty body means we should check the verification state of the account (FxA sends this
-            // when the account email is verified for example).
-            // TODO: We're only registering the push endpoint when we are in the Married state, that's why we're skipping the message :(
-            Log.d(LOG_TAG, "Skipping empty message");
-            return;
-        }
-        try {
-            String command = message.getString("command");
-            JSONObject data = message.getJSONObject("data");
-            switch (command) {
-                case COMMAND_DEVICE_DISCONNECTED:
-                    handleDeviceDisconnection(context, data);
-                    break;
-                case COMMAND_COLLECTION_CHANGED:
-                    handleCollectionChanged(context, data);
-                    break;
-                default:
-                    Log.d(LOG_TAG, "No handler defined for FxA Push command " + command);
-                    break;
-            }
-        } catch (JSONException e) {
-            Log.e(LOG_TAG, "Error while handling FxA push notification", e);
-        }
-    }
-
-    private static void handleCollectionChanged(Context context, JSONObject data) throws JSONException {
-        JSONArray collections = data.getJSONArray("collections");
-        int len = collections.length();
-        for (int i = 0; i < len; i++) {
-            if (collections.getString(i).equals(CLIENTS_COLLECTION)) {
-                final Account account = FirefoxAccounts.getFirefoxAccount(context);
-                if (account == null) {
-                    Log.e(LOG_TAG, "The account does not exist anymore");
-                    return;
-                }
-                final AndroidFxAccount fxAccount = new AndroidFxAccount(context, account);
-                fxAccount.requestImmediateSync(new String[] { CLIENTS_COLLECTION }, null);
-                return;
-            }
-        }
-    }
-
-    private static void handleDeviceDisconnection(Context context, JSONObject data) throws JSONException {
-        final Account account = FirefoxAccounts.getFirefoxAccount(context);
-        if (account == null) {
-            Log.e(LOG_TAG, "The account does not exist anymore");
-            return;
-        }
-        final AndroidFxAccount fxAccount = new AndroidFxAccount(context, account);
-        if (!fxAccount.getDeviceId().equals(data.getString("id"))) {
-            Log.e(LOG_TAG, "The device ID to disconnect doesn't match with the local device ID.\n"
-                            + "Local: " + fxAccount.getDeviceId() + ", ID to disconnect: " + data.getString("id"));
-            return;
-        }
-        AccountManager.get(context).removeAccount(account, null, null);
-    }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/SyncStatusListener.java b/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/SyncStatusListener.java
deleted file mode 100644
index 2f70a36..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/SyncStatusListener.java
+++ /dev/null
@@ -1,31 +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.fxa;
-
-import android.accounts.Account;
-import android.content.Context;
-import android.support.annotation.UiThread;
-
-/**
- * Interface definition for a callback to be invoked when an sync status change.
- */
-public interface SyncStatusListener {
-    public Context getContext();
-    public Account getAccount();
-
-    /**
-     * Called when sync has started.
-     * This is always called in UiThread.
-     */
-    @UiThread
-    public void onSyncStarted();
-
-    /**
-     * Called when sync has finished.
-     * This is always called in UiThread.
-     */
-    @UiThread
-    public void onSyncFinished();
-}
\ No newline at end of file
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/activities/CustomColorPreference.java b/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/activities/CustomColorPreference.java
deleted file mode 100644
index 5c4d7f3..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/activities/CustomColorPreference.java
+++ /dev/null
@@ -1,52 +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.fxa.activities;
-
-import org.mozilla.gecko.R;
-import android.content.Context;
-import android.content.res.TypedArray;
-import android.preference.Preference;
-import android.util.AttributeSet;
-import android.view.View;
-import android.widget.TextView;
-
- /**
-  * This preference is used to define custom  colors for both title and summary texts.
-  * Color code #777777 (placeholder_grey) is used as the fallback color for both title and summary.
-  */
-public class CustomColorPreference extends Preference {
-    private int mTitleColor;
-    private int mSummaryColor;
-
-    public CustomColorPreference(Context context) {
-        super(context);
-    }
-
-    public CustomColorPreference(Context context, AttributeSet attrs) {
-        super(context, attrs);
-        init(context, attrs);
-    }
-
-    public CustomColorPreference(Context context, AttributeSet attrs, int defStyle) {
-        super(context, attrs, defStyle);
-        init(context, attrs);
-    }
-
-    public void init(Context context, AttributeSet attrs) {
-        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CustomColorPreference);
-        mTitleColor = a.getColor(R.styleable.CustomColorPreference_titleColor, R.color.placeholder_grey);
-        mSummaryColor = a.getColor(R.styleable.CustomColorPreference_summaryColor, R.color.placeholder_grey);
-        a.recycle();
-    }
-
-    @Override
-    protected void onBindView(View view) {
-        super.onBindView(view);
-        final TextView title = (TextView) view.findViewById(android.R.id.title);
-        final TextView summary = (TextView) view.findViewById(android.R.id.summary);
-        title.setTextColor(mTitleColor);
-        summary.setTextColor(mSummaryColor);
-    }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/activities/FxAccountAbstractActivity.java b/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/activities/FxAccountAbstractActivity.java
deleted file mode 100644
index fc8cbf0..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/activities/FxAccountAbstractActivity.java
+++ /dev/null
@@ -1,80 +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.fxa.activities;
-
-import android.accounts.Account;
-import android.app.Activity;
-import android.content.Intent;
-
-import org.mozilla.gecko.Locales.LocaleAwareActivity;
-import org.mozilla.gecko.fxa.FirefoxAccounts;
-import org.mozilla.gecko.fxa.FxAccountConstants;
-
-public abstract class FxAccountAbstractActivity extends LocaleAwareActivity {
-  private static final String LOG_TAG = FxAccountAbstractActivity.class.getSimpleName();
-
-  protected final boolean cannotResumeWhenAccountsExist;
-  protected final boolean cannotResumeWhenNoAccountsExist;
-
-  public static final int CAN_ALWAYS_RESUME = 0;
-  public static final int CANNOT_RESUME_WHEN_ACCOUNTS_EXIST = 1 << 0;
-  public static final int CANNOT_RESUME_WHEN_NO_ACCOUNTS_EXIST = 1 << 1;
-
-  public FxAccountAbstractActivity(int resume) {
-    super();
-    this.cannotResumeWhenAccountsExist = 0 != (resume & CANNOT_RESUME_WHEN_ACCOUNTS_EXIST);
-    this.cannotResumeWhenNoAccountsExist = 0 != (resume & CANNOT_RESUME_WHEN_NO_ACCOUNTS_EXIST);
-  }
-
-  /**
-   * Many Firefox Accounts activities shouldn't display if an account already
-   * exists. This function redirects as appropriate.
-   *
-   * @return true if redirected.
-   */
-  protected boolean redirectIfAppropriate() {
-    if (cannotResumeWhenAccountsExist || cannotResumeWhenNoAccountsExist) {
-      final Account account = FirefoxAccounts.getFirefoxAccount(this);
-      if (cannotResumeWhenAccountsExist && account != null) {
-        redirectToAction(FxAccountConstants.ACTION_FXA_STATUS);
-        return true;
-      }
-      if (cannotResumeWhenNoAccountsExist && account == null) {
-        redirectToAction(FxAccountConstants.ACTION_FXA_GET_STARTED);
-        return true;
-      }
-    }
-    return false;
-  }
-
-  @Override
-  public void onResume() {
-    super.onResume();
-    redirectIfAppropriate();
-  }
-
-  @Override
-  public void onBackPressed() {
-      super.onBackPressed();
-      overridePendingTransition(0, 0);
-  }
-
-  protected void launchActivity(Class<? extends Activity> activityClass) {
-    Intent intent = new Intent(this, activityClass);
-    // Per http://stackoverflow.com/a/8992365, this triggers a known bug with
-    // the soft keyboard not being shown for the started activity. Why, Android, why?
-    intent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
-    startActivity(intent);
-  }
-
-  protected void redirectToAction(final String action) {
-    final Intent intent = new Intent(action);
-    // Per http://stackoverflow.com/a/8992365, this triggers a known bug with
-    // the soft keyboard not being shown for the started activity. Why, Android, why?
-    intent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
-    startActivity(intent);
-    finish();
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/activities/FxAccountConfirmAccountActivityWeb.java b/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/activities/FxAccountConfirmAccountActivityWeb.java
deleted file mode 100644
index b2afd9c..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/activities/FxAccountConfirmAccountActivityWeb.java
+++ /dev/null
@@ -1,11 +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.fxa.activities;
-
-public class FxAccountConfirmAccountActivityWeb extends FxAccountWebFlowActivity {
-  public FxAccountConfirmAccountActivityWeb() {
-    super(CANNOT_RESUME_WHEN_NO_ACCOUNTS_EXIST, "manage");
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/activities/FxAccountFinishMigratingActivityWeb.java b/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/activities/FxAccountFinishMigratingActivityWeb.java
deleted file mode 100644
index 0e66f1d..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/activities/FxAccountFinishMigratingActivityWeb.java
+++ /dev/null
@@ -1,11 +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.fxa.activities;
-
-public class FxAccountFinishMigratingActivityWeb extends FxAccountWebFlowActivity {
-  public FxAccountFinishMigratingActivityWeb() {
-    super(CANNOT_RESUME_WHEN_NO_ACCOUNTS_EXIST, "signin", "migration=sync11");
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/activities/FxAccountGetStartedActivityWeb.java b/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/activities/FxAccountGetStartedActivityWeb.java
deleted file mode 100644
index 39a907a..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/activities/FxAccountGetStartedActivityWeb.java
+++ /dev/null
@@ -1,11 +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.fxa.activities;
-
-public class FxAccountGetStartedActivityWeb extends FxAccountWebFlowActivity {
-  public FxAccountGetStartedActivityWeb() {
-    super(CANNOT_RESUME_WHEN_ACCOUNTS_EXIST, "signup");
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/activities/FxAccountStatusActivity.java b/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/activities/FxAccountStatusActivity.java
deleted file mode 100644
index 4bb929f..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/activities/FxAccountStatusActivity.java
+++ /dev/null
@@ -1,228 +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.fxa.activities;
-
-import android.accounts.Account;
-import android.accounts.AccountManager;
-import android.accounts.AccountManagerCallback;
-import android.accounts.AccountManagerFuture;
-import android.annotation.SuppressLint;
-import android.annotation.TargetApi;
-import android.app.Activity;
-import android.app.AlertDialog;
-import android.app.Dialog;
-import android.content.DialogInterface;
-import android.content.Intent;
-import android.os.Build;
-import android.os.Bundle;
-import android.support.v7.app.ActionBar;
-import android.support.v7.widget.Toolbar;
-import android.util.TypedValue;
-import android.view.Menu;
-import android.view.MenuInflater;
-import android.view.MenuItem;
-import android.view.View;
-import android.view.Window;
-import android.widget.Toast;
-import org.mozilla.gecko.AppConstants;
-import org.mozilla.gecko.Locales.LocaleAwareAppCompatActivity;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.background.fxa.FxAccountUtils;
-import org.mozilla.gecko.fxa.FirefoxAccounts;
-import org.mozilla.gecko.fxa.FxAccountConstants;
-import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
-import org.mozilla.gecko.sync.Utils;
-
-/**
- * Activity which displays account status.
- */
-public class FxAccountStatusActivity extends LocaleAwareAppCompatActivity {
-  private static final String LOG_TAG = FxAccountStatusActivity.class.getSimpleName();
-
-  protected FxAccountStatusFragment statusFragment;
-
-  @Override
-  protected void onCreate(Bundle savedInstanceState) {
-    super.onCreate(savedInstanceState);
-
-    // Display the fragment as the content.
-    statusFragment = new FxAccountStatusFragment();
-    getSupportFragmentManager()
-      .beginTransaction()
-      .replace(android.R.id.content, statusFragment)
-      .commit();
-
-    maybeSetHomeButtonEnabled();
-  }
-
-  /**
-   * Sufficiently recent Android versions need additional code to receive taps
-   * on the status bar to go "up". See <a
-   * href="http://stackoverflow.com/a/8953148">this stackoverflow answer</a> for
-   * more information.
-   */
-  @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
-  protected void maybeSetHomeButtonEnabled() {
-    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
-      Logger.debug(LOG_TAG, "Not enabling home button; version too low.");
-      return;
-    }
-    final ActionBar actionBar = getSupportActionBar();
-    if (actionBar != null) {
-      Logger.debug(LOG_TAG, "Enabling home button.");
-      actionBar.setHomeButtonEnabled(true);
-      actionBar.setDisplayHomeAsUpEnabled(true);
-      return;
-    }
-    Logger.debug(LOG_TAG, "Not enabling home button.");
-  }
-
-  @Override
-  public void onResume() {
-    super.onResume();
-
-    final AndroidFxAccount fxAccount = getAndroidFxAccount();
-    if (fxAccount == null) {
-      Logger.warn(LOG_TAG, "Could not get Firefox Account.");
-
-      // Gracefully redirect to get started.
-      final Intent intent = new Intent(FxAccountConstants.ACTION_FXA_GET_STARTED);
-      // Per http://stackoverflow.com/a/8992365, this triggers a known bug with
-      // the soft keyboard not being shown for the started activity. Why, Android, why?
-      intent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
-      startActivity(intent);
-
-      setResult(RESULT_CANCELED);
-      finish();
-      return;
-    }
-    statusFragment.refresh(fxAccount);
-  }
-
-  /**
-   * Helper to fetch (unique) Android Firefox Account if one exists, or return null.
-   */
-  protected AndroidFxAccount getAndroidFxAccount() {
-    Account account = FirefoxAccounts.getFirefoxAccount(this);
-    if (account == null) {
-      return null;
-    }
-    return new AndroidFxAccount(this, account);
-  }
-
-
-  /**
-   * Helper function to maybe remove the given Android account.
-   */
-  @SuppressLint("InlinedApi")
-  public static void maybeDeleteAndroidAccount(final Activity activity, final Account account, final Intent intent) {
-    if (account == null) {
-      Logger.warn(LOG_TAG, "Trying to delete null account; ignoring request.");
-      return;
-    }
-
-    final AccountManagerCallback<Boolean> callback = new AccountManagerCallback<Boolean>() {
-      @Override
-      public void run(AccountManagerFuture<Boolean> future) {
-        Logger.info(LOG_TAG, "Account " + Utils.obfuscateEmail(account.name) + " removed.");
-        final String text = activity.getResources().getString(R.string.fxaccount_remove_account_toast, account.name);
-        Toast.makeText(activity, text, Toast.LENGTH_LONG).show();
-        if (intent != null) {
-            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-            activity.startActivity(intent);
-        }
-        activity.finish();
-      }
-    };
-
-    /*
-     * Get the best dialog icon from the theme on v11+.
-     * See http://stackoverflow.com/questions/14910536/android-dialog-theme-makes-icon-too-light/14910945#14910945.
-     */
-    final int icon;
-    final TypedValue typedValue = new TypedValue();
-    activity.getTheme().resolveAttribute(android.R.attr.alertDialogIcon, typedValue, true);
-    icon = typedValue.resourceId;
-
-    final AlertDialog dialog = new AlertDialog.Builder(activity)
-      .setTitle(R.string.fxaccount_remove_account_dialog_title)
-      .setIcon(icon)
-      .setMessage(R.string.fxaccount_remove_account_dialog_message)
-      .setPositiveButton(android.R.string.ok, new Dialog.OnClickListener() {
-        @Override
-        public void onClick(DialogInterface dialog, int which) {
-          AccountManager.get(activity).removeAccount(account, callback, null);
-        }
-      })
-      .setNegativeButton(android.R.string.cancel, new Dialog.OnClickListener() {
-        @Override
-        public void onClick(DialogInterface dialog, int which) {
-          dialog.cancel();
-        }
-      })
-      .create();
-
-    dialog.show();
-  }
-
-  @Override
-  public boolean onOptionsItemSelected(MenuItem item) {
-    int itemId = item.getItemId();
-    if (itemId == android.R.id.home) {
-      finish();
-      return true;
-    }
-
-    if (itemId == R.id.enable_debug_mode) {
-      FxAccountUtils.LOG_PERSONAL_INFORMATION = !FxAccountUtils.LOG_PERSONAL_INFORMATION;
-      Toast.makeText(this, (FxAccountUtils.LOG_PERSONAL_INFORMATION ? "Enabled" : "Disabled") +
-          " Firefox Account personal information!", Toast.LENGTH_LONG).show();
-      item.setChecked(!item.isChecked());
-      // Display or hide debug options.
-      statusFragment.hardRefresh();
-      return true;
-    }
-
-    return super.onOptionsItemSelected(item);
-  }
-
-  @Override
-  public boolean onCreateOptionsMenu(Menu menu) {
-    final MenuInflater inflater = getMenuInflater();
-    inflater.inflate(R.menu.fxaccount_status_menu, menu);
-    // !defined(MOZILLA_OFFICIAL) || defined(NIGHTLY_BUILD) || defined(MOZ_DEBUG)
-    boolean enabled = !AppConstants.MOZILLA_OFFICIAL || AppConstants.NIGHTLY_BUILD || AppConstants.DEBUG_BUILD;
-    if (!enabled) {
-      menu.removeItem(R.id.enable_debug_mode);
-    } else {
-      final MenuItem debugModeItem = menu.findItem(R.id.enable_debug_mode);
-      if (debugModeItem != null) {
-        // Update checked state based on internal flag.
-        menu.findItem(R.id.enable_debug_mode).setChecked(FxAccountUtils.LOG_PERSONAL_INFORMATION);
-      }
-    }
-    return super.onCreateOptionsMenu(menu);
-  };
-
-  @Override
-  public void openOptionsMenu() {
-    // This is a workaround of an Android bug:
-    // https://code.google.com/p/android/issues/detail?id=185217
-    // openOptionsMenu isn't overriden by WindowDecorActionBar, which is used by AppCompatActivity,
-    // meaning getSupportActionbar().openOptionsMenu doesn't work.
-    // Based loosely on the code in:
-    // http://androidxref.com/6.0.1_r10/xref/frameworks/support/v7/appcompat/src/android/support/v7/internal/app/WindowDecorActionBar.java#getDecorToolbar
-
-    final Window window = getWindow();
-    final View decor = window.getDecorView();
-    final View view = decor.findViewById(R.id.action_bar);
-
-    if (view instanceof Toolbar) {
-      final Toolbar toolbar = (Toolbar) view;
-      toolbar.showOverflowMenu();
-    }
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/activities/FxAccountStatusFragment.java b/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/activities/FxAccountStatusFragment.java
deleted file mode 100644
index a30b92e..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/activities/FxAccountStatusFragment.java
+++ /dev/null
@@ -1,949 +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.fxa.activities;
-
-import android.accounts.Account;
-import android.content.BroadcastReceiver;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.SharedPreferences;
-import android.os.Bundle;
-import android.os.Handler;
-import android.preference.CheckBoxPreference;
-import android.preference.EditTextPreference;
-import android.preference.Preference;
-import android.preference.Preference.OnPreferenceChangeListener;
-import android.preference.Preference.OnPreferenceClickListener;
-import android.preference.PreferenceCategory;
-import android.preference.PreferenceScreen;
-import android.support.v4.content.LocalBroadcastManager;
-import android.text.TextUtils;
-import android.text.format.DateUtils;
-
-import com.squareup.picasso.Picasso;
-import com.squareup.picasso.Target;
-
-import org.mozilla.gecko.AppConstants;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.background.fxa.FxAccountUtils;
-import org.mozilla.gecko.background.preferences.PreferenceFragment;
-import org.mozilla.gecko.fxa.FxAccountConstants;
-import org.mozilla.gecko.fxa.SyncStatusListener;
-import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
-import org.mozilla.gecko.fxa.login.Married;
-import org.mozilla.gecko.fxa.login.State;
-import org.mozilla.gecko.fxa.sync.FxAccountSyncStatusHelper;
-import org.mozilla.gecko.sync.ExtendedJSONObject;
-import org.mozilla.gecko.sync.SharedPreferencesClientsDataDelegate;
-import org.mozilla.gecko.sync.SyncConfiguration;
-import org.mozilla.gecko.sync.setup.activities.ActivityUtils;
-import org.mozilla.gecko.util.HardwareUtils;
-import org.mozilla.gecko.util.ThreadUtils;
-
-import java.util.Calendar;
-import java.util.Date;
-import java.util.GregorianCalendar;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * A fragment that displays the status of an AndroidFxAccount.
- * <p>
- * The owning activity is responsible for providing an AndroidFxAccount at
- * appropriate times.
- */
-public class FxAccountStatusFragment
-    extends PreferenceFragment
-    implements OnPreferenceClickListener, OnPreferenceChangeListener {
-  private static final String LOG_TAG = FxAccountStatusFragment.class.getSimpleName();
-
-  /**
-   * If a device claims to have synced before this date, we will assume it has never synced.
-   */
-  private static final Date EARLIEST_VALID_SYNCED_DATE;
-
-  static {
-    final Calendar c = GregorianCalendar.getInstance();
-    c.set(2000, Calendar.JANUARY, 1, 0, 0, 0);
-    EARLIEST_VALID_SYNCED_DATE = c.getTime();
-  }
-
-  // When a checkbox is toggled, wait 5 seconds (for other checkbox actions)
-  // before trying to sync. Should we kill off the fragment before the sync
-  // request happens, that's okay: the runnable will run if the UI thread is
-  // still around to service it, and since we're not updating any UI, we'll just
-  // schedule the sync as usual. See also comment below about garbage
-  // collection.
-  private static final long DELAY_IN_MILLISECONDS_BEFORE_REQUESTING_SYNC = 5 * 1000;
-  private static final long LAST_SYNCED_TIME_UPDATE_INTERVAL_IN_MILLISECONDS = 60 * 1000;
-  private static final long PROFILE_FETCH_RETRY_INTERVAL_IN_MILLISECONDS = 60 * 1000;
-
-  private static final String[] STAGES_TO_SYNC_ON_DEVICE_NAME_CHANGE = new String[] { "clients" };
-
-  // By default, the auth/account server preference is only shown when the
-  // account is configured to use a custom server. In debug mode, this is set.
-  private static boolean ALWAYS_SHOW_AUTH_SERVER = false;
-
-  // By default, the Sync server preference is only shown when the account is
-  // configured to use a custom Sync server. In debug mode, this is set.
-  private static boolean ALWAYS_SHOW_SYNC_SERVER = false;
-
-  protected PreferenceCategory accountCategory;
-  protected Preference profilePreference;
-  protected Preference manageAccountPreference;
-  protected Preference authServerPreference;
-  protected Preference removeAccountPreference;
-
-  protected Preference needsPasswordPreference;
-  protected Preference needsUpgradePreference;
-  protected Preference needsVerificationPreference;
-  protected Preference needsMasterSyncAutomaticallyEnabledPreference;
-  protected Preference needsFinishMigratingPreference;
-
-  protected PreferenceCategory syncCategory;
-
-  protected CheckBoxPreference bookmarksPreference;
-  protected CheckBoxPreference historyPreference;
-  protected CheckBoxPreference tabsPreference;
-  protected CheckBoxPreference passwordsPreference;
-  protected CheckBoxPreference readingListPreference;
-
-  protected EditTextPreference deviceNamePreference;
-  protected Preference syncServerPreference;
-  protected Preference morePreference;
-  protected Preference syncNowPreference;
-
-  protected volatile AndroidFxAccount fxAccount;
-  // The contract is: when fxAccount is non-null, then clientsDataDelegate is
-  // non-null.  If violated then an IllegalStateException is thrown.
-  protected volatile SharedPreferencesClientsDataDelegate clientsDataDelegate;
-
-  // Used to post delayed sync requests.
-  protected Handler handler;
-
-  // Member variable so that re-posting pushes back the already posted instance.
-  // This Runnable references the fxAccount above, but it is not specific to a
-  // single account. (That is, it does not capture a single account instance.)
-  protected Runnable requestSyncRunnable;
-
-  // Runnable to update last synced time.
-  protected Runnable lastSyncedTimeUpdateRunnable;
-
-  // Broadcast Receiver to update profile Information.
-  protected FxAccountProfileInformationReceiver accountProfileInformationReceiver;
-
-  protected final InnerSyncStatusDelegate syncStatusDelegate = new InnerSyncStatusDelegate();
-  private Target profileAvatarTarget;
-
-  protected Preference ensureFindPreference(String key) {
-    Preference preference = findPreference(key);
-    if (preference == null) {
-      throw new IllegalStateException("Could not find preference with key: " + key);
-    }
-    return preference;
-  }
-
-  @Override
-  public void onCreate(Bundle savedInstanceState) {
-    super.onCreate(savedInstanceState);
-
-    // We need to do this before we can query the hardware menu button state.
-    // We're guaranteed to have an activity at this point (onAttach is called
-    // before onCreate). It's okay to call this multiple times (with different
-    // contexts).
-    HardwareUtils.init(getActivity());
-
-    addPreferences();
-  }
-
-  protected void addPreferences() {
-    addPreferencesFromResource(R.xml.fxaccount_status_prefscreen);
-
-    accountCategory = (PreferenceCategory) ensureFindPreference("signed_in_as_category");
-    profilePreference = ensureFindPreference("profile");
-    manageAccountPreference = ensureFindPreference("manage_account");
-    authServerPreference = ensureFindPreference("auth_server");
-    removeAccountPreference = ensureFindPreference("remove_account");
-
-    needsPasswordPreference = ensureFindPreference("needs_credentials");
-    needsUpgradePreference = ensureFindPreference("needs_upgrade");
-    needsVerificationPreference = ensureFindPreference("needs_verification");
-    needsMasterSyncAutomaticallyEnabledPreference = ensureFindPreference("needs_master_sync_automatically_enabled");
-    needsFinishMigratingPreference = ensureFindPreference("needs_finish_migrating");
-
-    syncCategory = (PreferenceCategory) ensureFindPreference("sync_category");
-
-    bookmarksPreference = (CheckBoxPreference) ensureFindPreference("bookmarks");
-    historyPreference = (CheckBoxPreference) ensureFindPreference("history");
-    tabsPreference = (CheckBoxPreference) ensureFindPreference("tabs");
-    passwordsPreference = (CheckBoxPreference) ensureFindPreference("passwords");
-
-    if (!FxAccountUtils.LOG_PERSONAL_INFORMATION) {
-      removeDebugButtons();
-    } else {
-      connectDebugButtons();
-      ALWAYS_SHOW_AUTH_SERVER = true;
-      ALWAYS_SHOW_SYNC_SERVER = true;
-    }
-
-    profilePreference.setOnPreferenceClickListener(this);
-    manageAccountPreference.setOnPreferenceClickListener(this);
-    removeAccountPreference.setOnPreferenceClickListener(this);
-
-    needsPasswordPreference.setOnPreferenceClickListener(this);
-    needsVerificationPreference.setOnPreferenceClickListener(this);
-    needsFinishMigratingPreference.setOnPreferenceClickListener(this);
-
-    bookmarksPreference.setOnPreferenceClickListener(this);
-    historyPreference.setOnPreferenceClickListener(this);
-    tabsPreference.setOnPreferenceClickListener(this);
-    passwordsPreference.setOnPreferenceClickListener(this);
-
-    deviceNamePreference = (EditTextPreference) ensureFindPreference("device_name");
-    deviceNamePreference.setOnPreferenceChangeListener(this);
-
-    syncServerPreference = ensureFindPreference("sync_server");
-    morePreference = ensureFindPreference("more");
-    morePreference.setOnPreferenceClickListener(this);
-
-    syncNowPreference = ensureFindPreference("sync_now");
-    syncNowPreference.setEnabled(true);
-    syncNowPreference.setOnPreferenceClickListener(this);
-
-    ensureFindPreference("linktos").setOnPreferenceClickListener(this);
-    ensureFindPreference("linkprivacy").setOnPreferenceClickListener(this);
-  }
-
-  /**
-   * We intentionally don't refresh here. Our owning activity is responsible for
-   * providing an AndroidFxAccount to our refresh method in its onResume method.
-   */
-  @Override
-  public void onResume() {
-    super.onResume();
-  }
-
-  @Override
-  public boolean onPreferenceClick(Preference preference) {
-    if (preference == profilePreference) {
-      ActivityUtils.openURLInFennec(getActivity().getApplicationContext(), "about:accounts?action=avatar");
-      return true;
-    }
-
-    if (preference == manageAccountPreference) {
-      ActivityUtils.openURLInFennec(getActivity().getApplicationContext(), "about:accounts?action=manage");
-      return true;
-    }
-
-    if (preference == removeAccountPreference) {
-      FxAccountStatusActivity.maybeDeleteAndroidAccount(getActivity(), fxAccount.getAndroidAccount(), null);
-      return true;
-    }
-
-    if (preference == needsPasswordPreference) {
-      final Intent intent = new Intent(FxAccountConstants.ACTION_FXA_UPDATE_CREDENTIALS);
-      intent.putExtra(FxAccountWebFlowActivity.EXTRA_ENDPOINT, FxAccountConstants.ENDPOINT_PREFERENCES);
-      // Per http://stackoverflow.com/a/8992365, this triggers a known bug with
-      // the soft keyboard not being shown for the started activity. Why, Android, why?
-      intent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
-      startActivity(intent);
-
-      return true;
-    }
-
-    if (preference == needsFinishMigratingPreference) {
-      final Intent intent = new Intent(FxAccountConstants.ACTION_FXA_FINISH_MIGRATING);
-      intent.putExtra(FxAccountWebFlowActivity.EXTRA_ENDPOINT, FxAccountConstants.ENDPOINT_PREFERENCES);
-      // Per http://stackoverflow.com/a/8992365, this triggers a known bug with
-      // the soft keyboard not being shown for the started activity. Why, Android, why?
-      intent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
-      startActivity(intent);
-
-      return true;
-    }
-
-    if (preference == needsVerificationPreference) {
-      final Intent intent = new Intent(FxAccountConstants.ACTION_FXA_CONFIRM_ACCOUNT);
-      // Per http://stackoverflow.com/a/8992365, this triggers a known bug with
-      // the soft keyboard not being shown for the started activity. Why, Android, why?
-      intent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
-      intent.putExtra(FxAccountWebFlowActivity.EXTRA_ENDPOINT, FxAccountConstants.ENDPOINT_PREFERENCES);
-      startActivity(intent);
-
-      return true;
-    }
-
-    if (preference == bookmarksPreference ||
-        preference == historyPreference ||
-        preference == passwordsPreference ||
-        preference == tabsPreference) {
-      saveEngineSelections();
-      return true;
-    }
-
-    if (preference == morePreference) {
-      getActivity().openOptionsMenu();
-      return true;
-    }
-
-    if (preference == syncNowPreference) {
-      if (fxAccount != null) {
-        fxAccount.requestImmediateSync(null, null);
-      }
-      return true;
-    }
-
-    if (TextUtils.equals("linktos", preference.getKey())) {
-      ActivityUtils.openURLInFennec(getActivity().getApplicationContext(), getResources().getString(R.string.fxaccount_link_tos));
-      return true;
-    }
-
-    if (TextUtils.equals("linkprivacy", preference.getKey())) {
-      ActivityUtils.openURLInFennec(getActivity().getApplicationContext(), getResources().getString(R.string.fxaccount_link_pn));
-      return true;
-    }
-
-    return false;
-  }
-
-  protected void setCheckboxesEnabled(boolean enabled) {
-    bookmarksPreference.setEnabled(enabled);
-    historyPreference.setEnabled(enabled);
-    tabsPreference.setEnabled(enabled);
-    passwordsPreference.setEnabled(enabled);
-    // Since we can't sync, we can't update our remote client record.
-    deviceNamePreference.setEnabled(enabled);
-    syncNowPreference.setEnabled(enabled);
-  }
-
-  /**
-   * Show at most one error preference, hiding all others.
-   *
-   * @param errorPreferenceToShow
-   *          single error preference to show; if null, hide all error preferences
-   */
-  protected void showOnlyOneErrorPreference(Preference errorPreferenceToShow) {
-    final Preference[] errorPreferences = new Preference[] {
-        this.needsPasswordPreference,
-        this.needsUpgradePreference,
-        this.needsVerificationPreference,
-        this.needsMasterSyncAutomaticallyEnabledPreference,
-        this.needsFinishMigratingPreference,
-    };
-    for (Preference errorPreference : errorPreferences) {
-      final boolean currentlyShown = null != findPreference(errorPreference.getKey());
-      final boolean shouldBeShown = errorPreference == errorPreferenceToShow;
-      if (currentlyShown == shouldBeShown) {
-        continue;
-      }
-      if (shouldBeShown) {
-        syncCategory.addPreference(errorPreference);
-      } else {
-        syncCategory.removePreference(errorPreference);
-      }
-    }
-  }
-
-  protected void showNeedsPassword() {
-    syncCategory.setTitle(R.string.fxaccount_status_sync);
-    showOnlyOneErrorPreference(needsPasswordPreference);
-    setCheckboxesEnabled(false);
-  }
-
-  protected void showNeedsUpgrade() {
-    syncCategory.setTitle(R.string.fxaccount_status_sync);
-    showOnlyOneErrorPreference(needsUpgradePreference);
-    setCheckboxesEnabled(false);
-  }
-
-  protected void showNeedsVerification() {
-    syncCategory.setTitle(R.string.fxaccount_status_sync);
-    showOnlyOneErrorPreference(needsVerificationPreference);
-    setCheckboxesEnabled(false);
-  }
-
-  protected void showNeedsMasterSyncAutomaticallyEnabled() {
-    syncCategory.setTitle(R.string.fxaccount_status_sync);
-    needsMasterSyncAutomaticallyEnabledPreference.setTitle(AppConstants.Versions.preLollipop ?
-                                                   R.string.fxaccount_status_needs_master_sync_automatically_enabled :
-                                                   R.string.fxaccount_status_needs_master_sync_automatically_enabled_v21);
-    showOnlyOneErrorPreference(needsMasterSyncAutomaticallyEnabledPreference);
-    setCheckboxesEnabled(false);
-  }
-
-  protected void showNeedsFinishMigrating() {
-    syncCategory.setTitle(R.string.fxaccount_status_sync);
-    showOnlyOneErrorPreference(needsFinishMigratingPreference);
-    setCheckboxesEnabled(false);
-  }
-
-  protected void showConnected() {
-    syncCategory.setTitle(R.string.fxaccount_status_sync_enabled);
-    showOnlyOneErrorPreference(null);
-    setCheckboxesEnabled(true);
-  }
-
-  protected class InnerSyncStatusDelegate implements SyncStatusListener {
-    protected final Runnable refreshRunnable = new Runnable() {
-      @Override
-      public void run() {
-        refresh();
-      }
-    };
-
-    @Override
-    public Context getContext() {
-      return FxAccountStatusFragment.this.getActivity();
-    }
-
-    @Override
-    public Account getAccount() {
-      return fxAccount.getAndroidAccount();
-    }
-
-    @Override
-    public void onSyncStarted() {
-      if (fxAccount == null) {
-        return;
-      }
-      Logger.info(LOG_TAG, "Got sync started message; refreshing.");
-      getActivity().runOnUiThread(refreshRunnable);
-    }
-
-    @Override
-    public void onSyncFinished() {
-      if (fxAccount == null) {
-        return;
-      }
-      Logger.info(LOG_TAG, "Got sync finished message; refreshing.");
-      getActivity().runOnUiThread(refreshRunnable);
-    }
-  }
-
-  /**
-   * Notify the fragment that a new AndroidFxAccount instance is current.
-   * <p>
-   * <b>Important:</b> call this method on the UI thread!
-   * <p>
-   * In future, this might be a Loader.
-   *
-   * @param fxAccount new instance.
-   */
-  public void refresh(AndroidFxAccount fxAccount) {
-    if (fxAccount == null) {
-      throw new IllegalArgumentException("fxAccount must not be null");
-    }
-    this.fxAccount = fxAccount;
-    try {
-      this.clientsDataDelegate = new SharedPreferencesClientsDataDelegate(fxAccount.getSyncPrefs(), getActivity().getApplicationContext());
-    } catch (Exception e) {
-      Logger.error(LOG_TAG, "Got exception fetching Sync prefs associated to Firefox Account; aborting.", e);
-      // Something is terribly wrong; best to get a stack trace rather than
-      // continue with a null clients delegate.
-      throw new IllegalStateException(e);
-    }
-
-    handler = new Handler(); // Attached to current (assumed to be UI) thread.
-
-    // Runnable is not specific to one Firefox Account. This runnable will keep
-    // a reference to this fragment alive, but we expect posted runnables to be
-    // serviced very quickly, so this is not an issue.
-    requestSyncRunnable = new RequestSyncRunnable();
-    lastSyncedTimeUpdateRunnable = new LastSyncTimeUpdateRunnable();
-
-    // We would very much like register these status observers in bookended
-    // onResume/onPause calls, but because the Fragment gets onResume during the
-    // Activity's super.onResume, it hasn't yet been told its Firefox Account.
-    // So we register the observer here (and remove it in onPause), and open
-    // ourselves to the possibility that we don't have properly paired
-    // register/unregister calls.
-    FxAccountSyncStatusHelper.getInstance().startObserving(syncStatusDelegate);
-
-    // Register a local broadcast receiver to get profile cached notification.
-    final IntentFilter intentFilter = new IntentFilter();
-    intentFilter.addAction(FxAccountConstants.ACCOUNT_PROFILE_JSON_UPDATED_ACTION);
-    accountProfileInformationReceiver = new FxAccountProfileInformationReceiver();
-    LocalBroadcastManager.getInstance(getActivity()).registerReceiver(accountProfileInformationReceiver, intentFilter);
-
-    // profilePreference is set during onCreate, so it's definitely not null here.
-    final float cornerRadius = getResources().getDimension(R.dimen.fxaccount_profile_image_width) / 2;
-    profileAvatarTarget = new PicassoPreferenceIconTarget(getResources(), profilePreference, cornerRadius);
-
-    refresh();
-  }
-
-  @Override
-  public void onPause() {
-    super.onPause();
-    FxAccountSyncStatusHelper.getInstance().stopObserving(syncStatusDelegate);
-
-    // Focus lost, remove scheduled update if any.
-    if (lastSyncedTimeUpdateRunnable != null) {
-      handler.removeCallbacks(lastSyncedTimeUpdateRunnable);
-    }
-
-    // Focus lost, unregister broadcast receiver.
-    if (accountProfileInformationReceiver != null) {
-      LocalBroadcastManager.getInstance(getActivity()).unregisterReceiver(accountProfileInformationReceiver);
-    }
-
-    if (profileAvatarTarget != null) {
-      Picasso.with(getActivity()).cancelRequest(profileAvatarTarget);
-      profileAvatarTarget = null;
-    }
-  }
-
-  protected void hardRefresh() {
-    // This is the only way to guarantee that the EditText dialogs created by
-    // EditTextPreferences are re-created. This works around the issue described
-    // at http://androiddev.orkitra.com/?p=112079.
-    final PreferenceScreen statusScreen = (PreferenceScreen) ensureFindPreference("status_screen");
-    statusScreen.removeAll();
-    addPreferences();
-
-    refresh();
-  }
-
-  protected void refresh() {
-    // refresh is called from our onResume, which can happen before the owning
-    // Activity tells us about an account (via our public
-    // refresh(AndroidFxAccount) method).
-    if (fxAccount == null) {
-      throw new IllegalArgumentException("fxAccount must not be null");
-    }
-
-    updateProfileInformation();
-    updateAuthServerPreference();
-    updateSyncServerPreference();
-
-    try {
-      // There are error states determined by Android, not the login state
-      // machine, and we have a chance to present these states here. We handle
-      // them specially, since we can't surface these states as part of syncing,
-      // because they generally stop syncs from happening regularly. Right now
-      // there are no such states.
-
-      // Interrogate the Firefox Account's state.
-      State state = fxAccount.getState();
-      switch (state.getNeededAction()) {
-      case NeedsUpgrade:
-        showNeedsUpgrade();
-        break;
-      case NeedsPassword:
-        showNeedsPassword();
-        break;
-      case NeedsVerification:
-        showNeedsVerification();
-        break;
-      case NeedsFinishMigrating:
-        showNeedsFinishMigrating();
-        break;
-      case None:
-        showConnected();
-        break;
-      }
-
-      // We check for the master setting last, since it is not strictly
-      // necessary for the user to address this error state: it's really a
-      // warning state. We surface it for the user's convenience, and to prevent
-      // confused folks wondering why Sync is not working at all.
-      final boolean masterSyncAutomatically = ContentResolver.getMasterSyncAutomatically();
-      if (!masterSyncAutomatically) {
-        showNeedsMasterSyncAutomaticallyEnabled();
-        return;
-      }
-    } finally {
-      // No matter our state, we should update the checkboxes.
-      updateSelectedEngines();
-    }
-
-    final String clientName = clientsDataDelegate.getClientName();
-    deviceNamePreference.setSummary(clientName);
-    deviceNamePreference.setText(clientName);
-
-    updateSyncNowPreference();
-  }
-
-  // This is a helper function similar to TabsAccessor.getLastSyncedString() to calculate relative "Last synced" time span.
-  private String getLastSyncedString(final long startTime) {
-    if (new Date(startTime).before(EARLIEST_VALID_SYNCED_DATE)) {
-      return getActivity().getString(R.string.fxaccount_status_never_synced);
-    }
-    final CharSequence relativeTimeSpanString = DateUtils.getRelativeTimeSpanString(startTime);
-    return getActivity().getResources().getString(R.string.fxaccount_status_last_synced, relativeTimeSpanString);
-  }
-
-  protected void updateSyncNowPreference() {
-    final boolean currentlySyncing = fxAccount.isCurrentlySyncing();
-    syncNowPreference.setEnabled(!currentlySyncing);
-    if (currentlySyncing) {
-      syncNowPreference.setTitle(R.string.fxaccount_status_syncing);
-    } else {
-      syncNowPreference.setTitle(R.string.fxaccount_status_sync_now);
-    }
-    scheduleAndUpdateLastSyncedTime();
-  }
-
-  private void updateProfileInformation() {
-
-    final ExtendedJSONObject profileJSON = fxAccount.getProfileJSON();
-    if (profileJSON == null) {
-      // Update the profile title with email as the fallback.
-      // Profile icon by default use the default avatar as the fallback.
-      profilePreference.setTitle(fxAccount.getEmail());
-      return;
-    }
-
-    updateProfileInformation(profileJSON);
-  }
-
-  /**
-   * Update profile information from json on UI thread.
-   *
-   * @param profileJSON json fetched from server.
-   */
-  protected void updateProfileInformation(final ExtendedJSONObject profileJSON) {
-    // View changes must always be done on UI thread.
-    ThreadUtils.assertOnUiThread();
-
-    FxAccountUtils.pii(LOG_TAG, "Profile JSON is: " + profileJSON.toJSONString());
-
-    final String userName = profileJSON.getString(FxAccountConstants.KEY_PROFILE_JSON_USERNAME);
-    // Update the profile username and email if available.
-    if (!TextUtils.isEmpty(userName)) {
-      profilePreference.setTitle(userName);
-      profilePreference.setSummary(fxAccount.getEmail());
-    } else {
-      profilePreference.setTitle(fxAccount.getEmail());
-    }
-
-    // Avatar URI empty, skip profile image fetch.
-    final String avatarURI = profileJSON.getString(FxAccountConstants.KEY_PROFILE_JSON_AVATAR);
-    if (TextUtils.isEmpty(avatarURI)) {
-      Logger.info(LOG_TAG, "AvatarURI is empty, skipping profile image fetch.");
-      return;
-    }
-
-    // Using noPlaceholder would avoid a pop of the default image, but it's not available in the version of Picasso
-    // we ship in the tree.
-    Picasso
-        .with(getActivity())
-        .load(avatarURI)
-        .centerInside()
-        .resizeDimen(R.dimen.fxaccount_profile_image_width, R.dimen.fxaccount_profile_image_height)
-        .placeholder(R.drawable.sync_avatar_default)
-        .error(R.drawable.sync_avatar_default)
-        .into(profileAvatarTarget);
-  }
-
-  private void scheduleAndUpdateLastSyncedTime() {
-    final String lastSynced = getLastSyncedString(fxAccount.getLastSyncedTimestamp());
-    syncNowPreference.setSummary(lastSynced);
-    handler.postDelayed(lastSyncedTimeUpdateRunnable, LAST_SYNCED_TIME_UPDATE_INTERVAL_IN_MILLISECONDS);
-  }
-
-  protected void updateAuthServerPreference() {
-    final String authServer = fxAccount.getAccountServerURI();
-    final boolean shouldBeShown = ALWAYS_SHOW_AUTH_SERVER || !FxAccountConstants.DEFAULT_AUTH_SERVER_ENDPOINT.equals(authServer);
-    final boolean currentlyShown = null != findPreference(authServerPreference.getKey());
-    if (currentlyShown != shouldBeShown) {
-      if (shouldBeShown) {
-        accountCategory.addPreference(authServerPreference);
-      } else {
-        accountCategory.removePreference(authServerPreference);
-      }
-    }
-    // Always set the summary, because on first run, the preference is visible,
-    // and the above block will be skipped if there is a custom value.
-    authServerPreference.setSummary(authServer);
-  }
-
-  protected void updateSyncServerPreference() {
-    final String syncServer = fxAccount.getTokenServerURI();
-    final boolean shouldBeShown = ALWAYS_SHOW_SYNC_SERVER || !FxAccountConstants.DEFAULT_TOKEN_SERVER_ENDPOINT.equals(syncServer);
-    final boolean currentlyShown = null != findPreference(syncServerPreference.getKey());
-    if (currentlyShown != shouldBeShown) {
-      if (shouldBeShown) {
-        syncCategory.addPreference(syncServerPreference);
-      } else {
-        syncCategory.removePreference(syncServerPreference);
-      }
-    }
-    // Always set the summary, because on first run, the preference is visible,
-    // and the above block will be skipped if there is a custom value.
-    syncServerPreference.setSummary(syncServer);
-  }
-
-  /**
-   * Query shared prefs for the current engine state, and update the UI
-   * accordingly.
-   * <p>
-   * In future, we might want this to be on a background thread, or implemented
-   * as a Loader.
-   */
-  protected void updateSelectedEngines() {
-    try {
-      SharedPreferences syncPrefs = fxAccount.getSyncPrefs();
-      Map<String, Boolean> engines = SyncConfiguration.getUserSelectedEngines(syncPrefs);
-      if (engines != null) {
-        bookmarksPreference.setChecked(engines.containsKey("bookmarks") && engines.get("bookmarks"));
-        historyPreference.setChecked(engines.containsKey("history") && engines.get("history"));
-        passwordsPreference.setChecked(engines.containsKey("passwords") && engines.get("passwords"));
-        tabsPreference.setChecked(engines.containsKey("tabs") && engines.get("tabs"));
-        return;
-      }
-
-      // We don't have user specified preferences.  Perhaps we have seen a meta/global?
-      Set<String> enabledNames = SyncConfiguration.getEnabledEngineNames(syncPrefs);
-      if (enabledNames != null) {
-        bookmarksPreference.setChecked(enabledNames.contains("bookmarks"));
-        historyPreference.setChecked(enabledNames.contains("history"));
-        passwordsPreference.setChecked(enabledNames.contains("passwords"));
-        tabsPreference.setChecked(enabledNames.contains("tabs"));
-        return;
-      }
-
-      // Okay, we don't have userSelectedEngines or enabledEngines. That means
-      // the user hasn't specified to begin with, we haven't specified here, and
-      // we haven't already seen, Sync engines. We don't know our state, so
-      // let's check everything (the default) and disable everything.
-      bookmarksPreference.setChecked(true);
-      historyPreference.setChecked(true);
-      passwordsPreference.setChecked(true);
-      tabsPreference.setChecked(true);
-      setCheckboxesEnabled(false);
-    } catch (Exception e) {
-      Logger.warn(LOG_TAG, "Got exception getting engines to select; ignoring.", e);
-      return;
-    }
-  }
-
-  /**
-   * Persist engine selections to local shared preferences, and request a sync
-   * to persist selections to remote storage.
-   */
-  protected void saveEngineSelections() {
-    final Map<String, Boolean> engineSelections = new HashMap<String, Boolean>();
-    engineSelections.put("bookmarks", bookmarksPreference.isChecked());
-    engineSelections.put("history", historyPreference.isChecked());
-    engineSelections.put("passwords", passwordsPreference.isChecked());
-    engineSelections.put("tabs", tabsPreference.isChecked());
-
-    // No GlobalSession.config, so store directly to shared prefs. We do this on
-    // a background thread to avoid IO on the main thread and strict mode
-    // warnings.
-    new Thread(new PersistEngineSelectionsRunnable(engineSelections)).start();
-  }
-
-  protected void requestDelayedSync() {
-    Logger.info(LOG_TAG, "Posting a delayed request for a sync sometime soon.");
-    handler.removeCallbacks(requestSyncRunnable);
-    handler.postDelayed(requestSyncRunnable, DELAY_IN_MILLISECONDS_BEFORE_REQUESTING_SYNC);
-  }
-
-  /**
-   * Remove all traces of debug buttons. By default, no debug buttons are shown.
-   */
-  protected void removeDebugButtons() {
-    final PreferenceScreen statusScreen = (PreferenceScreen) ensureFindPreference("status_screen");
-    final PreferenceCategory debugCategory = (PreferenceCategory) ensureFindPreference("debug_category");
-    statusScreen.removePreference(debugCategory);
-  }
-
-  /**
-   * A Runnable that persists engine selections to shared prefs, and then
-   * requests a delayed sync.
-   * <p>
-   * References the member <code>fxAccount</code> and is specific to the Android
-   * account associated to that account.
-   */
-  protected class PersistEngineSelectionsRunnable implements Runnable {
-    private final Map<String, Boolean> engineSelections;
-
-    protected PersistEngineSelectionsRunnable(Map<String, Boolean> engineSelections) {
-      this.engineSelections = engineSelections;
-    }
-
-    @Override
-    public void run() {
-      try {
-        // Name shadowing -- do you like it, or do you love it?
-        AndroidFxAccount fxAccount = FxAccountStatusFragment.this.fxAccount;
-        if (fxAccount == null) {
-          return;
-        }
-        Logger.info(LOG_TAG, "Persisting engine selections: " + engineSelections.toString());
-        SyncConfiguration.storeSelectedEnginesToPrefs(fxAccount.getSyncPrefs(), engineSelections);
-        requestDelayedSync();
-      } catch (Exception e) {
-        Logger.warn(LOG_TAG, "Got exception persisting selected engines; ignoring.", e);
-        return;
-      }
-    }
-  }
-
-  /**
-   * A Runnable that requests a sync.
-   * <p>
-   * References the member <code>fxAccount</code>, but is not specific to the
-   * Android account associated to that account.
-   */
-  protected class RequestSyncRunnable implements Runnable {
-    @Override
-    public void run() {
-      // Name shadowing -- do you like it, or do you love it?
-      AndroidFxAccount fxAccount = FxAccountStatusFragment.this.fxAccount;
-      if (fxAccount == null) {
-        return;
-      }
-      Logger.info(LOG_TAG, "Requesting a sync sometime soon.");
-      fxAccount.requestEventualSync(null, null);
-    }
-  }
-
-  /**
-   * The Runnable that schedules a future update and updates the last synced time.
-   */
-  protected class LastSyncTimeUpdateRunnable implements Runnable  {
-    @Override
-    public void run() {
-      scheduleAndUpdateLastSyncedTime();
-    }
-  }
-
-  /**
-   * Broadcast receiver to receive updates for the cached profile action.
-   */
-  public class FxAccountProfileInformationReceiver extends BroadcastReceiver {
-    @Override
-    public void onReceive(Context context, Intent intent) {
-      if (!intent.getAction().equals(FxAccountConstants.ACCOUNT_PROFILE_JSON_UPDATED_ACTION)) {
-        return;
-      }
-
-      Logger.info(LOG_TAG, "Profile avatar cache update action broadcast received.");
-      // Update the UI from cached profile json on the main thread.
-      getActivity().runOnUiThread(new Runnable() {
-        @Override
-        public void run() {
-          updateProfileInformation();
-        }
-      });
-    }
-  }
-
-  /**
-   * A separate listener to separate debug logic from main code paths.
-   */
-  protected class DebugPreferenceClickListener implements OnPreferenceClickListener {
-    @Override
-    public boolean onPreferenceClick(Preference preference) {
-      final String key = preference.getKey();
-      if ("debug_refresh".equals(key)) {
-        Logger.info(LOG_TAG, "Refreshing.");
-        refresh();
-      } else if ("debug_dump".equals(key)) {
-        fxAccount.dump();
-      } else if ("debug_force_sync".equals(key)) {
-        Logger.info(LOG_TAG, "Force syncing.");
-        fxAccount.requestImmediateSync(null, null);
-        // No sense refreshing, since the sync will complete in the future.
-      } else if ("debug_forget_certificate".equals(key)) {
-        State state = fxAccount.getState();
-        try {
-          Married married = (Married) state;
-          Logger.info(LOG_TAG, "Moving to Cohabiting state: Forgetting certificate.");
-          fxAccount.setState(married.makeCohabitingState());
-          refresh();
-        } catch (ClassCastException e) {
-          Logger.info(LOG_TAG, "Not in Married state; can't forget certificate.");
-          // Ignore.
-        }
-      } else if ("debug_invalidate_certificate".equals(key)) {
-        State state = fxAccount.getState();
-        try {
-          Married married = (Married) state;
-          Logger.info(LOG_TAG, "Invalidating certificate.");
-          fxAccount.setState(married.makeCohabitingState().withCertificate("INVALID CERTIFICATE"));
-          refresh();
-        } catch (ClassCastException e) {
-          Logger.info(LOG_TAG, "Not in Married state; can't invalidate certificate.");
-          // Ignore.
-        }
-      } else if ("debug_require_password".equals(key)) {
-        Logger.info(LOG_TAG, "Moving to Separated state: Forgetting password.");
-        State state = fxAccount.getState();
-        fxAccount.setState(state.makeSeparatedState());
-        refresh();
-      } else if ("debug_require_upgrade".equals(key)) {
-        Logger.info(LOG_TAG, "Moving to Doghouse state: Requiring upgrade.");
-        State state = fxAccount.getState();
-        fxAccount.setState(state.makeDoghouseState());
-        refresh();
-      } else if ("debug_migrated_from_sync11".equals(key)) {
-        Logger.info(LOG_TAG, "Moving to MigratedFromSync11 state: Requiring password.");
-        State state = fxAccount.getState();
-        fxAccount.setState(state.makeMigratedFromSync11State(null));
-        refresh();
-      } else if ("debug_make_account_stage".equals(key)) {
-        Logger.info(LOG_TAG, "Moving Account endpoints, in place, to stage.  Deleting Sync and RL prefs and requiring password.");
-        fxAccount.unsafeTransitionToStageEndpoints();
-        refresh();
-      } else if ("debug_make_account_default".equals(key)) {
-        Logger.info(LOG_TAG, "Moving Account endpoints, in place, to default (production).  Deleting Sync and RL prefs and requiring password.");
-        fxAccount.unsafeTransitionToDefaultEndpoints();
-        refresh();
-      } else {
-        return false;
-      }
-      return true;
-    }
-  }
-
-  /**
-   * Iterate through debug buttons, adding a special debug preference click
-   * listener to each of them.
-   */
-  protected void connectDebugButtons() {
-    // Separate listener to really separate debug logic from main code paths.
-    final OnPreferenceClickListener listener = new DebugPreferenceClickListener();
-
-    // We don't want to use Android resource strings for debug UI, so we just
-    // use the keys throughout.
-    final PreferenceCategory debugCategory = (PreferenceCategory) ensureFindPreference("debug_category");
-    debugCategory.setTitle(debugCategory.getKey());
-
-    for (int i = 0; i < debugCategory.getPreferenceCount(); i++) {
-      final Preference button = debugCategory.getPreference(i);
-      button.setTitle(button.getKey()); // Not very friendly, but this is for debugging only!
-      button.setOnPreferenceClickListener(listener);
-    }
-  }
-
-  @Override
-  public boolean onPreferenceChange(Preference preference, Object newValue) {
-    if (preference == deviceNamePreference) {
-      String newClientName = (String) newValue;
-      if (TextUtils.isEmpty(newClientName)) {
-        newClientName = clientsDataDelegate.getDefaultClientName();
-      }
-      final long now = System.currentTimeMillis();
-      clientsDataDelegate.setClientName(newClientName, now);
-      // Force sync the client record, we want the user to see the device name change immediately
-      // on the FxA Device Manager if possible ( = we are online) to avoid confusion
-      // ("I changed my Android's device name but I don't see it on my computer").
-      fxAccount.requestImmediateSync(STAGES_TO_SYNC_ON_DEVICE_NAME_CHANGE, null);
-      hardRefresh(); // Updates the value displayed to the user, among other things.
-      return true;
-    }
-
-    // For everything else, accept the change.
-    return true;
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/activities/FxAccountUpdateCredentialsActivityWeb.java b/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/activities/FxAccountUpdateCredentialsActivityWeb.java
deleted file mode 100644
index 5a2ea79..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/activities/FxAccountUpdateCredentialsActivityWeb.java
+++ /dev/null
@@ -1,11 +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.fxa.activities;
-
-public class FxAccountUpdateCredentialsActivityWeb extends FxAccountWebFlowActivity {
-  public FxAccountUpdateCredentialsActivityWeb() {
-    super(CANNOT_RESUME_WHEN_NO_ACCOUNTS_EXIST, "force_auth");
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/activities/FxAccountWebFlowActivity.java b/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/activities/FxAccountWebFlowActivity.java
deleted file mode 100644
index e33e9c5..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/activities/FxAccountWebFlowActivity.java
+++ /dev/null
@@ -1,91 +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.fxa.activities;
-
-import android.content.Intent;
-import android.os.Bundle;
-import org.mozilla.gecko.Locales;
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.fxa.FxAccountConstants;
-import org.mozilla.gecko.sync.setup.activities.ActivityUtils;
-
-/**
- * Activity which shows the status activity or passes through to web flow.
- */
-public abstract class FxAccountWebFlowActivity extends FxAccountAbstractActivity {
-    protected static final String LOG_TAG = FxAccountWebFlowActivity.class.getSimpleName();
-
-    protected static final String ABOUT_ACCOUNTS = "about:accounts";
-
-    public static final String EXTRA_ENDPOINT = "entrypoint";
-
-    protected static final String[] EXTRAS_TO_PASSTHROUGH = new String[] {
-            EXTRA_ENDPOINT,
-    };
-
-    private final String action;
-    private final String extras;
-
-    public FxAccountWebFlowActivity(int resume, String action) {
-        this(resume, action, null);
-    }
-
-    public FxAccountWebFlowActivity(int resume, String action, String extras) {
-        super(resume);
-        this.action = action;
-        this.extras = (extras != null) ? ("&" + extras) : "";
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public void onCreate(Bundle icicle) {
-        Logger.setThreadLogTag(FxAccountConstants.GLOBAL_LOG_TAG);
-        Logger.debug(LOG_TAG, "onCreate(" + icicle + ")");
-
-        Locales.initializeLocale(getApplicationContext());
-
-        super.onCreate(icicle);
-    }
-
-    protected boolean redirectIfAppropriate() {
-        final boolean redirected = super.redirectIfAppropriate();
-        if (redirected) {
-            return true;
-        }
-
-        final StringBuilder sb = new StringBuilder();
-        sb.append(ABOUT_ACCOUNTS);
-        sb.append("?action=");
-        sb.append(action);
-        sb.append(extras);
-
-        // Pass through a set of known string values from intent extras to about:accounts.
-        final Intent intent = getIntent();
-        if (intent != null) {
-            for (String key : EXTRAS_TO_PASSTHROUGH) {
-                final String value = intent.getStringExtra(key);
-                if (value != null) {
-                    sb.append("&");
-                    sb.append(key);
-                    sb.append("=");
-                    sb.append(value);
-                }
-            }
-        }
-
-        ActivityUtils.openURLInFennec(getApplicationContext(), sb.toString());
-        return true;
-    }
-
-    @Override
-    public void onResume() {
-        super.onResume();
-
-        // We are always redirected.
-        this.finish();
-    }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/activities/PicassoPreferenceIconTarget.java b/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/activities/PicassoPreferenceIconTarget.java
deleted file mode 100644
index f71d3ed..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/activities/PicassoPreferenceIconTarget.java
+++ /dev/null
@@ -1,63 +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.fxa.activities;
-
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.Drawable;
-import android.preference.Preference;
-import android.support.v4.graphics.drawable.RoundedBitmapDrawable;
-import android.support.v4.graphics.drawable.RoundedBitmapDrawableFactory;
-import com.squareup.picasso.Picasso;
-import com.squareup.picasso.Target;
-import org.mozilla.gecko.AppConstants;
-
-/**
- * A Picasso Target that updates a preference icon.
- *
- * Nota bene: Android grew support for updating preference icons programatically
- * only in API 11.  This class silently ignores requests before API 11.
- */
-public class PicassoPreferenceIconTarget implements Target {
-    private final Preference preference;
-    private final Resources resources;
-    private final float cornerRadius;
-
-    public PicassoPreferenceIconTarget(Resources resources, Preference preference) {
-        this(resources, preference, 0);
-    }
-
-    public PicassoPreferenceIconTarget(Resources resources, Preference preference, float cornerRadius) {
-        this.resources = resources;
-        this.preference = preference;
-        this.cornerRadius = cornerRadius;
-    }
-
-    @Override
-    public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) {
-        final Drawable drawable;
-        if (cornerRadius > 0) {
-            final RoundedBitmapDrawable roundedBitmapDrawable;
-            roundedBitmapDrawable = RoundedBitmapDrawableFactory.create(resources, bitmap);
-            roundedBitmapDrawable.setCornerRadius(cornerRadius);
-            roundedBitmapDrawable.setAntiAlias(true);
-            drawable = roundedBitmapDrawable;
-        } else {
-            drawable = new BitmapDrawable(resources, bitmap);
-        }
-        preference.setIcon(drawable);
-    }
-
-    @Override
-    public void onBitmapFailed(Drawable errorDrawable) {
-        preference.setIcon(errorDrawable);
-    }
-
-    @Override
-    public void onPrepareLoad(Drawable placeHolderDrawable) {
-        preference.setIcon(placeHolderDrawable);
-    }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/authenticator/AccountPickler.java b/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/authenticator/AccountPickler.java
deleted file mode 100644
index 3f2c562..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/authenticator/AccountPickler.java
+++ /dev/null
@@ -1,362 +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.fxa.authenticator;
-
-import java.io.FileOutputStream;
-import java.io.PrintStream;
-import java.security.NoSuchAlgorithmException;
-import java.security.spec.InvalidKeySpecException;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Map.Entry;
-
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.db.BrowserContract;
-import org.mozilla.gecko.fxa.FxAccountConstants;
-import org.mozilla.gecko.fxa.login.State;
-import org.mozilla.gecko.fxa.login.State.StateLabel;
-import org.mozilla.gecko.fxa.login.StateFactory;
-import org.mozilla.gecko.sync.ExtendedJSONObject;
-import org.mozilla.gecko.sync.NonObjectJSONException;
-import org.mozilla.gecko.sync.Utils;
-
-import android.content.Context;
-
-/**
- * Android deletes Account objects when the Authenticator that owns the Account
- * disappears. This happens when an App is installed to the SD card and the SD
- * card is un-mounted or the device is rebooted.
- * <p>
- * We work around this by pickling the current Firefox account data every sync
- * and unpickling when we check if Firefox accounts exist (called from Fennec).
- * <p>
- * Android just doesn't support installing Apps that define long-lived Services
- * and/or own Account types onto the SD card. The documentation says not to do
- * it. There are hordes of developers who want to do it, and have tried to
- * register for almost every "package installation changed" broadcast intent
- * that Android supports. They all explicitly state that the package that has
- * changed does *not* receive the broadcast intent, thereby preventing an App
- * from re-establishing its state.
- * <p>
- * <a href="http://developer.android.com/guide/topics/data/install-location.html">Reference.</a>
- * <p>
- * <b>Quote</b>: Your AbstractThreadedSyncAdapter and all its sync functionality
- * will not work until external storage is remounted.
- * <p>
- * <b>Quote</b>: Your running Service will be killed and will not be restarted
- * when external storage is remounted. You can, however, register for the
- * ACTION_EXTERNAL_APPLICATIONS_AVAILABLE broadcast Intent, which will notify
- * your application when applications installed on external storage have become
- * available to the system again. At which time, you can restart your Service.
- * <p>
- * Problem: <a href="http://code.google.com/p/android/issues/detail?id=8485">that intent doesn't work</a>!
- * <p>
- * See bug 768102 for more information in the context of Sync.
- */
-public class AccountPickler {
-  public static final String LOG_TAG = AccountPickler.class.getSimpleName();
-
-  public static final long PICKLE_VERSION = 3;
-
-  public static final String KEY_PICKLE_VERSION = "pickle_version";
-  public static final String KEY_PICKLE_TIMESTAMP = "pickle_timestamp";
-
-  public static final String KEY_ACCOUNT_VERSION = "account_version";
-  public static final String KEY_ACCOUNT_TYPE = "account_type";
-  public static final String KEY_EMAIL = "email";
-  public static final String KEY_PROFILE = "profile";
-  public static final String KEY_IDP_SERVER_URI = "idpServerURI";
-  public static final String KEY_TOKEN_SERVER_URI = "tokenServerURI";
-  public static final String KEY_PROFILE_SERVER_URI = "profileServerURI";
-
-  public static final String KEY_AUTHORITIES_TO_SYNC_AUTOMATICALLY_MAP = "authoritiesToSyncAutomaticallyMap";
-
-  // Deprecated, but maintained for migration purposes.
-  public static final String KEY_IS_SYNCING_ENABLED = "isSyncingEnabled";
-
-  public static final String KEY_BUNDLE = "bundle";
-
-  /**
-   * Remove Firefox account persisted to disk.
-   * This operation is synchronized to avoid race condition while deleting the account.
-   *
-   * @param context Android context.
-   * @param filename name of persisted pickle file; must not contain path separators.
-   * @return <code>true</code> if given pickle existed and was successfully deleted.
-   */
-  public synchronized static boolean deletePickle(final Context context, final String filename) {
-    return context.deleteFile(filename);
-  }
-
-  public static ExtendedJSONObject toJSON(final AndroidFxAccount account, final long now) {
-    final ExtendedJSONObject o = new ExtendedJSONObject();
-    o.put(KEY_PICKLE_VERSION, PICKLE_VERSION);
-    o.put(KEY_PICKLE_TIMESTAMP, now);
-
-    o.put(KEY_ACCOUNT_VERSION, AndroidFxAccount.CURRENT_ACCOUNT_VERSION);
-    o.put(KEY_ACCOUNT_TYPE, FxAccountConstants.ACCOUNT_TYPE);
-    o.put(KEY_EMAIL, account.getEmail());
-    o.put(KEY_PROFILE, account.getProfile());
-    o.put(KEY_IDP_SERVER_URI, account.getAccountServerURI());
-    o.put(KEY_TOKEN_SERVER_URI, account.getTokenServerURI());
-    o.put(KEY_PROFILE_SERVER_URI, account.getProfileServerURI());
-
-    final ExtendedJSONObject p = new ExtendedJSONObject();
-    for (Entry<String, Boolean> pair : account.getAuthoritiesToSyncAutomaticallyMap().entrySet()) {
-      p.put(pair.getKey(), pair.getValue());
-    }
-    o.put(KEY_AUTHORITIES_TO_SYNC_AUTOMATICALLY_MAP, p);
-
-    // TODO: If prefs version changes under us, SyncPrefsPath will change, "clearing" prefs.
-
-    final ExtendedJSONObject bundle = account.unbundle();
-    if (bundle == null) {
-      Logger.warn(LOG_TAG, "Unable to obtain account bundle; aborting.");
-      return null;
-    }
-    o.put(KEY_BUNDLE, bundle);
-
-    return o;
-  }
-
-  /**
-   * Persist Firefox account to disk as a JSON object.
-   * This operation is synchronized to avoid race condition while deleting the account.
-   *
-   * @param account the AndroidFxAccount to persist to disk
-   * @param filename name of file to persist to; must not contain path separators.
-   */
-  public synchronized static void pickle(final AndroidFxAccount account, final String filename) {
-    final ExtendedJSONObject o = toJSON(account, System.currentTimeMillis());
-    writeToDisk(account.context, filename, o);
-  }
-
-  private static void writeToDisk(final Context context, final String filename,
-      final ExtendedJSONObject pickle) {
-    try {
-      final FileOutputStream fos = context.openFileOutput(filename, Context.MODE_PRIVATE);
-      try {
-        final PrintStream ps = new PrintStream(fos);
-        try {
-          ps.print(pickle.toJSONString());
-          Logger.debug(LOG_TAG, "Persisted " + pickle.keySet().size() +
-              " account settings to " + filename + ".");
-        } finally {
-          ps.close();
-        }
-      } finally {
-        fos.close();
-      }
-    } catch (Exception e) {
-      Logger.warn(LOG_TAG, "Caught exception persisting account settings to " + filename +
-          "; ignoring.", e);
-    }
-  }
-
-  /**
-   * Create Android account from saved JSON object. Assumes that an account does not exist.
-   * This operation is synchronized to avoid race condition while deleting the account.
-   *
-   * @param context
-   *          Android context.
-   * @param filename
-   *          name of file to read from; must not contain path separators.
-   * @return created Android account, or null on error.
-   */
-  public synchronized static AndroidFxAccount unpickle(final Context context, final String filename) {
-    final String jsonString = Utils.readFile(context, filename);
-    if (jsonString == null) {
-      Logger.info(LOG_TAG, "Pickle file '" + filename + "' not found; aborting.");
-      return null;
-    }
-
-    ExtendedJSONObject json = null;
-    try {
-      json = new ExtendedJSONObject(jsonString);
-    } catch (Exception e) {
-      Logger.warn(LOG_TAG, "Got exception reading pickle file '" + filename + "'; aborting.", e);
-      return null;
-    }
-
-    final UnpickleParams params;
-    try {
-      params = UnpickleParams.fromJSON(json);
-    } catch (Exception e) {
-      Logger.warn(LOG_TAG, "Got exception extracting unpickle json; aborting.", e);
-      return null;
-    }
-
-    final AndroidFxAccount account;
-    try {
-      account = AndroidFxAccount.addAndroidAccount(context, params.email, params.profile,
-          params.authServerURI, params.tokenServerURI, params.profileServerURI, params.state,
-          params.authoritiesToSyncAutomaticallyMap,
-          params.accountVersion,
-          true, params.bundle);
-    } catch (Exception e) {
-      Logger.warn(LOG_TAG, "Exception when adding Android Account; aborting.", e);
-      return null;
-    }
-
-    if (account == null) {
-      Logger.warn(LOG_TAG, "Failed to add Android Account; aborting.");
-      return null;
-    }
-
-    Long timestamp = json.getLong(KEY_PICKLE_TIMESTAMP);
-    if (timestamp == null) {
-      Logger.warn(LOG_TAG, "Did not find timestamp in pickle file; ignoring.");
-      timestamp = -1L;
-    }
-
-    Logger.info(LOG_TAG, "Un-pickled Android account named " + params.email + " (version " +
-        params.pickleVersion + ", pickled at " + timestamp + ").");
-
-    return account;
-  }
-
-  private static class UnpickleParams {
-    private Long pickleVersion;
-
-    private int accountVersion;
-    private String email;
-    private String profile;
-    private String authServerURI;
-    private String tokenServerURI;
-    private String profileServerURI;
-    private final Map<String, Boolean> authoritiesToSyncAutomaticallyMap = new HashMap<>();
-
-    private ExtendedJSONObject bundle;
-    private State state;
-
-    private UnpickleParams() {
-    }
-
-    private static UnpickleParams fromJSON(final ExtendedJSONObject json)
-        throws InvalidKeySpecException, NoSuchAlgorithmException, NonObjectJSONException {
-      final UnpickleParams params = new UnpickleParams();
-      params.pickleVersion = json.getLong(KEY_PICKLE_VERSION);
-      if (params.pickleVersion == null) {
-        throw new IllegalStateException("Pickle version not found.");
-      }
-
-      /*
-       * Version 1 and version 2 are identical, except version 2 throws if the
-       * internal Android Account type has changed. Version 1 used to throw in
-       * this case, but we intentionally used the pickle file to migrate across
-       * Account types, bumping the version simultaneously.
-       *
-       * Version 3 replaces "isSyncEnabled" with a map (String -> Boolean)
-       * associating Android authorities to whether or not they are configured
-       * to sync automatically.
-       */
-      switch (params.pickleVersion.intValue()) {
-      case 3: {
-        // Sanity check.
-        final String accountType = json.getString(KEY_ACCOUNT_TYPE);
-        if (!FxAccountConstants.ACCOUNT_TYPE.equals(accountType)) {
-          throw new IllegalStateException("Account type has changed from " + accountType + " to " + FxAccountConstants.ACCOUNT_TYPE + ".");
-        }
-
-        params.unpickleV3(json);
-      }
-      break;
-
-      case 2: {
-        // Sanity check.
-        final String accountType = json.getString(KEY_ACCOUNT_TYPE);
-        if (!FxAccountConstants.ACCOUNT_TYPE.equals(accountType)) {
-          throw new IllegalStateException("Account type has changed from " + accountType + " to " + FxAccountConstants.ACCOUNT_TYPE + ".");
-        }
-
-        params.unpickleV1(json);
-      }
-      break;
-
-      case 1: {
-        // Warn about account type changing, but don't throw over it.
-        final String accountType = json.getString(KEY_ACCOUNT_TYPE);
-        if (!FxAccountConstants.ACCOUNT_TYPE.equals(accountType)) {
-          Logger.warn(LOG_TAG, "Account type has changed from " + accountType + " to " + FxAccountConstants.ACCOUNT_TYPE + "; ignoring.");
-        }
-
-        params.unpickleV1(json);
-      }
-      break;
-
-      default:
-        throw new IllegalStateException("Unknown pickle version, " + params.pickleVersion + ".");
-      }
-
-      return params;
-    }
-
-    private void unpickleV1(final ExtendedJSONObject json)
-        throws NonObjectJSONException, NoSuchAlgorithmException, InvalidKeySpecException {
-
-      this.accountVersion = json.getIntegerSafely(KEY_ACCOUNT_VERSION);
-      this.email = json.getString(KEY_EMAIL);
-      this.profile = json.getString(KEY_PROFILE);
-      this.authServerURI = json.getString(KEY_IDP_SERVER_URI);
-      this.tokenServerURI = json.getString(KEY_TOKEN_SERVER_URI);
-      this.profileServerURI = json.getString(KEY_PROFILE_SERVER_URI);
-
-      // Fallback to default value when profile server URI was not pickled.
-      if (this.profileServerURI == null) {
-        this.profileServerURI = FxAccountConstants.DEFAULT_AUTH_SERVER_ENDPOINT.equals(this.authServerURI)
-            ? FxAccountConstants.DEFAULT_PROFILE_SERVER_ENDPOINT
-            : FxAccountConstants.STAGE_PROFILE_SERVER_ENDPOINT;
-      }
-
-      // We get the default value for everything except syncing browser data.
-      this.authoritiesToSyncAutomaticallyMap.put(BrowserContract.AUTHORITY, json.getBoolean(KEY_IS_SYNCING_ENABLED));
-
-      this.bundle = json.getObject(KEY_BUNDLE);
-      if (bundle == null) {
-        throw new IllegalStateException("Pickle bundle is null.");
-      }
-      this.state = getState(bundle);
-    }
-
-    private void unpickleV3(final ExtendedJSONObject json)
-        throws NonObjectJSONException, NoSuchAlgorithmException, InvalidKeySpecException {
-      // We'll overwrite the extracted sync automatically map.
-      unpickleV1(json);
-
-      // Extract the map of authorities to sync automatically.
-      authoritiesToSyncAutomaticallyMap.clear();
-      final ExtendedJSONObject o = json.getObject(KEY_AUTHORITIES_TO_SYNC_AUTOMATICALLY_MAP);
-      if (o == null) {
-        return;
-      }
-      for (String key : o.keySet()) {
-        final Boolean enabled = o.getBoolean(key);
-        if (enabled != null) {
-          authoritiesToSyncAutomaticallyMap.put(key, enabled);
-        }
-      }
-    }
-
-    private State getState(final ExtendedJSONObject bundle) throws InvalidKeySpecException,
-            NonObjectJSONException, NoSuchAlgorithmException {
-      // TODO: Should copy-pasta BUNDLE_KEY_STATE & LABEL to this file to ensure we maintain
-      // old versions?
-      final StateLabel stateLabelString = StateLabel.valueOf(
-          bundle.getString(AndroidFxAccount.BUNDLE_KEY_STATE_LABEL));
-      final String stateString = bundle.getString(AndroidFxAccount.BUNDLE_KEY_STATE);
-      if (stateLabelString == null || stateString == null) {
-        throw new IllegalStateException("stateLabel and stateString must not be null, but: " +
-            "(stateLabel == null) = " + (stateLabelString == null) +
-            " and (stateString == null) = " + (stateString == null));
-      }
-
-      try {
-        return StateFactory.fromJSONObject(stateLabelString, new ExtendedJSONObject(stateString));
-      } catch (Exception e) {
-        throw new IllegalStateException("could not get state", e);
-      }
-    }
-  }
-}
diff --git 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
deleted file mode 100644
index d7ce7c4..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/authenticator/AndroidFxAccount.java
+++ /dev/null
@@ -1,929 +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.fxa.authenticator;
-
-import android.accounts.Account;
-import android.accounts.AccountManager;
-import android.annotation.SuppressLint;
-import android.app.Activity;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.ResultReceiver;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-import android.support.v4.content.LocalBroadcastManager;
-import android.text.TextUtils;
-import android.util.Log;
-
-import org.mozilla.gecko.background.common.GlobalConstants;
-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.fxa.FirefoxAccounts;
-import org.mozilla.gecko.fxa.FxAccountConstants;
-import org.mozilla.gecko.fxa.login.State;
-import org.mozilla.gecko.fxa.login.State.StateLabel;
-import org.mozilla.gecko.fxa.login.StateFactory;
-import org.mozilla.gecko.fxa.login.TokensAndKeysState;
-import org.mozilla.gecko.fxa.sync.FxAccountProfileService;
-import org.mozilla.gecko.sync.ExtendedJSONObject;
-import org.mozilla.gecko.sync.Utils;
-import org.mozilla.gecko.sync.setup.Constants;
-import org.mozilla.gecko.util.ThreadUtils;
-
-import java.io.UnsupportedEncodingException;
-import java.net.URISyntaxException;
-import java.security.GeneralSecurityException;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.Semaphore;
-
-/**
- * A Firefox Account that stores its details and state as user data attached to
- * an Android Account instance.
- * <p>
- * Account user data is accessible only to the Android App(s) that own the
- * Account type. Account user data is not removed when the App's private data is
- * cleared.
- */
-public class AndroidFxAccount {
-  protected static final String LOG_TAG = AndroidFxAccount.class.getSimpleName();
-
-  public static final int CURRENT_SYNC_PREFS_VERSION = 1;
-  public static final int CURRENT_RL_PREFS_VERSION = 1;
-
-  // When updating the account, do not forget to update AccountPickler.
-  public static final int CURRENT_ACCOUNT_VERSION = 3;
-  public static final String ACCOUNT_KEY_ACCOUNT_VERSION = "version";
-  public static final String ACCOUNT_KEY_PROFILE = "profile";
-  public static final String ACCOUNT_KEY_IDP_SERVER = "idpServerURI";
-  private static final String ACCOUNT_KEY_PROFILE_SERVER = "profileServerURI";
-
-  public static final String ACCOUNT_KEY_TOKEN_SERVER = "tokenServerURI";       // Sync-specific.
-  public static final String ACCOUNT_KEY_DESCRIPTOR = "descriptor";
-
-  public static final int CURRENT_BUNDLE_VERSION = 2;
-  public static final String BUNDLE_KEY_BUNDLE_VERSION = "version";
-  public static final String BUNDLE_KEY_STATE_LABEL = "stateLabel";
-  public static final String BUNDLE_KEY_STATE = "state";
-  public static final String BUNDLE_KEY_PROFILE_JSON = "profile";
-
-  public static final String ACCOUNT_KEY_DEVICE_ID = "deviceId";
-  public static final String ACCOUNT_KEY_DEVICE_REGISTRATION_VERSION = "deviceRegistrationVersion";
-
-  // Account authentication token type for fetching account profile.
-  public static final String PROFILE_OAUTH_TOKEN_TYPE = "oauth::profile";
-
-  // 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<>();
-    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.
-    m.put(BrowserContract.AUTHORITY, true);
-    DEFAULT_AUTHORITIES_TO_SYNC_AUTOMATICALLY_MAP = Collections.unmodifiableMap(m);
-  }
-
-  private static final String PREF_KEY_LAST_SYNCED_TIMESTAMP = "lastSyncedTimestamp";
-
-  protected final Context context;
-  protected final AccountManager accountManager;
-  protected final Account account;
-
-  /**
-   * A cache associating Account name (email address) to a representation of the
-   * account's internal bundle.
-   * <p>
-   * The cache is invalidated entirely when <it>any</it> new Account is added,
-   * because there is no reliable way to know that an Account has been removed
-   * and then re-added.
-   */
-  protected static final ConcurrentHashMap<String, ExtendedJSONObject> perAccountBundleCache =
-      new ConcurrentHashMap<>();
-
-  public static void invalidateCaches() {
-    perAccountBundleCache.clear();
-  }
-
-  /**
-   * Create an Android Firefox Account instance backed by an Android Account
-   * instance.
-   * <p>
-   * We expect a long-lived application context to avoid life-cycle issues that
-   * might arise if the internally cached AccountManager instance surfaces UI.
-   * <p>
-   * We take care to not install any listeners or observers that might outlive
-   * the AccountManager; and Android ensures the AccountManager doesn't outlive
-   * the associated context.
-   *
-   * @param applicationContext
-   *          to use as long-lived ambient Android context.
-   * @param account
-   *          Android account to use for storage.
-   */
-  public AndroidFxAccount(Context applicationContext, Account account) {
-    this.context = applicationContext;
-    this.account = account;
-    this.accountManager = AccountManager.get(this.context);
-  }
-
-  public static AndroidFxAccount fromContext(Context context) {
-    context = context.getApplicationContext();
-    Account account = FirefoxAccounts.getFirefoxAccount(context);
-    if (account == null) {
-      return null;
-    }
-    return new AndroidFxAccount(context, account);
-  }
-
-  /**
-   * Persist the Firefox account to disk as a JSON object. Note that this is a wrapper around
-   * {@link AccountPickler#pickle}, and is identical to calling it directly.
-   * <p>
-   * Note that pickling is different from bundling, which involves operations on a
-   * {@link android.os.Bundle Bundle} object of miscellaneous data associated with the account.
-   * See {@link #persistBundle} and {@link #unbundle} for more.
-   */
-  public void pickle(final String filename) {
-    AccountPickler.pickle(this, filename);
-  }
-
-  public Account getAndroidAccount() {
-    return this.account;
-  }
-
-  protected int getAccountVersion() {
-    String v = accountManager.getUserData(account, ACCOUNT_KEY_ACCOUNT_VERSION);
-    if (v == null) {
-      return 0;         // Implicit.
-    }
-
-    try {
-      return Integer.parseInt(v, 10);
-    } catch (NumberFormatException ex) {
-      return 0;
-    }
-  }
-
-  /**
-   * Saves the given data as the internal bundle associated with this account.
-   * @param bundle to write to account.
-   */
-  protected synchronized void persistBundle(ExtendedJSONObject bundle) {
-    perAccountBundleCache.put(account.name, bundle);
-    accountManager.setUserData(account, ACCOUNT_KEY_DESCRIPTOR, bundle.toJSONString());
-  }
-
-  protected ExtendedJSONObject unbundle() {
-    return unbundle(true);
-  }
-
-  /**
-   * Retrieve the internal bundle associated with this account.
-   * @return bundle associated with account.
-   */
-  protected synchronized ExtendedJSONObject unbundle(boolean allowCachedBundle) {
-    if (allowCachedBundle) {
-      final ExtendedJSONObject cachedBundle = perAccountBundleCache.get(account.name);
-      if (cachedBundle != null) {
-        Logger.debug(LOG_TAG, "Returning cached account bundle.");
-        return cachedBundle;
-      }
-    }
-
-    final int version = getAccountVersion();
-    if (version < CURRENT_ACCOUNT_VERSION) {
-      // Needs upgrade. For now, do nothing. We'd like to just put your account
-      // into the Separated state here and have you update your credentials.
-      return null;
-    }
-
-    if (version > CURRENT_ACCOUNT_VERSION) {
-      // Oh dear.
-      return null;
-    }
-
-    String bundleString = accountManager.getUserData(account, ACCOUNT_KEY_DESCRIPTOR);
-    if (bundleString == null) {
-      return null;
-    }
-    final ExtendedJSONObject bundle = unbundleAccountV2(bundleString);
-    perAccountBundleCache.put(account.name, bundle);
-    Logger.info(LOG_TAG, "Account bundle persisted to cache.");
-    return bundle;
-  }
-
-  protected String getBundleData(String key) {
-    ExtendedJSONObject o = unbundle();
-    if (o == null) {
-      return null;
-    }
-    return o.getString(key);
-  }
-
-  protected boolean getBundleDataBoolean(String key, boolean def) {
-    ExtendedJSONObject o = unbundle();
-    if (o == null) {
-      return def;
-    }
-    Boolean b = o.getBoolean(key);
-    if (b == null) {
-      return def;
-    }
-    return b;
-  }
-
-  protected byte[] getBundleDataBytes(String key) {
-    ExtendedJSONObject o = unbundle();
-    if (o == null) {
-      return null;
-    }
-    return o.getByteArrayHex(key);
-  }
-
-  protected void updateBundleValues(String key, String value, String... more) {
-    if (more.length % 2 != 0) {
-      throw new IllegalArgumentException("more must be a list of key, value pairs");
-    }
-    ExtendedJSONObject descriptor = unbundle();
-    if (descriptor == null) {
-      return;
-    }
-    descriptor.put(key, value);
-    for (int i = 0; i + 1 < more.length; i += 2) {
-      descriptor.put(more[i], more[i+1]);
-    }
-    persistBundle(descriptor);
-  }
-
-  private ExtendedJSONObject unbundleAccountV1(String bundle) {
-    ExtendedJSONObject o;
-    try {
-      o = new ExtendedJSONObject(bundle);
-    } catch (Exception e) {
-      return null;
-    }
-    if (CURRENT_BUNDLE_VERSION == o.getIntegerSafely(BUNDLE_KEY_BUNDLE_VERSION)) {
-      return o;
-    }
-    return null;
-  }
-
-  private ExtendedJSONObject unbundleAccountV2(String bundle) {
-    return unbundleAccountV1(bundle);
-  }
-
-  /**
-   * Note that if the user clears data, an account will be left pointing to a
-   * deleted profile. Such is life.
-   */
-  public String getProfile() {
-    return accountManager.getUserData(account, ACCOUNT_KEY_PROFILE);
-  }
-
-  public String getAccountServerURI() {
-    return accountManager.getUserData(account, ACCOUNT_KEY_IDP_SERVER);
-  }
-
-  public String getTokenServerURI() {
-    return accountManager.getUserData(account, ACCOUNT_KEY_TOKEN_SERVER);
-  }
-
-  public String getProfileServerURI() {
-    String profileURI = accountManager.getUserData(account, ACCOUNT_KEY_PROFILE_SERVER);
-    if (profileURI == null) {
-      if (isStaging()) {
-        return FxAccountConstants.STAGE_PROFILE_SERVER_ENDPOINT;
-      }
-      return FxAccountConstants.DEFAULT_PROFILE_SERVER_ENDPOINT;
-    }
-    return profileURI;
-  }
-
-  public String getOAuthServerURI() {
-    // Allow testing against stage.
-    if (isStaging()) {
-      return FxAccountConstants.STAGE_OAUTH_SERVER_ENDPOINT;
-    } else {
-      return FxAccountConstants.DEFAULT_OAUTH_SERVER_ENDPOINT;
-    }
-  }
-
-  private boolean isStaging() {
-    return FxAccountConstants.STAGE_AUTH_SERVER_ENDPOINT.equals(getAccountServerURI());
-  }
-
-  private String constructPrefsPath(String product, long version, String extra) throws GeneralSecurityException, UnsupportedEncodingException {
-    String profile = getProfile();
-    String username = account.name;
-
-    if (profile == null) {
-      throw new IllegalStateException("Missing profile. Cannot fetch prefs.");
-    }
-
-    if (username == null) {
-      throw new IllegalStateException("Missing username. Cannot fetch prefs.");
-    }
-
-    final String fxaServerURI = getAccountServerURI();
-    if (fxaServerURI == null) {
-      throw new IllegalStateException("No account server URI. Cannot fetch prefs.");
-    }
-
-    // This is unique for each syncing 'view' of the account.
-    final String serverURLThing = fxaServerURI + "!" + extra;
-    return Utils.getPrefsPath(product, username, serverURLThing, profile, version);
-  }
-
-  /**
-   * This needs to return a string because of the tortured prefs access in GlobalSession.
-   */
-  public String getSyncPrefsPath() throws GeneralSecurityException, UnsupportedEncodingException {
-    final String tokenServerURI = getTokenServerURI();
-    if (tokenServerURI == null) {
-      throw new IllegalStateException("No token server URI. Cannot fetch prefs.");
-    }
-
-    final String product = GlobalConstants.BROWSER_INTENT_PACKAGE + ".fxa";
-    final long version = CURRENT_SYNC_PREFS_VERSION;
-    return constructPrefsPath(product, version, tokenServerURI);
-  }
-
-  public String getReadingListPrefsPath() throws GeneralSecurityException, UnsupportedEncodingException {
-    final String product = GlobalConstants.BROWSER_INTENT_PACKAGE + ".reading";
-    final long version = CURRENT_RL_PREFS_VERSION;
-    return constructPrefsPath(product, version, "");
-  }
-
-  public SharedPreferences getSyncPrefs() throws UnsupportedEncodingException, GeneralSecurityException {
-    return context.getSharedPreferences(getSyncPrefsPath(), Utils.SHARED_PREFERENCES_MODE);
-  }
-
-  public SharedPreferences getReadingListPrefs() throws UnsupportedEncodingException, GeneralSecurityException {
-    return context.getSharedPreferences(getReadingListPrefsPath(), Utils.SHARED_PREFERENCES_MODE);
-  }
-
-  /**
-   * Extract a JSON dictionary of the string values associated to this account.
-   * <p>
-   * <b>For debugging use only!</b> The contents of this JSON object completely
-   * determine the user's Firefox Account status and yield access to whatever
-   * user data the device has access to.
-   *
-   * @return JSON-object of Strings.
-   */
-  public ExtendedJSONObject toJSONObject() {
-    ExtendedJSONObject o = unbundle();
-    o.put("email", account.name);
-    try {
-      o.put("emailUTF8", Utils.byte2Hex(account.name.getBytes("UTF-8")));
-    } catch (UnsupportedEncodingException e) {
-      // Ignore.
-    }
-    o.put("fxaDeviceId", getDeviceId());
-    o.put("fxaDeviceRegistrationVersion", getDeviceRegistrationVersion());
-    return o;
-  }
-
-  public static AndroidFxAccount addAndroidAccount(
-      Context context,
-      String email,
-      String profile,
-      String idpServerURI,
-      String tokenServerURI,
-      String profileServerURI,
-      State state,
-      final Map<String, Boolean> authoritiesToSyncAutomaticallyMap)
-          throws UnsupportedEncodingException, GeneralSecurityException, URISyntaxException {
-    return addAndroidAccount(context, email, profile, idpServerURI, tokenServerURI, profileServerURI, state,
-        authoritiesToSyncAutomaticallyMap,
-        CURRENT_ACCOUNT_VERSION, false, null);
-  }
-
-  public static AndroidFxAccount addAndroidAccount(
-      Context context,
-      String email,
-      String profile,
-      String idpServerURI,
-      String tokenServerURI,
-      String profileServerURI,
-      State state,
-      final Map<String, Boolean> authoritiesToSyncAutomaticallyMap,
-      final int accountVersion,
-      final boolean fromPickle,
-      ExtendedJSONObject bundle)
-          throws UnsupportedEncodingException, GeneralSecurityException, URISyntaxException {
-    if (email == null) {
-      throw new IllegalArgumentException("email must not be null");
-    }
-    if (profile == null) {
-      throw new IllegalArgumentException("profile must not be null");
-    }
-    if (idpServerURI == null) {
-      throw new IllegalArgumentException("idpServerURI must not be null");
-    }
-    if (tokenServerURI == null) {
-      throw new IllegalArgumentException("tokenServerURI must not be null");
-    }
-    if (profileServerURI == null) {
-      throw new IllegalArgumentException("profileServerURI must not be null");
-    }
-    if (state == null) {
-      throw new IllegalArgumentException("state must not be null");
-    }
-
-    // TODO: Add migration code.
-    if (accountVersion != CURRENT_ACCOUNT_VERSION) {
-      throw new IllegalStateException("Could not create account of version " + accountVersion +
-          ". Current version is " + CURRENT_ACCOUNT_VERSION + ".");
-    }
-
-    // Android has internal restrictions that require all values in this
-    // bundle to be strings. *sigh*
-    Bundle userdata = new Bundle();
-    userdata.putString(ACCOUNT_KEY_ACCOUNT_VERSION, "" + CURRENT_ACCOUNT_VERSION);
-    userdata.putString(ACCOUNT_KEY_IDP_SERVER, idpServerURI);
-    userdata.putString(ACCOUNT_KEY_TOKEN_SERVER, tokenServerURI);
-    userdata.putString(ACCOUNT_KEY_PROFILE_SERVER, profileServerURI);
-    userdata.putString(ACCOUNT_KEY_PROFILE, profile);
-
-    if (bundle == null) {
-      bundle = new ExtendedJSONObject();
-      // TODO: How to upgrade?
-      bundle.put(BUNDLE_KEY_BUNDLE_VERSION, CURRENT_BUNDLE_VERSION);
-    }
-    bundle.put(BUNDLE_KEY_STATE_LABEL, state.getStateLabel().name());
-    bundle.put(BUNDLE_KEY_STATE, state.toJSONObject().toJSONString());
-
-    userdata.putString(ACCOUNT_KEY_DESCRIPTOR, bundle.toJSONString());
-
-    Account account = new Account(email, FxAccountConstants.ACCOUNT_TYPE);
-    AccountManager accountManager = AccountManager.get(context);
-    // We don't set an Android password, because we don't want to persist the
-    // password (or anything else as powerful as the password). Instead, we
-    // internally manage a sessionToken with a remotely owned lifecycle.
-    boolean added = accountManager.addAccountExplicitly(account, null, userdata);
-    if (!added) {
-      return null;
-    }
-
-    // Try to work around an intermittent issue described at
-    // http://stackoverflow.com/a/11698139.  What happens is that tests that
-    // delete and re-create the same account frequently will find the account
-    // missing all or some of the userdata bundle, possibly due to an Android
-    // AccountManager caching bug.
-    for (String key : userdata.keySet()) {
-      accountManager.setUserData(account, key, userdata.getString(key));
-    }
-
-    AndroidFxAccount fxAccount = new AndroidFxAccount(context, account);
-
-    if (!fromPickle) {
-      fxAccount.clearSyncPrefs();
-    }
-
-    fxAccount.setAuthoritiesToSyncAutomaticallyMap(authoritiesToSyncAutomaticallyMap);
-
-    return fxAccount;
-  }
-
-  public void clearSyncPrefs() throws UnsupportedEncodingException, GeneralSecurityException {
-    getSyncPrefs().edit().clear().commit();
-  }
-
-  public void setAuthoritiesToSyncAutomaticallyMap(Map<String, Boolean> authoritiesToSyncAutomaticallyMap) {
-    if (authoritiesToSyncAutomaticallyMap == null) {
-      throw new IllegalArgumentException("authoritiesToSyncAutomaticallyMap must not be null");
-    }
-
-    for (String authority : DEFAULT_AUTHORITIES_TO_SYNC_AUTOMATICALLY_MAP.keySet()) {
-      boolean authorityEnabled = DEFAULT_AUTHORITIES_TO_SYNC_AUTOMATICALLY_MAP.get(authority);
-      final Boolean enabled = authoritiesToSyncAutomaticallyMap.get(authority);
-      if (enabled != null) {
-        authorityEnabled = enabled.booleanValue();
-      }
-      // Accounts are always capable of being synced ...
-      ContentResolver.setIsSyncable(account, authority, 1);
-      // ... but not always automatically synced.
-      ContentResolver.setSyncAutomatically(account, authority, authorityEnabled);
-    }
-  }
-
-  public Map<String, Boolean> getAuthoritiesToSyncAutomaticallyMap() {
-    final Map<String, Boolean> authoritiesToSync = new HashMap<>();
-    for (String authority : DEFAULT_AUTHORITIES_TO_SYNC_AUTOMATICALLY_MAP.keySet()) {
-      final boolean enabled = ContentResolver.getSyncAutomatically(account, authority);
-      authoritiesToSync.put(authority, enabled);
-    }
-    return authoritiesToSync;
-  }
-
-  /**
-   * Is a sync currently in progress?
-   *
-   * @return true if Android is currently syncing the underlying Android Account.
-   */
-  public boolean isCurrentlySyncing() {
-    boolean active = false;
-    for (String authority : AndroidFxAccount.DEFAULT_AUTHORITIES_TO_SYNC_AUTOMATICALLY_MAP.keySet()) {
-      active |= ContentResolver.isSyncActive(account, authority);
-    }
-    return active;
-  }
-
-  /**
-   * Request an immediate sync.  Use this to sync as soon as possible in response to user action.
-   *
-   * @param stagesToSync stage names to sync; can be null to sync <b>all</b> known stages.
-   * @param stagesToSkip stage names to skip; can be null to skip <b>no</b> known stages.
-   */
-  public void requestImmediateSync(String[] stagesToSync, String[] stagesToSkip) {
-    FirefoxAccounts.requestImmediateSync(getAndroidAccount(), stagesToSync, stagesToSkip);
-  }
-
-  /**
-   * Request an eventual sync.  Use this to request the system queue a sync for some time in the
-   * future.
-   *
-   * @param stagesToSync stage names to sync; can be null to sync <b>all</b> known stages.
-   * @param stagesToSkip stage names to skip; can be null to skip <b>no</b> known stages.
-   */
-  public void requestEventualSync(String[] stagesToSync, String[] stagesToSkip) {
-    FirefoxAccounts.requestEventualSync(getAndroidAccount(), stagesToSync, stagesToSkip);
-  }
-
-  public synchronized void setState(State state) {
-    if (state == null) {
-      throw new IllegalArgumentException("state must not be null");
-    }
-    Logger.info(LOG_TAG, "Moving account named like " + getObfuscatedEmail() +
-        " to state " + state.getStateLabel().toString());
-    updateBundleValues(
-        BUNDLE_KEY_STATE_LABEL, state.getStateLabel().name(),
-        BUNDLE_KEY_STATE, state.toJSONObject().toJSONString());
-    broadcastAccountStateChangedIntent();
-  }
-
-  protected void broadcastAccountStateChangedIntent() {
-    final Intent intent = new Intent(FxAccountConstants.ACCOUNT_STATE_CHANGED_ACTION);
-    intent.putExtra(Constants.JSON_KEY_ACCOUNT, account.name);
-    LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
-  }
-
-  public synchronized State getState() {
-    String stateLabelString = getBundleData(BUNDLE_KEY_STATE_LABEL);
-    String stateString = getBundleData(BUNDLE_KEY_STATE);
-    if (stateLabelString == null || stateString == null) {
-      throw new IllegalStateException("stateLabelString and stateString must not be null, but: " +
-          "(stateLabelString == null) = " + (stateLabelString == null) +
-          " and (stateString == null) = " + (stateString == null));
-    }
-
-    try {
-      StateLabel stateLabel = StateLabel.valueOf(stateLabelString);
-      Logger.debug(LOG_TAG, "Account is in state " + stateLabel);
-      return StateFactory.fromJSONObject(stateLabel, new ExtendedJSONObject(stateString));
-    } catch (Exception e) {
-      throw new IllegalStateException("could not get state", e);
-    }
-  }
-
-  public byte[] getSessionToken() throws InvalidFxAState {
-    State state = getState();
-    StateLabel stateLabel = state.getStateLabel();
-    if (stateLabel == StateLabel.Cohabiting || stateLabel == StateLabel.Married) {
-      TokensAndKeysState tokensAndKeysState = (TokensAndKeysState) state;
-      return tokensAndKeysState.getSessionToken();
-    }
-    throw new InvalidFxAState("Cannot get sessionToken: not in a TokensAndKeysState state");
-  }
-
-  public static class InvalidFxAState extends Exception {
-    private static final long serialVersionUID = -8537626959811195978L;
-
-    public InvalidFxAState(String message) {
-      super(message);
-    }
-  }
-
-  /**
-   * <b>For debugging only!</b>
-   */
-  public void dump() {
-    if (!FxAccountUtils.LOG_PERSONAL_INFORMATION) {
-      return;
-    }
-    ExtendedJSONObject o = toJSONObject();
-    ArrayList<String> list = new ArrayList<String>(o.keySet());
-    Collections.sort(list);
-    for (String key : list) {
-      FxAccountUtils.pii(LOG_TAG, key + ": " + o.get(key));
-    }
-  }
-
-  /**
-   * Return the Firefox Account's local email address.
-   * <p>
-   * It is important to note that this is the local email address, and not
-   * necessarily the normalized remote email address that the server expects.
-   *
-   * @return local email address.
-   */
-  public String getEmail() {
-    return account.name;
-  }
-
-  /**
-   * Return the Firefox Account's local email address, obfuscated.
-   * <p>
-   * Use this when logging.
-   *
-   * @return local email address, obfuscated.
-   */
-  public String getObfuscatedEmail() {
-    return Utils.obfuscateEmail(account.name);
-  }
-
-  /**
-   * Populate an intent used for starting FxAccountDeletedService service.
-   *
-   * @param intent Intent to populate with necessary extras
-   * @return <code>Intent</code> with a deleted action and account/OAuth information extras
-   */
-  public Intent populateDeletedAccountIntent(final Intent intent) {
-    final List<String> tokens = new ArrayList<>();
-
-    intent.putExtra(FxAccountConstants.ACCOUNT_DELETED_INTENT_VERSION_KEY,
-        Long.valueOf(FxAccountConstants.ACCOUNT_DELETED_INTENT_VERSION));
-    intent.putExtra(FxAccountConstants.ACCOUNT_DELETED_INTENT_ACCOUNT_KEY, account.name);
-    intent.putExtra(FxAccountConstants.ACCOUNT_DELETED_INTENT_ACCOUNT_PROFILE, getProfile());
-
-    // Get the tokens from AccountManager. Note: currently, only reading list service supports OAuth. The following logic will
-    // be extended in future to support OAuth for other services.
-    for (String tokenKey : KNOWN_OAUTH_TOKEN_TYPES) {
-      final String authToken = accountManager.peekAuthToken(account, tokenKey);
-      if (authToken != null) {
-        tokens.add(authToken);
-      }
-    }
-
-    // Update intent with tokens and service URI.
-    intent.putExtra(FxAccountConstants.ACCOUNT_OAUTH_SERVICE_ENDPOINT_KEY, getOAuthServerURI());
-    // Deleted broadcasts are package-private, so there's no security risk include the tokens in the extras
-    intent.putExtra(FxAccountConstants.ACCOUNT_DELETED_INTENT_ACCOUNT_AUTH_TOKENS, tokens.toArray(new String[tokens.size()]));
-    return intent;
-  }
-
-  /**
-   * Create an intent announcing that the profile JSON attached to this Firefox Account has been updated.
-   * <p>
-   * It is not guaranteed that the profile JSON has changed.
-   *
-   * @return <code>Intent</code> to broadcast.
-   */
-  private Intent makeProfileJSONUpdatedIntent() {
-    final Intent intent = new Intent();
-    intent.setAction(FxAccountConstants.ACCOUNT_PROFILE_JSON_UPDATED_ACTION);
-    return intent;
-  }
-
-  public void setLastSyncedTimestamp(long now) {
-    try {
-      getSyncPrefs().edit().putLong(PREF_KEY_LAST_SYNCED_TIMESTAMP, now).commit();
-    } catch (Exception e) {
-      Logger.warn(LOG_TAG, "Got exception setting last synced time; ignoring.", e);
-    }
-  }
-
-  public long getLastSyncedTimestamp() {
-    final long neverSynced = -1L;
-    try {
-      return getSyncPrefs().getLong(PREF_KEY_LAST_SYNCED_TIMESTAMP, neverSynced);
-    } catch (Exception e) {
-      Logger.warn(LOG_TAG, "Got exception getting last synced time; ignoring.", e);
-      return neverSynced;
-    }
-  }
-
-  // Debug only!  This is dangerous!
-  public void unsafeTransitionToDefaultEndpoints() {
-    unsafeTransitionToStageEndpoints(
-        FxAccountConstants.DEFAULT_AUTH_SERVER_ENDPOINT,
-        FxAccountConstants.DEFAULT_TOKEN_SERVER_ENDPOINT,
-        FxAccountConstants.DEFAULT_PROFILE_SERVER_ENDPOINT);
-    }
-
-  // Debug only!  This is dangerous!
-  public void unsafeTransitionToStageEndpoints() {
-    unsafeTransitionToStageEndpoints(
-        FxAccountConstants.STAGE_AUTH_SERVER_ENDPOINT,
-        FxAccountConstants.STAGE_TOKEN_SERVER_ENDPOINT,
-        FxAccountConstants.STAGE_PROFILE_SERVER_ENDPOINT);
-  }
-
-  protected void unsafeTransitionToStageEndpoints(String authServerEndpoint, String tokenServerEndpoint, String profileServerEndpoint) {
-    try {
-      getReadingListPrefs().edit().clear().commit();
-    } catch (UnsupportedEncodingException | GeneralSecurityException e) {
-      // Ignore.
-    }
-    try {
-      getSyncPrefs().edit().clear().commit();
-    } catch (UnsupportedEncodingException | GeneralSecurityException e) {
-      // Ignore.
-    }
-    State state = getState();
-    setState(state.makeSeparatedState());
-    accountManager.setUserData(account, ACCOUNT_KEY_IDP_SERVER, authServerEndpoint);
-    accountManager.setUserData(account, ACCOUNT_KEY_TOKEN_SERVER, tokenServerEndpoint);
-    accountManager.setUserData(account, ACCOUNT_KEY_PROFILE_SERVER, profileServerEndpoint);
-    ContentResolver.setIsSyncable(account, BrowserContract.READING_LIST_AUTHORITY, 1);
-  }
-
-  /**
-   * Returns the current profile JSON if available, or null.
-   *
-   * @return profile JSON object.
-   */
-  public ExtendedJSONObject getProfileJSON() {
-    final String profileString = getBundleData(BUNDLE_KEY_PROFILE_JSON);
-    if (profileString == null) {
-      return null;
-    }
-
-    try {
-      return new ExtendedJSONObject(profileString);
-    } catch (Exception e) {
-      Logger.error(LOG_TAG, "Failed to parse profile JSON; ignoring and returning null.", e);
-    }
-    return null;
-  }
-
-  /**
-   * Fetch the profile JSON associated to the underlying Firefox Account from the server and update the local store.
-   * <p>
-   * The LocalBroadcastManager is used to notify the receivers asynchronously after a successful fetch.
-   */
-  public void fetchProfileJSON() {
-    ThreadUtils.postToBackgroundThread(new Runnable() {
-      @Override
-      public void run() {
-        // Fetch profile information from server.
-        String authToken;
-        try {
-          authToken = accountManager.blockingGetAuthToken(account, AndroidFxAccount.PROFILE_OAUTH_TOKEN_TYPE, true);
-          if (authToken == null) {
-            throw new RuntimeException("Couldn't get oauth token!  Aborting profile fetch.");
-          }
-        } catch (Exception e) {
-          Logger.error(LOG_TAG, "Error fetching profile information; ignoring.", e);
-          return;
-        }
-
-        Logger.info(LOG_TAG, "Intent service launched to fetch profile.");
-        final Intent intent = new Intent(context, FxAccountProfileService.class);
-        intent.putExtra(FxAccountProfileService.KEY_AUTH_TOKEN, authToken);
-        intent.putExtra(FxAccountProfileService.KEY_PROFILE_SERVER_URI, getProfileServerURI());
-        intent.putExtra(FxAccountProfileService.KEY_RESULT_RECEIVER, new ProfileResultReceiver(new Handler()));
-        context.startService(intent);
-      }
-    });
-  }
-
-  @Nullable
-  public synchronized String getDeviceId() {
-    return accountManager.getUserData(account, ACCOUNT_KEY_DEVICE_ID);
-  }
-
-  @NonNull
-  public synchronized int getDeviceRegistrationVersion() {
-    String versionStr = accountManager.getUserData(account, ACCOUNT_KEY_DEVICE_REGISTRATION_VERSION);
-    if (TextUtils.isEmpty(versionStr)) {
-      return 0;
-    } else {
-      try {
-        return Integer.parseInt(versionStr);
-      } catch (NumberFormatException ex) {
-        return 0;
-      }
-    }
-  }
-
-  public synchronized void setDeviceId(String id) {
-    accountManager.setUserData(account, ACCOUNT_KEY_DEVICE_ID, id);
-  }
-
-  public synchronized void setDeviceRegistrationVersion(int deviceRegistrationVersion) {
-    accountManager.setUserData(account, ACCOUNT_KEY_DEVICE_REGISTRATION_VERSION,
-        Integer.toString(deviceRegistrationVersion));
-  }
-
-  public synchronized void resetDeviceRegistrationVersion() {
-    setDeviceRegistrationVersion(0);
-  }
-
-  public synchronized void setFxAUserData(String id, int deviceRegistrationVersion) {
-    accountManager.setUserData(account, ACCOUNT_KEY_DEVICE_ID, id);
-    accountManager.setUserData(account, ACCOUNT_KEY_DEVICE_REGISTRATION_VERSION,
-        Integer.toString(deviceRegistrationVersion));
-  }
-
-  @SuppressLint("ParcelCreator") // The CREATOR field is defined in the super class.
-  private class ProfileResultReceiver extends ResultReceiver {
-    public ProfileResultReceiver(Handler handler) {
-      super(handler);
-    }
-
-    @Override
-    protected void onReceiveResult(int resultCode, Bundle bundle) {
-      super.onReceiveResult(resultCode, bundle);
-      switch (resultCode) {
-        case Activity.RESULT_OK:
-          final String resultData = bundle.getString(FxAccountProfileService.KEY_RESULT_STRING);
-          updateBundleValues(BUNDLE_KEY_PROFILE_JSON, resultData);
-          Logger.info(LOG_TAG, "Profile JSON fetch succeeeded!");
-          FxAccountUtils.pii(LOG_TAG, "Profile JSON fetch returned: " + resultData);
-          LocalBroadcastManager.getInstance(context).sendBroadcast(makeProfileJSONUpdatedIntent());
-          break;
-        case Activity.RESULT_CANCELED:
-          Logger.warn(LOG_TAG, "Failed to fetch profile JSON; ignoring.");
-          break;
-        default:
-          Logger.warn(LOG_TAG, "Invalid result code received; ignoring.");
-          break;
-      }
-    }
-  }
-
-  /**
-   * Take the lock to own updating any Firefox Account's internal state.
-   *
-   * We use a <code>Semaphore</code> rather than a <code>ReentrantLock</code>
-   * because the callback that needs to release the lock may not be invoked on
-   * the thread that initially acquired the lock. Be aware!
-   */
-  protected static final Semaphore sLock = new Semaphore(1, true /* fair */);
-
-  // Which consumer took the lock?
-  // Synchronized by this.
-  protected String lockTag = null;
-
-  // Are we locked?  (It's not easy to determine who took the lock dynamically,
-  // so we maintain this flag internally.)
-  // Synchronized by this.
-  protected boolean locked = false;
-
-  // Block until we can take the shared state lock.
-  public synchronized void acquireSharedAccountStateLock(final String tag) throws InterruptedException {
-    final long id = Thread.currentThread().getId();
-    this.lockTag = tag;
-    Log.d(Logger.DEFAULT_LOG_TAG, "Thread with tag and thread id acquiring lock: " + lockTag + ", " + id + " ...");
-    sLock.acquire();
-    locked = true;
-    Log.d(Logger.DEFAULT_LOG_TAG, "Thread with tag and thread id acquiring lock: " + lockTag + ", " + id + " ... ACQUIRED");
-  }
-
-  // If we hold the shared state lock, release it.  Otherwise, ignore the request.
-  public synchronized void releaseSharedAccountStateLock() {
-    final long id = Thread.currentThread().getId();
-    Log.d(Logger.DEFAULT_LOG_TAG, "Thread with tag and thread id releasing lock: " + lockTag + ", " + id + " ...");
-    if (locked) {
-      sLock.release();
-      locked = false;
-      Log.d(Logger.DEFAULT_LOG_TAG, "Thread with tag and thread id releasing lock: " + lockTag + ", " + id + " ... RELEASED");
-    } else {
-      Log.d(Logger.DEFAULT_LOG_TAG, "Thread with tag and thread id releasing lock: " + lockTag + ", " + id + " ... NOT LOCKED");
-    }
-  }
-
-  @Override
-  protected synchronized void finalize() {
-    if (locked) {
-      // Should never happen, but...
-      sLock.release();
-      locked = false;
-      final long id = Thread.currentThread().getId();
-      Log.e(Logger.DEFAULT_LOG_TAG, "Thread with tag and thread id releasing lock: " + lockTag + ", " + id + " ... RELEASED DURING FINALIZE");
-    }
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/authenticator/FxADefaultLoginStateMachineDelegate.java b/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/authenticator/FxADefaultLoginStateMachineDelegate.java
deleted file mode 100644
index ff31223..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/authenticator/FxADefaultLoginStateMachineDelegate.java
+++ /dev/null
@@ -1,84 +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.fxa.authenticator;
-
-import java.security.NoSuchAlgorithmException;
-import java.util.concurrent.Executor;
-import java.util.concurrent.Executors;
-
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.background.fxa.FxAccountClient;
-import org.mozilla.gecko.background.fxa.FxAccountClient20;
-import org.mozilla.gecko.browserid.BrowserIDKeyPair;
-import org.mozilla.gecko.fxa.login.FxAccountLoginStateMachine.LoginStateMachineDelegate;
-import org.mozilla.gecko.fxa.login.FxAccountLoginTransition.Transition;
-import org.mozilla.gecko.fxa.login.Married;
-import org.mozilla.gecko.fxa.login.State;
-import org.mozilla.gecko.fxa.login.State.StateLabel;
-import org.mozilla.gecko.fxa.login.StateFactory;
-import org.mozilla.gecko.fxa.sync.FxAccountNotificationManager;
-import org.mozilla.gecko.fxa.sync.FxAccountSyncAdapter;
-
-import android.content.Context;
-
-public abstract class FxADefaultLoginStateMachineDelegate implements LoginStateMachineDelegate {
-  protected final static String LOG_TAG = LoginStateMachineDelegate.class.getSimpleName();
-
-  protected final Context context;
-  protected final AndroidFxAccount fxAccount;
-  protected final Executor executor;
-  protected final FxAccountClient client;
-
-  public FxADefaultLoginStateMachineDelegate(Context context, AndroidFxAccount fxAccount) {
-    this.context = context;
-    this.fxAccount = fxAccount;
-    this.executor = Executors.newSingleThreadExecutor();
-    this.client = new FxAccountClient20(fxAccount.getAccountServerURI(), executor);
-  }
-
-  abstract public void handleNotMarried(State notMarried);
-  abstract public void handleMarried(Married married);
-
-  @Override
-  public FxAccountClient getClient() {
-    return client;
-  }
-
-  @Override
-  public long getCertificateDurationInMilliseconds() {
-    return 12 * 60 * 60 * 1000;
-  }
-
-  @Override
-  public long getAssertionDurationInMilliseconds() {
-    return 15 * 60 * 1000;
-  }
-
-  @Override
-  public BrowserIDKeyPair generateKeyPair() throws NoSuchAlgorithmException {
-    return StateFactory.generateKeyPair();
-  }
-
-  @Override
-  public void handleTransition(Transition transition, State state) {
-    Logger.info(LOG_TAG, "handleTransition: " + transition + " to " + state.getStateLabel());
-  }
-
-  @Override
-  public void handleFinal(State state) {
-    Logger.info(LOG_TAG, "handleFinal: in " + state.getStateLabel());
-    fxAccount.setState(state);
-    // Update any notifications displayed.
-    final FxAccountNotificationManager notificationManager = new FxAccountNotificationManager(FxAccountSyncAdapter.NOTIFICATION_ID);
-    notificationManager.update(context, fxAccount);
-
-    if (state.getStateLabel() != StateLabel.Married) {
-      handleNotMarried(state);
-      return;
-    } else {
-      handleMarried((Married) state);
-    }
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/authenticator/FxAccountAuthenticator.java b/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/authenticator/FxAccountAuthenticator.java
deleted file mode 100644
index 259b1cb..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/authenticator/FxAccountAuthenticator.java
+++ /dev/null
@@ -1,385 +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.fxa.authenticator;
-
-import android.accounts.AbstractAccountAuthenticator;
-import android.accounts.Account;
-import android.accounts.AccountAuthenticatorResponse;
-import android.accounts.AccountManager;
-import android.accounts.NetworkErrorException;
-import android.content.Context;
-import android.content.Intent;
-import android.os.Bundle;
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.background.fxa.FxAccountClient;
-import org.mozilla.gecko.background.fxa.FxAccountClient20;
-import org.mozilla.gecko.background.fxa.FxAccountUtils;
-import org.mozilla.gecko.background.fxa.oauth.FxAccountAbstractClient.RequestDelegate;
-import org.mozilla.gecko.background.fxa.oauth.FxAccountAbstractClientException.FxAccountAbstractClientRemoteException;
-import org.mozilla.gecko.background.fxa.oauth.FxAccountOAuthClient10;
-import org.mozilla.gecko.background.fxa.oauth.FxAccountOAuthClient10.AuthorizationResponse;
-import org.mozilla.gecko.browserid.BrowserIDKeyPair;
-import org.mozilla.gecko.browserid.JSONWebTokenUtils;
-import org.mozilla.gecko.fxa.FxAccountConstants;
-import org.mozilla.gecko.fxa.login.FxAccountLoginStateMachine;
-import org.mozilla.gecko.fxa.login.FxAccountLoginStateMachine.LoginStateMachineDelegate;
-import org.mozilla.gecko.fxa.login.FxAccountLoginTransition.Transition;
-import org.mozilla.gecko.fxa.login.Married;
-import org.mozilla.gecko.fxa.login.State;
-import org.mozilla.gecko.fxa.login.State.StateLabel;
-import org.mozilla.gecko.fxa.login.StateFactory;
-import org.mozilla.gecko.fxa.receivers.FxAccountDeletedService;
-import org.mozilla.gecko.fxa.sync.FxAccountNotificationManager;
-import org.mozilla.gecko.fxa.sync.FxAccountSyncAdapter;
-import org.mozilla.gecko.util.ThreadUtils;
-
-import java.security.NoSuchAlgorithmException;
-import java.util.concurrent.Executor;
-import java.util.concurrent.Executors;
-
-public class FxAccountAuthenticator extends AbstractAccountAuthenticator {
-  public static final String LOG_TAG = FxAccountAuthenticator.class.getSimpleName();
-  public static final int UNKNOWN_ERROR_CODE = 999;
-
-  protected final Context context;
-  protected final AccountManager accountManager;
-
-  public FxAccountAuthenticator(Context context) {
-    super(context);
-    this.context = context;
-    this.accountManager = AccountManager.get(context);
-  }
-
-  @Override
-  public Bundle addAccount(AccountAuthenticatorResponse response,
-      String accountType, String authTokenType, String[] requiredFeatures,
-      Bundle options)
-          throws NetworkErrorException {
-    Logger.debug(LOG_TAG, "addAccount");
-
-    // The data associated to each Account should be invalidated when we change
-    // the set of Firefox Accounts on the system.
-    AndroidFxAccount.invalidateCaches();
-
-    final Bundle res = new Bundle();
-
-    if (!FxAccountConstants.ACCOUNT_TYPE.equals(accountType)) {
-      res.putInt(AccountManager.KEY_ERROR_CODE, -1);
-      res.putString(AccountManager.KEY_ERROR_MESSAGE, "Not adding unknown account type.");
-      return res;
-    }
-
-    final Intent intent = new Intent(FxAccountConstants.ACTION_FXA_GET_STARTED);
-    res.putParcelable(AccountManager.KEY_INTENT, intent);
-    return res;
-  }
-
-  @Override
-  public Bundle confirmCredentials(AccountAuthenticatorResponse response, Account account, Bundle options)
-      throws NetworkErrorException {
-    Logger.debug(LOG_TAG, "confirmCredentials");
-
-    return null;
-  }
-
-  @Override
-  public Bundle editProperties(AccountAuthenticatorResponse response, String accountType) {
-    Logger.debug(LOG_TAG, "editProperties");
-
-    return null;
-  }
-
-  protected static class Responder {
-    final AccountAuthenticatorResponse response;
-    final AndroidFxAccount fxAccount;
-
-    public Responder(AccountAuthenticatorResponse response, AndroidFxAccount fxAccount) {
-      this.response = response;
-      this.fxAccount = fxAccount;
-    }
-
-    public void fail(Exception e) {
-      Logger.warn(LOG_TAG, "Responding with error!", e);
-      fxAccount.releaseSharedAccountStateLock();
-      final Bundle result = new Bundle();
-      result.putInt(AccountManager.KEY_ERROR_CODE, UNKNOWN_ERROR_CODE);
-      result.putString(AccountManager.KEY_ERROR_MESSAGE, e.toString());
-      response.onResult(result);
-    }
-
-    public void succeed(String authToken) {
-      Logger.info(LOG_TAG, "Responding with success!");
-      fxAccount.releaseSharedAccountStateLock();
-      final Bundle result = new Bundle();
-      result.putString(AccountManager.KEY_ACCOUNT_NAME, fxAccount.account.name);
-      result.putString(AccountManager.KEY_ACCOUNT_TYPE, fxAccount.account.type);
-      result.putString(AccountManager.KEY_AUTHTOKEN, authToken);
-      response.onResult(result);
-    }
-  }
-
-  public abstract static class FxADefaultLoginStateMachineDelegate implements LoginStateMachineDelegate {
-    protected final Context context;
-    protected final AndroidFxAccount fxAccount;
-    protected final Executor executor;
-    protected final FxAccountClient client;
-
-    public FxADefaultLoginStateMachineDelegate(Context context, AndroidFxAccount fxAccount) {
-      this.context = context;
-      this.fxAccount = fxAccount;
-      this.executor = Executors.newSingleThreadExecutor();
-      this.client = new FxAccountClient20(fxAccount.getAccountServerURI(), executor);
-    }
-
-    @Override
-    public FxAccountClient getClient() {
-      return client;
-    }
-
-    @Override
-    public long getCertificateDurationInMilliseconds() {
-      return 12 * 60 * 60 * 1000;
-    }
-
-    @Override
-    public long getAssertionDurationInMilliseconds() {
-      return 15 * 60 * 1000;
-    }
-
-    @Override
-    public BrowserIDKeyPair generateKeyPair() throws NoSuchAlgorithmException {
-      return StateFactory.generateKeyPair();
-    }
-
-    @Override
-    public void handleTransition(Transition transition, State state) {
-      Logger.info(LOG_TAG, "handleTransition: " + transition + " to " + state.getStateLabel());
-    }
-
-    abstract public void handleNotMarried(State notMarried);
-    abstract public void handleMarried(Married married);
-
-    @Override
-    public void handleFinal(State state) {
-      Logger.info(LOG_TAG, "handleFinal: in " + state.getStateLabel());
-      fxAccount.setState(state);
-      // Update any notifications displayed.
-      final FxAccountNotificationManager notificationManager = new FxAccountNotificationManager(FxAccountSyncAdapter.NOTIFICATION_ID);
-      notificationManager.update(context, fxAccount);
-
-      if (state.getStateLabel() != StateLabel.Married) {
-        handleNotMarried(state);
-        return;
-      } else {
-        handleMarried((Married) state);
-      }
-    }
-  }
-
-  protected void getOAuthToken(final AccountAuthenticatorResponse response, final AndroidFxAccount fxAccount, final String scope) throws NetworkErrorException {
-    Logger.info(LOG_TAG, "Fetching oauth token with scope: " + scope);
-
-    final Responder responder = new Responder(response, fxAccount);
-    final String oauthServerUri = fxAccount.getOAuthServerURI();
-
-    final String audience;
-    try {
-      audience = FxAccountUtils.getAudienceForURL(oauthServerUri); // The assertion gets traded in for an oauth bearer token.
-    } catch (Exception e) {
-      Logger.warn(LOG_TAG, "Got exception fetching oauth token.", e);
-      responder.fail(e);
-      return;
-    }
-
-    final FxAccountLoginStateMachine stateMachine = new FxAccountLoginStateMachine();
-
-    stateMachine.advance(fxAccount.getState(), StateLabel.Married, new FxADefaultLoginStateMachineDelegate(context, fxAccount) {
-      @Override
-      public void handleNotMarried(State state) {
-        final String message = "Cannot fetch oauth token from state: " + state.getStateLabel();
-        Logger.warn(LOG_TAG, message);
-        responder.fail(new RuntimeException(message));
-      }
-
-      @Override
-      public void handleMarried(final Married married) {
-        final String assertion;
-        try {
-          assertion = married.generateAssertion(audience, JSONWebTokenUtils.DEFAULT_ASSERTION_ISSUER);
-          if (FxAccountUtils.LOG_PERSONAL_INFORMATION) {
-            JSONWebTokenUtils.dumpAssertion(assertion);
-          }
-        } catch (Exception e) {
-          Logger.warn(LOG_TAG, "Got exception fetching oauth token.", e);
-          responder.fail(e);
-          return;
-        }
-
-        final FxAccountOAuthClient10 oauthClient = new FxAccountOAuthClient10(oauthServerUri, executor);
-        Logger.debug(LOG_TAG, "OAuth fetch for scope: " + scope);
-        oauthClient.authorization(FxAccountConstants.OAUTH_CLIENT_ID_FENNEC, assertion, null, scope, new RequestDelegate<FxAccountOAuthClient10.AuthorizationResponse>() {
-          @Override
-          public void handleSuccess(AuthorizationResponse result) {
-            Logger.debug(LOG_TAG, "OAuth success.");
-            FxAccountUtils.pii(LOG_TAG, "Fetched oauth token: " + result.access_token);
-            responder.succeed(result.access_token);
-          }
-
-          @Override
-          public void handleFailure(FxAccountAbstractClientRemoteException e) {
-            Logger.error(LOG_TAG, "OAuth failure.", e);
-            if (e.isInvalidAuthentication()) {
-              // We were married, generated an assertion, and our assertion was rejected by the
-              // oauth client. If it's a 401, we probably have a stale certificate.  If instead of
-              // a stale certificate we have bad credentials, the state machine will fail to sign
-              // our public key and drive us back to Separated.
-              fxAccount.setState(married.makeCohabitingState());
-            }
-            responder.fail(e);
-          }
-
-          @Override
-          public void handleError(Exception e) {
-            Logger.error(LOG_TAG, "OAuth error.", e);
-            responder.fail(e);
-          }
-        });
-      }
-    });
-  }
-
-  @Override
-  public Bundle getAuthToken(final AccountAuthenticatorResponse response,
-      final Account account, final String authTokenType, final Bundle options)
-          throws NetworkErrorException {
-    Logger.debug(LOG_TAG, "getAuthToken: " + authTokenType);
-
-    // If we have a cached authToken, hand it over.
-    final String cachedAuthToken = AccountManager.get(context).peekAuthToken(account, authTokenType);
-    if (cachedAuthToken != null && !cachedAuthToken.isEmpty()) {
-      Logger.info(LOG_TAG, "Return cached token.");
-      final Bundle result = new Bundle();
-      result.putString(AccountManager.KEY_ACCOUNT_NAME, account.name);
-      result.putString(AccountManager.KEY_ACCOUNT_TYPE, account.type);
-      result.putString(AccountManager.KEY_AUTHTOKEN, cachedAuthToken);
-      return result;
-    }
-
-    // If we're asked for an oauth::scope token, try to generate one.
-    final String oauthPrefix = "oauth::";
-    if (authTokenType != null && authTokenType.startsWith(oauthPrefix)) {
-      final String scope = authTokenType.substring(oauthPrefix.length());
-      final AndroidFxAccount fxAccount = new AndroidFxAccount(context, account);
-      try {
-        fxAccount.acquireSharedAccountStateLock(LOG_TAG);
-      } catch (InterruptedException e) {
-        Logger.warn(LOG_TAG, "Could not acquire account state lock; return error bundle.");
-        final Bundle bundle = new Bundle();
-        bundle.putInt(AccountManager.KEY_ERROR_CODE, 1);
-        bundle.putString(AccountManager.KEY_ERROR_MESSAGE, "Could not acquire account state lock.");
-        return bundle;
-      }
-      getOAuthToken(response, fxAccount, scope);
-      return null;
-    }
-
-    // Otherwise, fail.
-    Logger.warn(LOG_TAG, "Returning error bundle for getAuthToken with unknown token type.");
-    final Bundle bundle = new Bundle();
-    bundle.putInt(AccountManager.KEY_ERROR_CODE, 2);
-    bundle.putString(AccountManager.KEY_ERROR_MESSAGE, "Unknown token type: " + authTokenType);
-    return bundle;
-  }
-
-  @Override
-  public String getAuthTokenLabel(String authTokenType) {
-    Logger.debug(LOG_TAG, "getAuthTokenLabel");
-
-    return null;
-  }
-
-  @Override
-  public Bundle hasFeatures(AccountAuthenticatorResponse response,
-      Account account, String[] features) throws NetworkErrorException {
-    Logger.debug(LOG_TAG, "hasFeatures");
-
-    return null;
-  }
-
-  @Override
-  public Bundle updateCredentials(AccountAuthenticatorResponse response,
-      Account account, String authTokenType, Bundle options)
-          throws NetworkErrorException {
-    Logger.debug(LOG_TAG, "updateCredentials");
-
-    return null;
-  }
-
-  /**
-   * If the account is going to be removed, broadcast an "account deleted"
-   * intent. This allows us to clean up the account.
-   * <p>
-   * It is preferable to receive Android's LOGIN_ACCOUNTS_CHANGED_ACTION broadcast
-   * than to create our own hacky broadcast here, but that doesn't include enough
-   * information about which Accounts changed to correctly identify whether a Sync
-   * account has been removed (when some Firefox channels are installed on the SD
-   * card). We can work around this by storing additional state but it's both messy
-   * and expensive because the broadcast is noisy.
-   * <p>
-   * Note that this is <b>not</b> called when an Android Account is blown away
-   * due to the SD card being unmounted.
-   */
-  @Override
-  public Bundle getAccountRemovalAllowed(final AccountAuthenticatorResponse response, Account account)
-      throws NetworkErrorException {
-    Bundle result = super.getAccountRemovalAllowed(response, account);
-
-    if (result == null ||
-        !result.containsKey(AccountManager.KEY_BOOLEAN_RESULT) ||
-        result.containsKey(AccountManager.KEY_INTENT)) {
-      return result;
-    }
-
-    final boolean removalAllowed = result.getBoolean(AccountManager.KEY_BOOLEAN_RESULT);
-    if (!removalAllowed) {
-      return result;
-    }
-
-    // Broadcast a message to all Firefox channels sharing this Android
-    // Account type telling that this Firefox account has been deleted.
-    //
-    // Broadcast intents protected with permissions are secure, so it's okay
-    // to include private information such as a password.
-    final AndroidFxAccount androidFxAccount = new AndroidFxAccount(context, account);
-
-    // Deleting the pickle file in a blocking manner will avoid race conditions that might happen when
-    // an account is unpickled while an FxAccount is being deleted.
-    // Also we have an assumption that this method is always called from a background thread, so we delete
-    // the pickle file directly without being afraid from a StrictMode violation.
-    ThreadUtils.assertNotOnUiThread();
-
-    final Intent serviceIntent = androidFxAccount.populateDeletedAccountIntent(
-            new Intent(context, FxAccountDeletedService.class)
-    );
-    Logger.info(LOG_TAG, "Account named " + account.name + " being removed; " +
-        "starting FxAccountDeletedService with action: " + serviceIntent.getAction() + ".");
-    context.startService(serviceIntent);
-
-    Logger.info(LOG_TAG, "Firefox account named " + account.name + " being removed; " +
-            "deleting saved pickle file '" + FxAccountConstants.ACCOUNT_PICKLE_FILENAME + "'.");
-    deletePickle();
-
-    return result;
-  }
-
-  private void deletePickle() {
-    try {
-      AccountPickler.deletePickle(context, FxAccountConstants.ACCOUNT_PICKLE_FILENAME);
-    } catch (Exception e) {
-      // This should never happen, but we really don't want to die in a background thread.
-      Logger.warn(LOG_TAG, "Got exception deleting saved pickle file; ignoring.", e);
-    }
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/authenticator/FxAccountAuthenticatorService.java b/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/authenticator/FxAccountAuthenticatorService.java
deleted file mode 100644
index d138e6c..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/authenticator/FxAccountAuthenticatorService.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.fxa.authenticator;
-
-import org.mozilla.gecko.background.common.log.Logger;
-
-import android.app.Service;
-import android.content.Intent;
-import android.os.IBinder;
-
-public class FxAccountAuthenticatorService extends Service {
-  public static final String LOG_TAG = FxAccountAuthenticatorService.class.getSimpleName();
-
-  // Lazily initialized by <code>getAuthenticator</code>.
-  protected FxAccountAuthenticator accountAuthenticator;
-
-  protected synchronized FxAccountAuthenticator getAuthenticator() {
-    if (accountAuthenticator == null) {
-      accountAuthenticator = new FxAccountAuthenticator(this);
-    }
-
-    return accountAuthenticator;
-  }
-
-  @Override
-  public void onCreate() {
-    Logger.debug(LOG_TAG, "onCreate");
-
-    accountAuthenticator = getAuthenticator();
-  }
-
-  @Override
-  public IBinder onBind(Intent intent) {
-    Logger.debug(LOG_TAG, "onBind");
-
-    if (intent == null) {
-      // Should never happen, but can -- Bug 1025937.
-      return null;
-    }
-
-    if (!android.accounts.AccountManager.ACTION_AUTHENTICATOR_INTENT.equals(intent.getAction())) {
-      return null;
-    }
-
-    final FxAccountAuthenticator authenticator = getAuthenticator();
-    if (authenticator == null) {
-      // Should never happen.
-      return null;
-    }
-
-    return authenticator.getIBinder();
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/authenticator/FxAccountLoginDelegate.java b/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/authenticator/FxAccountLoginDelegate.java
deleted file mode 100644
index 71006e7..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/authenticator/FxAccountLoginDelegate.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.fxa.authenticator;
-
-/**
- * Abstraction around things that might need to be signalled to the user via UI,
- * such as:
- * <ul>
- * <li>account not yet verified;</li>
- * <li>account password needs to be updated;</li>
- * <li>account key management required or changed;</li>
- * <li>auth protocol has changed and Firefox needs to be upgraded;</li>
- * </ul>
- * etc.
- * <p>
- * Consumers of this code should differentiate error classes based on the types
- * of the exceptions thrown. Exceptions that do not have special meaning are of
- * type <code>FxAccountLoginException</code> with an appropriate
- * <code>cause</code> inner exception.
- */
-public interface FxAccountLoginDelegate {
-  public void handleError(FxAccountLoginException e);
-  public void handleSuccess(String assertion);
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/authenticator/FxAccountLoginException.java b/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/authenticator/FxAccountLoginException.java
deleted file mode 100644
index 56c0140..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/authenticator/FxAccountLoginException.java
+++ /dev/null
@@ -1,33 +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.fxa.authenticator;
-
-public class FxAccountLoginException extends Exception {
-  public FxAccountLoginException(String string) {
-    super(string);
-  }
-
-  public FxAccountLoginException(Exception e) {
-    super(e);
-  }
-
-  private static final long serialVersionUID = 397685959625820798L;
-
-  public static class FxAccountLoginBadPasswordException extends FxAccountLoginException {
-    public FxAccountLoginBadPasswordException(String string) {
-      super(string);
-    }
-
-    private static final long serialVersionUID = 397685959625820799L;
-  }
-
-  public static class FxAccountLoginAccountNotVerifiedException extends FxAccountLoginException {
-    public FxAccountLoginAccountNotVerifiedException(String string) {
-      super(string);
-    }
-
-    private static final long serialVersionUID = 397685959625820800L;
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/login/BaseRequestDelegate.java b/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/login/BaseRequestDelegate.java
deleted file mode 100644
index 5d3e71e..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/login/BaseRequestDelegate.java
+++ /dev/null
@@ -1,49 +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.fxa.login;
-
-import org.mozilla.gecko.background.fxa.FxAccountClient20;
-import org.mozilla.gecko.background.fxa.FxAccountClientException.FxAccountClientRemoteException;
-import org.mozilla.gecko.fxa.login.FxAccountLoginStateMachine.ExecuteDelegate;
-import org.mozilla.gecko.fxa.login.FxAccountLoginTransition.AccountNeedsVerification;
-import org.mozilla.gecko.fxa.login.FxAccountLoginTransition.LocalError;
-import org.mozilla.gecko.fxa.login.FxAccountLoginTransition.RemoteError;
-
-public abstract class BaseRequestDelegate<T> implements FxAccountClient20.RequestDelegate<T> {
-  protected final ExecuteDelegate delegate;
-  protected final State state;
-
-  public BaseRequestDelegate(State state, ExecuteDelegate delegate) {
-    this.delegate = delegate;
-    this.state = state;
-  }
-
-  @Override
-  public void handleFailure(FxAccountClientRemoteException e) {
-    // Order matters here: we don't want to ignore upgrade required responses
-    // even if the server tells us something else as well. We don't go directly
-    // to the Doghouse on upgrade required; we want the user to try to update
-    // their credentials, and then display UI telling them they need to upgrade.
-    // Then they go to the Doghouse.
-    if (e.isUpgradeRequired()) {
-      delegate.handleTransition(new RemoteError(e), new Separated(state.email, state.uid, state.verified));
-      return;
-    }
-    if (e.isInvalidAuthentication()) {
-      delegate.handleTransition(new RemoteError(e), new Separated(state.email, state.uid, state.verified));
-      return;
-    }
-    if (e.isUnverified()) {
-      delegate.handleTransition(new AccountNeedsVerification(), state);
-      return;
-    }
-    delegate.handleTransition(new RemoteError(e), state);
-  }
-
-  @Override
-  public void handleError(Exception e) {
-    delegate.handleTransition(new LocalError(e), state);
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/login/Cohabiting.java b/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/login/Cohabiting.java
deleted file mode 100644
index dd3477a..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/login/Cohabiting.java
+++ /dev/null
@@ -1,50 +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.fxa.login;
-
-import org.mozilla.gecko.background.fxa.FxAccountUtils;
-import org.mozilla.gecko.browserid.BrowserIDKeyPair;
-import org.mozilla.gecko.browserid.JSONWebTokenUtils;
-import org.mozilla.gecko.fxa.login.FxAccountLoginStateMachine.ExecuteDelegate;
-import org.mozilla.gecko.fxa.login.FxAccountLoginTransition.LogMessage;
-import org.mozilla.gecko.sync.ExtendedJSONObject;
-
-public class Cohabiting extends TokensAndKeysState {
-  private static final String LOG_TAG = Cohabiting.class.getSimpleName();
-
-  public Cohabiting(String email, String uid, byte[] sessionToken, byte[] kA, byte[] kB, BrowserIDKeyPair keyPair) {
-    super(StateLabel.Cohabiting, email, uid, sessionToken, kA, kB, keyPair);
-  }
-
-  public Married withCertificate(String certificate) {
-    return new Married(email, uid, sessionToken, kA, kB, keyPair, certificate);
-  }
-
-  @Override
-  public void execute(final ExecuteDelegate delegate) {
-    delegate.getClient().sign(sessionToken, keyPair.getPublic().toJSONObject(), delegate.getCertificateDurationInMilliseconds(),
-        new BaseRequestDelegate<String>(this, delegate) {
-      @Override
-      public void handleSuccess(String certificate) {
-        if (FxAccountUtils.LOG_PERSONAL_INFORMATION) {
-          try {
-            FxAccountUtils.pii(LOG_TAG, "Fetched certificate: " + certificate);
-            ExtendedJSONObject c = JSONWebTokenUtils.parseCertificate(certificate);
-            if (c != null) {
-              FxAccountUtils.pii(LOG_TAG, "Header   : " + c.getObject("header"));
-              FxAccountUtils.pii(LOG_TAG, "Payload  : " + c.getObject("payload"));
-              FxAccountUtils.pii(LOG_TAG, "Signature: " + c.getString("signature"));
-            } else {
-              FxAccountUtils.pii(LOG_TAG, "Could not parse certificate!");
-            }
-          } catch (Exception e) {
-            FxAccountUtils.pii(LOG_TAG, "Could not parse certificate!");
-          }
-        }
-        delegate.handleTransition(new LogMessage("sign succeeded"), withCertificate(certificate));
-      }
-    });
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/login/Doghouse.java b/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/login/Doghouse.java
deleted file mode 100644
index 5760057..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/login/Doghouse.java
+++ /dev/null
@@ -1,25 +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.fxa.login;
-
-import org.mozilla.gecko.fxa.login.FxAccountLoginStateMachine.ExecuteDelegate;
-import org.mozilla.gecko.fxa.login.FxAccountLoginTransition.LogMessage;
-
-
-public class Doghouse extends State {
-  public Doghouse(String email, String uid, boolean verified) {
-    super(StateLabel.Doghouse, email, uid, verified);
-  }
-
-  @Override
-  public void execute(final ExecuteDelegate delegate) {
-    delegate.handleTransition(new LogMessage("Upgraded Firefox clients might know what to do here."), this);
-  }
-
-  @Override
-  public Action getNeededAction() {
-    return Action.NeedsUpgrade;
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/login/Engaged.java b/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/login/Engaged.java
deleted file mode 100644
index f192cb5..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/login/Engaged.java
+++ /dev/null
@@ -1,91 +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.fxa.login;
-
-import java.security.NoSuchAlgorithmException;
-
-import org.mozilla.gecko.background.fxa.FxAccountClient20.TwoKeys;
-import org.mozilla.gecko.background.fxa.FxAccountUtils;
-import org.mozilla.gecko.browserid.BrowserIDKeyPair;
-import org.mozilla.gecko.fxa.login.FxAccountLoginStateMachine.ExecuteDelegate;
-import org.mozilla.gecko.fxa.login.FxAccountLoginTransition.AccountVerified;
-import org.mozilla.gecko.fxa.login.FxAccountLoginTransition.LocalError;
-import org.mozilla.gecko.fxa.login.FxAccountLoginTransition.LogMessage;
-import org.mozilla.gecko.fxa.login.FxAccountLoginTransition.RemoteError;
-import org.mozilla.gecko.fxa.login.FxAccountLoginTransition.Transition;
-import org.mozilla.gecko.sync.ExtendedJSONObject;
-import org.mozilla.gecko.sync.Utils;
-
-public class Engaged extends State {
-  private static final String LOG_TAG = Engaged.class.getSimpleName();
-
-  protected final byte[] sessionToken;
-  protected final byte[] keyFetchToken;
-  protected final byte[] unwrapkB;
-
-  public Engaged(String email, String uid, boolean verified, byte[] unwrapkB, byte[] sessionToken, byte[] keyFetchToken) {
-    super(StateLabel.Engaged, email, uid, verified);
-    Utils.throwIfNull(unwrapkB, sessionToken, keyFetchToken);
-    this.unwrapkB = unwrapkB;
-    this.sessionToken = sessionToken;
-    this.keyFetchToken = keyFetchToken;
-  }
-
-  @Override
-  public ExtendedJSONObject toJSONObject() {
-    ExtendedJSONObject o = super.toJSONObject();
-    // Fields are non-null by constructor.
-    o.put("unwrapkB", Utils.byte2Hex(unwrapkB));
-    o.put("sessionToken", Utils.byte2Hex(sessionToken));
-    o.put("keyFetchToken", Utils.byte2Hex(keyFetchToken));
-    return o;
-  }
-
-  @Override
-  public void execute(final ExecuteDelegate delegate) {
-    BrowserIDKeyPair theKeyPair;
-    try {
-      theKeyPair = delegate.generateKeyPair();
-    } catch (NoSuchAlgorithmException e) {
-      delegate.handleTransition(new LocalError(e), new Doghouse(email, uid, verified));
-      return;
-    }
-    final BrowserIDKeyPair keyPair = theKeyPair;
-
-    delegate.getClient().keys(keyFetchToken, new BaseRequestDelegate<TwoKeys>(this, delegate) {
-      @Override
-      public void handleSuccess(TwoKeys result) {
-        byte[] kB;
-        try {
-          kB = FxAccountUtils.unwrapkB(unwrapkB, result.wrapkB);
-          if (FxAccountUtils.LOG_PERSONAL_INFORMATION) {
-            FxAccountUtils.pii(LOG_TAG, "Fetched kA: " + Utils.byte2Hex(result.kA));
-            FxAccountUtils.pii(LOG_TAG, "And wrapkB: " + Utils.byte2Hex(result.wrapkB));
-            FxAccountUtils.pii(LOG_TAG, "Giving kB : " + Utils.byte2Hex(kB));
-          }
-        } catch (Exception e) {
-          delegate.handleTransition(new RemoteError(e), new Separated(email, uid, verified));
-          return;
-        }
-        Transition transition = verified
-            ? new LogMessage("keys succeeded")
-            : new AccountVerified();
-        delegate.handleTransition(transition, new Cohabiting(email, uid, sessionToken, result.kA, kB, keyPair));
-      }
-    });
-  }
-
-  @Override
-  public Action getNeededAction() {
-    if (!verified) {
-      return Action.NeedsVerification;
-    }
-    return Action.None;
-  }
-
-  public byte[] getSessionToken() {
-    return sessionToken;
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/login/FxAccountLoginStateMachine.java b/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/login/FxAccountLoginStateMachine.java
deleted file mode 100644
index 34e5075..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/login/FxAccountLoginStateMachine.java
+++ /dev/null
@@ -1,84 +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.fxa.login;
-
-import java.security.NoSuchAlgorithmException;
-import java.util.EnumSet;
-import java.util.Set;
-
-import org.mozilla.gecko.background.fxa.FxAccountClient;
-import org.mozilla.gecko.browserid.BrowserIDKeyPair;
-import org.mozilla.gecko.fxa.login.FxAccountLoginTransition.Transition;
-import org.mozilla.gecko.fxa.login.State.StateLabel;
-
-public class FxAccountLoginStateMachine {
-  public static final String LOG_TAG = FxAccountLoginStateMachine.class.getSimpleName();
-
-  public interface LoginStateMachineDelegate {
-    public FxAccountClient getClient();
-    public long getCertificateDurationInMilliseconds();
-    public long getAssertionDurationInMilliseconds();
-    public void handleTransition(Transition transition, State state);
-    public void handleFinal(State state);
-    public BrowserIDKeyPair generateKeyPair() throws NoSuchAlgorithmException;
-  }
-
-  public static class ExecuteDelegate {
-    protected final LoginStateMachineDelegate delegate;
-    protected final StateLabel desiredStateLabel;
-    // It's as difficult to detect arbitrary cycles as repeated states.
-    protected final Set<StateLabel> stateLabelsSeen = EnumSet.noneOf(StateLabel.class);
-
-    protected ExecuteDelegate(StateLabel initialStateLabel, StateLabel desiredStateLabel, LoginStateMachineDelegate delegate) {
-      this.delegate = delegate;
-      this.desiredStateLabel = desiredStateLabel;
-      this.stateLabelsSeen.add(initialStateLabel);
-    }
-
-    public FxAccountClient getClient() {
-      return delegate.getClient();
-    }
-
-    public long getCertificateDurationInMilliseconds() {
-      return delegate.getCertificateDurationInMilliseconds();
-    }
-
-    public long getAssertionDurationInMilliseconds() {
-      return delegate.getAssertionDurationInMilliseconds();
-    }
-
-    public BrowserIDKeyPair generateKeyPair() throws NoSuchAlgorithmException {
-      return delegate.generateKeyPair();
-    }
-
-    public void handleTransition(Transition transition, State state) {
-      // Always trigger the transition callback.
-      delegate.handleTransition(transition, state);
-
-      // Possibly trigger the final callback. We trigger if we're at our desired
-      // state, or if we've seen this state before.
-      StateLabel stateLabel = state.getStateLabel();
-      if (stateLabel == desiredStateLabel || stateLabelsSeen.contains(stateLabel)) {
-        delegate.handleFinal(state);
-        return;
-      }
-
-      // If this wasn't the last state, leave a bread crumb and move on to the
-      // next state.
-      stateLabelsSeen.add(stateLabel);
-      state.execute(this);
-    }
-  }
-
-  public void advance(State initialState, final StateLabel desiredStateLabel, final LoginStateMachineDelegate delegate) {
-    if (initialState.getStateLabel() == desiredStateLabel) {
-      // We're already where we want to be!
-      delegate.handleFinal(initialState);
-      return;
-    }
-    ExecuteDelegate executeDelegate = new ExecuteDelegate(initialState.getStateLabel(), desiredStateLabel, delegate);
-    initialState.execute(executeDelegate);
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/login/FxAccountLoginTransition.java b/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/login/FxAccountLoginTransition.java
deleted file mode 100644
index 6832178..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/login/FxAccountLoginTransition.java
+++ /dev/null
@@ -1,68 +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.fxa.login;
-
-
-public class FxAccountLoginTransition {
-  public interface Transition {
-  }
-
-  public static class LogMessage implements Transition {
-    public final String detailMessage;
-
-    public LogMessage(String detailMessage) {
-      this.detailMessage = detailMessage;
-    }
-
-    @Override
-    public String toString() {
-      return getClass().getSimpleName() + (this.detailMessage == null ? "" : "('" + this.detailMessage + "')");
-    }
-  }
-
-  public static class AccountNeedsVerification extends LogMessage {
-    public AccountNeedsVerification() {
-      super(null);
-    }
-  }
-
-  public static class AccountVerified extends LogMessage {
-    public AccountVerified() {
-      super(null);
-    }
-  }
-
-  public static class PasswordRequired extends LogMessage {
-    public PasswordRequired() {
-      super(null);
-    }
-  }
-
-  public static class LocalError implements Transition {
-    public final Exception e;
-
-    public LocalError(Exception e) {
-      this.e = e;
-    }
-
-    @Override
-    public String toString() {
-      return "Log(" + this.e + ")";
-    }
-  }
-
-  public static class RemoteError implements Transition {
-    public final Exception e;
-
-    public RemoteError(Exception e) {
-      this.e = e;
-    }
-
-    @Override
-    public String toString() {
-      return "Log(" + (this.e == null ? "null" : this.e) + ")";
-    }
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/login/Married.java b/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/login/Married.java
deleted file mode 100644
index 1ec7b40..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/login/Married.java
+++ /dev/null
@@ -1,117 +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.fxa.login;
-
-import java.io.IOException;
-import java.io.UnsupportedEncodingException;
-import java.security.GeneralSecurityException;
-import java.security.InvalidKeyException;
-import java.security.NoSuchAlgorithmException;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-
-import org.mozilla.gecko.background.fxa.FxAccountUtils;
-import org.mozilla.gecko.browserid.BrowserIDKeyPair;
-import org.mozilla.gecko.browserid.JSONWebTokenUtils;
-import org.mozilla.gecko.fxa.login.FxAccountLoginStateMachine.ExecuteDelegate;
-import org.mozilla.gecko.fxa.login.FxAccountLoginTransition.LogMessage;
-import org.mozilla.gecko.sync.ExtendedJSONObject;
-import org.mozilla.gecko.sync.NonObjectJSONException;
-import org.mozilla.gecko.sync.Utils;
-import org.mozilla.gecko.sync.crypto.KeyBundle;
-
-public class Married extends TokensAndKeysState {
-  private static final String LOG_TAG = Married.class.getSimpleName();
-
-  protected final String certificate;
-  protected final String clientState;
-
-  public Married(String email, String uid, byte[] sessionToken, byte[] kA, byte[] kB, BrowserIDKeyPair keyPair, String certificate) {
-    super(StateLabel.Married, email, uid, sessionToken, kA, kB, keyPair);
-    Utils.throwIfNull(certificate);
-    this.certificate = certificate;
-    try {
-      this.clientState = FxAccountUtils.computeClientState(kB);
-    } catch (NoSuchAlgorithmException e) {
-      // This should never occur.
-      throw new IllegalStateException("Unable to compute client state from kB.");
-    }
-  }
-
-  @Override
-  public ExtendedJSONObject toJSONObject() {
-    ExtendedJSONObject o = super.toJSONObject();
-    // Fields are non-null by constructor.
-    o.put("certificate", certificate);
-    return o;
-  }
-
-  @Override
-  public void execute(final ExecuteDelegate delegate) {
-    delegate.handleTransition(new LogMessage("staying married"), this);
-  }
-
-  public String generateAssertion(String audience, String issuer) throws NonObjectJSONException, IOException, GeneralSecurityException {
-    // We generate assertions with no iat and an exp after 2050 to avoid
-    // invalid-timestamp errors from the token server.
-    final long expiresAt = JSONWebTokenUtils.DEFAULT_FUTURE_EXPIRES_AT_IN_MILLISECONDS;
-    String assertion = JSONWebTokenUtils.createAssertion(keyPair.getPrivate(), certificate, audience, issuer, null, expiresAt);
-    if (!FxAccountUtils.LOG_PERSONAL_INFORMATION) {
-      return assertion;
-    }
-
-    try {
-      FxAccountUtils.pii(LOG_TAG, "Generated assertion: " + assertion);
-      ExtendedJSONObject a = JSONWebTokenUtils.parseAssertion(assertion);
-      if (a != null) {
-        FxAccountUtils.pii(LOG_TAG, "aHeader   : " + a.getObject("header"));
-        FxAccountUtils.pii(LOG_TAG, "aPayload  : " + a.getObject("payload"));
-        FxAccountUtils.pii(LOG_TAG, "aSignature: " + a.getString("signature"));
-        String certificate = a.getString("certificate");
-        if (certificate != null) {
-          ExtendedJSONObject c = JSONWebTokenUtils.parseCertificate(certificate);
-          FxAccountUtils.pii(LOG_TAG, "cHeader   : " + c.getObject("header"));
-          FxAccountUtils.pii(LOG_TAG, "cPayload  : " + c.getObject("payload"));
-          FxAccountUtils.pii(LOG_TAG, "cSignature: " + c.getString("signature"));
-          // Print the relevant timestamps in sorted order with labels.
-          HashMap<Long, String> map = new HashMap<Long, String>();
-          map.put(a.getObject("payload").getLong("iat"), "aiat");
-          map.put(a.getObject("payload").getLong("exp"), "aexp");
-          map.put(c.getObject("payload").getLong("iat"), "ciat");
-          map.put(c.getObject("payload").getLong("exp"), "cexp");
-          ArrayList<Long> values = new ArrayList<Long>(map.keySet());
-          Collections.sort(values);
-          for (Long value : values) {
-            FxAccountUtils.pii(LOG_TAG, map.get(value) + ": " + value);
-          }
-        } else {
-          FxAccountUtils.pii(LOG_TAG, "Could not parse certificate!");
-        }
-      } else {
-        FxAccountUtils.pii(LOG_TAG, "Could not parse assertion!");
-      }
-    } catch (Exception e) {
-      FxAccountUtils.pii(LOG_TAG, "Got exception dumping assertion debug info.");
-    }
-    return assertion;
-  }
-
-  public KeyBundle getSyncKeyBundle() throws InvalidKeyException, NoSuchAlgorithmException, UnsupportedEncodingException {
-    // TODO Document this choice for deriving from kB.
-    return FxAccountUtils.generateSyncKeyBundle(kB);
-  }
-
-  public String getClientState() {
-    if (FxAccountUtils.LOG_PERSONAL_INFORMATION) {
-      FxAccountUtils.pii(LOG_TAG, "Client state: " + this.clientState);
-    }
-    return this.clientState;
-  }
-
-  public Cohabiting makeCohabitingState() {
-    return new Cohabiting(email, uid, sessionToken, kA, kB, keyPair);
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/login/MigratedFromSync11.java b/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/login/MigratedFromSync11.java
deleted file mode 100644
index c30ac2f..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/login/MigratedFromSync11.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.fxa.login;
-
-import org.mozilla.gecko.fxa.login.FxAccountLoginStateMachine.ExecuteDelegate;
-import org.mozilla.gecko.fxa.login.FxAccountLoginTransition.PasswordRequired;
-
-public class MigratedFromSync11 extends State {
-  public final String password;
-
-  public MigratedFromSync11(String email, String uid, boolean verified, String password) {
-    super(StateLabel.MigratedFromSync11, email, uid, verified);
-    // Null password is allowed.
-    this.password = password;
-  }
-
-  @Override
-  public void execute(final ExecuteDelegate delegate) {
-    delegate.handleTransition(new PasswordRequired(), this);
-  }
-
-  @Override
-  public Action getNeededAction() {
-    return Action.NeedsFinishMigrating;
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/login/Separated.java b/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/login/Separated.java
deleted file mode 100644
index bda620d..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/login/Separated.java
+++ /dev/null
@@ -1,25 +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.fxa.login;
-
-import org.mozilla.gecko.fxa.login.FxAccountLoginStateMachine.ExecuteDelegate;
-import org.mozilla.gecko.fxa.login.FxAccountLoginTransition.PasswordRequired;
-
-
-public class Separated extends State {
-  public Separated(String email, String uid, boolean verified) {
-    super(StateLabel.Separated, email, uid, verified);
-  }
-
-  @Override
-  public void execute(final ExecuteDelegate delegate) {
-    delegate.handleTransition(new PasswordRequired(), this);
-  }
-
-  @Override
-  public Action getNeededAction() {
-    return Action.NeedsPassword;
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/login/State.java b/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/login/State.java
deleted file mode 100644
index 797011e..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/login/State.java
+++ /dev/null
@@ -1,72 +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.fxa.login;
-
-import org.mozilla.gecko.fxa.login.FxAccountLoginStateMachine.ExecuteDelegate;
-import org.mozilla.gecko.sync.ExtendedJSONObject;
-import org.mozilla.gecko.sync.Utils;
-
-public abstract class State {
-  public static final long CURRENT_VERSION = 3L;
-
-  public enum StateLabel {
-    Engaged,
-    Cohabiting,
-    Married,
-    Separated,
-    Doghouse,
-    MigratedFromSync11,
-  }
-
-  public enum Action {
-    NeedsUpgrade,
-    NeedsPassword,
-    NeedsVerification,
-    NeedsFinishMigrating,
-    None,
-  }
-
-  protected final StateLabel stateLabel;
-  public final String email;
-  public final String uid;
-  public final boolean verified;
-
-  public State(StateLabel stateLabel, String email, String uid, boolean verified) {
-    Utils.throwIfNull(email, uid);
-    this.stateLabel = stateLabel;
-    this.email = email;
-    this.uid = uid;
-    this.verified = verified;
-  }
-
-  public StateLabel getStateLabel() {
-    return this.stateLabel;
-  }
-
-  public ExtendedJSONObject toJSONObject() {
-    ExtendedJSONObject o = new ExtendedJSONObject();
-    o.put("version", State.CURRENT_VERSION);
-    o.put("email", email);
-    o.put("uid", uid);
-    o.put("verified", verified);
-    return o;
-  }
-
-  public State makeSeparatedState() {
-    return new Separated(email, uid, verified);
-  }
-
-  public State makeDoghouseState() {
-    return new Doghouse(email, uid, verified);
-  }
-
-  public State makeMigratedFromSync11State(String password) {
-    return new MigratedFromSync11(email, uid, verified, password);
-  }
-
-  public abstract void execute(ExecuteDelegate delegate);
-
-  public abstract Action getNeededAction();
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/login/StateFactory.java b/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/login/StateFactory.java
deleted file mode 100644
index a98f2fb..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/login/StateFactory.java
+++ /dev/null
@@ -1,206 +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.fxa.login;
-
-import java.security.NoSuchAlgorithmException;
-import java.security.spec.InvalidKeySpecException;
-
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.background.fxa.FxAccountUtils;
-import org.mozilla.gecko.browserid.BrowserIDKeyPair;
-import org.mozilla.gecko.browserid.DSACryptoImplementation;
-import org.mozilla.gecko.browserid.RSACryptoImplementation;
-import org.mozilla.gecko.fxa.login.State.StateLabel;
-import org.mozilla.gecko.sync.ExtendedJSONObject;
-import org.mozilla.gecko.sync.NonObjectJSONException;
-import org.mozilla.gecko.sync.Utils;
-
-/**
- * Create {@link State} instances from serialized representations.
- * <p>
- * Version 1 recognizes 5 state labels (Engaged, Cohabiting, Married, Separated,
- * Doghouse). In the Cohabiting and Married states, the associated key pairs are
- * always RSA key pairs.
- * <p>
- * Version 2 is identical to version 1, except that in the Cohabiting and
- * Married states, the associated keypairs are always DSA key pairs.
- */
-public class StateFactory {
-  private static final String LOG_TAG = StateFactory.class.getSimpleName();
-
-  private static final int KEY_PAIR_SIZE_IN_BITS_V1 = 1024;
-
-  public static BrowserIDKeyPair generateKeyPair() throws NoSuchAlgorithmException {
-    // New key pairs are always DSA.
-    return DSACryptoImplementation.generateKeyPair(KEY_PAIR_SIZE_IN_BITS_V1);
-  }
-
-  protected static BrowserIDKeyPair keyPairFromJSONObjectV1(ExtendedJSONObject o) throws InvalidKeySpecException, NoSuchAlgorithmException {
-    // V1 key pairs are RSA.
-    return RSACryptoImplementation.fromJSONObject(o);
-  }
-
-  protected static BrowserIDKeyPair keyPairFromJSONObjectV2(ExtendedJSONObject o) throws InvalidKeySpecException, NoSuchAlgorithmException {
-    // V2 key pairs are DSA.
-    return DSACryptoImplementation.fromJSONObject(o);
-  }
-
-  public static State fromJSONObject(StateLabel stateLabel, ExtendedJSONObject o) throws InvalidKeySpecException, NoSuchAlgorithmException, NonObjectJSONException {
-    Long version = o.getLong("version");
-    if (version == null) {
-      throw new IllegalStateException("version must not be null");
-    }
-
-    final int v = version.intValue();
-    if (v == 3) {
-      // The most common case is the most recent version.
-      return fromJSONObjectV3(stateLabel, o);
-    }
-    if (v == 2) {
-      return fromJSONObjectV2(stateLabel, o);
-    }
-    if (v == 1) {
-      final State state = fromJSONObjectV1(stateLabel, o);
-      return migrateV1toV2(stateLabel, state);
-    }
-    throw new IllegalStateException("version must be in {1, 2}");
-  }
-
-  protected static State fromJSONObjectV1(StateLabel stateLabel, ExtendedJSONObject o) throws InvalidKeySpecException, NoSuchAlgorithmException, NonObjectJSONException {
-    switch (stateLabel) {
-    case Engaged:
-      return new Engaged(
-          o.getString("email"),
-          o.getString("uid"),
-          o.getBoolean("verified"),
-          Utils.hex2Byte(o.getString("unwrapkB")),
-          Utils.hex2Byte(o.getString("sessionToken")),
-          Utils.hex2Byte(o.getString("keyFetchToken")));
-    case Cohabiting:
-      return new Cohabiting(
-          o.getString("email"),
-          o.getString("uid"),
-          Utils.hex2Byte(o.getString("sessionToken")),
-          Utils.hex2Byte(o.getString("kA")),
-          Utils.hex2Byte(o.getString("kB")),
-          keyPairFromJSONObjectV1(o.getObject("keyPair")));
-    case Married:
-      return new Married(
-          o.getString("email"),
-          o.getString("uid"),
-          Utils.hex2Byte(o.getString("sessionToken")),
-          Utils.hex2Byte(o.getString("kA")),
-          Utils.hex2Byte(o.getString("kB")),
-          keyPairFromJSONObjectV1(o.getObject("keyPair")),
-          o.getString("certificate"));
-    case Separated:
-      return new Separated(
-          o.getString("email"),
-          o.getString("uid"),
-          o.getBoolean("verified"));
-    case Doghouse:
-      return new Doghouse(
-          o.getString("email"),
-          o.getString("uid"),
-          o.getBoolean("verified"));
-    default:
-      throw new IllegalStateException("unrecognized state label: " + stateLabel);
-    }
-  }
-
-  /**
-   * Exactly the same as {@link fromJSONObjectV1}, except that all key pairs are DSA key pairs.
-   */
-  protected static State fromJSONObjectV2(StateLabel stateLabel, ExtendedJSONObject o) throws InvalidKeySpecException, NoSuchAlgorithmException, NonObjectJSONException {
-    switch (stateLabel) {
-    case Cohabiting:
-      return new Cohabiting(
-          o.getString("email"),
-          o.getString("uid"),
-          Utils.hex2Byte(o.getString("sessionToken")),
-          Utils.hex2Byte(o.getString("kA")),
-          Utils.hex2Byte(o.getString("kB")),
-          keyPairFromJSONObjectV2(o.getObject("keyPair")));
-    case Married:
-      return new Married(
-          o.getString("email"),
-          o.getString("uid"),
-          Utils.hex2Byte(o.getString("sessionToken")),
-          Utils.hex2Byte(o.getString("kA")),
-          Utils.hex2Byte(o.getString("kB")),
-          keyPairFromJSONObjectV2(o.getObject("keyPair")),
-          o.getString("certificate"));
-    default:
-      return fromJSONObjectV1(stateLabel, o);
-    }
-  }
-
-  /**
-   * Exactly the same as {@link fromJSONObjectV2}, except that there's a new
-   * MigratedFromSyncV11 state.
-   */
-  protected static State fromJSONObjectV3(StateLabel stateLabel, ExtendedJSONObject o) throws InvalidKeySpecException, NoSuchAlgorithmException, NonObjectJSONException {
-    switch (stateLabel) {
-    case MigratedFromSync11:
-      return new MigratedFromSync11(
-          o.getString("email"),
-          o.getString("uid"),
-          o.getBoolean("verified"),
-          o.getString("password"));
-    default:
-      return fromJSONObjectV2(stateLabel, o);
-    }
-  }
-
-  protected static void logMigration(State from, State to) {
-    if (!FxAccountUtils.LOG_PERSONAL_INFORMATION) {
-      return;
-    }
-    try {
-      FxAccountUtils.pii(LOG_TAG, "V1 persisted state is: " + from.toJSONObject().toJSONString());
-    } catch (Exception e) {
-      Logger.warn(LOG_TAG, "Error producing JSON representation of V1 state.", e);
-    }
-    FxAccountUtils.pii(LOG_TAG, "Generated new V2 state: " + to.toJSONObject().toJSONString());
-  }
-
-  protected static State migrateV1toV2(StateLabel stateLabel, State state) throws NoSuchAlgorithmException {
-    if (state == null) {
-      // This should never happen, but let's be careful.
-      Logger.error(LOG_TAG, "Got null state in migrateV1toV2; returning null.");
-      return state;
-    }
-
-    Logger.info(LOG_TAG, "Migrating V1 persisted State to V2; stateLabel: " + stateLabel);
-
-    // In V1, we use an RSA keyPair. In V2, we use a DSA keyPair. Only
-    // Cohabiting and Married states have a persisted keyPair at all; all
-    // other states need no conversion at all.
-    switch (stateLabel) {
-    case Cohabiting: {
-      // In the Cohabiting state, we can just generate a new key pair and move on.
-      final Cohabiting cohabiting = (Cohabiting) state;
-      final BrowserIDKeyPair keyPair = generateKeyPair();
-      final State migrated = new Cohabiting(cohabiting.email, cohabiting.uid, cohabiting.sessionToken, cohabiting.kA, cohabiting.kB, keyPair);
-      logMigration(cohabiting, migrated);
-      return migrated;
-    }
-    case Married: {
-      // In the Married state, we cannot only change the key pair: the stored
-      // certificate signs the public key of the now obsolete key pair. We
-      // regress to the Cohabiting state; the next time we sync, we should
-      // advance back to Married.
-      final Married married = (Married) state;
-      final BrowserIDKeyPair keyPair = generateKeyPair();
-      final State migrated = new Cohabiting(married.email, married.uid, married.sessionToken, married.kA, married.kB, keyPair);
-      logMigration(married, migrated);
-      return migrated;
-    }
-    default:
-      // Otherwise, V1 and V2 states are identical.
-      return state;
-    }
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/login/TokensAndKeysState.java b/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/login/TokensAndKeysState.java
deleted file mode 100644
index b5121a4..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/login/TokensAndKeysState.java
+++ /dev/null
@@ -1,45 +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.fxa.login;
-
-import org.mozilla.gecko.browserid.BrowserIDKeyPair;
-import org.mozilla.gecko.sync.ExtendedJSONObject;
-import org.mozilla.gecko.sync.Utils;
-
-public abstract class TokensAndKeysState extends State {
-  protected final byte[] sessionToken;
-  protected final byte[] kA;
-  protected final byte[] kB;
-  protected final BrowserIDKeyPair keyPair;
-
-  public TokensAndKeysState(StateLabel stateLabel, String email, String uid, byte[] sessionToken, byte[] kA, byte[] kB, BrowserIDKeyPair keyPair) {
-    super(stateLabel, email, uid, true);
-    Utils.throwIfNull(sessionToken, kA, kB, keyPair);
-    this.sessionToken = sessionToken;
-    this.kA = kA;
-    this.kB = kB;
-    this.keyPair = keyPair;
-  }
-
-  @Override
-  public ExtendedJSONObject toJSONObject() {
-    ExtendedJSONObject o = super.toJSONObject();
-    // Fields are non-null by constructor.
-    o.put("sessionToken", Utils.byte2Hex(sessionToken));
-    o.put("kA", Utils.byte2Hex(kA));
-    o.put("kB", Utils.byte2Hex(kB));
-    o.put("keyPair", keyPair.toJSONObject());
-    return o;
-  }
-
-  public byte[] getSessionToken() {
-    return sessionToken;
-  }
-
-  @Override
-  public Action getNeededAction() {
-    return Action.None;
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/receivers/FxAccountDeletedService.java b/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/receivers/FxAccountDeletedService.java
deleted file mode 100644
index 60a63a5..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/receivers/FxAccountDeletedService.java
+++ /dev/null
@@ -1,154 +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.fxa.receivers;
-
-import android.app.IntentService;
-import android.content.Context;
-import android.content.Intent;
-
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.background.fxa.oauth.FxAccountAbstractClient;
-import org.mozilla.gecko.background.fxa.oauth.FxAccountAbstractClientException.FxAccountAbstractClientRemoteException;
-import org.mozilla.gecko.background.fxa.oauth.FxAccountOAuthClient10;
-import org.mozilla.gecko.fxa.FxAccountConstants;
-import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
-import org.mozilla.gecko.fxa.sync.FxAccountNotificationManager;
-import org.mozilla.gecko.fxa.sync.FxAccountSyncAdapter;
-import org.mozilla.gecko.sync.repositories.android.ClientsDatabase;
-import org.mozilla.gecko.sync.repositories.android.FennecTabsRepository;
-
-import java.util.concurrent.Executor;
-
-/**
- * A background service to clean up after a Firefox Account is deleted.
- * <p>
- * Note that we specifically handle deleting the pickle file using a Service and a
- * BroadcastReceiver, rather than a background thread, to allow channels sharing a Firefox account
- * to delete their respective pickle files (since, if one remains, the account will be restored
- * when that channel is used).
- */
-public class FxAccountDeletedService extends IntentService {
-  public static final String LOG_TAG = FxAccountDeletedService.class.getSimpleName();
-
-  public FxAccountDeletedService() {
-    super(LOG_TAG);
-  }
-
-  @Override
-  protected void onHandleIntent(final Intent intent) {
-    // We have an in-memory accounts cache which we use for a variety of tasks; it needs to be cleared.
-    // It should be fine to invalidate it before doing anything else, as the tasks below do not rely
-    // on this data.
-    AndroidFxAccount.invalidateCaches();
-
-    // Intent can, in theory, be null. Bug 1025937.
-    if (intent == null) {
-      Logger.debug(LOG_TAG, "Short-circuiting on null intent.");
-      return;
-    }
-
-    final Context context = this;
-
-    long intentVersion = intent.getLongExtra(
-        FxAccountConstants.ACCOUNT_DELETED_INTENT_VERSION_KEY, 0);
-    long expectedVersion = FxAccountConstants.ACCOUNT_DELETED_INTENT_VERSION;
-    if (intentVersion != expectedVersion) {
-      Logger.warn(LOG_TAG, "Intent malformed: version " + intentVersion + " given but " +
-          "version " + expectedVersion + "expected. Not cleaning up after deleted Account.");
-      return;
-    }
-
-    // Android Account name, not Sync encoded account name.
-    final String accountName = intent.getStringExtra(
-        FxAccountConstants.ACCOUNT_DELETED_INTENT_ACCOUNT_KEY);
-    if (accountName == null) {
-      Logger.warn(LOG_TAG, "Intent malformed: no account name given. Not cleaning up after " +
-          "deleted Account.");
-      return;
-    }
-
-
-    // Fire up gecko and unsubscribe push
-    final Intent geckoIntent = new Intent();
-    geckoIntent.setAction("create-services");
-    geckoIntent.setClassName(context, "org.mozilla.gecko.GeckoService");
-    geckoIntent.putExtra("category", "android-push-service");
-    geckoIntent.putExtra("data", "android-fxa-unsubscribe");
-    final AndroidFxAccount fxAccount = AndroidFxAccount.fromContext(context);
-    geckoIntent.putExtra("org.mozilla.gecko.intent.PROFILE_NAME",
-            intent.getStringExtra(FxAccountConstants.ACCOUNT_DELETED_INTENT_ACCOUNT_PROFILE));
-    context.startService(geckoIntent);
-
-    // Delete client database and non-local tabs.
-    Logger.info(LOG_TAG, "Deleting the entire Fennec clients database and non-local tabs");
-    FennecTabsRepository.deleteNonLocalClientsAndTabs(context);
-
-
-    // Clear Firefox Sync client tables.
-    try {
-      Logger.info(LOG_TAG, "Deleting the Firefox Sync clients database.");
-      ClientsDatabase db = null;
-      try {
-        db = new ClientsDatabase(context);
-        db.wipeClientsTable();
-        db.wipeCommandsTable();
-      } finally {
-        if (db != null) {
-          db.close();
-        }
-      }
-    } catch (Exception e) {
-      Logger.warn(LOG_TAG, "Got exception deleting the Firefox Sync clients database; ignoring.", e);
-    }
-
-    // Remove any displayed notifications.
-    new FxAccountNotificationManager(FxAccountSyncAdapter.NOTIFICATION_ID).clear(context);
-
-    // Bug 1147275: Delete cached oauth tokens. There's no way to query all
-    // oauth tokens from Android, so this is tricky to do comprehensively. We
-    // can query, individually, for specific oauth tokens to delete, however.
-    final String oauthServerURI = intent.getStringExtra(FxAccountConstants.ACCOUNT_OAUTH_SERVICE_ENDPOINT_KEY);
-    final String[] tokens = intent.getStringArrayExtra(FxAccountConstants.ACCOUNT_DELETED_INTENT_ACCOUNT_AUTH_TOKENS);
-    if (oauthServerURI != null && tokens != null) {
-      final Executor directExecutor = new Executor() {
-        @Override
-        public void execute(Runnable runnable) {
-          runnable.run();
-        }
-      };
-
-      final FxAccountOAuthClient10 oauthClient = new FxAccountOAuthClient10(oauthServerURI, directExecutor);
-
-      for (String token : tokens) {
-        if (token == null) {
-          Logger.error(LOG_TAG, "Cached OAuth token is null; should never happen.  Ignoring.");
-          continue;
-        }
-        try {
-          oauthClient.deleteToken(token, new FxAccountAbstractClient.RequestDelegate<Void>() {
-            @Override
-            public void handleSuccess(Void result) {
-              Logger.info(LOG_TAG, "Successfully deleted cached OAuth token.");
-            }
-
-            @Override
-            public void handleError(Exception e) {
-              Logger.error(LOG_TAG, "Failed to delete cached OAuth token; ignoring.", e);
-            }
-
-            @Override
-            public void handleFailure(FxAccountAbstractClientRemoteException e) {
-              Logger.error(LOG_TAG, "Exception during cached OAuth token deletion; ignoring.", e);
-            }
-          });
-        } catch (Exception e) {
-          Logger.error(LOG_TAG, "Exception during cached OAuth token deletion; ignoring.", e);
-        }
-      }
-    } else {
-      Logger.error(LOG_TAG, "Cached OAuth server URI is null or cached OAuth tokens are null; ignoring.");
-    }
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/receivers/FxAccountUpgradeReceiver.java b/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/receivers/FxAccountUpgradeReceiver.java
deleted file mode 100644
index ad81e04..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/receivers/FxAccountUpgradeReceiver.java
+++ /dev/null
@@ -1,133 +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.fxa.receivers;
-
-import java.util.LinkedList;
-import java.util.List;
-import java.util.concurrent.Executor;
-import java.util.concurrent.Executors;
-
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.background.fxa.FxAccountUtils;
-import org.mozilla.gecko.fxa.FirefoxAccounts;
-import org.mozilla.gecko.fxa.FxAccountConstants;
-import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
-import org.mozilla.gecko.fxa.login.State;
-import org.mozilla.gecko.fxa.login.State.StateLabel;
-import org.mozilla.gecko.sync.Utils;
-
-import android.accounts.Account;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-
-/**
- * A receiver that takes action when our Android package is upgraded (replaced).
- */
-public class FxAccountUpgradeReceiver extends BroadcastReceiver {
-  private static final String LOG_TAG = FxAccountUpgradeReceiver.class.getSimpleName();
-
-  /**
-   * Produce a list of Runnable instances to be executed sequentially on
-   * upgrade.
-   * <p>
-   * Each Runnable will be executed sequentially on a background thread. Any
-   * unchecked Exception thrown will be caught and ignored.
-   *
-   * @param context Android context.
-   * @return list of Runnable instances.
-   */
-  protected List<Runnable> onUpgradeRunnables(Context context) {
-    List<Runnable> runnables = new LinkedList<Runnable>();
-    runnables.add(new MaybeUnpickleRunnable(context));
-    // Recovering accounts that are in the Doghouse should happen *after* we
-    // unpickle any accounts saved to disk.
-    runnables.add(new AdvanceFromDoghouseRunnable(context));
-    return runnables;
-  }
-
-  @Override
-  public void onReceive(final Context context, Intent intent) {
-    Logger.setThreadLogTag(FxAccountConstants.GLOBAL_LOG_TAG);
-    Logger.info(LOG_TAG, "Upgrade broadcast received.");
-
-    // Iterate Runnable instances one at a time.
-    final Executor executor = Executors.newSingleThreadExecutor();
-    for (final Runnable runnable : onUpgradeRunnables(context)) {
-      executor.execute(new Runnable() {
-        @Override
-        public void run() {
-          try {
-            runnable.run();
-          } catch (Exception e) {
-            // We really don't want to throw on a background thread, so we
-            // catch, log, and move on.
-            Logger.error(LOG_TAG, "Got exception executing background upgrade Runnable; ignoring.", e);
-          }
-        }
-      });
-    }
-  }
-
-  /**
-   * A Runnable that tries to unpickle any pickled Firefox Accounts.
-   */
-  protected static class MaybeUnpickleRunnable implements Runnable {
-    protected final Context context;
-
-    public MaybeUnpickleRunnable(Context context) {
-      this.context = context;
-    }
-
-    @Override
-    public void run() {
-      // Querying the accounts will unpickle any pickled Firefox Account.
-      Logger.info(LOG_TAG, "Trying to unpickle any pickled Firefox Account.");
-      FirefoxAccounts.getFirefoxAccounts(context);
-    }
-  }
-
-  /**
-   * A Runnable that tries to advance existing Firefox Accounts that are in the
-   * Doghouse state to the Separated state.
-   * <p>
-   * This is our main deprecation-and-upgrade mechanism: in some way, the
-   * Account gets moved to the Doghouse state. If possible, an upgraded version
-   * of the package advances to Separated, prompting the user to re-connect the
-   * Account.
-   */
-  protected static class AdvanceFromDoghouseRunnable implements Runnable {
-    protected final Context context;
-
-    public AdvanceFromDoghouseRunnable(Context context) {
-      this.context = context;
-    }
-
-    @Override
-    public void run() {
-      final Account[] accounts = FirefoxAccounts.getFirefoxAccounts(context);
-      Logger.info(LOG_TAG, "Trying to advance " + accounts.length + " existing Firefox Accounts from the Doghouse to Separated (if necessary).");
-      for (Account account : accounts) {
-        try {
-          final AndroidFxAccount fxAccount = new AndroidFxAccount(context, account);
-          // For great debugging.
-          if (FxAccountUtils.LOG_PERSONAL_INFORMATION) {
-            fxAccount.dump();
-          }
-          State state = fxAccount.getState();
-          if (state == null || state.getStateLabel() != StateLabel.Doghouse) {
-            Logger.debug(LOG_TAG, "Account named like " + Utils.obfuscateEmail(account.name) + " is not in the Doghouse; skipping.");
-            continue;
-          }
-          Logger.debug(LOG_TAG, "Account named like " + Utils.obfuscateEmail(account.name) + " is in the Doghouse; advancing to Separated.");
-          fxAccount.setState(state.makeSeparatedState());
-        } catch (Exception e) {
-          Logger.warn(LOG_TAG, "Got exception trying to advance account named like " + Utils.obfuscateEmail(account.name) +
-              " from Doghouse to Separated state; ignoring.", e);
-        }
-      }
-    }
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/sync/FxAccountNotificationManager.java b/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/sync/FxAccountNotificationManager.java
deleted file mode 100644
index b44da76..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/sync/FxAccountNotificationManager.java
+++ /dev/null
@@ -1,114 +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.fxa.sync;
-
-import android.app.NotificationManager;
-import android.app.PendingIntent;
-import android.content.Context;
-import android.content.Intent;
-import android.support.v4.app.NotificationCompat;
-import android.support.v4.app.NotificationCompat.Builder;
-import org.mozilla.gecko.Locales;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.background.common.telemetry.TelemetryWrapper;
-import org.mozilla.gecko.background.fxa.FxAccountUtils;
-import org.mozilla.gecko.fxa.FxAccountConstants;
-import org.mozilla.gecko.fxa.activities.FxAccountWebFlowActivity;
-import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
-import org.mozilla.gecko.fxa.login.State;
-import org.mozilla.gecko.fxa.login.State.Action;
-import org.mozilla.gecko.sync.telemetry.TelemetryContract;
-
-/**
- * Abstraction that manages notifications shown or hidden for a Firefox Account.
- * <p>
- * In future, we anticipate this tracking things like:
- * <ul>
- * <li>new engines to offer to Sync;</li>
- * <li>service interruption updates;</li>
- * <li>messages from other clients.</li>
- * </ul>
- */
-public class FxAccountNotificationManager {
-  private static final String LOG_TAG = FxAccountNotificationManager.class.getSimpleName();
-
-  protected final int notificationId;
-
-  // We're lazy about updating our locale info, because most syncs don't notify.
-  private volatile boolean localeUpdated;
-
-  public FxAccountNotificationManager(int notificationId) {
-    this.notificationId = notificationId;
-  }
-
-  /**
-   * Remove all Firefox Account related notifications from the notification manager.
-   *
-   * @param context
-   *          Android context.
-   */
-  public void clear(Context context) {
-    final NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
-    notificationManager.cancel(notificationId);
-  }
-
-  /**
-   * Reflect new Firefox Account state to the notification manager: show or hide
-   * notifications reflecting the state of a Firefox Account.
-   *
-   * @param context
-   *          Android context.
-   * @param fxAccount
-   *          Firefox Account to reflect to the notification manager.
-   */
-  public void update(Context context, AndroidFxAccount fxAccount) {
-    final NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
-
-    final State state = fxAccount.getState();
-    final Action action = state.getNeededAction();
-    if (action == Action.None) {
-      Logger.info(LOG_TAG, "State " + state.getStateLabel() + " needs no action; cancelling any existing notification.");
-      notificationManager.cancel(notificationId);
-      return;
-    }
-
-    if (!localeUpdated) {
-      localeUpdated = true;
-      Locales.getLocaleManager().getAndApplyPersistedLocale(context);
-    }
-
-    final String title;
-    final String text;
-    final Intent notificationIntent;
-    if (action == Action.NeedsFinishMigrating) {
-      TelemetryWrapper.addToHistogram(TelemetryContract.SYNC11_MIGRATION_NOTIFICATIONS_OFFERED, 1);
-
-      title = context.getResources().getString(R.string.fxaccount_sync_finish_migrating_notification_title);
-      text = context.getResources().getString(R.string.fxaccount_sync_finish_migrating_notification_text, state.email);
-      notificationIntent = new Intent(FxAccountConstants.ACTION_FXA_FINISH_MIGRATING);
-    } else {
-      title = context.getResources().getString(R.string.fxaccount_sync_sign_in_error_notification_title);
-      text = context.getResources().getString(R.string.fxaccount_sync_sign_in_error_notification_text, state.email);
-      notificationIntent = new Intent(FxAccountConstants.ACTION_FXA_STATUS);
-    }
-
-    notificationIntent.putExtra(FxAccountWebFlowActivity.EXTRA_ENDPOINT, FxAccountConstants.ENDPOINT_NOTIFICATION);
-
-    Logger.info(LOG_TAG, "State " + state.getStateLabel() + " needs action; offering notification with title: " + title);
-    FxAccountUtils.pii(LOG_TAG, "And text: " + text);
-
-    final PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, notificationIntent, 0);
-
-    final Builder builder = new NotificationCompat.Builder(context);
-    builder
-    .setContentTitle(title)
-    .setContentText(text)
-    .setSmallIcon(R.drawable.ic_status_logo)
-    .setAutoCancel(true)
-    .setContentIntent(pendingIntent);
-    notificationManager.notify(notificationId, builder.build());
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/sync/FxAccountProfileService.java b/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/sync/FxAccountProfileService.java
deleted file mode 100644
index 7f03eff..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/sync/FxAccountProfileService.java
+++ /dev/null
@@ -1,107 +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.fxa.sync;
-
-import android.accounts.AccountManager;
-import android.app.Activity;
-import android.app.IntentService;
-import android.content.Intent;
-import android.os.Bundle;
-import android.os.ResultReceiver;
-
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.background.fxa.FxAccountUtils;
-import org.mozilla.gecko.background.fxa.oauth.FxAccountAbstractClient;
-import org.mozilla.gecko.background.fxa.oauth.FxAccountAbstractClientException;
-import org.mozilla.gecko.background.fxa.profile.FxAccountProfileClient10;
-import org.mozilla.gecko.fxa.FxAccountConstants;
-import org.mozilla.gecko.sync.ExtendedJSONObject;
-
-import java.util.concurrent.Executor;
-import java.util.concurrent.Executors;
-
-public class FxAccountProfileService extends IntentService {
-  private static final String LOG_TAG = "FxAccountProfileService";
-  private static final Executor EXECUTOR_SERVICE = Executors.newSingleThreadExecutor();
-  public static final String KEY_AUTH_TOKEN = "auth_token";
-  public static final String KEY_PROFILE_SERVER_URI = "profileServerURI";
-  public static final String KEY_RESULT_RECEIVER = "resultReceiver";
-  public static final String KEY_RESULT_STRING = "RESULT_STRING";
-
-  public FxAccountProfileService() {
-    super("FxAccountProfileService");
-  }
-
-  @Override
-  protected void onHandleIntent(Intent intent) {
-    final String authToken = intent.getStringExtra(KEY_AUTH_TOKEN);
-    final String profileServerURI = intent.getStringExtra(KEY_PROFILE_SERVER_URI);
-    final ResultReceiver resultReceiver = intent.getParcelableExtra(KEY_RESULT_RECEIVER);
-
-    if (resultReceiver == null) {
-      Logger.warn(LOG_TAG, "Result receiver must not be null; ignoring intent.");
-      return;
-    }
-
-    if (authToken == null || authToken.length() == 0) {
-      Logger.warn(LOG_TAG, "Invalid Auth Token");
-      sendResult("Invalid Auth Token", resultReceiver, Activity.RESULT_CANCELED);
-      return;
-    }
-
-    if (profileServerURI == null || profileServerURI.length() == 0) {
-      Logger.warn(LOG_TAG, "Invalid profile Server Endpoint");
-      sendResult("Invalid profile Server Endpoint", resultReceiver, Activity.RESULT_CANCELED);
-      return;
-    }
-
-    // This delegate fetches the profile avatar json.
-    FxAccountProfileClient10.RequestDelegate<ExtendedJSONObject> delegate = new FxAccountAbstractClient.RequestDelegate<ExtendedJSONObject>() {
-      @Override
-      public void handleError(Exception e) {
-        Logger.error(LOG_TAG, "Error fetching Account profile.", e);
-        sendResult("Error fetching Account profile.", resultReceiver, Activity.RESULT_CANCELED);
-      }
-
-      @Override
-      public void handleFailure(FxAccountAbstractClientException.FxAccountAbstractClientRemoteException e) {
-        Logger.warn(LOG_TAG, "Failed to fetch Account profile.", e);
-
-        if (e.isInvalidAuthentication()) {
-          // The profile server rejected the cached oauth token! Invalidate it.
-          // A new token will be generated upon next request.
-          Logger.info(LOG_TAG, "Invalidating oauth token after 401!");
-          AccountManager.get(FxAccountProfileService.this).invalidateAuthToken(FxAccountConstants.ACCOUNT_TYPE, authToken);
-        }
-
-        sendResult("Failed to fetch Account profile.", resultReceiver, Activity.RESULT_CANCELED);
-      }
-
-      @Override
-      public void handleSuccess(ExtendedJSONObject result) {
-        if (result != null){
-          FxAccountUtils.pii(LOG_TAG, "Profile server return profile: " + result.toJSONString());
-          sendResult(result.toJSONString(), resultReceiver, Activity.RESULT_OK);
-        }
-      }
-    };
-
-    FxAccountProfileClient10 client = new FxAccountProfileClient10(profileServerURI, EXECUTOR_SERVICE);
-    try {
-      client.profile(authToken, delegate);
-    } catch (Exception e) {
-      Logger.error(LOG_TAG, "Got exception fetching profile.", e);
-      delegate.handleError(e);
-    }
-  }
-
-  private void sendResult(final String result, final ResultReceiver resultReceiver, final int code) {
-    if (resultReceiver != null) {
-      final Bundle bundle = new Bundle();
-      bundle.putString(KEY_RESULT_STRING, result);
-      resultReceiver.send(code, bundle);
-    }
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/sync/FxAccountSchedulePolicy.java b/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/sync/FxAccountSchedulePolicy.java
deleted file mode 100644
index 708686e..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/sync/FxAccountSchedulePolicy.java
+++ /dev/null
@@ -1,178 +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.fxa.sync;
-
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.db.BrowserContract;
-import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
-import org.mozilla.gecko.fxa.login.State.Action;
-import org.mozilla.gecko.sync.BackoffHandler;
-
-import android.accounts.Account;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.os.Bundle;
-
-public class FxAccountSchedulePolicy implements SchedulePolicy {
-  private static final String LOG_TAG = "FxAccountSchedulePolicy";
-
-  // Our poll intervals are used to trigger automatic background syncs
-  // in the absence of user activity.
-  //
-  // We also receive sync requests as a result of network tickles, so
-  // these intervals are long, with the exception of the rapid polling
-  // while we wait for verification: if we're waiting for the user to
-  // click on a verification link, we sync very often in order to detect
-  // a change in state.
-  //
-  // In the case of unverified -> unverified (no transition), this should be
-  // very close to a single HTTP request (with the SyncAdapter overhead, of
-  // course, but that's not wildly different from alarm manager overhead).
-  //
-  // The /account/status endpoint is HAWK authed by sessionToken, so we still
-  // have to do some crypto no matter what.
-
-  // TODO: only do this for a while...
-  public static final long POLL_INTERVAL_PENDING_VERIFICATION = 60;         // 1 minute.
-
-  // If we're in some kind of error state, there's no point trying often.
-  // This is not the same as a server-imposed backoff, which will be
-  // reflected dynamically.
-  public static final long POLL_INTERVAL_ERROR_STATE_SEC = 24 * 60 * 60;    // 24 hours.
-
-  // If we're the only device, just sync once or twice a day in case that
-  // changes.
-  public static final long POLL_INTERVAL_SINGLE_DEVICE_SEC = 18 * 60 * 60;  // 18 hours.
-
-  // And if we know there are other devices, let's sync often enough that
-  // we'll be more likely to be caught up (even if not completely) by the
-  // time you next use this device. This is also achieved via Android's
-  // network tickles.
-  public static final long POLL_INTERVAL_MULTI_DEVICE_SEC = 12 * 60 * 60;   // 12 hours.
-
-  // This is used solely as an optimization for backoff handling, so it's not
-  // persisted.
-  private static volatile long POLL_INTERVAL_CURRENT_SEC = POLL_INTERVAL_SINGLE_DEVICE_SEC;
-
-  // Never sync more frequently than this, unless forced.
-  // This is to avoid overly-frequent syncs during active browsing.
-  public static final long RATE_LIMIT_FUNDAMENTAL_SEC = 90;                 // 90 seconds.
-
-  /**
-   * We are prompted to sync by several inputs:
-   * * Periodic syncs that we schedule at long intervals. See the POLL constants.
-   * * Network-tickle-based syncs that Android starts.
-   * * Upload-only syncs that are caused by local database writes.
-   *
-   * We rate-limit periodic and network-sourced events with this constant.
-   * We rate limit <b>both</b> with {@link FxAccountSchedulePolicy#RATE_LIMIT_FUNDAMENTAL_SEC}.
-   */
-  public static final long RATE_LIMIT_BACKGROUND_SEC = 60 * 60;             // 1 hour.
-
-  private final AndroidFxAccount account;
-  private final Context context;
-
-  public FxAccountSchedulePolicy(Context context, AndroidFxAccount account) {
-    this.account = account;
-    this.context = context;
-  }
-
-  /**
-   * Return a millisecond timestamp in the future, offset from the current
-   * time by the provided amount.
-   * @param millis the duration by which to delay
-   * @return a timestamp.
-   */
-  private static long delay(long millis) {
-    return System.currentTimeMillis() + millis;
-  }
-
-  /**
-   * 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 long intervalSeconds) {
-    final String authority = BrowserContract.AUTHORITY;
-    final Account account = this.account.getAndroidAccount();
-    this.context.getContentResolver();
-    Logger.info(LOG_TAG, "Scheduling periodic sync for " + intervalSeconds + ".");
-    ContentResolver.addPeriodicSync(account, authority, Bundle.EMPTY, intervalSeconds);
-    POLL_INTERVAL_CURRENT_SEC = intervalSeconds;
-  }
-
-  @Override
-  public void onSuccessfulSync(int otherClientsCount) {
-    this.account.setLastSyncedTimestamp(System.currentTimeMillis());
-    // This undoes the change made in observeBackoffMillis -- once we hit backoff we'll
-    // periodically sync at the backoff duration, but as soon as we succeed we'll switch
-    // into the client-count-dependent interval.
-    long interval = (otherClientsCount > 0) ? POLL_INTERVAL_MULTI_DEVICE_SEC : POLL_INTERVAL_SINGLE_DEVICE_SEC;
-    requestPeriodicSync(interval);
-  }
-
-  @Override
-  public void onHandleFinal(Action needed) {
-    switch (needed) {
-    case NeedsPassword:
-    case NeedsUpgrade:
-    case NeedsFinishMigrating:
-      requestPeriodicSync(POLL_INTERVAL_ERROR_STATE_SEC);
-      break;
-    case NeedsVerification:
-      requestPeriodicSync(POLL_INTERVAL_PENDING_VERIFICATION);
-      break;
-    case None:
-      // No action needed: we'll set the periodic sync interval
-      // when the sync finishes, via the SessionCallback.
-      break;
-    }
-  }
-
-  @Override
-  public void onUpgradeRequired() {
-    // TODO: this shouldn't occur in FxA, but when we upgrade we
-    // need to reduce the interval again.
-    requestPeriodicSync(POLL_INTERVAL_ERROR_STATE_SEC);
-  }
-
-  @Override
-  public void onUnauthorized() {
-    // TODO: this shouldn't occur in FxA, but when we fix our credentials
-    // we need to reduce the interval again.
-    requestPeriodicSync(POLL_INTERVAL_ERROR_STATE_SEC);
-  }
-
-  @Override
-  public void configureBackoffMillisOnBackoff(BackoffHandler backoffHandler, long backoffMillis, boolean onlyExtend) {
-    if (onlyExtend) {
-      backoffHandler.extendEarliestNextRequest(delay(backoffMillis));
-    } else {
-      backoffHandler.setEarliestNextRequest(delay(backoffMillis));
-    }
-
-    // Yes, we might be part-way through the interval, in which case the backoff
-    // code will do its job. But we certainly don't want to reduce the interval
-    // if we're given a small backoff instruction.
-    // We'll reset the poll interval next time we sync without a backoff instruction.
-    if (backoffMillis > (POLL_INTERVAL_CURRENT_SEC * 1000)) {
-      // Slightly inflate the backoff duration to ensure that a fuzzed
-      // periodic sync doesn't occur before our backoff has passed. Android
-      // 19+ default to a 4% fuzz factor.
-      requestPeriodicSync((long) Math.ceil((1.05 * backoffMillis) / 1000));
-    }
-  }
-
-  /**
-   * Accepts two {@link BackoffHandler} instances as input. These are used
-   * respectively to track fundamental rate limiting, and to separately
-   * rate-limit periodic and network-tickled syncs.
-   */
-  @Override
-  public void configureBackoffMillisBeforeSyncing(BackoffHandler fundamentalRateHandler, BackoffHandler backgroundRateHandler) {
-    fundamentalRateHandler.setEarliestNextRequest(delay(RATE_LIMIT_FUNDAMENTAL_SEC * 1000));
-    backgroundRateHandler.setEarliestNextRequest(delay(RATE_LIMIT_BACKGROUND_SEC * 1000));
-  }
-}
\ No newline at end of file
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/sync/FxAccountSyncAdapter.java b/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/sync/FxAccountSyncAdapter.java
deleted file mode 100644
index 30990cf..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/sync/FxAccountSyncAdapter.java
+++ /dev/null
@@ -1,568 +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.fxa.sync;
-
-import android.accounts.Account;
-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;
-import android.os.SystemClock;
-import android.text.TextUtils;
-
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.background.common.telemetry.TelemetryWrapper;
-import org.mozilla.gecko.background.fxa.FxAccountUtils;
-import org.mozilla.gecko.background.fxa.SkewHandler;
-import org.mozilla.gecko.browserid.JSONWebTokenUtils;
-import org.mozilla.gecko.fxa.FirefoxAccounts;
-import org.mozilla.gecko.fxa.FxAccountConstants;
-import org.mozilla.gecko.fxa.FxAccountDeviceRegistrator;
-import org.mozilla.gecko.fxa.authenticator.AccountPickler;
-import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
-import org.mozilla.gecko.fxa.authenticator.FxADefaultLoginStateMachineDelegate;
-import org.mozilla.gecko.fxa.authenticator.FxAccountAuthenticator;
-import org.mozilla.gecko.fxa.login.FxAccountLoginStateMachine;
-import org.mozilla.gecko.fxa.login.Married;
-import org.mozilla.gecko.fxa.login.State;
-import org.mozilla.gecko.fxa.login.State.StateLabel;
-import org.mozilla.gecko.fxa.sync.FxAccountSyncDelegate.Result;
-import org.mozilla.gecko.sync.BackoffHandler;
-import org.mozilla.gecko.sync.GlobalSession;
-import org.mozilla.gecko.sync.PrefsBackoffHandler;
-import org.mozilla.gecko.sync.SharedPreferencesClientsDataDelegate;
-import org.mozilla.gecko.sync.SyncConfiguration;
-import org.mozilla.gecko.sync.ThreadPool;
-import org.mozilla.gecko.sync.Utils;
-import org.mozilla.gecko.sync.crypto.KeyBundle;
-import org.mozilla.gecko.sync.delegates.GlobalSessionCallback;
-import org.mozilla.gecko.sync.delegates.ClientsDataDelegate;
-import org.mozilla.gecko.sync.net.AuthHeaderProvider;
-import org.mozilla.gecko.sync.net.HawkAuthHeaderProvider;
-import org.mozilla.gecko.sync.stage.GlobalSyncStage.Stage;
-import org.mozilla.gecko.sync.telemetry.TelemetryContract;
-import org.mozilla.gecko.tokenserver.TokenServerClient;
-import org.mozilla.gecko.tokenserver.TokenServerClientDelegate;
-import org.mozilla.gecko.tokenserver.TokenServerException;
-import org.mozilla.gecko.tokenserver.TokenServerToken;
-
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.EnumSet;
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.LinkedBlockingQueue;
-
-public class FxAccountSyncAdapter extends AbstractThreadedSyncAdapter {
-  private static final String LOG_TAG = FxAccountSyncAdapter.class.getSimpleName();
-
-  public static final int NOTIFICATION_ID = LOG_TAG.hashCode();
-
-  // Tracks the last seen storage hostname for backoff purposes.
-  private static final String PREF_BACKOFF_STORAGE_HOST = "backoffStorageHost";
-
-  // Used to do cheap in-memory rate limiting. Don't sync again if we
-  // successfully synced within this duration.
-  private static final int MINIMUM_SYNC_DELAY_MILLIS = 15 * 1000;        // 15 seconds.
-  private volatile long lastSyncRealtimeMillis;
-
-  protected final ExecutorService executor;
-  protected final FxAccountNotificationManager notificationManager;
-
-  public FxAccountSyncAdapter(Context context, boolean autoInitialize) {
-    super(context, autoInitialize);
-    this.executor = Executors.newSingleThreadExecutor();
-    this.notificationManager = new FxAccountNotificationManager(NOTIFICATION_ID);
-  }
-
-  protected static class SyncDelegate extends FxAccountSyncDelegate {
-    @Override
-    public void handleSuccess() {
-      Logger.info(LOG_TAG, "Sync succeeded.");
-      super.handleSuccess();
-      TelemetryWrapper.addToHistogram(TelemetryContract.SYNC_COMPLETED, 1);
-    }
-
-    @Override
-    public void handleError(Exception e) {
-      Logger.error(LOG_TAG, "Got exception syncing.", e);
-      super.handleError(e);
-      TelemetryWrapper.addToHistogram(TelemetryContract.SYNC_FAILED, 1);
-    }
-
-    @Override
-    public void handleCannotSync(State finalState) {
-      Logger.warn(LOG_TAG, "Cannot sync from state: " + finalState.getStateLabel());
-      super.handleCannotSync(finalState);
-    }
-
-    @Override
-    public void postponeSync(long millis) {
-      if (millis <= 0) {
-        Logger.debug(LOG_TAG, "Asked to postpone sync, but zero delay.");
-      }
-      super.postponeSync(millis);
-    }
-
-    @Override
-    public void rejectSync() {
-      super.rejectSync();
-    }
-
-    protected final Collection<String> stageNamesToSync;
-
-    public SyncDelegate(BlockingQueue<Result> latch, SyncResult syncResult, AndroidFxAccount fxAccount, Collection<String> stageNamesToSync) {
-      super(latch, syncResult);
-      this.stageNamesToSync = Collections.unmodifiableCollection(stageNamesToSync);
-    }
-
-    public Collection<String> getStageNamesToSync() {
-      return this.stageNamesToSync;
-    }
-  }
-
-  protected static class SessionCallback implements GlobalSessionCallback {
-    protected final SyncDelegate syncDelegate;
-    protected final SchedulePolicy schedulePolicy;
-    protected volatile BackoffHandler storageBackoffHandler;
-
-    public SessionCallback(SyncDelegate syncDelegate, SchedulePolicy schedulePolicy) {
-      this.syncDelegate = syncDelegate;
-      this.schedulePolicy = schedulePolicy;
-    }
-
-    public void setBackoffHandler(BackoffHandler backoffHandler) {
-      this.storageBackoffHandler = backoffHandler;
-    }
-
-    @Override
-    public boolean shouldBackOffStorage() {
-      return storageBackoffHandler.delayMilliseconds() > 0;
-    }
-
-    @Override
-    public void requestBackoff(long backoffMillis) {
-      final boolean onlyExtend = true;      // Because we trust what the storage server says.
-      schedulePolicy.configureBackoffMillisOnBackoff(storageBackoffHandler, backoffMillis, onlyExtend);
-    }
-
-    @Override
-    public void informUpgradeRequiredResponse(GlobalSession session) {
-      schedulePolicy.onUpgradeRequired();
-    }
-
-    @Override
-    public void informUnauthorizedResponse(GlobalSession globalSession, URI oldClusterURL) {
-      schedulePolicy.onUnauthorized();
-    }
-
-    @Override
-    public void informMigrated(GlobalSession globalSession) {
-      // It's not possible to migrate a Firefox Account to another Account type
-      // yet. Yell loudly but otherwise ignore.
-      Logger.error(LOG_TAG,
-          "Firefox Account informMigrated called, but it's not yet possible to migrate.  " +
-          "Ignoring even though something is terribly wrong.");
-    }
-
-    @Override
-    public void handleStageCompleted(Stage currentState, GlobalSession globalSession) {
-    }
-
-    @Override
-    public void handleSuccess(GlobalSession globalSession) {
-      Logger.info(LOG_TAG, "Global session succeeded.");
-
-      // Get the number of clients, so we can schedule the sync interval accordingly.
-      try {
-        int otherClientsCount = globalSession.getClientsDelegate().getClientsCount();
-        Logger.debug(LOG_TAG, "" + otherClientsCount + " other client(s).");
-        this.schedulePolicy.onSuccessfulSync(otherClientsCount);
-      } finally {
-        // Continue with the usual success flow.
-        syncDelegate.handleSuccess();
-      }
-    }
-
-    @Override
-    public void handleError(GlobalSession globalSession, Exception e) {
-      Logger.warn(LOG_TAG, "Global session failed."); // Exception will be dumped by delegate below.
-      syncDelegate.handleError(e);
-      // TODO: should we reduce the periodic sync interval?
-    }
-
-    @Override
-    public void handleAborted(GlobalSession globalSession, String reason) {
-      Logger.warn(LOG_TAG, "Global session aborted: " + reason);
-      syncDelegate.handleError(null);
-      // TODO: should we reduce the periodic sync interval?
-    }
-  };
-
-  /**
-   * Return true if the provided {@link BackoffHandler} isn't reporting that we're in
-   * a backoff state, or the provided {@link Bundle} contains flags that indicate
-   * we should force a sync.
-   */
-  private boolean shouldPerformSync(final BackoffHandler backoffHandler, final String kind, final Bundle extras) {
-    final long delay = backoffHandler.delayMilliseconds();
-    if (delay <= 0) {
-      return true;
-    }
-
-    if (extras == null) {
-      return false;
-    }
-
-    final boolean forced = extras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF, false);
-    if (forced) {
-      Logger.info(LOG_TAG, "Forced sync (" + kind + "): overruling remaining backoff of " + delay + "ms.");
-    } else {
-      Logger.info(LOG_TAG, "Not syncing (" + kind + "): must wait another " + delay + "ms.");
-    }
-    return forced;
-  }
-
-  protected void syncWithAssertion(final String audience,
-                                   final String assertion,
-                                   final URI tokenServerEndpointURI,
-                                   final BackoffHandler tokenBackoffHandler,
-                                   final SharedPreferences sharedPrefs,
-                                   final KeyBundle syncKeyBundle,
-                                   final String clientState,
-                                   final SessionCallback callback,
-                                   final Bundle extras,
-                                   final AndroidFxAccount fxAccount) {
-    final TokenServerClientDelegate delegate = new TokenServerClientDelegate() {
-      private boolean didReceiveBackoff = false;
-
-      @Override
-      public String getUserAgent() {
-        return FxAccountConstants.USER_AGENT;
-      }
-
-      @Override
-      public void handleSuccess(final TokenServerToken token) {
-        FxAccountUtils.pii(LOG_TAG, "Got token! uid is " + token.uid + " and endpoint is " + token.endpoint + ".");
-        fxAccount.releaseSharedAccountStateLock();
-
-        if (!didReceiveBackoff) {
-          // We must be OK to touch this token server.
-          tokenBackoffHandler.setEarliestNextRequest(0L);
-        }
-
-        final URI storageServerURI;
-        try {
-          storageServerURI = new URI(token.endpoint);
-        } catch (URISyntaxException e) {
-          handleError(e);
-          return;
-        }
-        final String storageHostname = storageServerURI.getHost();
-
-        // We back off on a per-host basis. When we have an endpoint URI from a token, we
-        // can check on the backoff status for that host.
-        // If we're supposed to be backing off, we abort the not-yet-started session.
-        final BackoffHandler storageBackoffHandler = new PrefsBackoffHandler(sharedPrefs, "sync.storage");
-        callback.setBackoffHandler(storageBackoffHandler);
-
-        String lastStorageHost = sharedPrefs.getString(PREF_BACKOFF_STORAGE_HOST, null);
-        final boolean storageHostIsUnchanged = lastStorageHost != null &&
-                                               lastStorageHost.equalsIgnoreCase(storageHostname);
-        if (storageHostIsUnchanged) {
-          Logger.debug(LOG_TAG, "Storage host is unchanged.");
-          if (!shouldPerformSync(storageBackoffHandler, "storage", extras)) {
-            Logger.info(LOG_TAG, "Not syncing: storage server requested backoff.");
-            callback.handleAborted(null, "Storage backoff");
-            return;
-          }
-        } else {
-          Logger.debug(LOG_TAG, "Received new storage host.");
-        }
-
-        // Invalidate the previous backoff, because our storage host has changed,
-        // or we never had one at all, or we're OK to sync.
-        storageBackoffHandler.setEarliestNextRequest(0L);
-
-        GlobalSession globalSession = null;
-        try {
-          final ClientsDataDelegate clientsDataDelegate = new SharedPreferencesClientsDataDelegate(sharedPrefs, getContext());
-          if (FxAccountUtils.LOG_PERSONAL_INFORMATION) {
-            FxAccountUtils.pii(LOG_TAG, "Client device name is: '" + clientsDataDelegate.getClientName() + "'.");
-            FxAccountUtils.pii(LOG_TAG, "Client device data last modified: " + clientsDataDelegate.getLastModifiedTimestamp());
-          }
-
-          // We compute skew over time using SkewHandler. This yields an unchanging
-          // skew adjustment that the HawkAuthHeaderProvider uses to adjust its
-          // timestamps. Eventually we might want this to adapt within the scope of a
-          // global session.
-          final SkewHandler storageServerSkewHandler = SkewHandler.getSkewHandlerForHostname(storageHostname);
-          final long storageServerSkew = storageServerSkewHandler.getSkewInSeconds();
-          // We expect Sync to upload large sets of records. Calculating the
-          // payload verification hash for these record sets could be expensive,
-          // so we explicitly do not send payload verification hashes to the
-          // Sync storage endpoint.
-          final boolean includePayloadVerificationHash = false;
-          final AuthHeaderProvider authHeaderProvider = new HawkAuthHeaderProvider(token.id, token.key.getBytes("UTF-8"), includePayloadVerificationHash, storageServerSkew);
-
-          final Context context = getContext();
-          final SyncConfiguration syncConfig = new SyncConfiguration(token.uid, authHeaderProvider, sharedPrefs, syncKeyBundle);
-
-          Collection<String> knownStageNames = SyncConfiguration.validEngineNames();
-          syncConfig.stagesToSync = Utils.getStagesToSyncFromBundle(knownStageNames, extras);
-          syncConfig.setClusterURL(storageServerURI);
-
-          globalSession = new GlobalSession(syncConfig, callback, context, clientsDataDelegate);
-          globalSession.start();
-        } catch (Exception e) {
-          callback.handleError(globalSession, e);
-          return;
-        }
-      }
-
-      @Override
-      public void handleFailure(TokenServerException e) {
-        Logger.error(LOG_TAG, "Failed to get token.", e);
-        try {
-          // We should only get here *after* we're locked into the married state.
-          State state = fxAccount.getState();
-          if (state.getStateLabel() == StateLabel.Married) {
-            Married married = (Married) state;
-            fxAccount.setState(married.makeCohabitingState());
-          }
-        } finally {
-          fxAccount.releaseSharedAccountStateLock();
-        }
-        callback.handleError(null, e);
-      }
-
-      @Override
-      public void handleError(Exception e) {
-        Logger.error(LOG_TAG, "Failed to get token.", e);
-        fxAccount.releaseSharedAccountStateLock();
-        callback.handleError(null, e);
-      }
-
-      @Override
-      public void handleBackoff(int backoffSeconds) {
-        // This is the token server telling us to back off.
-        Logger.info(LOG_TAG, "Token server requesting backoff of " + backoffSeconds + "s. Backoff handler: " + tokenBackoffHandler);
-        didReceiveBackoff = true;
-
-        // If we've already stored a backoff, overrule it: we only use the server
-        // value for token server scheduling.
-        tokenBackoffHandler.setEarliestNextRequest(delay(backoffSeconds * 1000));
-      }
-
-      private long delay(long delay) {
-        return System.currentTimeMillis() + delay;
-      }
-    };
-
-    TokenServerClient tokenServerclient = new TokenServerClient(tokenServerEndpointURI, executor);
-    tokenServerclient.getTokenFromBrowserIDAssertion(assertion, true, clientState, delegate);
-  }
-
-  /**
-   * A trivial Sync implementation that does not cache client keys,
-   * certificates, or tokens.
-   *
-   * This should be replaced with a full {@link FxAccountAuthenticator}-based
-   * token implementation.
-   */
-  @Override
-  public void onPerformSync(final Account account, final Bundle extras, final String authority, ContentProviderClient provider, final SyncResult syncResult) {
-    Logger.setThreadLogTag(FxAccountConstants.GLOBAL_LOG_TAG);
-    Logger.resetLogging();
-
-    final Context context = getContext();
-    final AndroidFxAccount fxAccount = new AndroidFxAccount(context, account);
-
-    Logger.info(LOG_TAG, "Syncing FxAccount" +
-        " account named like " + Utils.obfuscateEmail(account.name) +
-        " for authority " + authority +
-        " with instance " + this + ".");
-
-    Logger.info(LOG_TAG, "Account last synced at: " + fxAccount.getLastSyncedTimestamp());
-
-    if (FxAccountUtils.LOG_PERSONAL_INFORMATION) {
-      fxAccount.dump();
-    }
-
-    FirefoxAccounts.logSyncOptions(extras);
-
-    if (this.lastSyncRealtimeMillis > 0L &&
-        (this.lastSyncRealtimeMillis + MINIMUM_SYNC_DELAY_MILLIS) > SystemClock.elapsedRealtime() &&
-            !extras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF, false)) {
-      Logger.info(LOG_TAG, "Not syncing FxAccount " + Utils.obfuscateEmail(account.name) +
-                           ": minimum interval not met.");
-      TelemetryWrapper.addToHistogram(TelemetryContract.SYNC_FAILED_BACKOFF, 1);
-      return;
-    }
-
-    // Pickle in a background thread to avoid strict mode warnings.
-    ThreadPool.run(new Runnable() {
-      @Override
-      public void run() {
-        try {
-          AccountPickler.pickle(fxAccount, FxAccountConstants.ACCOUNT_PICKLE_FILENAME);
-        } catch (Exception e) {
-          // Should never happen, but we really don't want to die in a background thread.
-          Logger.warn(LOG_TAG, "Got exception pickling current account details; ignoring.", e);
-        }
-      }
-    });
-
-    final BlockingQueue<Result> latch = new LinkedBlockingQueue<>(1);
-
-    Collection<String> knownStageNames = SyncConfiguration.validEngineNames();
-    Collection<String> stageNamesToSync = Utils.getStagesToSyncFromBundle(knownStageNames, extras);
-
-    final SyncDelegate syncDelegate = new SyncDelegate(latch, syncResult, fxAccount, stageNamesToSync);
-
-    try {
-      // This will be the same chunk of SharedPreferences that we pass through to GlobalSession/SyncConfiguration.
-      final SharedPreferences sharedPrefs = fxAccount.getSyncPrefs();
-
-      final BackoffHandler backgroundBackoffHandler = new PrefsBackoffHandler(sharedPrefs, "background");
-      final BackoffHandler rateLimitBackoffHandler = new PrefsBackoffHandler(sharedPrefs, "rate");
-
-      // If this sync was triggered by user action, this will be true.
-      final boolean isImmediate = (extras != null) &&
-                                  (extras.getBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD, false) ||
-                                   extras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF, false));
-
-      // If it's not an immediate sync, it must be either periodic or tickled.
-      // Check our background rate limiter.
-      if (!isImmediate) {
-        if (!shouldPerformSync(backgroundBackoffHandler, "background", extras)) {
-          syncDelegate.rejectSync();
-          return;
-        }
-      }
-
-      // Regardless, let's make sure we're not syncing too often.
-      if (!shouldPerformSync(rateLimitBackoffHandler, "rate", extras)) {
-        syncDelegate.postponeSync(rateLimitBackoffHandler.delayMilliseconds());
-        return;
-      }
-
-      final SchedulePolicy schedulePolicy = new FxAccountSchedulePolicy(context, fxAccount);
-
-      // Set a small scheduled 'backoff' to rate-limit the next sync,
-      // and extend the background delay even further into the future.
-      schedulePolicy.configureBackoffMillisBeforeSyncing(rateLimitBackoffHandler, backgroundBackoffHandler);
-
-      final String tokenServerEndpoint = fxAccount.getTokenServerURI();
-      final URI tokenServerEndpointURI = new URI(tokenServerEndpoint);
-      final String audience = FxAccountUtils.getAudienceForURL(tokenServerEndpoint);
-
-      try {
-        // The clock starts... now!
-        fxAccount.acquireSharedAccountStateLock(FxAccountSyncAdapter.LOG_TAG);
-      } catch (InterruptedException e) {
-        // OK, skip this sync.
-        syncDelegate.handleError(e);
-        return;
-      }
-
-      final State state;
-      try {
-        state = fxAccount.getState();
-      } catch (Exception e) {
-        fxAccount.releaseSharedAccountStateLock();
-        syncDelegate.handleError(e);
-        return;
-      }
-
-      TelemetryWrapper.addToHistogram(TelemetryContract.SYNC_STARTED, 1);
-
-      final FxAccountLoginStateMachine stateMachine = new FxAccountLoginStateMachine();
-      stateMachine.advance(state, StateLabel.Married, new FxADefaultLoginStateMachineDelegate(context, fxAccount) {
-        @Override
-        public void handleNotMarried(State notMarried) {
-          Logger.info(LOG_TAG, "handleNotMarried: in " + notMarried.getStateLabel());
-          schedulePolicy.onHandleFinal(notMarried.getNeededAction());
-          syncDelegate.handleCannotSync(notMarried);
-        }
-
-        private boolean shouldRequestToken(final BackoffHandler tokenBackoffHandler, final Bundle extras) {
-          return shouldPerformSync(tokenBackoffHandler, "token", extras);
-        }
-
-        @Override
-        public void handleMarried(Married married) {
-          schedulePolicy.onHandleFinal(married.getNeededAction());
-          Logger.info(LOG_TAG, "handleMarried: in " + married.getStateLabel());
-
-          try {
-            final String assertion = married.generateAssertion(audience, JSONWebTokenUtils.DEFAULT_ASSERTION_ISSUER);
-
-            /*
-             * At this point we're in the correct state to sync, and we're ready to fetch
-             * a token and do some work.
-             *
-             * But first we need to do two things:
-             * 1. Check to see whether we're in a backoff situation for the token server.
-             *    If we are, but we're not forcing a sync, then we go no further.
-             * 2. Clear an existing backoff (if we're syncing it doesn't matter, and if
-             *    we're forcing we'll get a new backoff if things are still bad).
-             *
-             * Note that we don't check the storage backoff before the token dance: the token
-             * server tells us which server we're syncing to!
-             *
-             * That logic lives in the TokenServerClientDelegate elsewhere in this file.
-             */
-
-            // Strictly speaking this backoff check could be done prior to walking through
-            // the login state machine, allowing us to short-circuit sooner.
-            // We don't expect many token server backoffs, and most users will be sitting
-            // in the Married state, so instead we simply do this here, once.
-            final BackoffHandler tokenBackoffHandler = new PrefsBackoffHandler(sharedPrefs, "token");
-            if (!shouldRequestToken(tokenBackoffHandler, extras)) {
-              Logger.info(LOG_TAG, "Not syncing (token server).");
-              syncDelegate.postponeSync(tokenBackoffHandler.delayMilliseconds());
-              return;
-            }
-
-            final SessionCallback sessionCallback = new SessionCallback(syncDelegate, schedulePolicy);
-            final KeyBundle syncKeyBundle = married.getSyncKeyBundle();
-            final String clientState = married.getClientState();
-            syncWithAssertion(audience, assertion, tokenServerEndpointURI, tokenBackoffHandler, sharedPrefs, syncKeyBundle, clientState, sessionCallback, extras, fxAccount);
-
-            // Register the device if necessary (asynchronous, in another thread)
-            if (fxAccount.getDeviceRegistrationVersion() != FxAccountDeviceRegistrator.DEVICE_REGISTRATION_VERSION
-                || TextUtils.isEmpty(fxAccount.getDeviceId())) {
-              FxAccountDeviceRegistrator.register(context);
-            }
-
-            // Force fetch the profile avatar information. (asynchronous, in another thread)
-            Logger.info(LOG_TAG, "Fetching profile avatar information.");
-            fxAccount.fetchProfileJSON();
-          } catch (Exception e) {
-            syncDelegate.handleError(e);
-            return;
-          }
-        }
-      });
-
-      latch.take();
-    } catch (Exception e) {
-      Logger.error(LOG_TAG, "Got error syncing.", e);
-      syncDelegate.handleError(e);
-    } finally {
-      fxAccount.releaseSharedAccountStateLock();
-    }
-
-    Logger.info(LOG_TAG, "Syncing done.");
-    lastSyncRealtimeMillis = SystemClock.elapsedRealtime();
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/sync/FxAccountSyncDelegate.java b/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/sync/FxAccountSyncDelegate.java
deleted file mode 100644
index 71148f6..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/sync/FxAccountSyncDelegate.java
+++ /dev/null
@@ -1,110 +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.fxa.sync;
-
-import java.util.concurrent.BlockingQueue;
-
-import org.mozilla.gecko.fxa.login.State;
-
-import android.content.SyncResult;
-
-public class FxAccountSyncDelegate {
-  public enum Result {
-    Success,
-    Error,
-    Postponed,
-    Rejected,
-  }
-
-  protected final BlockingQueue<Result> latch;
-  protected final SyncResult syncResult;
-
-  public FxAccountSyncDelegate(BlockingQueue<Result> latch, SyncResult syncResult) {
-    if (latch == null) {
-      throw new IllegalArgumentException("latch must not be null");
-    }
-    if (syncResult == null) {
-      throw new IllegalArgumentException("syncResult must not be null");
-    }
-    this.latch = latch;
-    this.syncResult = syncResult;
-  }
-
-  /**
-   * No error!  Say that we made progress.
-   */
-  protected void setSyncResultSuccess() {
-    syncResult.stats.numUpdates += 1;
-  }
-
-  /**
-   * Soft error. Say that we made progress, so that Android will sync us again
-   * after exponential backoff.
-   */
-  protected void setSyncResultSoftError() {
-    syncResult.stats.numUpdates += 1;
-    syncResult.stats.numIoExceptions += 1;
-  }
-
-  /**
-   * Hard error. We don't want Android to sync us again, even if we make
-   * progress, until the user intervenes.
-   */
-  protected void setSyncResultHardError() {
-    syncResult.stats.numAuthExceptions += 1;
-  }
-
-  public void handleSuccess() {
-    setSyncResultSuccess();
-    latch.offer(Result.Success);
-  }
-
-  public void handleError(Exception e) {
-    setSyncResultSoftError();
-    latch.offer(Result.Error);
-  }
-
-  /**
-   * When the login machine terminates, we might not be in the
-   * <code>Married</code> state, and therefore we can't sync. This method
-   * messages as much to the user.
-   * <p>
-   * To avoid stopping us syncing altogether, we set a soft error rather than
-   * a hard error. In future, we would like to set a hard error if we are in,
-   * for example, the <code>Separated</code> state, and then have some user
-   * initiated activity mark the Android account as ready to sync again. This
-   * is tricky, though, so we play it safe for now.
-   *
-   * @param finalState
-   *          that login machine ended in.
-   */
-  public void handleCannotSync(State finalState) {
-    setSyncResultSoftError();
-    latch.offer(Result.Error);
-  }
-
-  public void postponeSync(long millis) {
-    if (millis > 0) {
-      // delayUntil is broken: https://code.google.com/p/android/issues/detail?id=65669
-      // So we don't bother doing this. Instead, we rely on the periodic sync
-      // we schedule, and the backoff handler for the rest.
-      /*
-      Logger.warn(LOG_TAG, "Postponing sync by " + millis + "ms.");
-      syncResult.delayUntil = millis / 1000;
-       */
-    }
-    setSyncResultSoftError();
-    latch.offer(Result.Postponed);
-  }
-
-  /**
-   * Simply don't sync, without setting any error flags.
-   * This is the appropriate behavior when a routine backoff has not yet
-   * been met.
-   */
-  public void rejectSync() {
-    latch.offer(Result.Rejected);
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/sync/FxAccountSyncService.java b/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/sync/FxAccountSyncService.java
deleted file mode 100644
index 59c06ca..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/sync/FxAccountSyncService.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.fxa.sync;
-
-import android.app.Service;
-import android.content.Intent;
-import android.os.IBinder;
-
-public class FxAccountSyncService extends Service {
-  private static final Object syncAdapterLock = new Object();
-  private static FxAccountSyncAdapter syncAdapter;
-
-  @Override
-  public void onCreate() {
-    synchronized (syncAdapterLock) {
-      if (syncAdapter == null) {
-        syncAdapter = new FxAccountSyncAdapter(getApplicationContext(), true);
-      }
-    }
-  }
-
-  @Override
-  public IBinder onBind(Intent intent) {
-    return syncAdapter.getSyncAdapterBinder();
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/sync/FxAccountSyncStatusHelper.java b/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/sync/FxAccountSyncStatusHelper.java
deleted file mode 100644
index ca64d4f..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/sync/FxAccountSyncStatusHelper.java
+++ /dev/null
@@ -1,113 +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.fxa.sync;
-
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.WeakHashMap;
-
-import org.mozilla.gecko.fxa.SyncStatusListener;
-import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
-import org.mozilla.gecko.util.ThreadUtils;
-
-import android.content.ContentResolver;
-import android.content.SyncStatusObserver;
-
-/**
- * Abstract away some details of Android's SyncStatusObserver.
- * <p>
- * Provides a simplified sync started/sync finished delegate.
- */
-public class FxAccountSyncStatusHelper implements SyncStatusObserver {
-  @SuppressWarnings("unused")
-  private static final String LOG_TAG = FxAccountSyncStatusHelper.class.getSimpleName();
-
-  protected static FxAccountSyncStatusHelper sInstance;
-
-  public synchronized static FxAccountSyncStatusHelper getInstance() {
-    if (sInstance == null) {
-      sInstance = new FxAccountSyncStatusHelper();
-    }
-    return sInstance;
-  }
-
-  // Used to unregister this as a listener.
-  protected Object handle;
-
-  // Maps delegates to whether their underlying Android account was syncing the
-  // last time we observed a status change.
-  protected Map<SyncStatusListener, Boolean> delegates = new WeakHashMap<SyncStatusListener, Boolean>();
-
-  @Override
-  public synchronized void onStatusChanged(int which) {
-    for (Entry<SyncStatusListener, Boolean> entry : delegates.entrySet()) {
-      final SyncStatusListener delegate = entry.getKey();
-      final AndroidFxAccount fxAccount = new AndroidFxAccount(delegate.getContext(), delegate.getAccount());
-      final boolean active = fxAccount.isCurrentlySyncing();
-      // Remember for later.
-      boolean wasActiveLastTime = entry.getValue();
-      // It's okay to update the value of an entry while iterating the entrySet.
-      entry.setValue(active);
-
-      if (active && !wasActiveLastTime) {
-        // We've started a sync.
-        ThreadUtils.postToUiThread(new Runnable() {
-          @Override
-          public void run() {
-            delegate.onSyncStarted();
-          }
-        });
-      }
-
-      if (!active && wasActiveLastTime) {
-        // We've finished a sync.
-        ThreadUtils.postToUiThread(new Runnable() {
-          @Override
-          public void run() {
-            delegate.onSyncFinished();
-          }
-        });
-      }
-    }
-  }
-
-  protected void addListener() {
-    final int mask = ContentResolver.SYNC_OBSERVER_TYPE_ACTIVE;
-    if (this.handle != null) {
-      throw new IllegalStateException("Already registered this as an observer?");
-    }
-    this.handle = ContentResolver.addStatusChangeListener(mask, this);
-  }
-
-  protected void removeListener() {
-    Object handle = this.handle;
-    this.handle = null;
-    if (handle != null) {
-      ContentResolver.removeStatusChangeListener(handle);
-    }
-  }
-
-  public synchronized void startObserving(SyncStatusListener delegate) {
-    if (delegate == null) {
-      throw new IllegalArgumentException("delegate must not be null");
-    }
-    if (delegates.containsKey(delegate)) {
-      return;
-    }
-    // If we are the first delegate to the party, start listening.
-    if (delegates.isEmpty()) {
-      addListener();
-    }
-    delegates.put(delegate, Boolean.FALSE);
-  }
-
-  public synchronized void stopObserving(SyncStatusListener delegate) {
-    delegates.remove(delegate);
-    // If we are the last delegate leaving the party, stop listening.
-    if (delegates.isEmpty()) {
-      removeListener();
-    }
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/sync/SchedulePolicy.java b/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/sync/SchedulePolicy.java
deleted file mode 100644
index 809191f..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/sync/SchedulePolicy.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.fxa.sync;
-
-import org.mozilla.gecko.fxa.login.State.Action;
-import org.mozilla.gecko.sync.BackoffHandler;
-
-public interface SchedulePolicy {
-  /**
-   * Call this with the number of other clients syncing to the account.
-   */
-  public abstract void onSuccessfulSync(int otherClientsCount);
-  public abstract void onHandleFinal(Action needed);
-  public abstract void onUpgradeRequired();
-  public abstract void onUnauthorized();
-
-  /**
-   * Before a sync we typically wish to adjust our backoff policy. This cleans
-   * the slate prior to encountering a new backoff, and also functions as a rate
-   * limiter.
-   *
-   * The {@link SchedulePolicy} acts as a controller for the {@link BackoffHandler}.
-   * As a result of calling these two methods, the {@link BackoffHandler} will be
-   * mutated, and additional side-effects (such as scheduling periodic syncs) can
-   * occur.
-   *
-   * @param rateHandler the backoff handler to configure for basic rate limiting.
-   * @param backgroundHandler the backoff handler to configure for background operations.
-   */
-  public abstract void configureBackoffMillisBeforeSyncing(BackoffHandler rateHandler, BackoffHandler backgroundHandler);
-
-  /**
-   * We received an explicit backoff instruction, typically from a server.
-   *
-   * @param onlyExtend
-   *          if <code>true</code>, the backoff handler will be asked to update
-   *          its backoff only if the provided value is greater than the current
-   *          backoff.
-   */
-  public abstract void configureBackoffMillisOnBackoff(BackoffHandler backoffHandler, long backoffMillis, boolean onlyExtend);
-}
\ No newline at end of file
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/push/RegisterUserAgentResponse.java b/mobile/android/services/src/main/java/org/mozilla/gecko/push/RegisterUserAgentResponse.java
deleted file mode 100644
index 3bbb7e8..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/push/RegisterUserAgentResponse.java
+++ /dev/null
@@ -1,19 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
- * 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.push;
-
-/**
- * Thin container for a register User-Agent response.
- */
-public class RegisterUserAgentResponse {
-    public final String uaid;
-    public final String secret;
-
-    public RegisterUserAgentResponse(String uaid, String secret) {
-        this.uaid = uaid;
-        this.secret = secret;
-    }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/push/SubscribeChannelResponse.java b/mobile/android/services/src/main/java/org/mozilla/gecko/push/SubscribeChannelResponse.java
deleted file mode 100644
index 009a7f8..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/push/SubscribeChannelResponse.java
+++ /dev/null
@@ -1,19 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
- * 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.push;
-
-/**
- * Thin container for a subscribe channel response.
- */
-public class SubscribeChannelResponse {
-    public final String channelID;
-    public final String endpoint;
-
-    public SubscribeChannelResponse(String channelID, String endpoint) {
-        this.channelID = channelID;
-        this.endpoint = endpoint;
-    }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/push/autopush/AutopushClient.java b/mobile/android/services/src/main/java/org/mozilla/gecko/push/autopush/AutopushClient.java
deleted file mode 100644
index 8edd92f..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/push/autopush/AutopushClient.java
+++ /dev/null
@@ -1,410 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * 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.push.autopush;
-
-import android.text.TextUtils;
-
-import org.mozilla.gecko.Locales;
-import org.mozilla.gecko.fxa.FxAccountConstants;
-import org.mozilla.gecko.push.RegisterUserAgentResponse;
-import org.mozilla.gecko.push.SubscribeChannelResponse;
-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.BearerAuthHeaderProvider;
-import org.mozilla.gecko.sync.net.Resource;
-import org.mozilla.gecko.sync.net.SyncResponse;
-import org.mozilla.gecko.sync.net.SyncStorageResponse;
-
-import java.io.IOException;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.security.GeneralSecurityException;
-import java.util.Locale;
-import java.util.concurrent.Executor;
-
-import ch.boye.httpclientandroidlib.HttpEntity;
-import ch.boye.httpclientandroidlib.HttpHeaders;
-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;
-
-
-/**
- * Interact with the autopush endpoint HTTP API.
- * <p/>
- * The API is a Mozilla-proprietary interface, and not even specified to Mozilla's usual ad-hoc standards.
- * This client is written against a work-in-progress, un-deployed upstream commit.
- */
-public class AutopushClient {
-    protected static final String LOG_TAG = AutopushClient.class.getSimpleName();
-
-    protected static final String ACCEPT_HEADER = "application/json;charset=utf-8";
-    protected static final String TYPE = "gcm";
-
-    protected static final String JSON_KEY_UAID = "uaid";
-    protected static final String JSON_KEY_SECRET = "secret";
-    protected static final String JSON_KEY_CHANNEL_ID = "channelID";
-    protected static final String JSON_KEY_ENDPOINT = "endpoint";
-
-    protected static final String[] REGISTER_USER_AGENT_RESPONSE_REQUIRED_STRING_FIELDS = new String[] { JSON_KEY_UAID, JSON_KEY_SECRET, JSON_KEY_CHANNEL_ID, JSON_KEY_ENDPOINT };
-    protected static final String[] REGISTER_CHANNEL_RESPONSE_REQUIRED_STRING_FIELDS = new String[] { JSON_KEY_CHANNEL_ID, JSON_KEY_ENDPOINT };
-
-    public static final String JSON_KEY_CODE = "code";
-    public static final String JSON_KEY_ERRNO = "errno";
-    public static final String JSON_KEY_ERROR = "error";
-    public static final String JSON_KEY_MESSAGE = "message";
-
-    protected static final String[] requiredErrorStringFields = { JSON_KEY_ERROR, JSON_KEY_MESSAGE };
-    protected static final String[] requiredErrorLongFields = { JSON_KEY_CODE, JSON_KEY_ERRNO };
-
-    /**
-     * The server's URI.
-     * <p>
-     * We assume throughout that this ends with a trailing slash (and guarantee as
-     * much in the constructor).
-     */
-    public final String serverURI;
-
-    protected final Executor executor;
-
-    public AutopushClient(String serverURI, Executor executor) {
-        if (serverURI == null) {
-            throw new IllegalArgumentException("Must provide a server URI.");
-        }
-        if (executor == null) {
-            throw new IllegalArgumentException("Must provide a non-null executor.");
-        }
-        this.serverURI = serverURI.endsWith("/") ? serverURI : serverURI + "/";
-        if (!this.serverURI.endsWith("/")) {
-            throw new IllegalArgumentException("Constructed serverURI must end with a trailing slash: " + this.serverURI);
-        }
-        this.executor = executor;
-    }
-
-    /**
-     * A legal autopush server URL includes a sender ID embedded into it.  Extract it.
-     *
-     * @return a non-null non-empty sender ID.
-     * @throws AutopushClientException on failure.
-     */
-    public String getSenderIDFromServerURI() throws AutopushClientException {
-        // Turn "https://updates-autopush-dev.stage.mozaws.net/v1/gcm/829133274407/" into "829133274407".
-        final String[] parts = serverURI.split("/", -1); // The -1 keeps the trailing empty part.
-        if (parts.length < 3) {
-            throw new AutopushClientException("Could not get sender ID from autopush server URI: " + serverURI);
-        }
-        if (!TextUtils.isEmpty(parts[parts.length - 1])) {
-            // We guarantee a trailing slash, so we should always have an empty part at the tail.
-            throw new AutopushClientException("Could not get sender ID from autopush server URI: " + serverURI);
-        }
-        if (!TextUtils.equals("gcm", parts[parts.length - 3])) {
-            // We should always have /gcm/senderID/.
-            throw new AutopushClientException("Could not get sender ID from autopush server URI: " + serverURI);
-        }
-        final String senderID = parts[parts.length - 2];
-        if (TextUtils.isEmpty(senderID)) {
-            // Something is horribly wrong -- we have /gcm//.  Abort.
-            throw new AutopushClientException("Could not get sender ID from autopush server URI: " + serverURI);
-        }
-        return senderID;
-    }
-
-    /**
-     * Process a typed value extracted from a successful response (in an
-     * endpoint-dependent way).
-     */
-    public interface RequestDelegate<T> {
-        void handleError(Exception e);
-        void handleFailure(AutopushClientException e);
-        void handleSuccess(T result);
-    }
-
-    /**
-     * Intepret a response from the autopush server.
-     * <p>
-     * Throw an appropriate exception on errors; otherwise, return the response's
-     * status code.
-     *
-     * @return response's HTTP status code.
-     * @throws AutopushClientException
-     */
-    public static int validateResponse(HttpResponse response) throws AutopushClientException {
-        final int status = response.getStatusLine().getStatusCode();
-        if (200 <= status && status <= 299) {
-            return status;
-        }
-        long code;
-        long errno;
-        String error;
-        String message;
-        String info;
-        ExtendedJSONObject body;
-        try {
-            body = new SyncStorageResponse(response).jsonObjectBody();
-            // TODO: The service doesn't do the right thing yet :(
-            // body.throwIfFieldsMissingOrMisTyped(requiredErrorStringFields, String.class);
-            body.throwIfFieldsMissingOrMisTyped(requiredErrorLongFields, Long.class);
-            // Would throw above if missing; the -1 defaults quiet NPE warnings.
-            code = body.getLong(JSON_KEY_CODE, -1);
-            errno = body.getLong(JSON_KEY_ERRNO, -1);
-            error = body.getString(JSON_KEY_ERROR);
-            message = body.getString(JSON_KEY_MESSAGE);
-        } catch (Exception e) {
-            throw new AutopushClientException.AutopushClientMalformedResponseException(response);
-        }
-        throw new AutopushClientException.AutopushClientRemoteException(response, code, errno, error, message, body);
-    }
-
-    protected <T> void invokeHandleError(final RequestDelegate<T> delegate, final Exception e) {
-        executor.execute(new Runnable() {
-            @Override
-            public void run() {
-                delegate.handleError(e);
-            }
-        });
-    }
-
-    protected <T> void post(BaseResource resource, final ExtendedJSONObject requestBody, final RequestDelegate<T> delegate) {
-        try {
-            if (requestBody == null) {
-                resource.post((HttpEntity) null);
-            } else {
-                resource.post(requestBody);
-            }
-        } catch (Exception e) {
-            invokeHandleError(delegate, e);
-            return;
-        }
-    }
-
-    /**
-     * Translate resource callbacks into request callbacks invoked on the provided
-     * executor.
-     * <p>
-     * Override <code>handleSuccess</code> to parse the body of the resource
-     * request and call the request callback. <code>handleSuccess</code> is
-     * invoked via the executor, so you don't need to delegate further.
-     */
-    protected abstract class ResourceDelegate<T> extends BaseResourceDelegate {
-        protected abstract void handleSuccess(final int status, HttpResponse response, final ExtendedJSONObject body);
-
-        protected final String secret;
-        protected final RequestDelegate<T> delegate;
-
-        /**
-         * Create a delegate for an un-authenticated resource.
-         */
-        public ResourceDelegate(final Resource resource, final String secret, final RequestDelegate<T> delegate) {
-            super(resource);
-            this.delegate = delegate;
-            this.secret = secret;
-        }
-
-        @Override
-        public AuthHeaderProvider getAuthHeaderProvider() {
-            if (secret != null) {
-                return new BearerAuthHeaderProvider(secret);
-            }
-            return null;
-        }
-
-        @Override
-        public String getUserAgent() {
-            return FxAccountConstants.USER_AGENT;
-        }
-
-        @Override
-        public void handleHttpResponse(HttpResponse response) {
-            try {
-                final int status = validateResponse(response);
-                invokeHandleSuccess(status, response);
-            } catch (AutopushClientException e) {
-                invokeHandleFailure(e);
-            }
-        }
-
-        protected void invokeHandleFailure(final AutopushClientException e) {
-            executor.execute(new Runnable() {
-                @Override
-                public void run() {
-                    delegate.handleFailure(e);
-                }
-            });
-        }
-
-        protected void invokeHandleSuccess(final int status, final HttpResponse response) {
-            executor.execute(new Runnable() {
-                @Override
-                public void run() {
-                    try {
-                        ExtendedJSONObject body = new SyncResponse(response).jsonObjectBody();
-                        ResourceDelegate.this.handleSuccess(status, response, body);
-                    } catch (Exception e) {
-                        delegate.handleError(e);
-                    }
-                }
-            });
-        }
-
-        @Override
-        public void handleHttpProtocolException(final ClientProtocolException e) {
-            invokeHandleError(delegate, e);
-        }
-
-        @Override
-        public void handleHttpIOException(IOException e) {
-            invokeHandleError(delegate, e);
-        }
-
-        @Override
-        public void handleTransportException(GeneralSecurityException e) {
-            invokeHandleError(delegate, e);
-        }
-
-        @Override
-        public void addHeaders(HttpRequestBase request, DefaultHttpClient client) {
-            super.addHeaders(request, client);
-
-            // The basics.
-            final Locale locale = Locale.getDefault();
-            request.addHeader(HttpHeaders.ACCEPT_LANGUAGE, Locales.getLanguageTag(locale));
-            request.addHeader(HttpHeaders.ACCEPT, ACCEPT_HEADER);
-        }
-    }
-
-    public void registerUserAgent(final String token, RequestDelegate<RegisterUserAgentResponse> delegate) {
-        BaseResource resource;
-        try {
-            resource = new BaseResource(new URI(serverURI + "registration"));
-        } catch (URISyntaxException e) {
-            invokeHandleError(delegate, e);
-            return;
-        }
-
-        resource.delegate = new ResourceDelegate<RegisterUserAgentResponse>(resource, null, delegate) {
-            @Override
-            public void handleSuccess(int status, HttpResponse response, ExtendedJSONObject body) {
-                try {
-                    body.throwIfFieldsMissingOrMisTyped(REGISTER_USER_AGENT_RESPONSE_REQUIRED_STRING_FIELDS, String.class);
-                    final String uaid = body.getString(JSON_KEY_UAID);
-                    final String secret = body.getString(JSON_KEY_SECRET);
-                    delegate.handleSuccess(new RegisterUserAgentResponse(uaid, secret));
-                    return;
-                } catch (Exception e) {
-                    delegate.handleError(e);
-                    return;
-                }
-            }
-        };
-
-        final ExtendedJSONObject body = new ExtendedJSONObject();
-        body.put("type", TYPE);
-        body.put("token", token);
-
-        resource.post(body);
-    }
-
-    public void reregisterUserAgent(final String uaid, final String secret, final String token, RequestDelegate<Void> delegate) {
-        final BaseResource resource;
-        try {
-            resource = new BaseResource(new URI(serverURI + "registration/" + uaid));
-        } catch (Exception e) {
-            invokeHandleError(delegate, e);
-            return;
-        }
-
-        resource.delegate = new ResourceDelegate<Void>(resource, secret, delegate) {
-            @Override
-            public void handleSuccess(int status, HttpResponse response, ExtendedJSONObject body) {
-                try {
-                    delegate.handleSuccess(null);
-                    return;
-                } catch (Exception e) {
-                    delegate.handleError(e);
-                    return;
-                }
-            }
-        };
-
-        final ExtendedJSONObject body = new ExtendedJSONObject();
-        body.put("type", TYPE);
-        body.put("token", token);
-
-        resource.put(body);
-    }
-
-
-    public void subscribeChannel(final String uaid, final String secret, final String appServerKey, RequestDelegate<SubscribeChannelResponse> delegate) {
-        final BaseResource resource;
-        try {
-            resource = new BaseResource(new URI(serverURI + "registration/" + uaid + "/subscription"));
-        } catch (Exception e) {
-            invokeHandleError(delegate, e);
-            return;
-        }
-
-        resource.delegate = new ResourceDelegate<SubscribeChannelResponse>(resource, secret, delegate) {
-            @Override
-            public void handleSuccess(int status, HttpResponse response, ExtendedJSONObject body) {
-                try {
-                    body.throwIfFieldsMissingOrMisTyped(REGISTER_CHANNEL_RESPONSE_REQUIRED_STRING_FIELDS, String.class);
-                    final String channelID = body.getString(JSON_KEY_CHANNEL_ID);
-                    final String endpoint = body.getString(JSON_KEY_ENDPOINT);
-                    delegate.handleSuccess(new SubscribeChannelResponse(channelID, endpoint));
-                    return;
-                } catch (Exception e) {
-                    delegate.handleError(e);
-                    return;
-                }
-            }
-        };
-
-        final ExtendedJSONObject body = new ExtendedJSONObject();
-        body.put("key", appServerKey);
-        resource.post(body);
-    }
-
-    public void unsubscribeChannel(final String uaid, final String secret, final String channelID, RequestDelegate<Void> delegate) {
-        final BaseResource resource;
-        try {
-            resource = new BaseResource(new URI(serverURI + "registration/" + uaid + "/subscription/" + channelID));
-        } catch (Exception e) {
-            invokeHandleError(delegate, e);
-            return;
-        }
-
-        resource.delegate = new ResourceDelegate<Void>(resource, secret, delegate) {
-            @Override
-            public void handleSuccess(int status, HttpResponse response, ExtendedJSONObject body) {
-                delegate.handleSuccess(null);
-            }
-        };
-
-        resource.delete();
-    }
-
-    public void unregisterUserAgent(final String uaid, final String secret, RequestDelegate<Void> delegate) {
-        final BaseResource resource;
-        try {
-            resource = new BaseResource(new URI(serverURI + "registration/" + uaid));
-        } catch (Exception e) {
-            invokeHandleError(delegate, e);
-            return;
-        }
-
-        resource.delegate = new ResourceDelegate<Void>(resource, secret, delegate) {
-            @Override
-            public void handleSuccess(int status, HttpResponse response, ExtendedJSONObject body) {
-                delegate.handleSuccess(null);
-            }
-        };
-
-        resource.delete();
-    }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/push/autopush/AutopushClientException.java b/mobile/android/services/src/main/java/org/mozilla/gecko/push/autopush/AutopushClientException.java
deleted file mode 100644
index e3fda7a..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/push/autopush/AutopushClientException.java
+++ /dev/null
@@ -1,81 +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.push.autopush;
-
-import ch.boye.httpclientandroidlib.HttpResponse;
-import ch.boye.httpclientandroidlib.HttpStatus;
-import org.mozilla.gecko.sync.ExtendedJSONObject;
-import org.mozilla.gecko.sync.HTTPFailureException;
-import org.mozilla.gecko.sync.net.SyncStorageResponse;
-
-public class AutopushClientException extends Exception {
-    private static final long serialVersionUID = 7953459541558266500L;
-
-    public AutopushClientException(String detailMessage) {
-        super(detailMessage);
-    }
-
-    public AutopushClientException(Exception e) {
-        super(e);
-    }
-
-    public boolean isTransientError() {
-        return false;
-    }
-
-    public static class AutopushClientRemoteException extends AutopushClientException {
-        private static final long serialVersionUID = 2209313149952001000L;
-
-        public final HttpResponse response;
-        public final long httpStatusCode;
-        public final long apiErrorNumber;
-        public final String error;
-        public final String message;
-        public final ExtendedJSONObject body;
-
-        public AutopushClientRemoteException(HttpResponse response, long httpStatusCode, long apiErrorNumber, String error, String message, ExtendedJSONObject body) {
-            super(new HTTPFailureException(new SyncStorageResponse(response)));
-            if (body == null) {
-                throw new IllegalArgumentException("body must not be null");
-            }
-            this.response = response;
-            this.httpStatusCode = httpStatusCode;
-            this.apiErrorNumber = apiErrorNumber;
-            this.error = error;
-            this.message = message;
-            this.body = body;
-        }
-
-        @Override
-        public String toString() {
-            return "<AutopushClientRemoteException " + this.httpStatusCode + " [" + this.apiErrorNumber + "]: " + this.message + ">";
-        }
-
-        public boolean isInvalidAuthentication() {
-            return httpStatusCode == HttpStatus.SC_UNAUTHORIZED;
-        }
-
-        public boolean isNotFound() {
-            return httpStatusCode == HttpStatus.SC_NOT_FOUND;
-        }
-
-        public boolean isGone() {
-            return httpStatusCode == HttpStatus.SC_GONE;
-        }
-
-        @Override
-        public boolean isTransientError() {
-            return httpStatusCode >= 500;
-        }
-    }
-
-    public static class AutopushClientMalformedResponseException extends AutopushClientRemoteException {
-        private static final long serialVersionUID = 2209313149952001909L;
-
-        public AutopushClientMalformedResponseException(HttpResponse response) {
-            super(response, 0, 999, "Response malformed", "Response malformed", new ExtendedJSONObject());
-        }
-    }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/AlreadySyncingException.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/AlreadySyncingException.java
deleted file mode 100644
index 75eb5ad..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/AlreadySyncingException.java
+++ /dev/null
@@ -1,22 +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.sync;
-
-import org.mozilla.gecko.sync.stage.GlobalSyncStage.Stage;
-
-import android.content.SyncResult;
-
-public class AlreadySyncingException extends SyncException {
-  Stage inState;
-  public AlreadySyncingException(Stage currentState) {
-    inState = currentState;
-  }
-
-  private static final long serialVersionUID = -5647548462539009893L;
-
-  @Override
-  public void updateStats(GlobalSession globalSession, SyncResult syncResult) {
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/BackoffHandler.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/BackoffHandler.java
deleted file mode 100644
index abb8806..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/BackoffHandler.java
+++ /dev/null
@@ -1,34 +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.sync;
-
-
-public interface BackoffHandler {
-  public long getEarliestNextRequest();
-
-  /**
-   * Provide a timestamp in millis before which we shouldn't sync again.
-   * Overrides any existing value.
-   *
-   * @param next
-   *          a timestamp in milliseconds.
-   */
-  public void setEarliestNextRequest(long next);
-
-  /**
-   * Provide a timestamp in millis before which we shouldn't sync again. Only
-   * change our persisted value if it's later than the existing time.
-   *
-   * @param next
-   *          a timestamp in milliseconds.
-   */
-  public void extendEarliestNextRequest(long next);
-
-  /**
-   * Return the number of milliseconds until we're allowed to sync again,
-   * or 0 if now is fine.
-   */
-  public long delayMilliseconds();
-}
\ No newline at end of file
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/BadRequiredFieldJSONException.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/BadRequiredFieldJSONException.java
deleted file mode 100644
index 3db9365..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/BadRequiredFieldJSONException.java
+++ /dev/null
@@ -1,5 +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.sync;
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/CollectionKeys.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/CollectionKeys.java
deleted file mode 100644
index 1fd363b..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/CollectionKeys.java
+++ /dev/null
@@ -1,199 +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.sync;
-
-import java.io.IOException;
-import java.io.UnsupportedEncodingException;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map.Entry;
-import java.util.Set;
-
-import org.json.simple.JSONArray;
-import org.mozilla.apache.commons.codec.binary.Base64;
-import org.mozilla.gecko.sync.crypto.CryptoException;
-import org.mozilla.gecko.sync.crypto.KeyBundle;
-
-public class CollectionKeys {
-  private KeyBundle                  defaultKeyBundle     = null;
-  private final HashMap<String, KeyBundle> collectionKeyBundles = new HashMap<String, KeyBundle>();
-
-  /**
-   * Randomly generate a basic CollectionKeys object.
-   * @throws CryptoException
-   */
-  public static CollectionKeys generateCollectionKeys() throws CryptoException {
-    CollectionKeys ck = new CollectionKeys();
-    ck.clear();
-    ck.defaultKeyBundle = KeyBundle.withRandomKeys();
-    // TODO: eventually we would like to keep per-collection keys, just generate
-    // new ones as appropriate.
-    return ck;
-  }
-
-  public KeyBundle defaultKeyBundle() throws NoCollectionKeysSetException {
-    if (this.defaultKeyBundle == null) {
-      throw new NoCollectionKeysSetException();
-    }
-    return this.defaultKeyBundle;
-  }
-
-  public boolean keyBundleForCollectionIsNotDefault(String collection) {
-    return collectionKeyBundles.containsKey(collection);
-  }
-
-  public KeyBundle keyBundleForCollection(String collection)
-      throws NoCollectionKeysSetException {
-    if (this.defaultKeyBundle == null) {
-      throw new NoCollectionKeysSetException();
-    }
-    if (keyBundleForCollectionIsNotDefault(collection)) {
-      return collectionKeyBundles.get(collection);
-    }
-    return this.defaultKeyBundle;
-  }
-
-  /**
-   * Take a pair of values in a JSON array, handing them off to KeyBundle to
-   * produce a usable keypair.
-   */
-  private static KeyBundle arrayToKeyBundle(JSONArray array) throws UnsupportedEncodingException {
-    String encKeyStr  = (String) array.get(0);
-    String hmacKeyStr = (String) array.get(1);
-    return KeyBundle.fromBase64EncodedKeys(encKeyStr, hmacKeyStr);
-  }
-
-  @SuppressWarnings("unchecked")
-  private static JSONArray keyBundleToArray(KeyBundle bundle) {
-    // Generate JSON.
-    JSONArray keysArray = new JSONArray();
-    keysArray.add(new String(Base64.encodeBase64(bundle.getEncryptionKey())));
-    keysArray.add(new String(Base64.encodeBase64(bundle.getHMACKey())));
-    return keysArray;
-  }
-
-  private ExtendedJSONObject asRecordContents() throws NoCollectionKeysSetException {
-    ExtendedJSONObject json = new ExtendedJSONObject();
-    json.put("id", "keys");
-    json.put("collection", "crypto");
-    json.put("default", keyBundleToArray(this.defaultKeyBundle()));
-    ExtendedJSONObject colls = new ExtendedJSONObject();
-    for (Entry<String, KeyBundle> collKey : collectionKeyBundles.entrySet()) {
-      colls.put(collKey.getKey(), keyBundleToArray(collKey.getValue()));
-    }
-    json.put("collections", colls);
-    return json;
-  }
-
-  public CryptoRecord asCryptoRecord() throws NoCollectionKeysSetException {
-    ExtendedJSONObject payload = this.asRecordContents();
-    CryptoRecord record = new CryptoRecord(payload);
-    record.collection = "crypto";
-    record.guid       = "keys";
-    record.deleted    = false;
-    return record;
-  }
-
-  /**
-   * Set my key bundle and collection keys with the given key bundle and data
-   * (possibly decrypted) from the given record.
-   *
-   * @param keys
-   *          A "crypto/keys" <code>CryptoRecord</code>, encrypted with
-   *          <code>syncKeyBundle</code> if <code>syncKeyBundle</code> is non-null.
-   * @param syncKeyBundle
-   *          If non-null, the sync key bundle to decrypt <code>keys</code> with.
-   */
-  public void setKeyPairsFromWBO(CryptoRecord keys, KeyBundle syncKeyBundle)
-      throws CryptoException, IOException, NonObjectJSONException {
-    if (keys == null) {
-      throw new IllegalArgumentException("cannot set key pairs from null record");
-    }
-    if (syncKeyBundle != null) {
-      keys.keyBundle = syncKeyBundle;
-      keys.decrypt();
-    }
-    ExtendedJSONObject cleartext = keys.payload;
-    KeyBundle defaultKey = arrayToKeyBundle((JSONArray) cleartext.get("default"));
-
-    ExtendedJSONObject collections = cleartext.getObject("collections");
-    HashMap<String, KeyBundle> collectionKeys = new HashMap<String, KeyBundle>();
-    for (Entry<String, Object> pair : collections.entrySet()) {
-      KeyBundle bundle = arrayToKeyBundle((JSONArray) pair.getValue());
-      collectionKeys.put(pair.getKey(), bundle);
-    }
-
-    this.collectionKeyBundles.clear();
-    this.collectionKeyBundles.putAll(collectionKeys);
-    this.defaultKeyBundle     = defaultKey;
-  }
-
-  public void setKeyBundleForCollection(String collection, KeyBundle keys) {
-    this.collectionKeyBundles.put(collection, keys);
-  }
-
-  public void setDefaultKeyBundle(KeyBundle keys) {
-    this.defaultKeyBundle = keys;
-  }
-
-  public void clear() {
-    this.defaultKeyBundle = null;
-    this.collectionKeyBundles.clear();
-  }
-
-  /**
-   * Return set of collections where key is either missing from one collection
-   * or not the same in both collections.
-   * <p>
-   * Does not check for different default keys.
-   */
-  public static Set<String> differences(CollectionKeys a, CollectionKeys b) {
-    Set<String> differences = new HashSet<String>();
-    Set<String> collections = new HashSet<String>(a.collectionKeyBundles.keySet());
-    collections.addAll(b.collectionKeyBundles.keySet());
-
-    // Iterate through one collection, collecting missing and differences.
-    for (String collection : collections) {
-      KeyBundle keyA;
-      KeyBundle keyB;
-      try {
-        keyA = a.keyBundleForCollection(collection); // Will return default key as appropriate.
-        keyB = b.keyBundleForCollection(collection); // Will return default key as appropriate.
-      } catch (NoCollectionKeysSetException e) {
-        differences.add(collection);
-        continue;
-      }
-      // keyA and keyB are not null at this point.
-      if (!keyA.equals(keyB)) {
-        differences.add(collection);
-      }
-    }
-
-    return differences;
-  }
-
-  @Override
-  public boolean equals(Object o) {
-    if (!(o instanceof CollectionKeys)) {
-      return false;
-    }
-    CollectionKeys other = (CollectionKeys) o;
-    try {
-      // It would be nice to use map equality here, but there can be map entries
-      // where the key is the default key that should compare equal to a missing
-      // map entry. Therefore, we always compute the set of differences.
-      return defaultKeyBundle().equals(other.defaultKeyBundle()) &&
-             CollectionKeys.differences(this, other).isEmpty();
-    } catch (NoCollectionKeysSetException e) {
-      // If either default key bundle is not set, we'll say the bundles are not equal.
-      return false;
-    }
-  }
-
-  @Override
-  public int hashCode() {
-    return super.hashCode();
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/CommandProcessor.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/CommandProcessor.java
deleted file mode 100644
index 371603d..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/CommandProcessor.java
+++ /dev/null
@@ -1,261 +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.sync;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.net.Uri;
-import org.json.simple.JSONArray;
-import org.json.simple.JSONObject;
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.db.BrowserContract;
-import org.mozilla.gecko.sync.repositories.NullCursorException;
-import org.mozilla.gecko.sync.repositories.android.ClientsDatabaseAccessor;
-import org.mozilla.gecko.sync.repositories.domain.ClientRecord;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.atomic.AtomicInteger;
-
-/**
- * Process commands received from Sync clients.
- * <p>
- * We need a command processor at two different times:
- * <ol>
- * <li>We execute commands during the "clients" engine stage of a Sync. Each
- * command takes a <code>GlobalSession</code> instance as a parameter.</li>
- * <li>We queue commands to be executed or propagated to other Sync clients
- * during an activity completely unrelated to a sync</li>
- * </ol>
- * To provide a processor for both these time frames, we maintain a static
- * long-lived singleton.
- */
-public class CommandProcessor {
-  private static final String LOG_TAG = "Command";
-  private static final AtomicInteger currentId = new AtomicInteger();
-  protected ConcurrentHashMap<String, CommandRunner> commands = new ConcurrentHashMap<String, CommandRunner>();
-
-  private final static CommandProcessor processor = new CommandProcessor();
-
-  /**
-   * Get the global singleton command processor.
-   *
-   * @return the singleton processor.
-   */
-  public static CommandProcessor getProcessor() {
-    return processor;
-  }
-
-  public static class Command {
-    public final String commandType;
-    public final JSONArray args;
-    private List<String> argsList;
-
-    public Command(String commandType, JSONArray args) {
-      this.commandType = commandType;
-      this.args = args;
-    }
-
-    /**
-     * Get list of arguments as strings.  Individual arguments may be null.
-     *
-     * @return list of strings.
-     */
-    public synchronized List<String> getArgsList() {
-      if (argsList == null) {
-        ArrayList<String> argsList = new ArrayList<String>(args.size());
-
-        for (int i = 0; i < args.size(); i++) {
-          final Object arg = args.get(i);
-          if (arg == null) {
-            argsList.add(null);
-            continue;
-          }
-          argsList.add(arg.toString());
-        }
-        this.argsList = argsList;
-      }
-      return this.argsList;
-    }
-
-    @SuppressWarnings("unchecked")
-    public JSONObject asJSONObject() {
-      JSONObject out = new JSONObject();
-      out.put("command", this.commandType);
-      out.put("args", this.args);
-      return out;
-    }
-  }
-
-  /**
-   * Register a command.
-   * <p>
-   * Any existing registration is overwritten.
-   *
-   * @param commandType
-   *          the name of the command, i.e., "displayURI".
-   * @param command
-   *          the <code>CommandRunner</code> instance that should handle the
-   *          command.
-   */
-  public void registerCommand(String commandType, CommandRunner command) {
-    commands.put(commandType, command);
-  }
-
-  /**
-   * Process a command in the context of the given global session.
-   *
-   * @param session
-   *          the <code>GlobalSession</code> instance currently executing.
-   * @param unparsedCommand
-   *          command as a <code>ExtendedJSONObject</code> instance.
-   */
-  public void processCommand(final GlobalSession session, ExtendedJSONObject unparsedCommand) {
-    Command command = parseCommand(unparsedCommand);
-    if (command == null) {
-      Logger.debug(LOG_TAG, "Invalid command: " + unparsedCommand + " will not be processed.");
-      return;
-    }
-
-    CommandRunner executableCommand = commands.get(command.commandType);
-    if (executableCommand == null) {
-      Logger.debug(LOG_TAG, "Command \"" + command.commandType + "\" not registered and will not be processed.");
-      return;
-    }
-
-    executableCommand.executeCommand(session, command.getArgsList());
-  }
-
-  /**
-   * Parse a JSON command into a ParsedCommand object for easier handling.
-   *
-   * @param unparsedCommand - command as ExtendedJSONObject
-   * @return - null if command is invalid, else return ParsedCommand with
-   *           no null attributes.
-   */
-  protected static Command parseCommand(ExtendedJSONObject unparsedCommand) {
-    String type = (String) unparsedCommand.get("command");
-    if (type == null) {
-      return null;
-    }
-
-    try {
-      JSONArray unparsedArgs = unparsedCommand.getArray("args");
-      if (unparsedArgs == null) {
-        return null;
-      }
-
-      return new Command(type, unparsedArgs);
-    } catch (NonArrayJSONException e) {
-      Logger.debug(LOG_TAG, "Unable to parse args array. Invalid command");
-      return null;
-    }
-  }
-
-  @SuppressWarnings("unchecked")
-  public void sendURIToClientForDisplay(String uri, String clientID, String title, String sender, Context context) {
-    Logger.info(LOG_TAG, "Sending URI to client " + clientID + ".");
-    if (Logger.LOG_PERSONAL_INFORMATION) {
-      Logger.pii(LOG_TAG, "URI is " + uri + "; title is '" + title + "'.");
-    }
-
-    final JSONArray args = new JSONArray();
-    args.add(uri);
-    args.add(sender);
-    args.add(title);
-
-    final Command displayURICommand = new Command("displayURI", args);
-    this.sendCommand(clientID, displayURICommand, context);
-  }
-
-  /**
-   * Validates and sends a command to a client or all clients.
-   *
-   * Calling this does not actually sync the command data to the server. If the
-   * client already has the command/args pair, it won't receive a duplicate
-   * command.
-   *
-   * @param clientID
-   *        Client ID to send command to. If null, send to all remote
-   *        clients.
-   * @param command
-   *        Command to invoke on remote clients
-   */
-  public void sendCommand(String clientID, Command command, Context context) {
-    Logger.debug(LOG_TAG, "In sendCommand.");
-
-    CommandRunner commandData = commands.get(command.commandType);
-
-    // Don't send commands that we don't know about.
-    if (commandData == null) {
-      Logger.error(LOG_TAG, "Unknown command to send: " + command);
-      return;
-    }
-
-    // Don't send a command with the wrong number of arguments.
-    if (!commandData.argumentsAreValid(command.getArgsList())) {
-      Logger.error(LOG_TAG, "Expected " + commandData.argCount + " args for '" +
-                   command + "', but got " + command.args);
-      return;
-    }
-
-    if (clientID != null) {
-      this.sendCommandToClient(clientID, command, context);
-      return;
-    }
-
-    ClientsDatabaseAccessor db = new ClientsDatabaseAccessor(context);
-    try {
-      Map<String, ClientRecord> clientMap = db.fetchAllClients();
-      for (ClientRecord client : clientMap.values()) {
-        this.sendCommandToClient(client.guid, command, context);
-      }
-    } catch (NullCursorException e) {
-      Logger.error(LOG_TAG, "NullCursorException when fetching all GUIDs");
-    } finally {
-      db.close();
-    }
-  }
-
-  protected void sendCommandToClient(String clientID, Command command, Context context) {
-    Logger.info(LOG_TAG, "Sending " + command.commandType + " to " + clientID);
-
-    ClientsDatabaseAccessor db = new ClientsDatabaseAccessor(context);
-    try {
-      db.store(clientID, command);
-    } catch (NullCursorException e) {
-      Logger.error(LOG_TAG, "NullCursorException: Unable to send command.");
-    } finally {
-      db.close();
-    }
-  }
-
-  public static void displayURI(final List<String> args, final Context context) {
-    // We trust the client sender that these exist.
-    final String uri = args.get(0);
-    final String clientId = args.get(1);
-    Logger.pii(LOG_TAG, "Received a URI for display: " + uri + " from " + clientId);
-
-    if (uri == null) {
-      Logger.pii(LOG_TAG, "URI is null – ignoring");
-      return;
-    }
-
-    String title = null;
-    if (args.size() == 3) {
-      title = args.get(2);
-    }
-
-    final Intent sendTabNotificationIntent = new Intent();
-    sendTabNotificationIntent.setClassName(context, BrowserContract.TAB_RECEIVED_SERVICE_CLASS_NAME);
-    sendTabNotificationIntent.setData(Uri.parse(uri));
-    sendTabNotificationIntent.putExtra(Intent.EXTRA_TITLE, title);
-    sendTabNotificationIntent.putExtra(BrowserContract.EXTRA_CLIENT_GUID, clientId);
-    final ComponentName componentName = context.startService(sendTabNotificationIntent);
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/CommandRunner.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/CommandRunner.java
deleted file mode 100644
index c7a0f17..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/CommandRunner.java
+++ /dev/null
@@ -1,22 +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.sync;
-
-import java.util.List;
-
-public abstract class CommandRunner {
-  public final int argCount;
-
-  public CommandRunner(int argCount) {
-    this.argCount = argCount;
-  }
-
-  public abstract void executeCommand(GlobalSession session, List<String> args);
-
-  public boolean argumentsAreValid(List<String> args) {
-    return args != null &&
-           args.size() == argCount;
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/CredentialException.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/CredentialException.java
deleted file mode 100644
index f9004e1..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/CredentialException.java
+++ /dev/null
@@ -1,56 +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.sync;
-
-import android.content.SyncResult;
-
-/**
- * There was a problem with the Sync account's credentials: bad username,
- * missing password, malformed sync key, etc.
- */
-public abstract class CredentialException extends SyncException {
-  private static final long serialVersionUID = 833010553314100538L;
-
-  public CredentialException() {
-    super();
-  }
-
-  public CredentialException(final Throwable e) {
-    super(e);
-  }
-
-  @Override
-  public void updateStats(GlobalSession globalSession, SyncResult syncResult) {
-    syncResult.stats.numAuthExceptions += 1;
-  }
-
-  /**
-   * No credentials at all.
-   */
-  public static class MissingAllCredentialsException extends CredentialException {
-    private static final long serialVersionUID = 3763937096217604611L;
-
-    public MissingAllCredentialsException() {
-      super();
-    }
-
-    public MissingAllCredentialsException(final Throwable e) {
-      super(e);
-    }
-  }
-
-  /**
-   * Some credential is missing.
-   */
-  public static class MissingCredentialException extends CredentialException {
-    private static final long serialVersionUID = -7543031216547596248L;
-
-    public final String missingCredential;
-
-    public MissingCredentialException(final String missingCredential) {
-      this.missingCredential = missingCredential;
-    }
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/CryptoRecord.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/CryptoRecord.java
deleted file mode 100644
index 65563d3..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/CryptoRecord.java
+++ /dev/null
@@ -1,255 +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.sync;
-
-import java.io.IOException;
-import java.io.UnsupportedEncodingException;
-
-import org.json.simple.JSONObject;
-import org.mozilla.apache.commons.codec.binary.Base64;
-import org.mozilla.gecko.sync.crypto.CryptoException;
-import org.mozilla.gecko.sync.crypto.CryptoInfo;
-import org.mozilla.gecko.sync.crypto.KeyBundle;
-import org.mozilla.gecko.sync.crypto.MissingCryptoInputException;
-import org.mozilla.gecko.sync.crypto.NoKeyBundleException;
-import org.mozilla.gecko.sync.repositories.domain.Record;
-import org.mozilla.gecko.sync.repositories.domain.RecordParseException;
-
-/**
- * A Sync crypto record has:
- *
- * <ul>
- * <li>a collection of fields which are not encrypted (id and collection);</il>
- * <li>a set of metadata fields (index, modified, ttl);</il>
- * <li>a payload, which is encrypted and decrypted on request.</il>
- * </ul>
- *
- * The payload flips between being a blob of JSON with hmac/IV/ciphertext
- * attributes and the cleartext itself.
- *
- * Until there's some benefit to the abstraction, we're simply going to call
- * this <code>CryptoRecord</code>.
- *
- * <code>CryptoRecord</code> uses <code>CryptoInfo</code> to do the actual
- * encryption and decryption.
- */
-public class CryptoRecord extends Record {
-
-  // JSON related constants.
-  private static final String KEY_ID         = "id";
-  private static final String KEY_COLLECTION = "collection";
-  private static final String KEY_PAYLOAD    = "payload";
-  private static final String KEY_MODIFIED   = "modified";
-  private static final String KEY_SORTINDEX  = "sortindex";
-  private static final String KEY_TTL        = "ttl";
-  private static final String KEY_CIPHERTEXT = "ciphertext";
-  private static final String KEY_HMAC       = "hmac";
-  private static final String KEY_IV         = "IV";
-
-  /**
-   * Helper method for doing actual decryption.
-   *
-   * Input: JSONObject containing a valid payload (cipherText, IV, HMAC),
-   * KeyBundle with keys for decryption. Output: byte[] clearText
-   * @throws CryptoException
-   * @throws UnsupportedEncodingException
-   */
-  private static byte[] decryptPayload(ExtendedJSONObject payload, KeyBundle keybundle) throws CryptoException, UnsupportedEncodingException {
-    byte[] ciphertext = Base64.decodeBase64(((String) payload.get(KEY_CIPHERTEXT)).getBytes("UTF-8"));
-    byte[] iv         = Base64.decodeBase64(((String) payload.get(KEY_IV)).getBytes("UTF-8"));
-    byte[] hmac       = Utils.hex2Byte((String) payload.get(KEY_HMAC));
-
-    return CryptoInfo.decrypt(ciphertext, iv, hmac, keybundle).getMessage();
-  }
-
-  // The encrypted JSON body object.
-  // The decrypted JSON body object. Fields are copied from `body`.
-
-  public ExtendedJSONObject payload;
-  public KeyBundle   keyBundle;
-
-  /**
-   * Don't forget to set cleartext or body!
-   */
-  public CryptoRecord() {
-    super(null, null, 0, false);
-  }
-
-  public CryptoRecord(ExtendedJSONObject payload) {
-    super(null, null, 0, false);
-    if (payload == null) {
-      throw new IllegalArgumentException(
-          "No payload provided to CryptoRecord constructor.");
-    }
-    this.payload = payload;
-  }
-
-  public CryptoRecord(String jsonString) throws IOException, NonObjectJSONException {
-
-    this(new ExtendedJSONObject(jsonString));
-  }
-
-  /**
-   * Create a new CryptoRecord with the same metadata as an existing record.
-   *
-   * @param source
-   */
-  public CryptoRecord(Record source) {
-    super(source.guid, source.collection, source.lastModified, source.deleted);
-    this.ttl = source.ttl;
-  }
-
-  @Override
-  public Record copyWithIDs(String guid, long androidID) {
-    CryptoRecord out = new CryptoRecord(this);
-    out.guid         = guid;
-    out.androidID    = androidID;
-    out.sortIndex    = this.sortIndex;
-    out.ttl          = this.ttl;
-    out.payload      = (this.payload == null) ? null : new ExtendedJSONObject(this.payload.object);
-    out.keyBundle    = this.keyBundle;    // TODO: copy me?
-    return out;
-  }
-
-  /**
-   * Take a whole record as JSON -- i.e., something like
-   *
-   *   {"payload": "{...}", "id":"foobarbaz"}
-   *
-   * and turn it into a CryptoRecord object.
-   *
-   * @param jsonRecord
-   * @return
-   *        A CryptoRecord that encapsulates the provided record.
-   *
-   * @throws NonObjectJSONException
-   * @throws IOException
-   */
-  public static CryptoRecord fromJSONRecord(String jsonRecord)
-      throws NonObjectJSONException, IOException, RecordParseException {
-    byte[] bytes = jsonRecord.getBytes("UTF-8");
-    ExtendedJSONObject object = ExtendedJSONObject.parseUTF8AsJSONObject(bytes);
-
-    return CryptoRecord.fromJSONRecord(object);
-  }
-
-  // TODO: defensive programming.
-  public static CryptoRecord fromJSONRecord(ExtendedJSONObject jsonRecord)
-      throws IOException, NonObjectJSONException, RecordParseException {
-    String id                  = (String) jsonRecord.get(KEY_ID);
-    String collection          = (String) jsonRecord.get(KEY_COLLECTION);
-    String jsonEncodedPayload  = (String) jsonRecord.get(KEY_PAYLOAD);
-
-    ExtendedJSONObject payload = new ExtendedJSONObject(jsonEncodedPayload);
-
-    CryptoRecord record = new CryptoRecord(payload);
-    record.guid         = id;
-    record.collection   = collection;
-    if (jsonRecord.containsKey(KEY_MODIFIED)) {
-      Long timestamp = jsonRecord.getTimestamp(KEY_MODIFIED);
-      if (timestamp == null) {
-        throw new RecordParseException("timestamp could not be parsed");
-      }
-      record.lastModified = timestamp;
-    }
-    if (jsonRecord.containsKey(KEY_SORTINDEX)) {
-      // getLong tries to cast to Long, and might return null. We catch all
-      // exceptions, just to be safe.
-      try {
-        record.sortIndex = jsonRecord.getLong(KEY_SORTINDEX);
-      } catch (Exception e) {
-        throw new RecordParseException("timestamp could not be parsed");
-      }
-    }
-    if (jsonRecord.containsKey(KEY_TTL)) {
-      // TTLs are never returned by the sync server, so should never be true if
-      // the record was fetched.
-      try {
-        record.ttl = jsonRecord.getLong(KEY_TTL);
-      } catch (Exception e) {
-        throw new RecordParseException("TTL could not be parsed");
-      }
-    }
-    // TODO: deleted?
-    return record;
-  }
-
-  public void setKeyBundle(KeyBundle bundle) {
-    this.keyBundle = bundle;
-  }
-
-  public CryptoRecord decrypt() throws CryptoException, IOException, NonObjectJSONException {
-    if (keyBundle == null) {
-      throw new NoKeyBundleException();
-    }
-
-    // Check that payload contains all pieces for crypto.
-    if (!payload.containsKey(KEY_CIPHERTEXT) ||
-        !payload.containsKey(KEY_IV) ||
-        !payload.containsKey(KEY_HMAC)) {
-      throw new MissingCryptoInputException();
-    }
-
-    // There's no difference between handling the crypto/keys object and
-    // anything else; we just get this.keyBundle from a different source.
-    byte[] cleartext = decryptPayload(payload, keyBundle);
-    payload = ExtendedJSONObject.parseUTF8AsJSONObject(cleartext);
-    return this;
-  }
-
-  public CryptoRecord encrypt() throws CryptoException, UnsupportedEncodingException {
-    if (this.keyBundle == null) {
-      throw new NoKeyBundleException();
-    }
-    String cleartext = payload.toJSONString();
-    byte[] cleartextBytes = cleartext.getBytes("UTF-8");
-    CryptoInfo info = CryptoInfo.encrypt(cleartextBytes, keyBundle);
-    String message = new String(Base64.encodeBase64(info.getMessage()));
-    String iv      = new String(Base64.encodeBase64(info.getIV()));
-    String hmac    = Utils.byte2Hex(info.getHMAC());
-    ExtendedJSONObject ciphertext = new ExtendedJSONObject();
-    ciphertext.put(KEY_CIPHERTEXT, message);
-    ciphertext.put(KEY_HMAC, hmac);
-    ciphertext.put(KEY_IV, iv);
-    this.payload = ciphertext;
-    return this;
-  }
-
-  @Override
-  public void initFromEnvelope(CryptoRecord payload) {
-    throw new IllegalStateException("Can't do this with a CryptoRecord.");
-  }
-
-  @Override
-  public CryptoRecord getEnvelope() {
-    throw new IllegalStateException("Can't do this with a CryptoRecord.");
-  }
-
-  @Override
-  protected void populatePayload(ExtendedJSONObject payload) {
-    throw new IllegalStateException("Can't do this with a CryptoRecord.");
-  }
-
-  @Override
-  protected void initFromPayload(ExtendedJSONObject payload) {
-    throw new IllegalStateException("Can't do this with a CryptoRecord.");
-  }
-
-  // TODO: this only works with encrypted object, and has other limitations.
-  public JSONObject toJSONObject() {
-    ExtendedJSONObject o = new ExtendedJSONObject();
-    o.put(KEY_PAYLOAD, payload.toJSONString());
-    o.put(KEY_ID,      this.guid);
-    if (this.ttl > 0) {
-      o.put(KEY_TTL, this.ttl);
-    }
-    return o.object;
-  }
-
-  @Override
-  public String toJSONString() {
-    return toJSONObject().toJSONString();
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/DelayedWorkTracker.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/DelayedWorkTracker.java
deleted file mode 100644
index ddcb541..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/DelayedWorkTracker.java
+++ /dev/null
@@ -1,69 +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.sync;
-
-import org.mozilla.gecko.background.common.log.Logger;
-
-/**
- * A little class to allow us to maintain a count of extant
- * things (in our case, callbacks that need to fire), and
- * some work that we want done when that count hits 0.
- *
- * @author rnewman
- *
- */
-public class DelayedWorkTracker {
-  private static final String LOG_TAG = "DelayedWorkTracker";
-  protected Runnable workItem = null;
-  protected int outstandingCount = 0;
-
-  public int incrementOutstanding() {
-    Logger.trace(LOG_TAG, "Incrementing outstanding.");
-    synchronized(this) {
-      return ++outstandingCount;
-    }
-  }
-  public int decrementOutstanding() {
-    Logger.trace(LOG_TAG, "Decrementing outstanding.");
-    Runnable job = null;
-    int count;
-    synchronized(this) {
-      if ((count = --outstandingCount) == 0 &&
-          workItem != null) {
-        job = workItem;
-        workItem = null;
-      } else {
-        return count;
-      }
-    }
-    job.run();
-    // In case it's changed.
-    return getOutstandingOperations();
-  }
-  public int getOutstandingOperations() {
-    synchronized(this) {
-      return outstandingCount;
-    }
-  }
-  public void delayWorkItem(Runnable item) {
-    Logger.trace(LOG_TAG, "delayWorkItem.");
-    boolean runnableNow = false;
-    synchronized(this) {
-      Logger.trace(LOG_TAG, "outstandingCount: " + outstandingCount);
-      if (outstandingCount == 0) {
-        runnableNow = true;
-      } else {
-        if (workItem != null) {
-          throw new IllegalStateException("Work item already set!");
-        }
-        workItem = item;
-      }
-    }
-    if (runnableNow) {
-      Logger.trace(LOG_TAG, "Running item now.");
-      item.run();
-    }
-  }
-}
\ No newline at end of file
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/EngineSettings.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/EngineSettings.java
deleted file mode 100644
index 0358160..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/EngineSettings.java
+++ /dev/null
@@ -1,31 +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.sync;
-
-public class EngineSettings {
-  public final String syncID;
-  public final int version;
-
-  public EngineSettings(final String syncID, final int version) {
-    this.syncID = syncID;
-    this.version = version;
-  }
-
-  public EngineSettings(ExtendedJSONObject object) {
-    try {
-      this.syncID = object.getString("syncID");
-      this.version = object.getIntegerSafely("version");
-    } catch (Exception e ) {
-      throw new IllegalArgumentException(e);
-    }
-  }
-
-  public ExtendedJSONObject toJSONObject() {
-    ExtendedJSONObject json = new ExtendedJSONObject();
-    json.put("syncID", syncID);
-    json.put("version", version);
-    return json;
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/ExtendedJSONObject.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/ExtendedJSONObject.java
deleted file mode 100644
index f5fac00..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/ExtendedJSONObject.java
+++ /dev/null
@@ -1,426 +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.sync;
-
-import org.json.simple.JSONArray;
-import org.json.simple.JSONObject;
-import org.json.simple.parser.JSONParser;
-import org.json.simple.parser.ParseException;
-import org.mozilla.apache.commons.codec.binary.Base64;
-import org.mozilla.gecko.sync.UnexpectedJSONException.BadRequiredFieldJSONException;
-
-import java.io.IOException;
-import java.io.Reader;
-import java.io.StringReader;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.Set;
-
-/**
- * Extend JSONObject to do little things, like, y'know, accessing members.
- *
- * @author rnewman
- *
- */
-public class ExtendedJSONObject {
-
-  public JSONObject object;
-
-  /**
-   * Return a <code>JSONParser</code> instance for immediate use.
-   * <p>
-   * <code>JSONParser</code> is not thread-safe, so we return a new instance
-   * each call. This is extremely inefficient in execution time and especially
-   * memory use -- each instance allocates a 16kb temporary buffer -- and we
-   * hope to improve matters eventually.
-   */
-  protected static JSONParser getJSONParser() {
-    return new JSONParser();
-  }
-
-  /**
-   * Parse a JSON encoded string.
-   *
-   * @param in <code>Reader</code> over a JSON-encoded input to parse; not
-   *            necessarily a JSON object.
-   * @return a regular Java <code>Object</code>.
-   * @throws ParseException
-   * @throws IOException
-   */
-  protected static Object parseRaw(Reader in) throws ParseException, IOException {
-    try {
-      return getJSONParser().parse(in);
-    } catch (Error e) {
-      // Don't be stupid, org.json.simple. Bug 1042929.
-      throw new ParseException(ParseException.ERROR_UNEXPECTED_EXCEPTION, e);
-    }
-  }
-
-  /**
-   * Parse a JSON encoded string.
-   * <p>
-   * You should prefer the streaming interface {@link #parseRaw(Reader)}.
-   *
-   * @param input JSON-encoded input string to parse; not necessarily a JSON object.
-   * @return a regular Java <code>Object</code>.
-   * @throws ParseException
-   */
-  protected static Object parseRaw(String input) throws ParseException {
-    try {
-      return getJSONParser().parse(input);
-    } catch (Error e) {
-      // Don't be stupid, org.json.simple. Bug 1042929.
-      throw new ParseException(ParseException.ERROR_UNEXPECTED_EXCEPTION, e);
-    }
-  }
-
-  /**
-   * Helper method to get a JSON array from a stream.
-   *
-   * @param in <code>Reader</code> over a JSON-encoded array to parse.
-   * @throws ParseException
-   * @throws IOException
-   * @throws NonArrayJSONException if the object is valid JSON, but not an array.
-   */
-  public static JSONArray parseJSONArray(Reader in)
-      throws IOException, ParseException, NonArrayJSONException {
-    Object o = parseRaw(in);
-
-    if (o == null) {
-      return null;
-    }
-
-    if (o instanceof JSONArray) {
-      return (JSONArray) o;
-    }
-
-    throw new NonArrayJSONException("value must be a JSON array");
-  }
-
-  /**
-   * Helper method to get a JSON array from a string.
-   * <p>
-   * You should prefer the stream interface {@link #parseJSONArray(Reader)}.
-   *
-   * @param jsonString input.
-   * @throws IOException
-   * @throws NonArrayJSONException if the object is invalid JSON or not an array.
-   */
-  public static JSONArray parseJSONArray(String jsonString)
-      throws IOException, NonArrayJSONException {
-    Object o = null;
-    try {
-      o = parseRaw(jsonString);
-    } catch (ParseException e) {
-      throw new NonArrayJSONException(e);
-    }
-
-    if (o == null) {
-      return null;
-    }
-
-    if (o instanceof JSONArray) {
-      return (JSONArray) o;
-    }
-
-    throw new NonArrayJSONException("value must be a JSON array");
-  }
-
-  /**
-   * Helper method to get a JSON object from a UTF-8 byte array.
-   *
-   * @param in UTF-8 bytes.
-   * @throws NonObjectJSONException if the object is not valid JSON or not an object.
-   * @throws IOException
-   */
-  public static ExtendedJSONObject parseUTF8AsJSONObject(byte[] in)
-      throws NonObjectJSONException, IOException {
-    return new ExtendedJSONObject(new String(in, "UTF-8"));
-  }
-
-  public ExtendedJSONObject() {
-    this.object = new JSONObject();
-  }
-
-  public ExtendedJSONObject(JSONObject o) {
-    this.object = o;
-  }
-
-  public ExtendedJSONObject(Reader in) throws IOException, NonObjectJSONException {
-    if (in == null) {
-      this.object = new JSONObject();
-      return;
-    }
-
-    Object obj = null;
-    try {
-      obj = parseRaw(in);
-    } catch (ParseException e) {
-      throw new NonObjectJSONException(e);
-    }
-
-    if (obj instanceof JSONObject) {
-      this.object = ((JSONObject) obj);
-    } else {
-      throw new NonObjectJSONException("value must be a JSON object");
-    }
-  }
-
-  public ExtendedJSONObject(String jsonString) throws IOException, NonObjectJSONException {
-    this(jsonString == null ? null : new StringReader(jsonString));
-  }
-
-  @Override
-  public ExtendedJSONObject clone() {
-    return new ExtendedJSONObject((JSONObject) this.object.clone());
-  }
-
-  // Passthrough methods.
-  public Object get(String key) {
-    return this.object.get(key);
-  }
-
-  public long getLong(String key, long def) {
-    if (!object.containsKey(key)) {
-      return def;
-    }
-
-    Long val = getLong(key);
-    if (val == null) {
-      return def;
-    }
-    return val.longValue();
-  }
-
-  public Long getLong(String key) {
-    return (Long) this.get(key);
-  }
-
-  public String getString(String key) {
-    return (String) this.get(key);
-  }
-
-  public Boolean getBoolean(String key) {
-    return (Boolean) this.get(key);
-  }
-
-  /**
-   * Return an Integer if the value for this key is an Integer, Long, or String
-   * that can be parsed as a base 10 Integer.
-   * Passes through null.
-   *
-   * @throws NumberFormatException
-   */
-  public Integer getIntegerSafely(String key) throws NumberFormatException {
-    Object val = this.object.get(key);
-    if (val == null) {
-      return null;
-    }
-    if (val instanceof Integer) {
-      return (Integer) val;
-    }
-    if (val instanceof Long) {
-      return ((Long) val).intValue();
-    }
-    if (val instanceof String) {
-      return Integer.parseInt((String) val, 10);
-    }
-    throw new NumberFormatException("Expecting Integer, got " + val.getClass());
-  }
-
-  /**
-   * Return a server timestamp value as milliseconds since epoch.
-   *
-   * @param key
-   * @return A Long, or null if the value is non-numeric or doesn't exist.
-   */
-  public Long getTimestamp(String key) {
-    Object val = this.object.get(key);
-
-    // This is absurd.
-    if (val instanceof Double) {
-      double millis = ((Double) val) * 1000;
-      return Double.valueOf(millis).longValue();
-    }
-    if (val instanceof Float) {
-      double millis = ((Float) val).doubleValue() * 1000;
-      return Double.valueOf(millis).longValue();
-    }
-    if (val instanceof Number) {
-      // Must be an integral number.
-      return ((Number) val).longValue() * 1000;
-    }
-
-    return null;
-  }
-
-  public boolean containsKey(String key) {
-    return this.object.containsKey(key);
-  }
-
-  public String toJSONString() {
-    return this.object.toJSONString();
-  }
-
-  @Override
-  public String toString() {
-    return this.object.toString();
-  }
-
-  protected void putRaw(String key, Object value) {
-    @SuppressWarnings("unchecked")
-    Map<Object, Object> map = this.object;
-    map.put(key, value);
-  }
-
-  public void put(String key, String value) {
-    this.putRaw(key, value);
-  }
-
-  public void put(String key, boolean value) {
-    this.putRaw(key, value);
-  }
-
-  public void put(String key, long value) {
-    this.putRaw(key, value);
-  }
-
-  public void put(String key, int value) {
-    this.putRaw(key, value);
-  }
-
-  public void put(String key, ExtendedJSONObject value) {
-    this.putRaw(key, value);
-  }
-
-  public void put(String key, JSONArray value) {
-    this.putRaw(key, value);
-  }
-
-  @SuppressWarnings("unchecked")
-  public void putArray(String key, List<String> value) {
-    // Frustratingly inefficient, but there you have it.
-    final JSONArray jsonArray = new JSONArray();
-    jsonArray.addAll(value);
-    this.putRaw(key, jsonArray);
-  }
-
-  /**
-   * Remove key-value pair from JSONObject.
-   *
-   * @param key
-   *          to be removed.
-   * @return true if key exists and was removed, false otherwise.
-   */
-  public boolean remove(String key) {
-    Object res = this.object.remove(key);
-    return (res != null);
-  }
-
-  public ExtendedJSONObject getObject(String key) throws NonObjectJSONException {
-    Object o = this.object.get(key);
-    if (o == null) {
-      return null;
-    }
-    if (o instanceof ExtendedJSONObject) {
-      return (ExtendedJSONObject) o;
-    }
-    if (o instanceof JSONObject) {
-      return new ExtendedJSONObject((JSONObject) o);
-    }
-    throw new NonObjectJSONException("value must be a JSON object for key: " + key);
-  }
-
-  @SuppressWarnings("unchecked")
-  public Set<Entry<String, Object>> entrySet() {
-    return this.object.entrySet();
-  }
-
-  @SuppressWarnings("unchecked")
-  public Set<String> keySet() {
-    return this.object.keySet();
-  }
-
-  public org.json.simple.JSONArray getArray(String key) throws NonArrayJSONException {
-    Object o = this.object.get(key);
-    if (o == null) {
-      return null;
-    }
-    if (o instanceof JSONArray) {
-      return (JSONArray) o;
-    }
-    throw new NonArrayJSONException("key must be a JSON array: " + key);
-  }
-
-  public int size() {
-    return this.object.size();
-  }
-
-  @Override
-  public int hashCode() {
-    if (this.object == null) {
-      return getClass().hashCode();
-    }
-    return this.object.hashCode() ^ getClass().hashCode();
-  }
-
-  @Override
-  public boolean equals(Object o) {
-    if (!(o instanceof ExtendedJSONObject)) {
-      return false;
-    }
-    if (o == this) {
-      return true;
-    }
-    ExtendedJSONObject other = (ExtendedJSONObject) o;
-    if (this.object == null) {
-      return other.object == null;
-    }
-    return this.object.equals(other.object);
-  }
-
-  /**
-   * Throw if keys are missing or values have wrong types.
-   *
-   * @param requiredFields list of required keys.
-   * @param requiredFieldClass class that values must be coercable to; may be null, which means don't check.
-   * @throws UnexpectedJSONException
-   */
-  public void throwIfFieldsMissingOrMisTyped(String[] requiredFields, Class<?> requiredFieldClass) throws BadRequiredFieldJSONException {
-    // Defensive as possible: verify object has expected key(s) with string value.
-    for (String k : requiredFields) {
-      Object value = get(k);
-      if (value == null) {
-        throw new BadRequiredFieldJSONException("Expected key not present in result: " + k);
-      }
-      if (requiredFieldClass != null && !(requiredFieldClass.isInstance(value))) {
-        throw new BadRequiredFieldJSONException("Value for key not an instance of " + requiredFieldClass + ": " + k);
-      }
-    }
-  }
-
-  /**
-   * Return a base64-encoded string value as a byte array.
-   */
-  public byte[] getByteArrayBase64(String key) {
-    String s = (String) this.object.get(key);
-    if (s == null) {
-      return null;
-    }
-    return Base64.decodeBase64(s);
-  }
-
-  /**
-   * Return a hex-encoded string value as a byte array.
-   */
-  public byte[] getByteArrayHex(String key) {
-    String s = (String) this.object.get(key);
-    if (s == null) {
-      return null;
-    }
-    return Utils.hex2Byte(s);
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/GlobalSession.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/GlobalSession.java
deleted file mode 100644
index e28bbe4..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/GlobalSession.java
+++ /dev/null
@@ -1,1167 +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.sync;
-
-import android.content.Context;
-
-import org.json.simple.JSONArray;
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.sync.crypto.CryptoException;
-import org.mozilla.gecko.sync.crypto.KeyBundle;
-import org.mozilla.gecko.sync.delegates.ClientsDataDelegate;
-import org.mozilla.gecko.sync.delegates.FreshStartDelegate;
-import org.mozilla.gecko.sync.delegates.GlobalSessionCallback;
-import org.mozilla.gecko.sync.delegates.JSONRecordFetchDelegate;
-import org.mozilla.gecko.sync.delegates.KeyUploadDelegate;
-import org.mozilla.gecko.sync.delegates.MetaGlobalDelegate;
-import org.mozilla.gecko.sync.delegates.WipeServerDelegate;
-import org.mozilla.gecko.sync.net.AuthHeaderProvider;
-import org.mozilla.gecko.sync.net.BaseResource;
-import org.mozilla.gecko.sync.net.HttpResponseObserver;
-import org.mozilla.gecko.sync.net.SyncResponse;
-import org.mozilla.gecko.sync.net.SyncStorageRecordRequest;
-import org.mozilla.gecko.sync.net.SyncStorageRequest;
-import org.mozilla.gecko.sync.net.SyncStorageRequestDelegate;
-import org.mozilla.gecko.sync.net.SyncStorageResponse;
-import org.mozilla.gecko.sync.stage.AndroidBrowserBookmarksServerSyncStage;
-import org.mozilla.gecko.sync.stage.AndroidBrowserHistoryServerSyncStage;
-import org.mozilla.gecko.sync.stage.CheckPreconditionsStage;
-import org.mozilla.gecko.sync.stage.CompletedStage;
-import org.mozilla.gecko.sync.stage.EnsureCrypto5KeysStage;
-import org.mozilla.gecko.sync.stage.FennecTabsServerSyncStage;
-import org.mozilla.gecko.sync.stage.FetchInfoCollectionsStage;
-import org.mozilla.gecko.sync.stage.FetchInfoConfigurationStage;
-import org.mozilla.gecko.sync.stage.FetchMetaGlobalStage;
-import org.mozilla.gecko.sync.stage.FormHistoryServerSyncStage;
-import org.mozilla.gecko.sync.stage.GlobalSyncStage;
-import org.mozilla.gecko.sync.stage.GlobalSyncStage.Stage;
-import org.mozilla.gecko.sync.stage.NoSuchStageException;
-import org.mozilla.gecko.sync.stage.PasswordsServerSyncStage;
-import org.mozilla.gecko.sync.stage.SyncClientsEngineStage;
-import org.mozilla.gecko.sync.stage.UploadMetaGlobalStage;
-
-import java.io.IOException;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.EnumMap;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.Set;
-import java.util.concurrent.atomic.AtomicLong;
-
-import ch.boye.httpclientandroidlib.HttpResponse;
-import ch.boye.httpclientandroidlib.client.methods.HttpUriRequest;
-
-public class GlobalSession implements HttpResponseObserver {
-  private static final String LOG_TAG = "GlobalSession";
-
-  public static final long STORAGE_VERSION = 5;
-
-  public SyncConfiguration config = null;
-
-  protected Map<Stage, GlobalSyncStage> stages;
-  public Stage currentState = Stage.idle;
-
-  public final GlobalSessionCallback callback;
-  protected final Context context;
-  protected final ClientsDataDelegate clientsDelegate;
-
-  /**
-   * Map from engine name to new settings for an updated meta/global record.
-   * Engines to remove will have <code>null</code> EngineSettings.
-   */
-  public final Map<String, EngineSettings> enginesToUpdate = new HashMap<String, EngineSettings>();
-
-   /*
-   * Key accessors.
-   */
-  public KeyBundle keyBundleForCollection(String collection) throws NoCollectionKeysSetException {
-    return config.getCollectionKeys().keyBundleForCollection(collection);
-  }
-
-  /*
-   * Config passthrough for convenience.
-   */
-  public AuthHeaderProvider getAuthHeaderProvider() {
-    return config.getAuthHeaderProvider();
-  }
-
-  public URI wboURI(String collection, String id) throws URISyntaxException {
-    return config.wboURI(collection, id);
-  }
-
-  public GlobalSession(SyncConfiguration config,
-                       GlobalSessionCallback callback,
-                       Context context,
-                       ClientsDataDelegate clientsDelegate)
-    throws SyncConfigurationException, IllegalArgumentException, IOException, NonObjectJSONException {
-
-    if (callback == null) {
-      throw new IllegalArgumentException("Must provide a callback to GlobalSession constructor.");
-    }
-
-    this.callback        = callback;
-    this.context         = context;
-    this.clientsDelegate = clientsDelegate;
-
-    this.config = config;
-    registerCommands();
-    prepareStages();
-
-    if (config.stagesToSync == null) {
-      Logger.info(LOG_TAG, "No stages to sync specified; defaulting to all valid engine names.");
-      config.stagesToSync = Collections.unmodifiableCollection(SyncConfiguration.validEngineNames());
-    }
-
-    // TODO: data-driven plan for the sync, referring to prepareStages.
-  }
-
-  /**
-   * Register commands this global session knows how to process.
-   * <p>
-   * Re-registering a command overwrites any existing registration.
-   */
-  protected static void registerCommands() {
-    final CommandProcessor processor = CommandProcessor.getProcessor();
-
-    processor.registerCommand("resetEngine", new CommandRunner(1) {
-      @Override
-      public void executeCommand(final GlobalSession session, List<String> args) {
-        HashSet<String> names = new HashSet<String>();
-        names.add(args.get(0));
-        session.resetStagesByName(names);
-      }
-    });
-
-    processor.registerCommand("resetAll", new CommandRunner(0) {
-      @Override
-      public void executeCommand(final GlobalSession session, List<String> args) {
-        session.resetAllStages();
-      }
-    });
-
-    processor.registerCommand("wipeEngine", new CommandRunner(1) {
-      @Override
-      public void executeCommand(final GlobalSession session, List<String> args) {
-        HashSet<String> names = new HashSet<String>();
-        names.add(args.get(0));
-        session.wipeStagesByName(names);
-      }
-    });
-
-    processor.registerCommand("wipeAll", new CommandRunner(0) {
-      @Override
-      public void executeCommand(final GlobalSession session, List<String> args) {
-        session.wipeAllStages();
-      }
-    });
-
-    processor.registerCommand("displayURI", new CommandRunner(3) {
-      @Override
-      public void executeCommand(final GlobalSession session, List<String> args) {
-        CommandProcessor.displayURI(args, session.getContext());
-      }
-    });
-  }
-
-  protected void prepareStages() {
-    Map<Stage, GlobalSyncStage> stages = new EnumMap<Stage, GlobalSyncStage>(Stage.class);
-
-    stages.put(Stage.checkPreconditions,      new CheckPreconditionsStage());
-    stages.put(Stage.fetchInfoCollections,    new FetchInfoCollectionsStage());
-    stages.put(Stage.fetchMetaGlobal,         new FetchMetaGlobalStage());
-    stages.put(Stage.fetchInfoConfiguration,  new FetchInfoConfigurationStage(
-            config.infoConfigurationURL(), getAuthHeaderProvider()));
-    stages.put(Stage.ensureKeysStage,         new EnsureCrypto5KeysStage());
-
-    stages.put(Stage.syncClientsEngine,       new SyncClientsEngineStage());
-
-    stages.put(Stage.syncTabs,                new FennecTabsServerSyncStage());
-    stages.put(Stage.syncPasswords,           new PasswordsServerSyncStage());
-    stages.put(Stage.syncBookmarks,           new AndroidBrowserBookmarksServerSyncStage());
-    stages.put(Stage.syncHistory,             new AndroidBrowserHistoryServerSyncStage());
-    stages.put(Stage.syncFormHistory,         new FormHistoryServerSyncStage());
-
-    stages.put(Stage.uploadMetaGlobal,        new UploadMetaGlobalStage());
-    stages.put(Stage.completed,               new CompletedStage());
-
-    this.stages = Collections.unmodifiableMap(stages);
-  }
-
-  public GlobalSyncStage getSyncStageByName(String name) throws NoSuchStageException {
-    return getSyncStageByName(Stage.byName(name));
-  }
-
-  public GlobalSyncStage getSyncStageByName(Stage next) throws NoSuchStageException {
-    GlobalSyncStage stage = stages.get(next);
-    if (stage == null) {
-      throw new NoSuchStageException(next);
-    }
-    return stage;
-  }
-
-  public Collection<GlobalSyncStage> getSyncStagesByEnum(Collection<Stage> enums) {
-    ArrayList<GlobalSyncStage> out = new ArrayList<GlobalSyncStage>();
-    for (Stage name : enums) {
-      try {
-        GlobalSyncStage stage = this.getSyncStageByName(name);
-        out.add(stage);
-      } catch (NoSuchStageException e) {
-        Logger.warn(LOG_TAG, "Unable to find stage with name " + name);
-      }
-    }
-    return out;
-  }
-
-  public Collection<GlobalSyncStage> getSyncStagesByName(Collection<String> names) {
-    ArrayList<GlobalSyncStage> out = new ArrayList<GlobalSyncStage>();
-    for (String name : names) {
-      try {
-        GlobalSyncStage stage = this.getSyncStageByName(name);
-        out.add(stage);
-      } catch (NoSuchStageException e) {
-        Logger.warn(LOG_TAG, "Unable to find stage with name " + name);
-      }
-    }
-    return out;
-  }
-
-  /**
-   * Advance and loop around the stages of a sync.
-   * @param current
-   * @return
-   *        The next stage to execute.
-   */
-  public static Stage nextStage(Stage current) {
-    int index = current.ordinal() + 1;
-    int max   = Stage.completed.ordinal() + 1;
-    return Stage.values()[index % max];
-  }
-
-  /**
-   * Move to the next stage in the syncing process.
-   */
-  public void advance() {
-    // If we have a backoff, request a backoff and don't advance to next stage.
-    long existingBackoff = largestBackoffObserved.get();
-    if (existingBackoff > 0) {
-      this.abort(null, "Aborting sync because of backoff of " + existingBackoff + " milliseconds.");
-      return;
-    }
-
-    this.callback.handleStageCompleted(this.currentState, this);
-    Stage next = nextStage(this.currentState);
-    GlobalSyncStage nextStage;
-    try {
-      nextStage = this.getSyncStageByName(next);
-    } catch (NoSuchStageException e) {
-      this.abort(e, "No such stage " + next);
-      return;
-    }
-    this.currentState = next;
-    Logger.info(LOG_TAG, "Running next stage " + next + " (" + nextStage + ")...");
-    try {
-      nextStage.execute(this);
-    } catch (Exception ex) {
-      Logger.warn(LOG_TAG, "Caught exception " + ex + " running stage " + next);
-      this.abort(ex, "Uncaught exception in stage.");
-      return;
-    }
-  }
-
-  public Context getContext() {
-    return this.context;
-  }
-
-  /**
-   * Begin a sync.
-   * <p>
-   * The caller is responsible for:
-   * <ul>
-   * <li>Verifying that any backoffs/minimum next sync requests are respected.</li>
-   * <li>Ensuring that the device is online.</li>
-   * <li>Ensuring that dependencies are ready.</li>
-   * </ul>
-   *
-   * @throws AlreadySyncingException
-   */
-  public void start() throws AlreadySyncingException {
-    if (this.currentState != GlobalSyncStage.Stage.idle) {
-      throw new AlreadySyncingException(this.currentState);
-    }
-    installAsHttpResponseObserver(); // Uninstalled by completeSync or abort.
-    this.advance();
-  }
-
-  /**
-   * Stop this sync and start again.
-   * @throws AlreadySyncingException
-   */
-  protected void restart() throws AlreadySyncingException {
-    this.currentState = GlobalSyncStage.Stage.idle;
-    if (callback.shouldBackOffStorage()) {
-      this.callback.handleAborted(this, "Told to back off.");
-      return;
-    }
-    this.start();
-  }
-
-  /**
-   * We're finished (aborted or succeeded): release resources.
-   */
-  protected void cleanUp() {
-    uninstallAsHttpResponseObserver();
-    this.stages = null;
-  }
-
-  public void completeSync() {
-    cleanUp();
-    this.currentState = GlobalSyncStage.Stage.idle;
-    this.callback.handleSuccess(this);
-  }
-
-  /**
-   * Record that an updated meta/global record should be uploaded with the given
-   * settings for the given engine.
-   *
-   * @param engineName engine to update.
-   * @param engineSettings new syncID and version.
-   */
-  public void recordForMetaGlobalUpdate(String engineName, EngineSettings engineSettings) {
-    enginesToUpdate.put(engineName, engineSettings);
-  }
-
-  /**
-   * Record that an updated meta/global record should be uploaded without the
-   * given engine name.
-   *
-   * @param engineName
-   *          engine to remove.
-   */
-  public void removeEngineFromMetaGlobal(String engineName) {
-    enginesToUpdate.put(engineName, null);
-  }
-
-  public boolean hasUpdatedMetaGlobal() {
-    if (enginesToUpdate.isEmpty()) {
-      Logger.info(LOG_TAG, "Not uploading updated meta/global record since there are no engines requesting upload.");
-      return false;
-    }
-
-    if (Logger.shouldLogVerbose(LOG_TAG)) {
-      Logger.trace(LOG_TAG, "Uploading updated meta/global record since there are engine changes to meta/global.");
-      Logger.trace(LOG_TAG, "Engines requesting update [" + Utils.toCommaSeparatedString(enginesToUpdate.keySet()) + "]");
-    }
-
-    return true;
-  }
-
-  public void updateMetaGlobalInPlace() {
-    config.metaGlobal.declined = this.declinedEngineNames();
-    ExtendedJSONObject engines = config.metaGlobal.getEngines();
-    for (Entry<String, EngineSettings> pair : enginesToUpdate.entrySet()) {
-      if (pair.getValue() == null) {
-        engines.remove(pair.getKey());
-      } else {
-        engines.put(pair.getKey(), pair.getValue().toJSONObject());
-      }
-    }
-
-    enginesToUpdate.clear();
-  }
-
-  /**
-   * Synchronously upload an updated meta/global.
-   * <p>
-   * All problems are logged and ignored.
-   */
-  public void uploadUpdatedMetaGlobal() {
-    updateMetaGlobalInPlace();
-
-    Logger.debug(LOG_TAG, "Uploading updated meta/global record.");
-    final Object monitor = new Object();
-
-    Runnable doUpload = new Runnable() {
-      @Override
-      public void run() {
-        config.metaGlobal.upload(new MetaGlobalDelegate() {
-          @Override
-          public void handleSuccess(MetaGlobal global, SyncStorageResponse response) {
-            Logger.info(LOG_TAG, "Successfully uploaded updated meta/global record.");
-            // Engine changes are stored as diffs, so update enabled engines in config to match uploaded meta/global.
-            config.enabledEngineNames = config.metaGlobal.getEnabledEngineNames();
-            // Clear userSelectedEngines because they are updated in config and meta/global.
-            config.userSelectedEngines = null;
-
-            synchronized (monitor) {
-              monitor.notify();
-            }
-          }
-
-          @Override
-          public void handleMissing(MetaGlobal global, SyncStorageResponse response) {
-            Logger.warn(LOG_TAG, "Got 404 missing uploading updated meta/global record; shouldn't happen.  Ignoring.");
-            synchronized (monitor) {
-              monitor.notify();
-            }
-          }
-
-          @Override
-          public void handleFailure(SyncStorageResponse response) {
-            Logger.warn(LOG_TAG, "Failed to upload updated meta/global record; ignoring.");
-            synchronized (monitor) {
-              monitor.notify();
-            }
-          }
-
-          @Override
-          public void handleError(Exception e) {
-            Logger.warn(LOG_TAG, "Got exception trying to upload updated meta/global record; ignoring.", e);
-            synchronized (monitor) {
-              monitor.notify();
-            }
-          }
-        });
-      }
-    };
-
-    final Thread upload = new Thread(doUpload);
-    synchronized (monitor) {
-      try {
-        upload.start();
-        monitor.wait();
-        Logger.debug(LOG_TAG, "Uploaded updated meta/global record.");
-      } catch (InterruptedException e) {
-        Logger.error(LOG_TAG, "Uploading updated meta/global interrupted; continuing.");
-      }
-    }
-  }
-
-
-  public void abort(Exception e, String reason) {
-    Logger.warn(LOG_TAG, "Aborting sync: " + reason, e);
-    cleanUp();
-    long existingBackoff = largestBackoffObserved.get();
-    if (existingBackoff > 0) {
-      callback.requestBackoff(existingBackoff);
-    }
-    if (!(e instanceof HTTPFailureException)) {
-      //  e is null, or we aborted for a non-HTTP reason; okay to upload new meta/global record.
-      if (this.hasUpdatedMetaGlobal()) {
-        this.uploadUpdatedMetaGlobal(); // Only logs errors; does not call abort.
-      }
-    }
-    this.callback.handleError(this, e);
-  }
-
-  public void handleHTTPError(SyncStorageResponse response, String reason) {
-    // TODO: handling of 50x (backoff), 401 (node reassignment or auth error).
-    // Fall back to aborting.
-    Logger.warn(LOG_TAG, "Aborting sync due to HTTP " + response.getStatusCode());
-    this.interpretHTTPFailure(response.httpResponse());
-    this.abort(new HTTPFailureException(response), reason);
-  }
-
-  /**
-   * Perform appropriate backoff etc. extraction.
-   */
-  public void interpretHTTPFailure(HttpResponse response) {
-    // TODO: handle permanent rejection.
-    long responseBackoff = (new SyncResponse(response)).totalBackoffInMilliseconds();
-    if (responseBackoff > 0) {
-      callback.requestBackoff(responseBackoff);
-    }
-
-    if (response.getStatusLine() != null) {
-      final int statusCode = response.getStatusLine().getStatusCode();
-      switch(statusCode) {
-
-      case 400:
-        SyncStorageResponse storageResponse = new SyncStorageResponse(response);
-        this.interpretHTTPBadRequestBody(storageResponse);
-        break;
-
-      case 401:
-        /*
-         * Alert our callback we have a 401 on a cluster URL. This GlobalSession
-         * will fail, but the next one will fetch a new cluster URL and will
-         * distinguish between "node reassignment" and "user password changed".
-         */
-        callback.informUnauthorizedResponse(this, config.getClusterURL());
-        break;
-      }
-    }
-  }
-
-  protected void interpretHTTPBadRequestBody(final SyncStorageResponse storageResponse) {
-    try {
-      final String body = storageResponse.body();
-      if (body == null) {
-        return;
-      }
-      if (SyncStorageResponse.RESPONSE_CLIENT_UPGRADE_REQUIRED.equals(body)) {
-        callback.informUpgradeRequiredResponse(this);
-        return;
-      }
-    } catch (Exception e) {
-      Logger.warn(LOG_TAG, "Exception parsing HTTP 400 body.", e);
-    }
-  }
-
-  public void fetchInfoCollections(JSONRecordFetchDelegate callback) throws URISyntaxException {
-    final JSONRecordFetcher fetcher = new JSONRecordFetcher(config.infoCollectionsURL(), getAuthHeaderProvider());
-    fetcher.fetch(callback);
-  }
-
-  /**
-   * Upload new crypto/keys.
-   *
-   * @param keys
-   *          new keys.
-   * @param keyUploadDelegate
-   *          a delegate.
-   */
-  public void uploadKeys(final CollectionKeys keys,
-                         final KeyUploadDelegate keyUploadDelegate) {
-    SyncStorageRecordRequest request;
-    try {
-      request = new SyncStorageRecordRequest(this.config.keysURI());
-    } catch (URISyntaxException e) {
-      keyUploadDelegate.onKeyUploadFailed(e);
-      return;
-    }
-
-    request.delegate = new SyncStorageRequestDelegate() {
-
-      @Override
-      public String ifUnmodifiedSince() {
-        return null;
-      }
-
-      @Override
-      public void handleRequestSuccess(SyncStorageResponse response) {
-        Logger.debug(LOG_TAG, "Keys uploaded.");
-        BaseResource.consumeEntity(response); // We don't need the response at all.
-        keyUploadDelegate.onKeysUploaded();
-      }
-
-      @Override
-      public void handleRequestFailure(SyncStorageResponse response) {
-        Logger.debug(LOG_TAG, "Failed to upload keys.");
-        GlobalSession.this.interpretHTTPFailure(response.httpResponse());
-        BaseResource.consumeEntity(response); // The exception thrown should not need the body of the response.
-        keyUploadDelegate.onKeyUploadFailed(new HTTPFailureException(response));
-      }
-
-      @Override
-      public void handleRequestError(Exception ex) {
-        Logger.warn(LOG_TAG, "Got exception trying to upload keys", ex);
-        keyUploadDelegate.onKeyUploadFailed(ex);
-      }
-
-      @Override
-      public AuthHeaderProvider getAuthHeaderProvider() {
-        return GlobalSession.this.getAuthHeaderProvider();
-      }
-    };
-
-    // Convert keys to an encrypted crypto record.
-    CryptoRecord keysRecord;
-    try {
-      keysRecord = keys.asCryptoRecord();
-      keysRecord.setKeyBundle(config.syncKeyBundle);
-      keysRecord.encrypt();
-    } catch (Exception e) {
-      Logger.warn(LOG_TAG, "Got exception trying creating crypto record from keys", e);
-      keyUploadDelegate.onKeyUploadFailed(e);
-      return;
-    }
-
-    request.put(keysRecord);
-  }
-
-  /*
-   * meta/global callbacks.
-   */
-  public void processMetaGlobal(MetaGlobal global) {
-    config.metaGlobal = global;
-
-    Long storageVersion = global.getStorageVersion();
-    if (storageVersion == null) {
-      Logger.warn(LOG_TAG, "Malformed remote meta/global: could not retrieve remote storage version.");
-      freshStart();
-      return;
-    }
-    if (storageVersion < STORAGE_VERSION) {
-      Logger.warn(LOG_TAG, "Outdated server: reported " +
-          "remote storage version " + storageVersion + " < " +
-          "local storage version " + STORAGE_VERSION);
-      freshStart();
-      return;
-    }
-    if (storageVersion > STORAGE_VERSION) {
-      Logger.warn(LOG_TAG, "Outdated client: reported " +
-          "remote storage version " + storageVersion + " > " +
-          "local storage version " + STORAGE_VERSION);
-      requiresUpgrade();
-      return;
-    }
-    String remoteSyncID = global.getSyncID();
-    if (remoteSyncID == null) {
-      Logger.warn(LOG_TAG, "Malformed remote meta/global: could not retrieve remote syncID.");
-      freshStart();
-      return;
-    }
-    String localSyncID = config.syncID;
-    if (!remoteSyncID.equals(localSyncID)) {
-      Logger.warn(LOG_TAG, "Remote syncID different from local syncID: resetting client and assuming remote syncID.");
-      resetAllStages();
-      config.purgeCryptoKeys();
-      config.syncID = remoteSyncID;
-    }
-    // Compare lastModified timestamps for remote/local engine selection times.
-    Logger.debug(LOG_TAG, "Comparing local engine selection timestamp [" + config.userSelectedEnginesTimestamp + "] to server meta/global timestamp [" + config.persistedMetaGlobal().lastModified() + "].");
-    if (config.userSelectedEnginesTimestamp < config.persistedMetaGlobal().lastModified()) {
-      // Remote has later meta/global timestamp. Don't upload engine changes.
-      config.userSelectedEngines = null;
-    }
-    // Persist enabled engine names.
-    config.enabledEngineNames = global.getEnabledEngineNames();
-    if (config.enabledEngineNames == null) {
-      Logger.warn(LOG_TAG, "meta/global reported no enabled engine names!");
-    } else {
-      if (Logger.shouldLogVerbose(LOG_TAG)) {
-        Logger.trace(LOG_TAG, "Persisting enabled engine names '" +
-            Utils.toCommaSeparatedString(config.enabledEngineNames) + "' from meta/global.");
-      }
-    }
-
-    // Persist declined.
-    // Our declined engines at any point are:
-    // Whatever they were remotely, plus whatever they were locally, less any
-    // engines that were just enabled locally or remotely.
-    // If remote just 'won', our recently enabled list just got cleared.
-    final HashSet<String> allDeclined = new HashSet<String>();
-
-    final Set<String> newRemoteDeclined = global.getDeclinedEngineNames();
-    final Set<String> oldLocalDeclined = config.declinedEngineNames;
-
-    allDeclined.addAll(newRemoteDeclined);
-    allDeclined.addAll(oldLocalDeclined);
-
-    if (config.userSelectedEngines != null) {
-      for (Entry<String, Boolean> selection : config.userSelectedEngines.entrySet()) {
-        if (selection.getValue()) {
-          allDeclined.remove(selection.getKey());
-        }
-      }
-    }
-
-    config.declinedEngineNames = allDeclined;
-    if (config.declinedEngineNames.isEmpty()) {
-      Logger.debug(LOG_TAG, "meta/global reported no declined engine names, and we have none declined locally.");
-    } else {
-      if (Logger.shouldLogVerbose(LOG_TAG)) {
-        Logger.trace(LOG_TAG, "Persisting declined engine names '" +
-            Utils.toCommaSeparatedString(config.declinedEngineNames) + "' from meta/global.");
-      }
-    }
-
-    config.persistToPrefs();
-    advance();
-  }
-
-  public void processMissingMetaGlobal(MetaGlobal global) {
-    freshStart();
-  }
-
-  /**
-   * Do a fresh start then quietly finish the sync, starting another.
-   */
-  public void freshStart() {
-    final GlobalSession globalSession = this;
-    freshStart(this, new FreshStartDelegate() {
-
-      @Override
-      public void onFreshStartFailed(Exception e) {
-        globalSession.abort(e, "Fresh start failed.");
-      }
-
-      @Override
-      public void onFreshStart() {
-        try {
-          Logger.warn(LOG_TAG, "Fresh start succeeded; restarting global session.");
-          globalSession.config.persistToPrefs();
-          globalSession.restart();
-        } catch (Exception e) {
-          Logger.warn(LOG_TAG, "Got exception when restarting sync after freshStart.", e);
-          globalSession.abort(e, "Got exception after freshStart.");
-        }
-      }
-    });
-  }
-
-  /**
-   * Clean the server, aborting the current sync.
-   * <p>
-   * <ol>
-   * <li>Wipe the server storage.</li>
-   * <li>Reset all stages and purge cached state: (meta/global and crypto/keys records).</li>
-   * <li>Upload fresh meta/global record.</li>
-   * <li>Upload fresh crypto/keys record.</li>
-   * <li>Restart the sync entirely in order to re-download meta/global and crypto/keys record.</li>
-   * </ol>
-   * @param session the current session.
-   * @param freshStartDelegate delegate to notify on fresh start or failure.
-   */
-  protected static void freshStart(final GlobalSession session, final FreshStartDelegate freshStartDelegate) {
-    Logger.debug(LOG_TAG, "Fresh starting.");
-
-    final MetaGlobal mg = session.generateNewMetaGlobal();
-
-    session.wipeServer(session.getAuthHeaderProvider(), new WipeServerDelegate() {
-
-      @Override
-      public void onWiped(long timestamp) {
-        Logger.debug(LOG_TAG, "Successfully wiped server.  Resetting all stages and purging cached meta/global and crypto/keys records.");
-
-        session.resetAllStages();
-        session.config.purgeMetaGlobal();
-        session.config.purgeCryptoKeys();
-        session.config.persistToPrefs();
-
-        Logger.info(LOG_TAG, "Uploading new meta/global with sync ID " + mg.syncID + ".");
-
-        // It would be good to set the X-If-Unmodified-Since header to `timestamp`
-        // for this PUT to ensure at least some level of transactionality.
-        // Unfortunately, the servers don't support it after a wipe right now
-        // (bug 693893), so we're going to defer this until bug 692700.
-        mg.upload(new MetaGlobalDelegate() {
-          @Override
-          public void handleSuccess(MetaGlobal uploadedGlobal, SyncStorageResponse uploadResponse) {
-            Logger.info(LOG_TAG, "Uploaded new meta/global with sync ID " + uploadedGlobal.syncID + ".");
-
-            // Generate new keys.
-            CollectionKeys keys = null;
-            try {
-              keys = session.generateNewCryptoKeys();
-            } catch (CryptoException e) {
-              Logger.warn(LOG_TAG, "Got exception generating new keys; failing fresh start.", e);
-              freshStartDelegate.onFreshStartFailed(e);
-            }
-            if (keys == null) {
-              Logger.warn(LOG_TAG, "Got null keys from generateNewKeys; failing fresh start.");
-              freshStartDelegate.onFreshStartFailed(null);
-            }
-
-            // Upload new keys.
-            Logger.info(LOG_TAG, "Uploading new crypto/keys.");
-            session.uploadKeys(keys, new KeyUploadDelegate() {
-              @Override
-              public void onKeysUploaded() {
-                Logger.info(LOG_TAG, "Uploaded new crypto/keys.");
-                freshStartDelegate.onFreshStart();
-              }
-
-              @Override
-              public void onKeyUploadFailed(Exception e) {
-                Logger.warn(LOG_TAG, "Got exception uploading new keys.", e);
-                freshStartDelegate.onFreshStartFailed(e);
-              }
-            });
-          }
-
-          @Override
-          public void handleMissing(MetaGlobal global, SyncStorageResponse response) {
-            // Shouldn't happen on upload.
-            Logger.warn(LOG_TAG, "Got 'missing' response uploading new meta/global.");
-            freshStartDelegate.onFreshStartFailed(new Exception("meta/global missing while uploading."));
-          }
-
-          @Override
-          public void handleFailure(SyncStorageResponse response) {
-            Logger.warn(LOG_TAG, "Got failure " + response.getStatusCode() + " uploading new meta/global.");
-            session.interpretHTTPFailure(response.httpResponse());
-            freshStartDelegate.onFreshStartFailed(new HTTPFailureException(response));
-          }
-
-          @Override
-          public void handleError(Exception e) {
-            Logger.warn(LOG_TAG, "Got error uploading new meta/global.", e);
-            freshStartDelegate.onFreshStartFailed(e);
-          }
-        });
-      }
-
-      @Override
-      public void onWipeFailed(Exception e) {
-        Logger.warn(LOG_TAG, "Wipe failed.");
-        freshStartDelegate.onFreshStartFailed(e);
-      }
-    });
-  }
-
-  // Note that we do not yet implement wipeRemote: it's only necessary for
-  // first sync options.
-  // -- reset local stages, wipe server for each stage *except* clients
-  //    (stages only, not whole server!), send wipeEngine commands to each client.
-  //
-  // Similarly for startOver (because we don't receive that notification).
-  // -- remove client data from server, reset local stages, clear keys, reset
-  //    backoff, clear all prefs, discard credentials.
-  //
-  // Change passphrase: wipe entire server, reset client to force upload, sync.
-  //
-  // When an engine is disabled: wipe its collections on the server, reupload
-  // meta/global.
-  //
-  // On syncing each stage: if server has engine version 0 or old, wipe server,
-  // reset client to prompt reupload.
-  // If sync ID mismatch: take that syncID and reset client.
-
-  protected void wipeServer(final AuthHeaderProvider authHeaderProvider, final WipeServerDelegate wipeDelegate) {
-    SyncStorageRequest request;
-    final GlobalSession self = this;
-
-    try {
-      request = new SyncStorageRequest(config.storageURL());
-    } catch (URISyntaxException ex) {
-      Logger.warn(LOG_TAG, "Invalid URI in wipeServer.");
-      wipeDelegate.onWipeFailed(ex);
-      return;
-    }
-
-    request.delegate = new SyncStorageRequestDelegate() {
-
-      @Override
-      public String ifUnmodifiedSince() {
-        return null;
-      }
-
-      @Override
-      public void handleRequestSuccess(SyncStorageResponse response) {
-        BaseResource.consumeEntity(response);
-        wipeDelegate.onWiped(response.normalizedWeaveTimestamp());
-      }
-
-      @Override
-      public void handleRequestFailure(SyncStorageResponse response) {
-        Logger.warn(LOG_TAG, "Got request failure " + response.getStatusCode() + " in wipeServer.");
-        // Process HTTP failures here to pick up backoffs, etc.
-        self.interpretHTTPFailure(response.httpResponse());
-        BaseResource.consumeEntity(response); // The exception thrown should not need the body of the response.
-        wipeDelegate.onWipeFailed(new HTTPFailureException(response));
-      }
-
-      @Override
-      public void handleRequestError(Exception ex) {
-        Logger.warn(LOG_TAG, "Got exception in wipeServer.", ex);
-        wipeDelegate.onWipeFailed(ex);
-      }
-
-      @Override
-      public AuthHeaderProvider getAuthHeaderProvider() {
-        return GlobalSession.this.getAuthHeaderProvider();
-      }
-    };
-    request.delete();
-  }
-
-  public void wipeAllStages() {
-    Logger.info(LOG_TAG, "Wiping all stages.");
-    // Includes "clients".
-    this.wipeStagesByEnum(Stage.getNamedStages());
-  }
-
-  public void wipeStages(Collection<GlobalSyncStage> stages) {
-    for (GlobalSyncStage stage : stages) {
-      try {
-        Logger.info(LOG_TAG, "Wiping " + stage);
-        stage.wipeLocal(this);
-      } catch (Exception e) {
-        Logger.error(LOG_TAG, "Ignoring wipe failure for stage " + stage, e);
-      }
-    }
-  }
-
-  public void wipeStagesByEnum(Collection<Stage> stages) {
-    wipeStages(this.getSyncStagesByEnum(stages));
-  }
-
-  public void wipeStagesByName(Collection<String> names) {
-    wipeStages(this.getSyncStagesByName(names));
-  }
-
-  public void resetAllStages() {
-    Logger.info(LOG_TAG, "Resetting all stages.");
-    // Includes "clients".
-    this.resetStagesByEnum(Stage.getNamedStages());
-  }
-
-  public void resetStages(Collection<GlobalSyncStage> stages) {
-    for (GlobalSyncStage stage : stages) {
-      try {
-        Logger.info(LOG_TAG, "Resetting " + stage);
-        stage.resetLocal(this);
-      } catch (Exception e) {
-        Logger.error(LOG_TAG, "Ignoring reset failure for stage " + stage, e);
-      }
-    }
-  }
-
-  public void resetStagesByEnum(Collection<Stage> stages) {
-    resetStages(this.getSyncStagesByEnum(stages));
-  }
-
-  public void resetStagesByName(Collection<String> names) {
-    resetStages(this.getSyncStagesByName(names));
-  }
-
-  /**
-   * Engines to explicitly mark as declined in a fresh meta/global record.
-   * <p>
-   * Returns an empty array if the user hasn't elected to customize data types,
-   * or an array of engines that the user un-checked during customization.
-   * <p>
-   * Engines that Android Sync doesn't recognize are <b>not</b> included in
-   * the returned array.
-   *
-   * @return a new JSONArray of engine names.
-   */
-  @SuppressWarnings("unchecked")
-  protected JSONArray declinedEngineNames() {
-    final JSONArray declined = new JSONArray();
-    for (String engine : config.declinedEngineNames) {
-      declined.add(engine);
-    };
-
-    return declined;
-  }
-
-  /**
-   * Engines to include in a fresh meta/global record.
-   * <p>
-   * Returns either the persisted engine names (perhaps we have been node
-   * re-assigned and are initializing a clean server: we want to upload the
-   * persisted engine names so that we don't accidentally disable engines that
-   * Android Sync doesn't recognize), or the set of engines names that Android
-   * Sync implements.
-   *
-   * @return set of engine names.
-   */
-  protected Set<String> enabledEngineNames() {
-    if (config.enabledEngineNames != null) {
-      return config.enabledEngineNames;
-    }
-
-    // These are the default set of engine names.
-    Set<String> validEngineNames = SyncConfiguration.validEngineNames();
-
-    // If the user hasn't set any selected engines, that's okay -- default to
-    // everything.
-    if (config.userSelectedEngines == null) {
-      return validEngineNames;
-    }
-
-    // userSelectedEngines has keys that are engine names, and boolean values
-    // corresponding to whether the user asked for the engine to sync or not. If
-    // an engine is not present, that means the user didn't change its sync
-    // setting. Since we default to everything on, that means the user didn't
-    // turn it off; therefore, it's included in the set of engines to sync.
-    Set<String> validAndSelectedEngineNames = new HashSet<String>();
-    for (String engineName : validEngineNames) {
-      if (config.userSelectedEngines.containsKey(engineName) &&
-          !config.userSelectedEngines.get(engineName)) {
-        continue;
-      }
-      validAndSelectedEngineNames.add(engineName);
-    }
-    return validAndSelectedEngineNames;
-  }
-
-  /**
-   * Generate fresh crypto/keys collection.
-   * @return crypto/keys collection.
-   * @throws CryptoException
-   */
-  @SuppressWarnings("static-method")
-  public CollectionKeys generateNewCryptoKeys() throws CryptoException {
-    return CollectionKeys.generateCollectionKeys();
-  }
-
-  /**
-   * Generate a fresh meta/global record.
-   * @return meta/global record.
-   */
-  public MetaGlobal generateNewMetaGlobal() {
-    final String newSyncID   = Utils.generateGuid();
-    final String metaURL     = this.config.metaURL();
-
-    ExtendedJSONObject engines = new ExtendedJSONObject();
-    for (String engineName : enabledEngineNames()) {
-      EngineSettings engineSettings = null;
-      try {
-        GlobalSyncStage globalStage = this.getSyncStageByName(engineName);
-        Integer version = globalStage.getStorageVersion();
-        if (version == null) {
-          continue; // Don't want this stage to be included in meta/global.
-        }
-        engineSettings = new EngineSettings(Utils.generateGuid(), version);
-      } catch (NoSuchStageException e) {
-        // No trouble; Android Sync might not recognize this engine yet.
-        // By default, version 0.  Other clients will see the 0 version and reset/wipe accordingly.
-        engineSettings = new EngineSettings(Utils.generateGuid(), 0);
-      }
-      engines.put(engineName, engineSettings.toJSONObject());
-    }
-
-    MetaGlobal metaGlobal = new MetaGlobal(metaURL, this.getAuthHeaderProvider());
-    metaGlobal.setSyncID(newSyncID);
-    metaGlobal.setStorageVersion(STORAGE_VERSION);
-    metaGlobal.setEngines(engines);
-
-    // We assume that the config's declined engines have been updated
-    // according to the user's selections.
-    metaGlobal.setDeclinedEngineNames(this.declinedEngineNames());
-
-    return metaGlobal;
-  }
-
-  /**
-   * Suggest that your Sync client needs to be upgraded to work
-   * with this server.
-   */
-  public void requiresUpgrade() {
-    Logger.info(LOG_TAG, "Client outdated storage version; requires update.");
-    // TODO: notify UI.
-    this.abort(null, "Requires upgrade");
-  }
-
-  /**
-   * If meta/global is missing or malformed, throws a MetaGlobalException.
-   * Otherwise, returns true if there is an entry for this engine in the
-   * meta/global "engines" object.
-   * <p>
-   * This is a global/permanent setting, not a local/temporary setting. For the
-   * latter, see {@link GlobalSession#isEngineLocallyEnabled(String)}.
-   *
-   * @param engineName the name to check (e.g., "bookmarks").
-   * @param engineSettings
-   *        if non-null, verify that the server engine settings are congruent
-   *        with this, throwing the appropriate MetaGlobalException if not.
-   * @return
-   *        true if the engine with the provided name is present in the
-   *        meta/global "engines" object, and verification passed.
-   *
-   * @throws MetaGlobalException
-   */
-  public boolean isEngineRemotelyEnabled(String engineName, EngineSettings engineSettings) throws MetaGlobalException {
-    if (this.config.metaGlobal == null) {
-      throw new MetaGlobalNotSetException();
-    }
-
-    // This should not occur.
-    if (this.config.enabledEngineNames == null) {
-      Logger.error(LOG_TAG, "No enabled engines in config. Giving up.");
-      throw new MetaGlobalMissingEnginesException();
-    }
-
-    if (!(this.config.enabledEngineNames.contains(engineName))) {
-      Logger.debug(LOG_TAG, "Engine " + engineName + " not enabled: no meta/global entry.");
-      return false;
-    }
-
-    // If we have a meta/global, check that it's safe for us to sync.
-    // (If we don't, we'll create one later, which is why we return `true` above.)
-    if (engineSettings != null) {
-      // Throws if there's a problem.
-      this.config.metaGlobal.verifyEngineSettings(engineName, engineSettings);
-    }
-
-    return true;
-  }
-
-
-  /**
-   * Return true if the named stage should be synced this session.
-   * <p>
-   * This is a local/temporary setting, in contrast to the meta/global record,
-   * which is a global/permanent setting. For the latter, see
-   * {@link GlobalSession#isEngineRemotelyEnabled(String, EngineSettings)}.
-   *
-   * @param stageName
-   *          to query.
-   * @return true if named stage is enabled for this sync.
-   */
-  public boolean isEngineLocallyEnabled(String stageName) {
-    if (config.stagesToSync == null) {
-      return true;
-    }
-    return config.stagesToSync.contains(stageName);
-  }
-
-  public ClientsDataDelegate getClientsDelegate() {
-    return this.clientsDelegate;
-  }
-
-  /**
-   * The longest backoff observed to date; -1 means no backoff observed.
-   */
-  protected final AtomicLong largestBackoffObserved = new AtomicLong(-1);
-
-  /**
-   * Reset any observed backoff and start observing HTTP responses for backoff
-   * requests.
-   */
-  protected void installAsHttpResponseObserver() {
-    Logger.debug(LOG_TAG, "Adding " + this + " as a BaseResource HttpResponseObserver.");
-    BaseResource.addHttpResponseObserver(this);
-    largestBackoffObserved.set(-1);
-  }
-
-  /**
-   * Stop observing HttpResponses for backoff requests.
-   */
-  protected void uninstallAsHttpResponseObserver() {
-    Logger.debug(LOG_TAG, "Removing " + this + " as a BaseResource HttpResponseObserver.");
-    BaseResource.removeHttpResponseObserver(this);
-  }
-
-  /**
-   * Observe all HTTP response for backoff requests on all status codes, not just errors.
-   */
-  @Override
-  public void observeHttpResponse(HttpUriRequest request, HttpResponse response) {
-    // Ignore non-Sync storage requests.
-    final URI clusterURL = config.getClusterURL();
-    if (clusterURL != null && !clusterURL.getHost().equals(request.getURI().getHost())) {
-      // It's possible to see requests without a clusterURL (in particular,
-      // during testing); allow some extra backoffs in this case.
-      return;
-    }
-
-    long responseBackoff = (new SyncResponse(response)).totalBackoffInMilliseconds(); // TODO: don't allocate object?
-    if (responseBackoff <= 0) {
-      return;
-    }
-
-    Logger.debug(LOG_TAG, "Observed " + responseBackoff + " millisecond backoff request.");
-    while (true) {
-      long existingBackoff = largestBackoffObserved.get();
-      if (existingBackoff >= responseBackoff) {
-        return;
-      }
-      if (largestBackoffObserved.compareAndSet(existingBackoff, responseBackoff)) {
-        return;
-      }
-    }
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/HTTPFailureException.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/HTTPFailureException.java
deleted file mode 100644
index 69bba88..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/HTTPFailureException.java
+++ /dev/null
@@ -1,47 +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.sync;
-
-import org.mozilla.gecko.sync.net.SyncStorageResponse;
-
-import android.content.SyncResult;
-
-public class HTTPFailureException extends SyncException {
-  private static final long serialVersionUID = -5415864029780770619L;
-  public SyncStorageResponse response;
-
-  public HTTPFailureException(SyncStorageResponse response) {
-    this.response = response;
-  }
-
-  @Override
-  public String toString() {
-    String errorMessage;
-    try {
-      errorMessage = this.response.getErrorMessage();
-    } catch (Exception e) {
-      // Oh well.
-      errorMessage = "[unknown error message]";
-    }
-    return "<HTTPFailureException " + this.response.getStatusCode() +
-           " :: (" + errorMessage + ")>";
-  }
-
-  @Override
-  public void updateStats(GlobalSession globalSession, SyncResult syncResult) {
-    switch (response.getStatusCode()) {
-    case 401:
-      // Node reassignment 401s get handled internally.
-      syncResult.stats.numAuthExceptions++;
-      return;
-    case 500:
-    case 501:
-    case 503:
-      // TODO: backoff.
-      syncResult.stats.numIoExceptions++;
-      return;
-    }
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/InfoCollections.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/InfoCollections.java
deleted file mode 100644
index 374fa5c..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/InfoCollections.java
+++ /dev/null
@@ -1,103 +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.sync;
-
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Map.Entry;
-
-import org.mozilla.gecko.background.common.log.Logger;
-
-/**
- * Fetches the timestamp information in <code>info/collections</code> on the
- * Sync server. Provides access to those timestamps, along with logic to check
- * for whether a collection requires an update.
- */
-public class InfoCollections {
-  private static final String LOG_TAG = "InfoCollections";
-
-  /**
-   * Fields fetched from the server, or <code>null</code> if not yet fetched.
-   * <p>
-   * Rather than storing decimal/double timestamps, as provided by the server,
-   * we convert immediately to milliseconds since epoch.
-   */
-  final Map<String, Long> timestamps;
-
-  public InfoCollections() {
-    this(new ExtendedJSONObject());
-  }
-
-  public InfoCollections(final ExtendedJSONObject record) {
-    Logger.debug(LOG_TAG, "info/collections is " + record.toJSONString());
-    HashMap<String, Long> map = new HashMap<String, Long>();
-
-    for (Entry<String, Object> entry : record.entrySet()) {
-      final String key = entry.getKey();
-      final Object value = entry.getValue();
-
-      // These objects are most likely going to be Doubles. Regardless, we
-      // want to get them in a more sane time format.
-      if (value instanceof Double) {
-        map.put(key, Utils.decimalSecondsToMilliseconds((Double) value));
-        continue;
-      }
-      if (value instanceof Long) {
-        map.put(key, Utils.decimalSecondsToMilliseconds((Long) value));
-        continue;
-      }
-      if (value instanceof Integer) {
-        map.put(key, Utils.decimalSecondsToMilliseconds((Integer) value));
-        continue;
-      }
-      Logger.warn(LOG_TAG, "Skipping info/collections entry for " + key);
-    }
-
-    this.timestamps = Collections.unmodifiableMap(map);
-  }
-
-  /**
-   * Return the timestamp for the given collection, or null if the timestamps
-   * have not been fetched or the given collection does not have a timestamp.
-   *
-   * @param collection
-   *          The collection to inspect.
-   * @return the timestamp in milliseconds since epoch.
-   */
-  public Long getTimestamp(String collection) {
-    if (timestamps == null) {
-      return null;
-    }
-    return timestamps.get(collection);
-  }
-
-  /**
-   * Test if a given collection needs to be updated.
-   *
-   * @param collection
-   *          The collection to test.
-   * @param lastModified
-   *          Timestamp when local record was last modified.
-   */
-  public boolean updateNeeded(String collection, long lastModified) {
-    Logger.trace(LOG_TAG, "Testing " + collection + " for updateNeeded. Local last modified is " + lastModified + ".");
-
-    // No local record of modification time? Need an update.
-    if (lastModified <= 0) {
-      return true;
-    }
-
-    // No meta/global on the server? We need an update. The server fetch will fail and
-    // then we will upload a fresh meta/global.
-    Long serverLastModified = getTimestamp(collection);
-    if (serverLastModified == null) {
-      return true;
-    }
-
-    // Otherwise, we need an update if our modification time is stale.
-    return serverLastModified > lastModified;
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/InfoConfiguration.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/InfoConfiguration.java
deleted file mode 100644
index eb24284..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/InfoConfiguration.java
+++ /dev/null
@@ -1,93 +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.sync;
-
-import android.util.Log;
-
-import org.mozilla.gecko.background.common.log.Logger;
-
-/**
- * Wraps and provides access to configuration data returned from info/configuration.
- * Docs: https://docs.services.mozilla.com/storage/apis-1.5.html#general-info
- *
- * - <bold>max_request_bytes</bold>: the maximum size in bytes of the overall
- *   HTTP request body that will be accepted by the server.
- *
- * - <bold>max_post_records</bold>: the maximum number of records that can be
- *   uploaded to a collection in a single POST request.
- *
- * - <bold>max_post_bytes</bold>: the maximum combined size in bytes of the
- *   record payloads that can be uploaded to a collection in a single
- *   POST request.
- *
- * - <bold>max_total_records</bold>: the maximum number of records that can be
- *   uploaded to a collection as part of a batched upload.
- *
- * - <bold>max_total_bytes</bold>: the maximum combined size in bytes of the
- *   record payloads that can be uploaded to a collection as part of
- *   a batched upload.
- */
-public class InfoConfiguration {
-    private static final String LOG_TAG = "InfoConfiguration";
-
-    public static final String MAX_REQUEST_BYTES = "max_request_bytes";
-    public static final String MAX_POST_RECORDS = "max_post_records";
-    public static final String MAX_POST_BYTES = "max_post_bytes";
-    public static final String MAX_TOTAL_RECORDS = "max_total_records";
-    public static final String MAX_TOTAL_BYTES = "max_total_bytes";
-
-    private static final long DEFAULT_MAX_REQUEST_BYTES = 1048576;
-    private static final long DEFAULT_MAX_POST_RECORDS = 100;
-    private static final long DEFAULT_MAX_POST_BYTES = 1048576;
-    private static final long DEFAULT_MAX_TOTAL_RECORDS = 10000;
-    private static final long DEFAULT_MAX_TOTAL_BYTES = 104857600;
-
-    // While int's upper range is (2^31-1), which in bytes is equivalent to 2.147 GB, let's be optimistic
-    // about the future and use long here, so that this code works if the server decides its clients are
-    // all on fiber and have congress-library sized bookmark collections.
-    // Record counts are long for the sake of simplicity.
-    public final long maxRequestBytes;
-    public final long maxPostRecords;
-    public final long maxPostBytes;
-    public final long maxTotalRecords;
-    public final long maxTotalBytes;
-
-    public InfoConfiguration() {
-        Logger.debug(LOG_TAG, "info/configuration is unavailable, using defaults");
-
-        maxRequestBytes = DEFAULT_MAX_REQUEST_BYTES;
-        maxPostRecords = DEFAULT_MAX_POST_RECORDS;
-        maxPostBytes = DEFAULT_MAX_POST_BYTES;
-        maxTotalRecords = DEFAULT_MAX_TOTAL_RECORDS;
-        maxTotalBytes = DEFAULT_MAX_TOTAL_BYTES;
-    }
-
-    public InfoConfiguration(final ExtendedJSONObject record) {
-        Logger.debug(LOG_TAG, "info/configuration is " + record.toJSONString());
-
-        maxRequestBytes = getValueFromRecord(record, MAX_REQUEST_BYTES, DEFAULT_MAX_REQUEST_BYTES);
-        maxPostRecords = getValueFromRecord(record, MAX_POST_RECORDS, DEFAULT_MAX_POST_RECORDS);
-        maxPostBytes = getValueFromRecord(record, MAX_POST_BYTES, DEFAULT_MAX_POST_BYTES);
-        maxTotalRecords = getValueFromRecord(record, MAX_TOTAL_RECORDS, DEFAULT_MAX_TOTAL_RECORDS);
-        maxTotalBytes = getValueFromRecord(record, MAX_TOTAL_BYTES, DEFAULT_MAX_TOTAL_BYTES);
-    }
-
-    private static Long getValueFromRecord(ExtendedJSONObject record, String key, long defaultValue) {
-        if (!record.containsKey(key)) {
-            return defaultValue;
-        }
-
-        try {
-            Long val = record.getLong(key);
-            if (val == null) {
-                return defaultValue;
-            }
-            return val;
-        } catch (NumberFormatException e) {
-            Log.w(LOG_TAG, "Could not parse key " + key + " from record: " + record, e);
-            return defaultValue;
-        }
-    }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/InfoCounts.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/InfoCounts.java
deleted file mode 100644
index 832e97d..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/InfoCounts.java
+++ /dev/null
@@ -1,67 +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.sync;
-
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.Set;
-
-import org.mozilla.gecko.background.common.log.Logger;
-
-public class InfoCounts {
-  static final String LOG_TAG = "InfoCounts";
-
-  /**
-   * Counts fetched from the server, or <code>null</code> if not yet fetched.
-   */
-  private Map<String, Integer> counts = null;
-
-  @SuppressWarnings("unchecked")
-  public InfoCounts(final ExtendedJSONObject record) {
-    Logger.debug(LOG_TAG, "info/collection_counts is " + record.toJSONString());
-    HashMap<String, Integer> map = new HashMap<String, Integer>();
-
-    Set<Entry<String, Object>> entrySet = record.object.entrySet();
-
-    String key;
-    Object value;
-
-    for (Entry<String, Object> entry : entrySet) {
-      key = entry.getKey();
-      value = entry.getValue();
-
-      if (value instanceof Integer) {
-        map.put(key, (Integer) value);
-        continue;
-      }
-
-      if (value instanceof Long) {
-        map.put(key, ((Long) value).intValue());
-        continue;
-      }
-
-      Logger.warn(LOG_TAG, "Skipping info/collection_counts entry for " + key);
-    }
-
-    this.counts = Collections.unmodifiableMap(map);
-  }
-
-  /**
-   * Return the server count for the given collection, or null if the counts
-   * have not been fetched or the given collection does not have a count.
-   *
-   * @param collection
-   *          The collection to inspect.
-   * @return the number of elements in the named collection.
-   */
-  public Integer getCount(String collection) {
-    if (counts == null) {
-      return null;
-    }
-    return counts.get(collection);
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/JSONRecordFetcher.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/JSONRecordFetcher.java
deleted file mode 100644
index 982b5b0..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/JSONRecordFetcher.java
+++ /dev/null
@@ -1,145 +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.sync;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.sync.delegates.JSONRecordFetchDelegate;
-import org.mozilla.gecko.sync.net.AuthHeaderProvider;
-import org.mozilla.gecko.sync.net.SyncStorageRecordRequest;
-import org.mozilla.gecko.sync.net.SyncStorageRequestDelegate;
-import org.mozilla.gecko.sync.net.SyncStorageResponse;
-
-/**
- * An object which fetches a chunk of JSON from a URI, using certain credentials,
- * and informs its delegate of the result.
- */
-public class JSONRecordFetcher {
-  private static final long DEFAULT_AWAIT_TIMEOUT_MSEC = 2 * 60 * 1000;   // Two minutes.
-  private static final String LOG_TAG = "JSONRecordFetcher";
-
-  protected final AuthHeaderProvider authHeaderProvider;
-  protected final String uri;
-  protected JSONRecordFetchDelegate delegate;
-
-  public JSONRecordFetcher(final String uri, final AuthHeaderProvider authHeaderProvider) {
-    if (uri == null) {
-      throw new IllegalArgumentException("uri must not be null");
-    }
-    this.uri = uri;
-    this.authHeaderProvider = authHeaderProvider;
-  }
-
-  protected String getURI() {
-    return this.uri;
-  }
-
-  private class JSONFetchHandler implements SyncStorageRequestDelegate {
-
-    // SyncStorageRequestDelegate methods for fetching.
-    @Override
-    public AuthHeaderProvider getAuthHeaderProvider() {
-      return authHeaderProvider;
-    }
-
-    @Override
-    public String ifUnmodifiedSince() {
-      return null;
-    }
-
-    @Override
-    public void handleRequestSuccess(SyncStorageResponse response) {
-      if (response.wasSuccessful()) {
-        try {
-          delegate.handleSuccess(response.jsonObjectBody());
-        } catch (Exception e) {
-          handleRequestError(e);
-        }
-        return;
-      }
-      handleRequestFailure(response);
-    }
-
-    @Override
-    public void handleRequestFailure(SyncStorageResponse response) {
-      delegate.handleFailure(response);
-    }
-
-    @Override
-    public void handleRequestError(Exception ex) {
-      delegate.handleError(ex);
-    }
-  }
-
-  public void fetch(final JSONRecordFetchDelegate delegate) {
-    this.delegate = delegate;
-    try {
-      final SyncStorageRecordRequest r = new SyncStorageRecordRequest(this.getURI());
-      r.delegate = new JSONFetchHandler();
-      r.get();
-    } catch (Exception e) {
-      delegate.handleError(e);
-    }
-  }
-
-  private class LatchedJSONRecordFetchDelegate implements JSONRecordFetchDelegate {
-    public ExtendedJSONObject body = null;
-    public Exception exception = null;
-    private final CountDownLatch latch;
-
-    public LatchedJSONRecordFetchDelegate(CountDownLatch latch) {
-      this.latch = latch;
-    }
-
-    @Override
-    public void handleFailure(SyncStorageResponse response) {
-      this.exception = new HTTPFailureException(response);
-      latch.countDown();
-    }
-
-    @Override
-    public void handleError(Exception e) {
-      this.exception = e;
-      latch.countDown();
-    }
-
-    @Override
-    public void handleSuccess(ExtendedJSONObject body) {
-      this.body = body;
-      latch.countDown();
-    }
-  }
-
-  /**
-   * Fetch the info record, blocking until it returns.
-   * @return the info record.
-   */
-  public ExtendedJSONObject fetchBlocking() throws HTTPFailureException, Exception {
-    CountDownLatch latch = new CountDownLatch(1);
-    LatchedJSONRecordFetchDelegate delegate = new LatchedJSONRecordFetchDelegate(latch);
-    this.delegate = delegate;
-    this.fetch(delegate);
-
-    // Sanity wait: the resource itself will time out and throw after two
-    // minutes, so we just want to avoid coding errors causing us to block
-    // endlessly.
-    if (!latch.await(DEFAULT_AWAIT_TIMEOUT_MSEC, TimeUnit.MILLISECONDS)) {
-      Logger.warn(LOG_TAG, "Interrupted fetching info record.");
-      throw new InterruptedException("info fetch timed out.");
-    }
-
-    if (delegate.body != null) {
-      return delegate.body;
-    }
-
-    if (delegate.exception != null) {
-      throw delegate.exception;
-    }
-
-    throw new Exception("Unknown error.");
-  }
-}
\ No newline at end of file
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/KeyBundleProvider.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/KeyBundleProvider.java
deleted file mode 100644
index 4a2be2a..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/KeyBundleProvider.java
+++ /dev/null
@@ -1,11 +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.sync;
-
-import org.mozilla.gecko.sync.crypto.KeyBundle;
-
-public interface KeyBundleProvider {
-  public abstract KeyBundle keyBundle();
-}
\ No newline at end of file
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/MetaGlobal.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/MetaGlobal.java
deleted file mode 100644
index a90c0fe..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/MetaGlobal.java
+++ /dev/null
@@ -1,372 +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.sync;
-
-import java.io.IOException;
-import java.net.URISyntaxException;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-
-import org.json.simple.JSONArray;
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.sync.MetaGlobalException.MetaGlobalMalformedSyncIDException;
-import org.mozilla.gecko.sync.MetaGlobalException.MetaGlobalMalformedVersionException;
-import org.mozilla.gecko.sync.delegates.MetaGlobalDelegate;
-import org.mozilla.gecko.sync.net.AuthHeaderProvider;
-import org.mozilla.gecko.sync.net.SyncStorageRecordRequest;
-import org.mozilla.gecko.sync.net.SyncStorageRequestDelegate;
-import org.mozilla.gecko.sync.net.SyncStorageResponse;
-
-public class MetaGlobal implements SyncStorageRequestDelegate {
-  private static final String LOG_TAG = "MetaGlobal";
-  protected String metaURL;
-
-  // Fields.
-  protected ExtendedJSONObject  engines;
-  protected JSONArray           declined;
-  protected Long                storageVersion;
-  protected String              syncID;
-
-  // Lookup tables.
-  protected Map<String, String> syncIDs;
-  protected Map<String, Integer> versions;
-  protected Map<String, MetaGlobalException> exceptions;
-
-  // Temporary location to store our callback.
-  private MetaGlobalDelegate callback;
-
-  // A little hack so we can use the same delegate implementation for upload and download.
-  private boolean isUploading;
-  protected final AuthHeaderProvider authHeaderProvider;
-
-  public MetaGlobal(String metaURL, AuthHeaderProvider authHeaderProvider) {
-    this.metaURL = metaURL;
-    this.authHeaderProvider = authHeaderProvider;
-  }
-
-  public void fetch(MetaGlobalDelegate delegate) {
-    this.callback = delegate;
-    try {
-      this.isUploading = false;
-      SyncStorageRecordRequest r = new SyncStorageRecordRequest(this.metaURL);
-      r.delegate = this;
-      r.get();
-    } catch (URISyntaxException e) {
-      this.callback.handleError(e);
-    }
-  }
-
-  public void upload(MetaGlobalDelegate callback) {
-    try {
-      this.isUploading = true;
-      SyncStorageRecordRequest r = new SyncStorageRecordRequest(this.metaURL);
-
-      r.delegate = this;
-      this.callback = callback;
-      r.put(this.asCryptoRecord());
-    } catch (Exception e) {
-      callback.handleError(e);
-    }
-  }
-
-  protected ExtendedJSONObject asRecordContents() {
-    ExtendedJSONObject json = new ExtendedJSONObject();
-    json.put("storageVersion", storageVersion);
-    json.put("engines", engines);
-    json.put("syncID", syncID);
-    json.put("declined", declined);
-    return json;
-  }
-
-  /**
-   * Return a copy ready for upload.
-   * @return an unencrypted <code>CryptoRecord</code>.
-   */
-  public CryptoRecord asCryptoRecord() {
-    ExtendedJSONObject payload = this.asRecordContents();
-    CryptoRecord record = new CryptoRecord(payload);
-    record.collection = "meta";
-    record.guid       = "global";
-    record.deleted    = false;
-    return record;
-  }
-
-  public void setFromRecord(CryptoRecord record) throws IllegalStateException, IOException, NonObjectJSONException, NonArrayJSONException {
-    if (record == null) {
-      throw new IllegalArgumentException("Cannot set meta/global from null record");
-    }
-    Logger.debug(LOG_TAG, "meta/global is " + record.payload.toJSONString());
-    this.storageVersion = (Long) record.payload.get("storageVersion");
-    this.syncID = (String) record.payload.get("syncID");
-
-    setEngines(record.payload.getObject("engines"));
-
-    // Accepts null -- declined can be missing.
-    setDeclinedEngineNames(record.payload.getArray("declined"));
-  }
-
-  public Long getStorageVersion() {
-    return this.storageVersion;
-  }
-
-  public void setStorageVersion(Long version) {
-    this.storageVersion = version;
-  }
-
-  public ExtendedJSONObject getEngines() {
-    return engines;
-  }
-
-  @SuppressWarnings("unchecked")
-  public void declineEngine(String engine) {
-    if (this.declined == null) {
-      JSONArray replacement = new JSONArray();
-      replacement.add(engine);
-      setDeclinedEngineNames(replacement);
-      return;
-    }
-
-    this.declined.add(engine);
-  }
-
-  @SuppressWarnings("unchecked")
-  public void declineEngineNames(Collection<String> additional) {
-    if (this.declined == null) {
-      JSONArray replacement = new JSONArray();
-      replacement.addAll(additional);
-      setDeclinedEngineNames(replacement);
-      return;
-    }
-
-    for (String engine : additional) {
-      if (!this.declined.contains(engine)) {
-        this.declined.add(engine);
-      }
-    }
-  }
-
-  public void setDeclinedEngineNames(JSONArray declined) {
-    if (declined == null) {
-      this.declined = new JSONArray();
-      return;
-    }
-    this.declined = declined;
-  }
-
-  /**
-   * Return the set of engines that we support (given as an argument)
-   * but the user hasn't explicitly declined on another device.
-   *
-   * Can return the input if the user hasn't declined any engines.
-   */
-  public Set<String> getNonDeclinedEngineNames(Set<String> supported) {
-    if (this.declined == null ||
-        this.declined.isEmpty()) {
-      return supported;
-    }
-
-    final Set<String> result = new HashSet<String>(supported);
-    result.removeAll(this.declined);
-    return result;
-  }
-
-  public void setEngines(ExtendedJSONObject engines) {
-    if (engines == null) {
-      engines = new ExtendedJSONObject();
-    }
-    this.engines = engines;
-    final int count = engines.size();
-    versions   = new HashMap<String, Integer>(count);
-    syncIDs    = new HashMap<String, String>(count);
-    exceptions = new HashMap<String, MetaGlobalException>(count);
-    for (String engineName : engines.keySet()) {
-      try {
-        ExtendedJSONObject engineEntry = engines.getObject(engineName);
-        recordEngineState(engineName, engineEntry);
-      } catch (NonObjectJSONException e) {
-        Logger.error(LOG_TAG, "Engine field for " + engineName + " in meta/global is not an object.");
-        recordEngineState(engineName, new ExtendedJSONObject()); // Doesn't have a version or syncID, for example, so will be server wiped.
-      }
-    }
-  }
-
-  /**
-   * Take a JSON object corresponding to the 'engines' field for the provided engine name,
-   * updating {@link #syncIDs} and {@link #versions} accordingly.
-   *
-   * If the record is malformed, an entry is added to {@link #exceptions}, to be rethrown
-   * during validation.
-   */
-  protected void recordEngineState(String engineName, ExtendedJSONObject engineEntry) {
-    if (engineEntry == null) {
-      throw new IllegalArgumentException("engineEntry cannot be null.");
-    }
-
-    // Record syncID first, so that engines with bad versions are recorded.
-    try {
-      String syncID = engineEntry.getString("syncID");
-      if (syncID == null) {
-        Logger.warn(LOG_TAG, "No syncID for " + engineName + ". Recording exception.");
-        exceptions.put(engineName, new MetaGlobalMalformedSyncIDException());
-      }
-      syncIDs.put(engineName, syncID);
-    } catch (ClassCastException e) {
-      // Malformed syncID on the server. Wipe the server.
-      Logger.warn(LOG_TAG, "Malformed syncID " + engineEntry.get("syncID") +
-                           " for " + engineName + ". Recording exception.");
-      exceptions.put(engineName, new MetaGlobalMalformedSyncIDException());
-    }
-
-    try {
-      Integer version = engineEntry.getIntegerSafely("version");
-      Logger.trace(LOG_TAG, "Engine " + engineName + " has server version " + version);
-      if (version == null ||
-          version == 0) {
-        // Invalid version. Wipe the server.
-        Logger.warn(LOG_TAG, "Malformed version " + version +
-                             " for " + engineName + ". Recording exception.");
-        exceptions.put(engineName, new MetaGlobalMalformedVersionException());
-        return;
-      }
-      versions.put(engineName, version);
-    } catch (NumberFormatException e) {
-      // Invalid version. Wipe the server.
-      Logger.warn(LOG_TAG, "Malformed version " + engineEntry.get("version") +
-                           " for " + engineName + ". Recording exception.");
-      exceptions.put(engineName, new MetaGlobalMalformedVersionException());
-      return;
-    }
-  }
-
-  /**
-   * Get enabled engine names.
-   *
-   * @return a collection of engine names or <code>null</code> if meta/global
-   *         was malformed.
-   */
-  public Set<String> getEnabledEngineNames() {
-    if (engines == null) {
-      return null;
-    }
-    return new HashSet<String>(engines.keySet());
-  }
-
-  @SuppressWarnings("unchecked")
-  public Set<String> getDeclinedEngineNames() {
-    if (declined == null) {
-      return null;
-    }
-    return new HashSet<String>(declined);
-  }
-
-  /**
-   * Returns if the server settings and local settings match.
-   * Throws a specific MetaGlobalException if that's not the case.
-   */
-  public void verifyEngineSettings(String engineName, EngineSettings engineSettings)
-  throws MetaGlobalException {
-
-    // We use syncIDs as our canary.
-    if (syncIDs == null) {
-      throw new IllegalStateException("No meta/global record yet processed.");
-    }
-
-    if (engineSettings == null) {
-      throw new IllegalArgumentException("engineSettings cannot be null.");
-    }
-
-    // First, see if we had a parsing problem.
-    final MetaGlobalException exception = exceptions.get(engineName);
-    if (exception != null) {
-      throw exception;
-    }
-
-    final String syncID = syncIDs.get(engineName);
-    if (syncID == null) {
-      // We have checked engineName against enabled engine names before this, so
-      // we should either have a syncID or an exception for this engine already.
-      throw new IllegalArgumentException("Unknown engine " + engineName);
-    }
-
-    // Since we don't have an exception, and we do have a syncID, we should have a version.
-    final Integer version = versions.get(engineName);
-    if (version > engineSettings.version) {
-      // We're out of date.
-      throw new MetaGlobalException.MetaGlobalStaleClientVersionException(version);
-    }
-
-    if (!syncID.equals(engineSettings.syncID)) {
-      // Our syncID is wrong. Reset client and take the server syncID.
-      throw new MetaGlobalException.MetaGlobalStaleClientSyncIDException(syncID);
-    }
-  }
-
-  public String getSyncID() {
-    return syncID;
-  }
-
-  public void setSyncID(String syncID) {
-    this.syncID = syncID;
-  }
-
-  // SyncStorageRequestDelegate methods for fetching.
-  public String credentials() {
-    return null;
-  }
-
-  @Override
-  public AuthHeaderProvider getAuthHeaderProvider() {
-    return authHeaderProvider;
-  }
-
-  @Override
-  public String ifUnmodifiedSince() {
-    return null;
-  }
-
-  @Override
-  public void handleRequestSuccess(SyncStorageResponse response) {
-    if (this.isUploading) {
-      this.handleUploadSuccess(response);
-    } else {
-      this.handleDownloadSuccess(response);
-    }
-  }
-
-  private void handleUploadSuccess(SyncStorageResponse response) {
-    this.callback.handleSuccess(this, response);
-  }
-
-  private void handleDownloadSuccess(SyncStorageResponse response) {
-    if (response.wasSuccessful()) {
-      try {
-        CryptoRecord record = CryptoRecord.fromJSONRecord(response.jsonObjectBody());
-        this.setFromRecord(record);
-        this.callback.handleSuccess(this, response);
-      } catch (Exception e) {
-        this.callback.handleError(e);
-      }
-      return;
-    }
-    this.callback.handleFailure(response);
-  }
-
-  @Override
-  public void handleRequestFailure(SyncStorageResponse response) {
-    if (response.getStatusCode() == 404) {
-      this.callback.handleMissing(this, response);
-      return;
-    }
-    this.callback.handleFailure(response);
-  }
-
-  @Override
-  public void handleRequestError(Exception e) {
-    this.callback.handleError(e);
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/MetaGlobalException.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/MetaGlobalException.java
deleted file mode 100644
index bec531d..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/MetaGlobalException.java
+++ /dev/null
@@ -1,45 +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.sync;
-
-public class MetaGlobalException extends SyncException {
-  private static final long serialVersionUID = -6182315615113508925L;
-
-  public static class MetaGlobalMalformedSyncIDException extends MetaGlobalException {
-    private static final long serialVersionUID = 1L;
-  }
-
-  public static class MetaGlobalMalformedVersionException extends MetaGlobalException {
-    private static final long serialVersionUID = 1L;
-  }
-
-  public static class MetaGlobalOutdatedVersionException extends MetaGlobalException {
-    private static final long serialVersionUID = 1L;
-  }
-
-  public static class MetaGlobalStaleClientVersionException extends MetaGlobalException {
-    private static final long serialVersionUID = 1L;
-    public final int serverVersion;
-    public MetaGlobalStaleClientVersionException(final int version) {
-      this.serverVersion = version;
-    }
-  }
-
-  public static class MetaGlobalStaleClientSyncIDException extends MetaGlobalException {
-    private static final long serialVersionUID = 1L;
-    public final String serverSyncID;
-    public MetaGlobalStaleClientSyncIDException(final String syncID) {
-      this.serverSyncID = syncID;
-    }
-  }
-
-  public static class MetaGlobalEngineStateChangedException extends MetaGlobalException {
-    private static final long serialVersionUID = 1L;
-    public final boolean isEnabled;
-    public MetaGlobalEngineStateChangedException(boolean isEnabled) {
-      this.isEnabled = isEnabled;
-    }
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/MetaGlobalMissingEnginesException.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/MetaGlobalMissingEnginesException.java
deleted file mode 100644
index 91bfd2f..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/MetaGlobalMissingEnginesException.java
+++ /dev/null
@@ -1,9 +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.sync;
-
-public class MetaGlobalMissingEnginesException extends MetaGlobalException {
-  private static final long serialVersionUID = -2662107402622277865L;
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/MetaGlobalNotSetException.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/MetaGlobalNotSetException.java
deleted file mode 100644
index ef059c7..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/MetaGlobalNotSetException.java
+++ /dev/null
@@ -1,9 +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.sync;
-
-public class MetaGlobalNotSetException extends MetaGlobalException {
-  private static final long serialVersionUID = 2959032409571832970L;
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/NoCollectionKeysSetException.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/NoCollectionKeysSetException.java
deleted file mode 100644
index 323e355..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/NoCollectionKeysSetException.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.sync;
-
-import android.content.SyncResult;
-
-public class NoCollectionKeysSetException extends SyncException {
-  private static final long serialVersionUID = -6185128075412771120L;
-
-  @Override
-  public void updateStats(GlobalSession globalSession, SyncResult syncResult) {
-    syncResult.stats.numAuthExceptions++;
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/NodeAuthenticationException.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/NodeAuthenticationException.java
deleted file mode 100644
index a5cd5f0..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/NodeAuthenticationException.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.sync;
-
-import android.content.SyncResult;
-
-public class NodeAuthenticationException extends SyncException {
-  private static final long serialVersionUID = 8156745873212364352L;
-
-  @Override
-  public void updateStats(GlobalSession globalSession, SyncResult syncResult) {
-    syncResult.stats.numAuthExceptions++;
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/NonArrayJSONException.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/NonArrayJSONException.java
deleted file mode 100644
index 554645b..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/NonArrayJSONException.java
+++ /dev/null
@@ -1,17 +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.sync;
-
-public class NonArrayJSONException extends UnexpectedJSONException {
-  private static final long serialVersionUID = 5582918057432365749L;
-
-  public NonArrayJSONException(String detailMessage) {
-    super(detailMessage);
-  }
-
-  public NonArrayJSONException(Throwable throwable) {
-    super(throwable);
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/NonObjectJSONException.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/NonObjectJSONException.java
deleted file mode 100644
index fd50d46..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/NonObjectJSONException.java
+++ /dev/null
@@ -1,17 +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.sync;
-
-public class NonObjectJSONException extends UnexpectedJSONException {
-  private static final long serialVersionUID = 2214238763035650087L;
-
-  public NonObjectJSONException(String detailMessage) {
-    super(detailMessage);
-  }
-
-  public NonObjectJSONException(Throwable throwable) {
-    super(throwable);
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/NullClusterURLException.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/NullClusterURLException.java
deleted file mode 100644
index c1d8833..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/NullClusterURLException.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.sync;
-
-import android.content.SyncResult;
-
-public class NullClusterURLException extends SyncException {
-  private static final long serialVersionUID = 4277845518548393161L;
-
-  @Override
-  public void updateStats(GlobalSession globalSession, SyncResult syncResult) {
-    syncResult.stats.numAuthExceptions++;
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/PersistedMetaGlobal.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/PersistedMetaGlobal.java
deleted file mode 100644
index d346754..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/PersistedMetaGlobal.java
+++ /dev/null
@@ -1,86 +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.sync;
-
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.sync.net.AuthHeaderProvider;
-
-import android.content.SharedPreferences;
-
-public class PersistedMetaGlobal {
-  public static final String LOG_TAG = "PersistedMetaGlobal";
-
-  public static final String META_GLOBAL_SERVER_RESPONSE_BODY = "metaGlobalServerResponseBody";
-  public static final String META_GLOBAL_LAST_MODIFIED        = "metaGlobalLastModified";
-
-  protected SharedPreferences prefs;
-
-  public PersistedMetaGlobal(SharedPreferences prefs) {
-    this.prefs = prefs;
-  }
-
-  /**
-   * Sets a <code>MetaGlobal</code> from persisted prefs.
-   *
-   * @param metaUrl
-   *          meta/global server URL
-   * @param credentials
-   *          Sync credentials
-   *
-   * @return <MetaGlobal> set from previously fetched meta/global record from
-   *         server
-   */
-  public MetaGlobal metaGlobal(String metaUrl, AuthHeaderProvider authHeaderProvider) {
-    String json = prefs.getString(META_GLOBAL_SERVER_RESPONSE_BODY, null);
-    if (json == null) {
-      return null;
-    }
-    MetaGlobal metaGlobal = null;
-    try {
-      CryptoRecord cryptoRecord = CryptoRecord.fromJSONRecord(json);
-      MetaGlobal mg = new MetaGlobal(metaUrl, authHeaderProvider);
-      mg.setFromRecord(cryptoRecord);
-      metaGlobal = mg;
-    } catch (Exception e) {
-      Logger.warn(LOG_TAG, "Got exception decrypting persisted meta/global.", e);
-    }
-    return metaGlobal;
-  }
-
-  public void persistMetaGlobal(MetaGlobal metaGlobal) {
-    if (metaGlobal == null) {
-      Logger.debug(LOG_TAG, "Clearing persisted meta/global.");
-      prefs.edit().remove(META_GLOBAL_SERVER_RESPONSE_BODY).commit();
-      return;
-    }
-    try {
-      CryptoRecord cryptoRecord = metaGlobal.asCryptoRecord();
-      String json = cryptoRecord.toJSONString();
-      Logger.debug(LOG_TAG, "Persisting meta/global.");
-      prefs.edit().putString(META_GLOBAL_SERVER_RESPONSE_BODY, json).commit();
-    } catch (Exception e) {
-      Logger.warn(LOG_TAG, "Got exception encrypting while persisting meta/global.", e);
-    }
-  }
-
-  public long lastModified() {
-    return prefs.getLong(META_GLOBAL_LAST_MODIFIED, -1);
-  }
-
-  public void persistLastModified(long lastModified) {
-    if (lastModified <= 0) {
-      Logger.debug(LOG_TAG, "Clearing persisted meta/global last modified timestamp.");
-      prefs.edit().remove(META_GLOBAL_LAST_MODIFIED).commit();
-      return;
-    }
-    Logger.debug(LOG_TAG, "Persisting meta/global last modified timestamp " + lastModified + ".");
-    prefs.edit().putLong(META_GLOBAL_LAST_MODIFIED, lastModified).commit();
-  }
-
-  public void purge() {
-    persistLastModified(-1);
-    persistMetaGlobal(null);
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/PrefsBackoffHandler.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/PrefsBackoffHandler.java
deleted file mode 100644
index 63f6446..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/PrefsBackoffHandler.java
+++ /dev/null
@@ -1,59 +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.sync;
-
-import android.content.SharedPreferences;
-import android.content.SharedPreferences.Editor;
-
-public class PrefsBackoffHandler implements BackoffHandler {
-  public static final String PREF_EARLIEST_NEXT = "earliestnext";
-
-  private final SharedPreferences prefs;
-  private final String prefEarliest;
-
-  public PrefsBackoffHandler(final SharedPreferences prefs, final String prefSuffix) {
-    if (prefs == null) {
-      throw new IllegalArgumentException("prefs must not be null.");
-    }
-    this.prefs = prefs;
-    this.prefEarliest = PREF_EARLIEST_NEXT + "." + prefSuffix;
-  }
-
-  @Override
-  public synchronized long getEarliestNextRequest() {
-    return prefs.getLong(prefEarliest, 0);
-  }
-
-  @Override
-  public synchronized void setEarliestNextRequest(final long next) {
-    final Editor edit = prefs.edit();
-    edit.putLong(prefEarliest, next);
-    edit.commit();
-  }
-
-  @Override
-  public synchronized void extendEarliestNextRequest(final long next) {
-    if (prefs.getLong(prefEarliest, 0) >= next) {
-      return;
-    }
-    final Editor edit = prefs.edit();
-    edit.putLong(prefEarliest, next);
-    edit.commit();
-  }
-
-  /**
-   * Return the number of milliseconds until we're allowed to touch the server again,
-   * or 0 if now is fine.
-   */
-  @Override
-  public long delayMilliseconds() {
-    long earliestNextRequest = getEarliestNextRequest();
-    if (earliestNextRequest <= 0) {
-      return 0;
-    }
-    long now = System.currentTimeMillis();
-    return Math.max(0, earliestNextRequest - now);
-  }
-}
\ No newline at end of file
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/README.txt b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/README.txt
deleted file mode 100644
index cf4624c..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/README.txt
+++ /dev/null
@@ -1 +0,0 @@
-These files are managed in the android-sync repo. Do not modify directly, or your changes will be lost.
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/Server11PreviousPostFailedException.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/Server11PreviousPostFailedException.java
deleted file mode 100644
index 4ea77f3..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/Server11PreviousPostFailedException.java
+++ /dev/null
@@ -1,12 +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.sync;
-
-/**
- * A previous POST failed, so we won't send any more records this session.
- */
-public class Server11PreviousPostFailedException extends SyncException {
-  private static final long serialVersionUID = -3582490631414624310L;
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/Server11RecordPostFailedException.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/Server11RecordPostFailedException.java
deleted file mode 100644
index d654d31..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/Server11RecordPostFailedException.java
+++ /dev/null
@@ -1,12 +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.sync;
-
-/**
- * The server rejected a record in its "failure" array.
- */
-public class Server11RecordPostFailedException extends SyncException {
-  private static final long serialVersionUID = -8517471217486190314L;
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/SharedPreferencesClientsDataDelegate.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/SharedPreferencesClientsDataDelegate.java
deleted file mode 100644
index 4c1584d..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/SharedPreferencesClientsDataDelegate.java
+++ /dev/null
@@ -1,121 +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.sync;
-
-import org.mozilla.gecko.background.fxa.FxAccountUtils;
-import org.mozilla.gecko.fxa.FirefoxAccounts;
-import org.mozilla.gecko.fxa.FxAccountDeviceRegistrator;
-import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
-import org.mozilla.gecko.sync.delegates.ClientsDataDelegate;
-import org.mozilla.gecko.util.HardwareUtils;
-
-import android.accounts.Account;
-import android.content.Context;
-import android.content.SharedPreferences;
-
-/**
- * A <code>ClientsDataDelegate</code> implementation that persists to a
- * <code>SharedPreferences</code> instance.
- */
-public class SharedPreferencesClientsDataDelegate implements ClientsDataDelegate {
-  protected final SharedPreferences sharedPreferences;
-  protected final Context context;
-
-  public SharedPreferencesClientsDataDelegate(SharedPreferences sharedPreferences, Context context) {
-    this.sharedPreferences = sharedPreferences;
-    this.context = context;
-
-    // It's safe to init this multiple times.
-    HardwareUtils.init(context);
-  }
-
-  @Override
-  public synchronized String getAccountGUID() {
-    String accountGUID = sharedPreferences.getString(SyncConfiguration.PREF_ACCOUNT_GUID, null);
-    if (accountGUID == null) {
-      accountGUID = Utils.generateGuid();
-      sharedPreferences.edit().putString(SyncConfiguration.PREF_ACCOUNT_GUID, accountGUID).commit();
-    }
-    return accountGUID;
-  }
-
-  private synchronized void saveClientNameToSharedPreferences(String clientName, long now) {
-    sharedPreferences
-            .edit()
-            .putString(SyncConfiguration.PREF_CLIENT_NAME, clientName)
-            .putLong(SyncConfiguration.PREF_CLIENT_DATA_TIMESTAMP, now)
-            .apply();
-  }
-
-  /**
-   * Set client name.
-   *
-   * @param clientName to change to.
-   */
-  @Override
-  public synchronized void setClientName(String clientName, long now) {
-    saveClientNameToSharedPreferences(clientName, now);
-
-    // Update the FxA device registration
-    final Account account = FirefoxAccounts.getFirefoxAccount(context);
-    if (account != null) {
-      final AndroidFxAccount fxAccount = new AndroidFxAccount(context, account);
-      fxAccount.resetDeviceRegistrationVersion();
-    }
-  }
-
-  @Override
-  public String getDefaultClientName() {
-    return FxAccountUtils.defaultClientName(context);
-  }
-
-  @Override
-  public synchronized String getClientName() {
-    String clientName = sharedPreferences.getString(SyncConfiguration.PREF_CLIENT_NAME, null);
-    if (clientName == null) {
-      clientName = getDefaultClientName();
-      long now = System.currentTimeMillis();
-      saveClientNameToSharedPreferences(clientName, now); // Save locally only to avoid a recursion loop
-    }
-    return clientName;
-  }
-
-  @Override
-  public synchronized void setClientsCount(int clientsCount) {
-    sharedPreferences.edit().putLong(SyncConfiguration.PREF_NUM_CLIENTS, clientsCount).commit();
-  }
-
-  @Override
-  public boolean isLocalGUID(String guid) {
-    return getAccountGUID().equals(guid);
-  }
-
-  @Override
-  public synchronized int getClientsCount() {
-    return (int) sharedPreferences.getLong(SyncConfiguration.PREF_NUM_CLIENTS, 0);
-  }
-
-  @Override
-  public long getLastModifiedTimestamp() {
-    return sharedPreferences.getLong(SyncConfiguration.PREF_CLIENT_DATA_TIMESTAMP, 0);
-  }
-
-  @Override
-  public String getFormFactor() {
-    if (HardwareUtils.isLargeTablet()) {
-      return "largetablet";
-    }
-
-    if (HardwareUtils.isSmallTablet()) {
-      return "smalltablet";
-    }
-
-    if (HardwareUtils.isTelevision()) {
-      return "tv";
-    }
-
-    return "phone";
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/Sync11Configuration.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/Sync11Configuration.java
deleted file mode 100644
index 4b22808..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/Sync11Configuration.java
+++ /dev/null
@@ -1,84 +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.sync;
-
-import java.net.URI;
-
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.sync.crypto.KeyBundle;
-import org.mozilla.gecko.sync.net.AuthHeaderProvider;
-
-import android.content.SharedPreferences;
-import android.content.SharedPreferences.Editor;
-
-/**
- * Override SyncConfiguration to restore the old behavior of clusterURL --
- * that is, a URL without the protocol version etc.
- *
- */
-public class Sync11Configuration extends SyncConfiguration {
-  private static final String LOG_TAG = "Sync11Configuration";
-  private static final String API_VERSION = "1.1";
-
-  public Sync11Configuration(String username,
-                             AuthHeaderProvider authHeaderProvider,
-                             SharedPreferences prefs) {
-    super(username, authHeaderProvider, prefs);
-  }
-
-  public Sync11Configuration(String username,
-                             AuthHeaderProvider authHeaderProvider,
-                             SharedPreferences prefs,
-                             KeyBundle keyBundle) {
-    super(username, authHeaderProvider, prefs, keyBundle);
-  }
-
-  @Override
-  public String getAPIVersion() {
-    return API_VERSION;
-  }
-
-  @Override
-  public String storageURL() {
-    return clusterURL + API_VERSION + "/" + username + "/storage";
-  }
-
-  @Override
-  protected String infoBaseURL() {
-    return clusterURL + API_VERSION + "/" + username + "/info/";
-  }
-
-  protected void setAndPersistClusterURL(URI u, SharedPreferences prefs) {
-    boolean shouldPersist = (prefs != null) && (clusterURL == null);
-
-    Logger.trace(LOG_TAG, "Setting cluster URL to " + u.toASCIIString() +
-                          (shouldPersist ? ". Persisting." : ". Not persisting."));
-    clusterURL = u;
-    if (shouldPersist) {
-      Editor edit = prefs.edit();
-      edit.putString(PREF_CLUSTER_URL, clusterURL.toASCIIString());
-      edit.commit();
-    }
-  }
-
-  protected void setClusterURL(URI u, SharedPreferences prefs) {
-    if (u == null) {
-      Logger.warn(LOG_TAG, "Refusing to set cluster URL to null.");
-      return;
-    }
-    URI uri = u.normalize();
-    if (uri.toASCIIString().endsWith("/")) {
-      setAndPersistClusterURL(u, prefs);
-      return;
-    }
-    setAndPersistClusterURL(uri.resolve("/"), prefs);
-    Logger.trace(LOG_TAG, "Set cluster URL to " + clusterURL.toASCIIString() + ", given input " + u.toASCIIString());
-  }
-
-  @Override
-  public void setClusterURL(URI u) {
-    setClusterURL(u, this.getPrefs());
-  }
-}
\ No newline at end of file
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/SyncConfiguration.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/SyncConfiguration.java
deleted file mode 100644
index 53edf5f..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/SyncConfiguration.java
+++ /dev/null
@@ -1,480 +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.sync;
-
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.Set;
-
-import org.mozilla.gecko.background.common.PrefsBranch;
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.sync.crypto.KeyBundle;
-import org.mozilla.gecko.sync.crypto.PersistedCrypto5Keys;
-import org.mozilla.gecko.sync.net.AuthHeaderProvider;
-import org.mozilla.gecko.sync.stage.GlobalSyncStage.Stage;
-
-import android.content.SharedPreferences;
-import android.content.SharedPreferences.Editor;
-
-public class SyncConfiguration {
-  private static final String LOG_TAG = "SyncConfiguration";
-
-  // These must be set in GlobalSession's constructor.
-  public URI             clusterURL;
-  public KeyBundle       syncKeyBundle;
-
-  public InfoConfiguration infoConfiguration;
-
-  public CollectionKeys  collectionKeys;
-  public InfoCollections infoCollections;
-  public MetaGlobal      metaGlobal;
-  public String          syncID;
-
-  protected final String username;
-
-  /**
-   * Persisted collection of enabledEngineNames.
-   * <p>
-   * Can contain engines Android Sync is not currently aware of, such as "prefs"
-   * or "addons".
-   * <p>
-   * Copied from latest downloaded meta/global record and used to generate a
-   * fresh meta/global record for upload.
-   */
-  public Set<String> enabledEngineNames;
-  public Set<String> declinedEngineNames = new HashSet<String>();
-
-  /**
-   * Names of stages to sync <it>this sync</it>, or <code>null</code> to sync
-   * all known stages.
-   * <p>
-   * Generated <it>each sync</it> from extras bundle passed to
-   * <code>SyncAdapter.onPerformSync</code> and not persisted.
-   * <p>
-   * Not synchronized! Set this exactly once per global session and don't modify
-   * it -- especially not from multiple threads.
-   */
-  public Collection<String> stagesToSync;
-
-  /**
-   * Engines whose sync state has been modified by the user through
-   * SelectEnginesActivity, where each key-value pair is an engine name and
-   * its sync state.
-   *
-   * This differs from <code>enabledEngineNames</code> in that
-   * <code>enabledEngineNames</code> reflects the downloaded meta/global,
-   * whereas <code>userSelectedEngines</code> stores the differences in engines to
-   * sync that the user has selected.
-   *
-   * Each engine stage will check for engine changes at the beginning of the
-   * stage.
-   *
-   * If no engine sync state changes have been made by the user, userSelectedEngines
-   * will be null, and Sync will proceed normally.
-   *
-   * If the user has made changes to engine syncing state, each engine will sync
-   * according to the sync state specified in userSelectedEngines and propagate that
-   * state to meta/global, to be uploaded.
-   */
-  public Map<String, Boolean> userSelectedEngines;
-  public long userSelectedEnginesTimestamp;
-
-  public SharedPreferences prefs;
-
-  protected final AuthHeaderProvider authHeaderProvider;
-
-  public static final String PREF_PREFS_VERSION = "prefs.version";
-  public static final long CURRENT_PREFS_VERSION = 1;
-
-  public static final String CLIENTS_COLLECTION_TIMESTAMP = "serverClientsTimestamp";  // When the collection was touched.
-  public static final String CLIENT_RECORD_TIMESTAMP = "serverClientRecordTimestamp";  // When our record was touched.
-  public static final String MIGRATION_SENTINEL_CHECK_TIMESTAMP = "migrationSentinelCheckTimestamp";  // When we last looked in meta/fxa_credentials.
-
-  public static final String PREF_CLUSTER_URL = "clusterURL";
-  public static final String PREF_SYNC_ID = "syncID";
-
-  public static final String PREF_ENABLED_ENGINE_NAMES = "enabledEngineNames";
-  public static final String PREF_DECLINED_ENGINE_NAMES = "declinedEngineNames";
-  public static final String PREF_USER_SELECTED_ENGINES_TO_SYNC = "userSelectedEngines";
-  public static final String PREF_USER_SELECTED_ENGINES_TO_SYNC_TIMESTAMP = "userSelectedEnginesTimestamp";
-
-  public static final String PREF_CLUSTER_URL_IS_STALE = "clusterurlisstale";
-
-  public static final String PREF_ACCOUNT_GUID = "account.guid";
-  public static final String PREF_CLIENT_NAME = "account.clientName";
-  public static final String PREF_NUM_CLIENTS = "account.numClients";
-  public static final String PREF_CLIENT_DATA_TIMESTAMP = "account.clientDataTimestamp";
-
-  private static final String API_VERSION = "1.5";
-
-  public SyncConfiguration(String username, AuthHeaderProvider authHeaderProvider, SharedPreferences prefs) {
-    this.username = username;
-    this.authHeaderProvider = authHeaderProvider;
-    this.prefs = prefs;
-    this.loadFromPrefs(prefs);
-  }
-
-  public SyncConfiguration(String username, AuthHeaderProvider authHeaderProvider, SharedPreferences prefs, KeyBundle syncKeyBundle) {
-    this(username, authHeaderProvider, prefs);
-    this.syncKeyBundle = syncKeyBundle;
-  }
-
-  public String getAPIVersion() {
-    return API_VERSION;
-  }
-
-  public SharedPreferences getPrefs() {
-    return this.prefs;
-  }
-
-  /**
-   * Valid engines supported by Android Sync.
-   *
-   * @return Set<String> of valid engine names that Android Sync implements.
-   */
-  public static Set<String> validEngineNames() {
-    Set<String> engineNames = new HashSet<String>();
-    for (Stage stage : Stage.getNamedStages()) {
-      engineNames.add(stage.getRepositoryName());
-    }
-    return engineNames;
-  }
-
-  /**
-   * Return a convenient accessor for part of prefs.
-   * @return
-   *        A PrefsBranch object representing this
-   *        section of the preferences space.
-   */
-  public PrefsBranch getBranch(String prefix) {
-    return new PrefsBranch(this.getPrefs(), prefix);
-  }
-
-  /**
-   * Gets the engine names that are enabled, declined, or other (depending on pref) in meta/global.
-   *
-   * @param prefs
-   *          SharedPreferences that the engines are associated with.
-   * @param pref
-   *          The preference name to use. E.g, PREF_ENABLED_ENGINE_NAMES.
-   * @return Set<String> of the enabled engine names if they have been stored,
-   *         or null otherwise.
-   */
-  protected static Set<String> getEngineNamesFromPref(SharedPreferences prefs, String pref) {
-    final String json = prefs.getString(pref, null);
-    if (json == null) {
-      return null;
-    }
-    try {
-      final ExtendedJSONObject o = new ExtendedJSONObject(json);
-      return new HashSet<String>(o.keySet());
-    } catch (Exception e) {
-      return null;
-    }
-  }
-
-  /**
-   * Returns the set of engine names that the user has enabled. If none
-   * have been stored in prefs, <code>null</code> is returned.
-   */
-  public static Set<String> getEnabledEngineNames(SharedPreferences prefs) {
-      return getEngineNamesFromPref(prefs, PREF_ENABLED_ENGINE_NAMES);
-  }
-
-  /**
-   * Returns the set of engine names that the user has declined.
-   */
-  public static Set<String> getDeclinedEngineNames(SharedPreferences prefs) {
-    final Set<String> names = getEngineNamesFromPref(prefs, PREF_DECLINED_ENGINE_NAMES);
-    if (names == null) {
-        return new HashSet<String>();
-    }
-    return names;
-  }
-
-  /**
-   * Gets the engines whose sync states have been changed by the user through the
-   * SelectEnginesActivity.
-   *
-   * @param prefs
-   *          SharedPreferences of account that the engines are associated with.
-   * @return Map<String, Boolean> of changed engines. Key is the lower-cased
-   *         engine name, Value is the new sync state.
-   */
-  public static Map<String, Boolean> getUserSelectedEngines(SharedPreferences prefs) {
-    String json = prefs.getString(PREF_USER_SELECTED_ENGINES_TO_SYNC, null);
-    if (json == null) {
-      return null;
-    }
-    try {
-      ExtendedJSONObject o = new ExtendedJSONObject(json);
-      Map<String, Boolean> map = new HashMap<String, Boolean>();
-      for (Entry<String, Object> e : o.entrySet()) {
-        String key = e.getKey();
-        Boolean value = (Boolean) e.getValue();
-        map.put(key, value);
-        // Forms depends on history. Add forms if history is selected.
-        if ("history".equals(key)) {
-          map.put("forms", value);
-        }
-      }
-      // Sanity check: remove forms if history does not exist.
-      if (!map.containsKey("history")) {
-        map.remove("forms");
-      }
-      return map;
-    } catch (Exception e) {
-      return null;
-    }
-  }
-
-  /**
-   * Store a Map of engines and their sync states to prefs.
-   *
-   * Any engine that's disabled in the input is also recorded
-   * as a declined engine, overwriting the stored values.
-   *
-   * @param prefs
-   *          SharedPreferences that the engines are associated with.
-   * @param selectedEngines
-   *          Map<String, Boolean> of engine name to sync state
-   */
-  public static void storeSelectedEnginesToPrefs(SharedPreferences prefs, Map<String, Boolean> selectedEngines) {
-    ExtendedJSONObject jObj = new ExtendedJSONObject();
-    HashSet<String> declined = new HashSet<String>();
-    for (Entry<String, Boolean> e : selectedEngines.entrySet()) {
-      final Boolean enabled = e.getValue();
-      final String engine = e.getKey();
-      jObj.put(engine, enabled);
-      if (!enabled) {
-        declined.add(engine);
-      }
-    }
-
-    // Our history checkbox drives form history, too.
-    // We don't need to do this for enablement: that's done at retrieval time.
-    if (selectedEngines.containsKey("history") && !selectedEngines.get("history")) {
-      declined.add("forms");
-    }
-
-    String json = jObj.toJSONString();
-    long currentTime = System.currentTimeMillis();
-    Editor edit = prefs.edit();
-    edit.putString(PREF_USER_SELECTED_ENGINES_TO_SYNC, json);
-    edit.putString(PREF_DECLINED_ENGINE_NAMES, setToJSONObjectString(declined));
-    edit.putLong(PREF_USER_SELECTED_ENGINES_TO_SYNC_TIMESTAMP, currentTime);
-    Logger.error(LOG_TAG, "Storing user-selected engines at [" + currentTime + "].");
-    edit.commit();
-  }
-
-  public void loadFromPrefs(SharedPreferences prefs) {
-    if (prefs.contains(PREF_CLUSTER_URL)) {
-      String u = prefs.getString(PREF_CLUSTER_URL, null);
-      try {
-        clusterURL = new URI(u);
-        Logger.trace(LOG_TAG, "Set clusterURL from bundle: " + u);
-      } catch (URISyntaxException e) {
-        Logger.warn(LOG_TAG, "Ignoring bundle clusterURL (" + u + "): invalid URI.", e);
-      }
-    }
-    if (prefs.contains(PREF_SYNC_ID)) {
-      syncID = prefs.getString(PREF_SYNC_ID, null);
-      Logger.trace(LOG_TAG, "Set syncID from bundle: " + syncID);
-    }
-    enabledEngineNames = getEnabledEngineNames(prefs);
-    declinedEngineNames = getDeclinedEngineNames(prefs);
-    userSelectedEngines = getUserSelectedEngines(prefs);
-    userSelectedEnginesTimestamp = prefs.getLong(PREF_USER_SELECTED_ENGINES_TO_SYNC_TIMESTAMP, 0);
-    // We don't set crypto/keys here because we need the syncKeyBundle to decrypt the JSON
-    // and we won't have it on construction.
-    // TODO: MetaGlobal, password, infoCollections.
-  }
-
-  public void persistToPrefs() {
-    this.persistToPrefs(this.getPrefs());
-  }
-
-  private static String setToJSONObjectString(Set<String> set) {
-    ExtendedJSONObject o = new ExtendedJSONObject();
-    for (String name : set) {
-      o.put(name, 0);
-    }
-    return o.toJSONString();
-  }
-
-  public void persistToPrefs(SharedPreferences prefs) {
-    Editor edit = prefs.edit();
-    if (clusterURL == null) {
-      edit.remove(PREF_CLUSTER_URL);
-    } else {
-      edit.putString(PREF_CLUSTER_URL, clusterURL.toASCIIString());
-    }
-    if (syncID != null) {
-      edit.putString(PREF_SYNC_ID, syncID);
-    }
-    if (enabledEngineNames == null) {
-      edit.remove(PREF_ENABLED_ENGINE_NAMES);
-    } else {
-      edit.putString(PREF_ENABLED_ENGINE_NAMES, setToJSONObjectString(enabledEngineNames));
-    }
-    if (declinedEngineNames == null || declinedEngineNames.isEmpty()) {
-      edit.remove(PREF_DECLINED_ENGINE_NAMES);
-    } else {
-      edit.putString(PREF_DECLINED_ENGINE_NAMES, setToJSONObjectString(declinedEngineNames));
-    }
-    if (userSelectedEngines == null) {
-      edit.remove(PREF_USER_SELECTED_ENGINES_TO_SYNC);
-      edit.remove(PREF_USER_SELECTED_ENGINES_TO_SYNC_TIMESTAMP);
-    }
-    // Don't bother saving userSelectedEngines - these should only be changed by
-    // SelectEnginesActivity.
-    edit.commit();
-    // TODO: keys.
-  }
-
-  public AuthHeaderProvider getAuthHeaderProvider() {
-    return authHeaderProvider;
-  }
-
-  public CollectionKeys getCollectionKeys() {
-    return collectionKeys;
-  }
-
-  public void setCollectionKeys(CollectionKeys k) {
-    collectionKeys = k;
-  }
-
-  /**
-   * Return path to storage endpoint without trailing slash.
-   *
-   * @return storage endpoint without trailing slash.
-   */
-  public String storageURL() {
-    return clusterURL + "/storage";
-  }
-
-  protected String infoBaseURL() {
-    return clusterURL + "/info/";
-  }
-
-  public String infoCollectionsURL() {
-    return infoBaseURL() + "collections";
-  }
-
-  public String infoConfigurationURL() {
-    return infoBaseURL() + "configuration";
-  }
-
-  public String infoCollectionCountsURL() {
-    return infoBaseURL() + "collection_counts";
-  }
-
-  public String metaURL() {
-    return storageURL() + "/meta/global";
-  }
-
-  public URI collectionURI(String collection) throws URISyntaxException {
-    return new URI(storageURL() + "/" + collection);
-  }
-
-  public URI collectionURI(String collection, boolean full) throws URISyntaxException {
-    // Do it this way to make it easier to add more params later.
-    // It's pretty ugly, I'll grant.
-    boolean anyParams = full;
-    String  uriParams = "";
-    if (anyParams) {
-      StringBuilder params = new StringBuilder("?");
-      if (full) {
-        params.append("full=1");
-      }
-      uriParams = params.toString();
-    }
-    String uri = storageURL() + "/" + collection + uriParams;
-    return new URI(uri);
-  }
-
-  public URI wboURI(String collection, String id) throws URISyntaxException {
-    return new URI(storageURL() + "/" + collection + "/" + id);
-  }
-
-  public URI keysURI() throws URISyntaxException {
-    return wboURI("crypto", "keys");
-  }
-
-  public URI getClusterURL() {
-    return clusterURL;
-  }
-
-  public String getClusterURLString() {
-    if (clusterURL == null) {
-      return null;
-    }
-    return clusterURL.toASCIIString();
-  }
-
-  public void setClusterURL(URI u) {
-    this.clusterURL = u;
-  }
-
-  /**
-   * Used for direct management of related prefs.
-   */
-  public Editor getEditor() {
-    return this.getPrefs().edit();
-  }
-
-  /**
-   * We persist two different clients timestamps: our own record's,
-   * and the timestamp for the collection.
-   */
-  public void persistServerClientRecordTimestamp(long timestamp) {
-    getEditor().putLong(SyncConfiguration.CLIENT_RECORD_TIMESTAMP, timestamp).commit();
-  }
-
-  public long getPersistedServerClientRecordTimestamp() {
-    return getPrefs().getLong(SyncConfiguration.CLIENT_RECORD_TIMESTAMP, 0L);
-  }
-
-  public void persistServerClientsTimestamp(long timestamp) {
-    getEditor().putLong(SyncConfiguration.CLIENTS_COLLECTION_TIMESTAMP, timestamp).commit();
-  }
-
-  public long getPersistedServerClientsTimestamp() {
-    return getPrefs().getLong(SyncConfiguration.CLIENTS_COLLECTION_TIMESTAMP, 0L);
-  }
-
-  public void persistLastMigrationSentinelCheckTimestamp(long timestamp) {
-    getEditor().putLong(SyncConfiguration.MIGRATION_SENTINEL_CHECK_TIMESTAMP, timestamp).commit();
-  }
-
-  public long getLastMigrationSentinelCheckTimestamp() {
-    return getPrefs().getLong(SyncConfiguration.MIGRATION_SENTINEL_CHECK_TIMESTAMP, 0L);
-  }
-
-  public void purgeCryptoKeys() {
-    if (collectionKeys != null) {
-      collectionKeys.clear();
-    }
-    persistedCryptoKeys().purge();
-  }
-
-  public void purgeMetaGlobal() {
-    metaGlobal = null;
-    persistedMetaGlobal().purge();
-  }
-
-  public PersistedCrypto5Keys persistedCryptoKeys() {
-    return new PersistedCrypto5Keys(getPrefs(), syncKeyBundle);
-  }
-
-  public PersistedMetaGlobal persistedMetaGlobal() {
-    return new PersistedMetaGlobal(getPrefs());
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/SyncConfigurationException.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/SyncConfigurationException.java
deleted file mode 100644
index 02ba118..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/SyncConfigurationException.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.sync;
-
-import android.content.SyncResult;
-
-public class SyncConfigurationException extends SyncException {
-  private static final long serialVersionUID = 1107080177269358381L;
-
-  @Override
-  public void updateStats(GlobalSession globalSession, SyncResult syncResult) {
-    syncResult.stats.numAuthExceptions++;
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/SyncConstants.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/SyncConstants.java
deleted file mode 100644
index 5dc7b28..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/SyncConstants.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.sync;
-
-import org.mozilla.gecko.AppConstants;
-
-public class SyncConstants {
-  public static final String GLOBAL_LOG_TAG = "FxSync";
-  public static final String SYNC_MAJOR_VERSION  = "1";
-  public static final String SYNC_MINOR_VERSION  = "0";
-  public static final String SYNC_VERSION_STRING = SYNC_MAJOR_VERSION + "." +
-                                                   AppConstants.MOZ_APP_VERSION + "." +
-                                                   SYNC_MINOR_VERSION;
-
-  public static final String USER_AGENT = "Firefox AndroidSync " +
-                                          SYNC_VERSION_STRING + " (" +
-                                          AppConstants.MOZ_APP_UA_NAME + ")";
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/SyncException.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/SyncException.java
deleted file mode 100644
index ee09025..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/SyncException.java
+++ /dev/null
@@ -1,34 +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.sync;
-
-import android.content.SyncResult;
-
-public abstract class SyncException extends Exception {
-  private static final long serialVersionUID = -6928990004393234738L;
-
-  public SyncException() {
-    super();
-  }
-
-  public SyncException(final Throwable e) {
-    super(e);
-  }
-
-  /**
-   * Update sync result statistics with information particular to this
-   * exception.
-   *
-   * @param globalSession
-   *          current session, or null.
-   * @param syncResult
-   *          Android sync result to update.
-   */
-  public void updateStats(GlobalSession globalSession, SyncResult syncResult) {
-    // Assume storage error.
-    // TODO: this logic is overly simplistic.
-    syncResult.databaseError = true;
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/SynchronizerConfiguration.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/SynchronizerConfiguration.java
deleted file mode 100644
index 2b08be9..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/SynchronizerConfiguration.java
+++ /dev/null
@@ -1,68 +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.sync;
-
-import android.content.SharedPreferences.Editor;
-
-import org.mozilla.gecko.background.common.PrefsBranch;
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.sync.repositories.RepositorySessionBundle;
-
-import java.io.IOException;
-
-public class SynchronizerConfiguration {
-  private static final String LOG_TAG = "SynczrConfiguration";
-
-  public String syncID;
-  public RepositorySessionBundle remoteBundle;
-  public RepositorySessionBundle localBundle;
-
-  public SynchronizerConfiguration(PrefsBranch config) throws NonObjectJSONException, IOException {
-    this.load(config);
-  }
-
-  public SynchronizerConfiguration(String syncID, RepositorySessionBundle remoteBundle, RepositorySessionBundle localBundle) {
-    this.syncID       = syncID;
-    this.remoteBundle = remoteBundle;
-    this.localBundle  = localBundle;
-  }
-
-  // This should get partly shuffled back into SyncConfiguration, I think.
-  public void load(PrefsBranch config) throws NonObjectJSONException, IOException {
-    if (config == null) {
-      throw new IllegalArgumentException("config cannot be null.");
-    }
-    String remoteJSON = config.getString("remote", null);
-    String localJSON  = config.getString("local",  null);
-    RepositorySessionBundle rB = new RepositorySessionBundle(remoteJSON);
-    RepositorySessionBundle lB = new RepositorySessionBundle(localJSON);
-    if (remoteJSON == null) {
-      rB.setTimestamp(0);
-    }
-    if (localJSON == null) {
-      lB.setTimestamp(0);
-    }
-    syncID = config.getString("syncID", null);
-    remoteBundle = rB;
-    localBundle  = lB;
-    Logger.debug(LOG_TAG, "Loaded SynchronizerConfiguration. syncID: " + syncID + ", remoteBundle: " + remoteBundle + ", localBundle: " + localBundle);
-  }
-
-  public void persist(PrefsBranch config) {
-    if (config == null) {
-      throw new IllegalArgumentException("config cannot be null.");
-    }
-    String jsonRemote = remoteBundle.toJSONString();
-    String jsonLocal  = localBundle.toJSONString();
-    Editor editor = config.edit();
-    editor.putString("remote", jsonRemote);
-    editor.putString("local",  jsonLocal);
-    editor.putString("syncID", syncID);
-
-    // Synchronous.
-    editor.commit();
-    Logger.debug(LOG_TAG, "Persisted SynchronizerConfiguration. syncID: " + syncID + ", remoteBundle: " + remoteBundle + ", localBundle: " + localBundle);
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/ThreadPool.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/ThreadPool.java
deleted file mode 100644
index 7f20295..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/ThreadPool.java
+++ /dev/null
@@ -1,15 +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.sync;
-
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-
-public class ThreadPool {
-  public static ExecutorService executorService = Executors.newCachedThreadPool();
-  public static void run(Runnable runnable) {
-    executorService.submit(runnable);
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/UnexpectedJSONException.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/UnexpectedJSONException.java
deleted file mode 100644
index e577145..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/UnexpectedJSONException.java
+++ /dev/null
@@ -1,25 +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.sync;
-
-public class UnexpectedJSONException extends Exception {
-  private static final long serialVersionUID = 4797570033096443169L;
-
-  public UnexpectedJSONException(String detailMessage) {
-    super(detailMessage);
-  }
-
-  public UnexpectedJSONException(Throwable throwable) {
-    super(throwable);
-  }
-
-  public static class BadRequiredFieldJSONException extends UnexpectedJSONException {
-    private static final long serialVersionUID = -9207736984784497612L;
-
-    public BadRequiredFieldJSONException(String string) {
-      super(string);
-    }
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/UnknownSynchronizerConfigurationVersionException.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/UnknownSynchronizerConfigurationVersionException.java
deleted file mode 100644
index e235009..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/UnknownSynchronizerConfigurationVersionException.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.sync;
-
-public class UnknownSynchronizerConfigurationVersionException extends
-    SyncConfigurationException {
-  public int badVersion;
-  private static final long serialVersionUID = -8497255862099517395L;
-
-  public UnknownSynchronizerConfigurationVersionException(int version) {
-    super();
-    badVersion = version;
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/Utils.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/Utils.java
deleted file mode 100644
index ef8859b..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/Utils.java
+++ /dev/null
@@ -1,575 +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.sync;
-
-import java.io.BufferedReader;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.io.UnsupportedEncodingException;
-import java.math.BigDecimal;
-import java.math.BigInteger;
-import java.net.URLDecoder;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.security.SecureRandom;
-import java.text.DecimalFormat;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Locale;
-import java.util.Map;
-import java.util.TreeMap;
-import java.util.concurrent.Executor;
-
-import org.json.simple.JSONArray;
-import org.mozilla.apache.commons.codec.binary.Base32;
-import org.mozilla.apache.commons.codec.binary.Base64;
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.background.nativecode.NativeCrypto;
-import org.mozilla.gecko.sync.setup.Constants;
-
-import android.content.Context;
-import android.content.SharedPreferences;
-import android.os.Bundle;
-
-public class Utils {
-
-  private static final String LOG_TAG = "Utils";
-
-  private static final SecureRandom sharedSecureRandom = new SecureRandom();
-
-  // See <http://developer.android.com/reference/android/content/Context.html#getSharedPreferences%28java.lang.String,%20int%29>
-  public static final int SHARED_PREFERENCES_MODE = 0;
-
-  public static String generateGuid() {
-    byte[] encodedBytes = Base64.encodeBase64(generateRandomBytes(9), false);
-    return new String(encodedBytes).replace("+", "-").replace("/", "_");
-  }
-
-  /**
-   * Helper to generate secure random bytes.
-   *
-   * @param length
-   *        Number of bytes to generate.
-   */
-  public static byte[] generateRandomBytes(int length) {
-    byte[] bytes = new byte[length];
-    sharedSecureRandom.nextBytes(bytes);
-    return bytes;
-  }
-
-  /**
-   * Helper to generate a random integer in a specified range.
-   *
-   * @param r
-   *        Generate an integer between 0 and r-1 inclusive.
-   */
-  public static BigInteger generateBigIntegerLessThan(BigInteger r) {
-    int maxBytes = (int) Math.ceil(((double) r.bitLength()) / 8);
-    BigInteger randInt = new BigInteger(generateRandomBytes(maxBytes));
-    return randInt.mod(r);
-  }
-
-  /**
-   * Helper to convert a byte array to a hex-encoded string
-   */
-  public static String byte2Hex(final byte[] b) {
-    return byte2Hex(b, 2 * b.length);
-  }
-
-  public static String byte2Hex(final byte[] b, int hexLength) {
-    final StringBuilder hs = new StringBuilder(Math.max(2*b.length, hexLength));
-    String stmp;
-
-    for (int n = 0; n < hexLength - 2*b.length; n++) {
-      hs.append("0");
-    }
-
-    for (int n = 0; n < b.length; n++) {
-      stmp = Integer.toHexString(b[n] & 0XFF);
-
-      if (stmp.length() == 1) {
-        hs.append("0");
-      }
-      hs.append(stmp);
-    }
-
-    return hs.toString();
-  }
-
-  public static byte[] concatAll(byte[] first, byte[]... rest) {
-    int totalLength = first.length;
-    for (byte[] array : rest) {
-      totalLength += array.length;
-    }
-
-    byte[] result = new byte[totalLength];
-    int offset = first.length;
-
-    System.arraycopy(first, 0, result, 0, offset);
-
-    for (byte[] array : rest) {
-      System.arraycopy(array, 0, result, offset, array.length);
-      offset += array.length;
-    }
-    return result;
-  }
-
-  /**
-   * Utility for Base64 decoding. Should ensure that the correct
-   * Apache Commons version is used.
-   *
-   * @param base64
-   *        An input string. Will be decoded as UTF-8.
-   * @return
-   *        A byte array of decoded values.
-   * @throws UnsupportedEncodingException
-   *         Should not occur.
-   */
-  public static byte[] decodeBase64(String base64) throws UnsupportedEncodingException {
-    return Base64.decodeBase64(base64.getBytes("UTF-8"));
-  }
-
-  public static byte[] decodeFriendlyBase32(String base32) {
-    Base32 converter = new Base32();
-    final String translated = base32.replace('8', 'l').replace('9', 'o');
-    return converter.decode(translated.toUpperCase(Locale.US));
-  }
-
-  public static byte[] hex2Byte(String str, int byteLength) {
-    byte[] second = hex2Byte(str);
-    if (second.length >= byteLength) {
-      return second;
-    }
-    // New Java arrays are zeroed:
-    // http://docs.oracle.com/javase/specs/jls/se7/html/jls-4.html#jls-4.12.5
-    byte[] first = new byte[byteLength - second.length];
-    return Utils.concatAll(first, second);
-  }
-
-  public static byte[] hex2Byte(String str) {
-    if (str.length() % 2 == 1) {
-      str = "0" + str;
-    }
-
-    byte[] bytes = new byte[str.length() / 2];
-    for (int i = 0; i < bytes.length; i++) {
-      bytes[i] = (byte) Integer.parseInt(str.substring(2 * i, 2 * i + 2), 16);
-    }
-    return bytes;
-  }
-
-  public static String millisecondsToDecimalSecondsString(long ms) {
-    return millisecondsToDecimalSeconds(ms).toString();
-  }
-
-  // For dumping into JSON without quotes.
-  public static BigDecimal millisecondsToDecimalSeconds(long ms) {
-    return new BigDecimal(ms).movePointLeft(3);
-  }
-
-  // This lives until Bug 708956 lands, and we don't have to do it any more.
-  public static long decimalSecondsToMilliseconds(String decimal) {
-    try {
-      return new BigDecimal(decimal).movePointRight(3).longValue();
-    } catch (Exception e) {
-      return -1;
-    }
-  }
-
-  // Oh, Java.
-  public static long decimalSecondsToMilliseconds(Double decimal) {
-    // Truncates towards 0.
-    return (long)(decimal * 1000);
-  }
-
-  public static long decimalSecondsToMilliseconds(Long decimal) {
-    return decimal * 1000;
-  }
-
-  public static long decimalSecondsToMilliseconds(Integer decimal) {
-    return (decimal * 1000);
-  }
-
-  public static byte[] sha256(byte[] in)
-      throws NoSuchAlgorithmException {
-    MessageDigest sha1 = MessageDigest.getInstance("SHA-256");
-    return sha1.digest(in);
-  }
-
-  protected static byte[] sha1(final String utf8)
-      throws NoSuchAlgorithmException, UnsupportedEncodingException {
-    final byte[] bytes = utf8.getBytes("UTF-8");
-    try {
-      return NativeCrypto.sha1(bytes);
-    } catch (final LinkageError e) {
-      // This will throw UnsatisifiedLinkError (missing mozglue) the first time it is called, and
-      // ClassNotDefFoundError, for the uninitialized NativeCrypto class, each subsequent time this
-      // is called; LinkageError is their common ancestor.
-      Logger.warn(LOG_TAG, "Got throwable stretching password using native sha1 implementation; " +
-          "ignoring and using Java implementation.", e);
-      final MessageDigest sha1 = MessageDigest.getInstance("SHA-1");
-      return sha1.digest(utf8.getBytes("UTF-8"));
-    }
-  }
-
-  protected static String sha1Base32(final String utf8)
-      throws NoSuchAlgorithmException, UnsupportedEncodingException {
-    return new Base32().encodeAsString(sha1(utf8)).toLowerCase(Locale.US);
-  }
-
-  /**
-   * If we encounter characters not allowed by the API (as found for
-   * instance in an email address), hash the value.
-   * @param account
-   *        An account string.
-   * @return
-   *        An acceptable string.
-   * @throws UnsupportedEncodingException
-   * @throws NoSuchAlgorithmException
-   */
-  public static String usernameFromAccount(final String account) throws NoSuchAlgorithmException, UnsupportedEncodingException {
-    if (account == null || account.equals("")) {
-      throw new IllegalArgumentException("No account name provided.");
-    }
-    if (account.matches("^[A-Za-z0-9._-]+$")) {
-      return account.toLowerCase(Locale.US);
-    }
-    return sha1Base32(account.toLowerCase(Locale.US));
-  }
-
-  public static SharedPreferences getSharedPreferences(final Context context, final String product, final String username, final String serverURL, final String profile, final long version)
-      throws NoSuchAlgorithmException, UnsupportedEncodingException {
-    String prefsPath = getPrefsPath(product, username, serverURL, profile, version);
-    return context.getSharedPreferences(prefsPath, SHARED_PREFERENCES_MODE);
-  }
-
-  /**
-   * Get shared preferences path for a Sync account.
-   *
-   * @param product the Firefox Sync product package name (like "org.mozilla.firefox").
-   * @param username the Sync account name, optionally encoded with <code>Utils.usernameFromAccount</code>.
-   * @param serverURL the Sync account server URL.
-   * @param profile the Firefox profile name.
-   * @param version the version of preferences to reference.
-   * @return the path.
-   * @throws NoSuchAlgorithmException
-   * @throws UnsupportedEncodingException
-   */
-  public static String getPrefsPath(final String product, final String username, final String serverURL, final String profile, final long version)
-      throws NoSuchAlgorithmException, UnsupportedEncodingException {
-    final String encodedAccount = sha1Base32(serverURL + ":" + usernameFromAccount(username));
-
-    if (version <= 0) {
-      return "sync.prefs." + encodedAccount;
-    } else {
-      final String sanitizedProduct = product.replace('.', '!').replace(' ', '!');
-      return "sync.prefs." + sanitizedProduct + "." + encodedAccount + "." + profile + "." + version;
-    }
-  }
-
-  public static void addToIndexBucketMap(TreeMap<Long, ArrayList<String>> map, long index, String value) {
-    ArrayList<String> bucket = map.get(index);
-    if (bucket == null) {
-      bucket = new ArrayList<String>();
-    }
-    bucket.add(value);
-    map.put(index, bucket);
-  }
-
-  /**
-   * Yes, an equality method that's null-safe.
-   */
-  private static boolean same(Object a, Object b) {
-    if (a == b) {
-      return true;
-    }
-    if (a == null || b == null) {
-      return false;      // If both null, case above applies.
-    }
-    return a.equals(b);
-  }
-
-  /**
-   * Return true if the two arrays are both null, or are both arrays
-   * containing the same elements in the same order.
-   */
-  public static boolean sameArrays(JSONArray a, JSONArray b) {
-    if (a == b) {
-      return true;
-    }
-    if (a == null || b == null) {
-      return false;
-    }
-    final int size = a.size();
-    if (size != b.size()) {
-      return false;
-    }
-    for (int i = 0; i < size; ++i) {
-      if (!same(a.get(i), b.get(i))) {
-        return false;
-      }
-    }
-    return true;
-  }
-
-  /**
-   * Takes a URI, extracting URI components.
-   * @param scheme the URI scheme on which to match.
-   */
-  @SuppressWarnings("deprecation")
-  public static Map<String, String> extractURIComponents(String scheme, String uri) {
-    if (uri.indexOf(scheme) != 0) {
-      throw new IllegalArgumentException("URI scheme does not match: " + scheme);
-    }
-
-    // Do this the hard way to avoid taking a large dependency on
-    // HttpClient or getting all regex-tastic.
-    String components = uri.substring(scheme.length());
-    HashMap<String, String> out = new HashMap<String, String>();
-    String[] parts = components.split("&");
-    for (int i = 0; i < parts.length; ++i) {
-      String part = parts[i];
-      if (part.length() == 0) {
-        continue;
-      }
-      String[] pair = part.split("=", 2);
-      switch (pair.length) {
-      case 0:
-        continue;
-      case 1:
-        out.put(URLDecoder.decode(pair[0]), null);
-        break;
-      case 2:
-        out.put(URLDecoder.decode(pair[0]), URLDecoder.decode(pair[1]));
-        break;
-      }
-    }
-    return out;
-  }
-
-  // Because TextUtils.join is not stubbed.
-  public static String toDelimitedString(String delimiter, Collection<? extends Object> items) {
-    if (items == null || items.size() == 0) {
-      return "";
-    }
-
-    StringBuilder sb = new StringBuilder();
-    int i = 0;
-    int c = items.size();
-    for (Object object : items) {
-      sb.append(object.toString());
-      if (++i < c) {
-        sb.append(delimiter);
-      }
-    }
-    return sb.toString();
-  }
-
-  public static String toCommaSeparatedString(Collection<? extends Object> items) {
-    return toDelimitedString(", ", items);
-  }
-
-  /**
-   * Names of stages to sync: (ALL intersect SYNC) intersect (ALL minus SKIP).
-   *
-   * @param knownStageNames collection of known stage names (set ALL above).
-   * @param toSync set SYNC above, or <code>null</code> to sync all known stages.
-   * @param toSkip set SKIP above, or <code>null</code> to not skip any stages.
-   * @return stage names.
-   */
-  public static Collection<String> getStagesToSync(final Collection<String> knownStageNames, Collection<String> toSync, Collection<String> toSkip) {
-    if (toSkip == null) {
-      toSkip = new HashSet<String>();
-    } else {
-      toSkip = new HashSet<String>(toSkip);
-    }
-
-    if (toSync == null) {
-      toSync = new HashSet<String>(knownStageNames);
-    } else {
-      toSync = new HashSet<String>(toSync);
-    }
-    toSync.retainAll(knownStageNames);
-    toSync.removeAll(toSkip);
-    return toSync;
-  }
-
-  /**
-   * Get names of stages to sync: (ALL intersect SYNC) intersect (ALL minus SKIP).
-   *
-   * @param knownStageNames collection of known stage names (set ALL above).
-   * @param extras
-   *          a <code>Bundle</code> instance (possibly null) optionally containing keys
-   *          <code>EXTRAS_KEY_STAGES_TO_SYNC</code> (set SYNC above) and
-   *          <code>EXTRAS_KEY_STAGES_TO_SKIP</code> (set SKIP above).
-   * @return stage names.
-   */
-  public static Collection<String> getStagesToSyncFromBundle(final Collection<String> knownStageNames, final Bundle extras) {
-    if (extras == null) {
-      return knownStageNames;
-    }
-    String toSyncString = extras.getString(Constants.EXTRAS_KEY_STAGES_TO_SYNC);
-    String toSkipString = extras.getString(Constants.EXTRAS_KEY_STAGES_TO_SKIP);
-    if (toSyncString == null && toSkipString == null) {
-      return knownStageNames;
-    }
-
-    ArrayList<String> toSync = null;
-    ArrayList<String> toSkip = null;
-    if (toSyncString != null) {
-      try {
-        toSync = new ArrayList<String>(new ExtendedJSONObject(toSyncString).keySet());
-      } catch (Exception e) {
-        Logger.warn(LOG_TAG, "Got exception parsing stages to sync: '" + toSyncString + "'.", e);
-      }
-    }
-    if (toSkipString != null) {
-      try {
-        toSkip = new ArrayList<String>(new ExtendedJSONObject(toSkipString).keySet());
-      } catch (Exception e) {
-        Logger.warn(LOG_TAG, "Got exception parsing stages to skip: '" + toSkipString + "'.", e);
-      }
-    }
-
-    Logger.info(LOG_TAG, "Asked to sync '" + Utils.toCommaSeparatedString(toSync) +
-                         "' and to skip '" + Utils.toCommaSeparatedString(toSkip) + "'.");
-    return getStagesToSync(knownStageNames, toSync, toSkip);
-  }
-
-  /**
-   * Put names of stages to sync and to skip into sync extras bundle.
-   *
-   * @param bundle
-   *          a <code>Bundle</code> instance (possibly null).
-   * @param stagesToSync
-   *          collection of stage names to sync: key
-   *          <code>EXTRAS_KEY_STAGES_TO_SYNC</code>; ignored if <code>null</code>.
-   * @param stagesToSkip
-   *          collection of stage names to skip: key
-   *          <code>EXTRAS_KEY_STAGES_TO_SKIP</code>; ignored if <code>null</code>.
-   */
-  public static void putStageNamesToSync(final Bundle bundle, final String[] stagesToSync, final String[] stagesToSkip) {
-    if (bundle == null) {
-      return;
-    }
-
-    if (stagesToSync != null) {
-      ExtendedJSONObject o = new ExtendedJSONObject();
-      for (String stageName : stagesToSync) {
-        o.put(stageName, 0);
-      }
-      bundle.putString(Constants.EXTRAS_KEY_STAGES_TO_SYNC, o.toJSONString());
-    }
-
-    if (stagesToSkip != null) {
-      ExtendedJSONObject o = new ExtendedJSONObject();
-      for (String stageName : stagesToSkip) {
-        o.put(stageName, 0);
-      }
-      bundle.putString(Constants.EXTRAS_KEY_STAGES_TO_SKIP, o.toJSONString());
-    }
-  }
-
-  /**
-   * Read contents of file as a string.
-   *
-   * @param context Android context.
-   * @param filename name of file to read; must not be null.
-   * @return <code>String</code> instance.
-   */
-  public static String readFile(final Context context, final String filename) {
-    if (filename == null) {
-      throw new IllegalArgumentException("Passed null filename in readFile.");
-    }
-
-    FileInputStream fis = null;
-    InputStreamReader isr = null;
-    BufferedReader br = null;
-
-    try {
-      fis = context.openFileInput(filename);
-      isr = new InputStreamReader(fis);
-      br = new BufferedReader(isr);
-      StringBuilder sb = new StringBuilder();
-      String line;
-      while ((line = br.readLine()) != null) {
-        sb.append(line);
-      }
-      return sb.toString();
-    } catch (Exception e) {
-      return null;
-    } finally {
-      if (isr != null) {
-        try {
-          isr.close();
-        } catch (IOException e) {
-          // Ignore.
-        }
-      }
-      if (fis != null) {
-        try {
-          fis.close();
-        } catch (IOException e) {
-          // Ignore.
-        }
-      }
-    }
-  }
-
-  /**
-   * Format a duration as a string, like "0.56 seconds".
-   *
-   * @param startMillis start time in milliseconds.
-   * @param endMillis end time in milliseconds.
-   * @return formatted string.
-   */
-  public static String formatDuration(long startMillis, long endMillis) {
-    final long duration = endMillis - startMillis;
-    return new DecimalFormat("#0.00 seconds").format(((double) duration) / 1000);
-  }
-
-  /**
-   * This will take a string containing a UTF-8 representation of a UTF-8
-   * byte array — e.g., "pïgéons1" — and return UTF-8 (e.g., "pïgéons1").
-   *
-   * This is the format produced by desktop Firefox when exchanging credentials
-   * containing non-ASCII characters.
-   */
-  public static String decodeUTF8(final String in) throws UnsupportedEncodingException {
-    final int length = in.length();
-    final byte[] asciiBytes = new byte[length];
-    for (int i = 0; i < length; ++i) {
-      asciiBytes[i] = (byte) in.codePointAt(i);
-    }
-    return new String(asciiBytes, "UTF-8");
-  }
-
-  /**
-   * Replace "foo at bar.com" with "XXX at XXX.XXX".
-   */
-  public static String obfuscateEmail(final String in) {
-    return in.replaceAll("[^@\\.]", "X");
-  }
-
-  public static void throwIfNull(Object... objects) {
-    for (Object object : objects) {
-      if (object == null) {
-        throw new IllegalArgumentException("object must not be null");
-      }
-    }
-  }
-
-  public static Executor newSynchronousExecutor() {
-    return new Executor() {
-      @Override
-      public void execute(Runnable runnable) {
-        runnable.run();
-      }
-    };
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/crypto/CryptoException.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/crypto/CryptoException.java
deleted file mode 100644
index a8d0483..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/crypto/CryptoException.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.sync.crypto;
-
-import java.security.GeneralSecurityException;
-
-public class CryptoException extends Exception { 
-  public GeneralSecurityException cause;
-  public CryptoException(GeneralSecurityException e) {
-    this();
-    this.cause = e;
-  }
-  public CryptoException() {
-    
-  }
-  private static final long serialVersionUID = -5219310989960126830L;
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/crypto/CryptoInfo.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/crypto/CryptoInfo.java
deleted file mode 100644
index 355571c..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/crypto/CryptoInfo.java
+++ /dev/null
@@ -1,232 +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.sync.crypto;
-
-import java.security.GeneralSecurityException;
-import java.security.InvalidKeyException;
-import java.security.NoSuchAlgorithmException;
-import java.util.Arrays;
-
-import javax.crypto.BadPaddingException;
-import javax.crypto.Cipher;
-import javax.crypto.IllegalBlockSizeException;
-import javax.crypto.Mac;
-import javax.crypto.NoSuchPaddingException;
-import javax.crypto.spec.IvParameterSpec;
-import javax.crypto.spec.SecretKeySpec;
-
-import org.mozilla.apache.commons.codec.binary.Base64;
-
-/*
- * All info in these objects should be decoded (i.e. not BaseXX encoded).
- */
-public class CryptoInfo {
-  private static final String TRANSFORMATION     = "AES/CBC/PKCS5Padding";
-  private static final String KEY_ALGORITHM_SPEC = "AES";
-
-  private byte[] message;
-  private byte[] iv;
-  private byte[] hmac;
-  private KeyBundle keys;
-
-  /**
-   * Return a CryptoInfo with given plaintext encrypted using given keys.
-   */
-  public static CryptoInfo encrypt(byte[] plaintextBytes, KeyBundle keys) throws CryptoException {
-    CryptoInfo info = new CryptoInfo(plaintextBytes, keys);
-    info.encrypt();
-    return info;
-  }
-
-  /**
-   * Return a CryptoInfo with given plaintext encrypted using given keys and initial vector.
-   */
-  public static CryptoInfo encrypt(byte[] plaintextBytes, byte[] iv, KeyBundle keys) throws CryptoException {
-    CryptoInfo info = new CryptoInfo(plaintextBytes, iv, null, keys);
-    info.encrypt();
-    return info;
-  }
-
-  /**
-   * Return a CryptoInfo with given ciphertext decrypted using given keys and initial vector, verifying that given HMAC validates.
-   */
-  public static CryptoInfo decrypt(byte[] ciphertext, byte[] iv, byte[] hmac, KeyBundle keys) throws CryptoException {
-    CryptoInfo info = new CryptoInfo(ciphertext, iv, hmac, keys);
-    info.decrypt();
-    return info;
-  }
-
-  /*
-   * Constructor typically used when encrypting.
-   */
-  public CryptoInfo(byte[] message, KeyBundle keys) {
-    this.setMessage(message);
-    this.setKeys(keys);
-  }
-
-  /*
-   * Constructor typically used when decrypting.
-   */
-  public CryptoInfo(byte[] message, byte[] iv, byte[] hmac, KeyBundle keys) {
-    this.setMessage(message);
-    this.setIV(iv);
-    this.setHMAC(hmac);
-    this.setKeys(keys);
-  }
-
-  public byte[] getMessage() {
-    return message;
-  }
-
-  public void setMessage(byte[] message) {
-    this.message = message;
-  }
-
-  public byte[] getIV() {
-    return iv;
-  }
-
-  public void setIV(byte[] iv) {
-    this.iv = iv;
-  }
-
-  public byte[] getHMAC() {
-    return hmac;
-  }
-
-  public void setHMAC(byte[] hmac) {
-    this.hmac = hmac;
-  }
-
-  public KeyBundle getKeys() {
-    return keys;
-  }
-
-  public void setKeys(KeyBundle keys) {
-    this.keys = keys;
-  }
-
-  /*
-   * Generate HMAC for given cipher text.
-   */
-  public static byte[] generatedHMACFor(byte[] message, KeyBundle keys) throws NoSuchAlgorithmException, InvalidKeyException {
-    Mac hmacHasher = HKDF.makeHMACHasher(keys.getHMACKey());
-    return hmacHasher.doFinal(Base64.encodeBase64(message));
-  }
-
-  /*
-   * Return true if generated HMAC is the same as the specified HMAC.
-   */
-  public boolean generatedHMACIsHMAC() throws NoSuchAlgorithmException, InvalidKeyException {
-    byte[] generatedHMAC = generatedHMACFor(getMessage(), getKeys());
-    byte[] expectedHMAC  = getHMAC();
-    return Arrays.equals(generatedHMAC, expectedHMAC);
-  }
-
-  /**
-   * Performs functionality common to both encryption and decryption.
-   *
-   * @param cipher
-   * @param inputMessage non-BaseXX-encoded message
-   * @return encrypted/decrypted message
-   * @throws CryptoException
-   */
-  private static byte[] commonCrypto(Cipher cipher, byte[] inputMessage)
-                        throws CryptoException {
-    byte[] outputMessage = null;
-    try {
-      outputMessage = cipher.doFinal(inputMessage);
-    } catch (IllegalBlockSizeException | BadPaddingException e) {
-      throw new CryptoException(e);
-    }
-      return outputMessage;
-  }
-
-  /**
-   * Encrypt a CryptoInfo in-place.
-   *
-   * @throws CryptoException
-   */
-  public void encrypt() throws CryptoException {
-
-    Cipher cipher = CryptoInfo.getCipher(TRANSFORMATION);
-    try {
-      byte[] encryptionKey = getKeys().getEncryptionKey();
-      SecretKeySpec spec = new SecretKeySpec(encryptionKey, KEY_ALGORITHM_SPEC);
-
-      // If no IV is provided, we allow the cipher to provide one.
-      if (getIV() == null || getIV().length == 0) {
-        cipher.init(Cipher.ENCRYPT_MODE, spec);
-      } else {
-        cipher.init(Cipher.ENCRYPT_MODE, spec, new IvParameterSpec(getIV()));
-      }
-    } catch (GeneralSecurityException ex) {
-      throw new CryptoException(ex);
-    }
-
-    // Encrypt.
-    byte[] encryptedBytes = commonCrypto(cipher, getMessage());
-    byte[] iv = cipher.getIV();
-
-    byte[] hmac;
-    // Generate HMAC.
-    try {
-      hmac = generatedHMACFor(encryptedBytes, keys);
-    } catch (NoSuchAlgorithmException | InvalidKeyException e) {
-      throw new CryptoException(e);
-    }
-
-    // Update in place.  keys is already set.
-    this.setHMAC(hmac);
-    this.setIV(iv);
-    this.setMessage(encryptedBytes);
-  }
-
-  /**
-   * Decrypt a CryptoInfo in-place.
-   *
-   * @throws CryptoException
-   */
-  public void decrypt() throws CryptoException {
-
-    // Check HMAC.
-    try {
-      if (!generatedHMACIsHMAC()) {
-        throw new HMACVerificationException();
-      }
-    } catch (NoSuchAlgorithmException | InvalidKeyException e) {
-      throw new CryptoException(e);
-    }
-
-    Cipher cipher = CryptoInfo.getCipher(TRANSFORMATION);
-    try {
-      byte[] encryptionKey = getKeys().getEncryptionKey();
-      SecretKeySpec spec = new SecretKeySpec(encryptionKey, KEY_ALGORITHM_SPEC);
-      cipher.init(Cipher.DECRYPT_MODE, spec, new IvParameterSpec(getIV()));
-    } catch (GeneralSecurityException ex) {
-      throw new CryptoException(ex);
-    }
-    byte[] decryptedBytes = commonCrypto(cipher, getMessage());
-    byte[] iv = cipher.getIV();
-
-    // Update in place.  keys is already set.
-    this.setHMAC(null);
-    this.setIV(iv);
-    this.setMessage(decryptedBytes);
-  }
-
-  /**
-   * Helper to get a Cipher object.
-   *
-   * @param transformation The type of Cipher to get.
-   */
-  private static Cipher getCipher(String transformation) throws CryptoException {
-    try {
-      return Cipher.getInstance(transformation);
-    } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
-      throw new CryptoException(e);
-    }
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/crypto/HKDF.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/crypto/HKDF.java
deleted file mode 100644
index 16c0d81..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/crypto/HKDF.java
+++ /dev/null
@@ -1,128 +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.sync.crypto;
-
-import java.security.InvalidKeyException;
-import java.security.Key;
-import java.security.NoSuchAlgorithmException;
-
-import javax.crypto.Mac;
-import javax.crypto.spec.SecretKeySpec;
-
-import org.mozilla.gecko.sync.Utils;
-
-/*
- * A standards-compliant implementation of RFC 5869
- * for HMAC-based Key Derivation Function.
- * HMAC uses HMAC SHA256 standard.
- */
-public class HKDF {
-  public static String HMAC_ALGORITHM = "hmacSHA256";
-
-  /**
-   * Used for conversion in cases in which you *know* the encoding exists.
-   */
-  public static final byte[] bytes(String in) {
-    try {
-      return in.getBytes("UTF-8");
-    } catch (java.io.UnsupportedEncodingException e) {
-      return null;
-    }
-  }
-
-  public static final int BLOCKSIZE     = 256 / 8;
-  public static final byte[] HMAC_INPUT = bytes("Sync-AES_256_CBC-HMAC256");
-
-  /*
-   * Step 1 of RFC 5869
-   * Get sha256HMAC Bytes
-   * Input: salt (message), IKM (input keyring material)
-   * Output: PRK (pseudorandom key)
-   */
-  public static byte[] hkdfExtract(byte[] salt, byte[] IKM) throws NoSuchAlgorithmException, InvalidKeyException {
-    return digestBytes(IKM, makeHMACHasher(salt));
-  }
-
-  /*
-   * Step 2 of RFC 5869.
-   * Input: PRK from step 1, info, length.
-   * Output: OKM (output keyring material).
-   */
-  public static byte[] hkdfExpand(byte[] prk, byte[] info, int len) throws NoSuchAlgorithmException, InvalidKeyException {
-    Mac hmacHasher = makeHMACHasher(prk);
-
-    byte[] T  = {};
-    byte[] Tn = {};
-
-    int iterations = (int) Math.ceil(((double)len) / (BLOCKSIZE));
-    for (int i = 0; i < iterations; i++) {
-      Tn = digestBytes(Utils.concatAll(Tn, info, Utils.hex2Byte(Integer.toHexString(i + 1))),
-                       hmacHasher);
-      T = Utils.concatAll(T, Tn);
-    }
-
-    byte[] result = new byte[len];
-    System.arraycopy(T, 0, result, 0, len);
-    return result;
-  }
-
-  /*
-   * Make HMAC key
-   * Input: key (salt)
-   * Output: Key HMAC-Key
-   */
-  public static Key makeHMACKey(byte[] key) {
-    if (key.length == 0) {
-      key = new byte[BLOCKSIZE];
-    }
-    return new SecretKeySpec(key, HMAC_ALGORITHM);
-  }
-
-  /*
-   * Make an HMAC hasher
-   * Input: Key hmacKey
-   * Ouput: An HMAC Hasher
-   */
-  public static Mac makeHMACHasher(byte[] key) throws NoSuchAlgorithmException, InvalidKeyException {
-    Mac hmacHasher = null;
-    hmacHasher = Mac.getInstance(HMAC_ALGORITHM);
-
-    // If Mac.getInstance doesn't throw NoSuchAlgorithmException, hmacHasher is
-    // non-null.
-    assert(hmacHasher != null);
-
-    hmacHasher.init(makeHMACKey(key));
-    return hmacHasher;
-  }
-
-  /*
-   * Hash bytes with given hasher
-   * Input: message to hash, HMAC hasher
-   * Output: hashed byte[].
-   */
-  public static byte[] digestBytes(byte[] message, Mac hasher) {
-    hasher.update(message);
-    byte[] ret = hasher.doFinal();
-    hasher.reset();
-    return ret;
-  }
-
-  public static byte[] derive(byte[] skm, byte[] xts, byte[] ctxInfo, int dkLen) throws InvalidKeyException, NoSuchAlgorithmException {
-    return hkdfExpand(hkdfExtract(xts, skm), ctxInfo, dkLen);
-  }
-
-  public static void deriveMany(byte[] skm, byte[] xts, byte[] ctxInfo, byte[]... keys) throws InvalidKeyException, NoSuchAlgorithmException {
-    int length = 0;
-    for (byte[] key : keys) {
-      length += key.length;
-    }
-    byte[] derived = hkdfExpand(hkdfExtract(xts, skm), ctxInfo, length);
-    int offset = 0;
-    for (byte[] key : keys) {
-      System.arraycopy(derived, offset, key, 0, key.length);
-      offset += key.length;
-    }
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/crypto/HMACVerificationException.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/crypto/HMACVerificationException.java
deleted file mode 100644
index f33babd..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/crypto/HMACVerificationException.java
+++ /dev/null
@@ -1,12 +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.sync.crypto;
-
-public class HMACVerificationException extends CryptoException {
-  private static final long serialVersionUID = 1235311303567074897L;
-  public HMACVerificationException() {
-  }
-
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/crypto/KeyBundle.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/crypto/KeyBundle.java
deleted file mode 100644
index 2063b1e..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/crypto/KeyBundle.java
+++ /dev/null
@@ -1,135 +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.sync.crypto;
-
-import java.io.UnsupportedEncodingException;
-import java.security.InvalidKeyException;
-import java.security.NoSuchAlgorithmException;
-import java.util.Arrays;
-
-import javax.crypto.KeyGenerator;
-import javax.crypto.Mac;
-
-import org.mozilla.apache.commons.codec.binary.Base64;
-import org.mozilla.gecko.sync.Utils;
-
-public class KeyBundle {
-    private static final String KEY_ALGORITHM_SPEC = "AES";
-    private static final int    KEY_SIZE           = 256;
-
-    private byte[] encryptionKey;
-    private byte[] hmacKey;
-
-    // These are the same for every sync key bundle.
-    private static final byte[] EMPTY_BYTES      = {};
-    private static final byte[] ENCR_INPUT_BYTES = {1};
-    private static final byte[] HMAC_INPUT_BYTES = {2};
-
-    /*
-     * Mozilla's use of HKDF for getting keys from the Sync Key string.
-     *
-     * We do exactly 2 HKDF iterations and make the first iteration the
-     * encryption key and the second iteration the HMAC key.
-     *
-     */
-    public KeyBundle(String username, String base32SyncKey) throws CryptoException {
-      if (base32SyncKey == null) {
-        throw new IllegalArgumentException("No sync key provided.");
-      }
-      if (username == null || username.equals("")) {
-        throw new IllegalArgumentException("No username provided.");
-      }
-      // Hash appropriately.
-      try {
-        username = Utils.usernameFromAccount(username);
-      } catch (NoSuchAlgorithmException | UnsupportedEncodingException e) {
-        throw new IllegalArgumentException("Invalid username.");
-      }
-
-      byte[] syncKey = Utils.decodeFriendlyBase32(base32SyncKey);
-      byte[] user    = username.getBytes();
-
-      Mac hmacHasher;
-      try {
-        hmacHasher = HKDF.makeHMACHasher(syncKey);
-      } catch (NoSuchAlgorithmException | InvalidKeyException e) {
-        throw new CryptoException(e);
-      }
-      assert(hmacHasher != null); // If makeHMACHasher doesn't throw, then hmacHasher is non-null.
-
-      byte[] encrBytes = Utils.concatAll(EMPTY_BYTES, HKDF.HMAC_INPUT, user, ENCR_INPUT_BYTES);
-      byte[] encrKey   = HKDF.digestBytes(encrBytes, hmacHasher);
-      byte[] hmacBytes = Utils.concatAll(encrKey, HKDF.HMAC_INPUT, user, HMAC_INPUT_BYTES);
-
-      this.hmacKey       = HKDF.digestBytes(hmacBytes, hmacHasher);
-      this.encryptionKey = encrKey;
-    }
-
-    public KeyBundle(byte[] encryptionKey, byte[] hmacKey) {
-       this.setEncryptionKey(encryptionKey);
-       this.setHMACKey(hmacKey);
-    }
-
-    /**
-     * Make a KeyBundle with the specified base64-encoded keys.
-     *
-     * @return A KeyBundle with the specified keys.
-     */
-    public static KeyBundle fromBase64EncodedKeys(String base64EncryptionKey, String base64HmacKey) throws UnsupportedEncodingException {
-      return new KeyBundle(Base64.decodeBase64(base64EncryptionKey.getBytes("UTF-8")),
-                           Base64.decodeBase64(base64HmacKey.getBytes("UTF-8")));
-    }
-
-    /**
-     * Make a KeyBundle with two random 256 bit keys (encryption and HMAC).
-     *
-     * @return A KeyBundle with random keys.
-     */
-    public static KeyBundle withRandomKeys() throws CryptoException {
-      KeyGenerator keygen;
-      try {
-        keygen = KeyGenerator.getInstance(KEY_ALGORITHM_SPEC);
-      } catch (NoSuchAlgorithmException e) {
-        throw new CryptoException(e);
-      }
-
-      keygen.init(KEY_SIZE);
-      byte[] encryptionKey = keygen.generateKey().getEncoded();
-      byte[] hmacKey = keygen.generateKey().getEncoded();
-
-      return new KeyBundle(encryptionKey, hmacKey);
-    }
-
-    public byte[] getEncryptionKey() {
-        return encryptionKey;
-    }
-
-    public void setEncryptionKey(byte[] encryptionKey) {
-        this.encryptionKey = encryptionKey;
-    }
-
-    public byte[] getHMACKey() {
-        return hmacKey;
-    }
-
-    public void setHMACKey(byte[] hmacKey) {
-        this.hmacKey = hmacKey;
-    }
-
-    @Override
-    public boolean equals(Object o) {
-      if (!(o instanceof KeyBundle)) {
-        return false;
-      }
-      KeyBundle other = (KeyBundle) o;
-      return Arrays.equals(other.encryptionKey, this.encryptionKey) &&
-             Arrays.equals(other.hmacKey, this.hmacKey);
-    }
-
-    @Override
-    public int hashCode() {
-      throw new UnsupportedOperationException("No hashCode for KeyBundle.");
-    }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/crypto/MissingCryptoInputException.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/crypto/MissingCryptoInputException.java
deleted file mode 100644
index 8add1cf..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/crypto/MissingCryptoInputException.java
+++ /dev/null
@@ -1,9 +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.sync.crypto;
-
-public class MissingCryptoInputException extends CryptoException {
-  private static final long serialVersionUID = 5334412407012972445L;
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/crypto/NoKeyBundleException.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/crypto/NoKeyBundleException.java
deleted file mode 100644
index 00e0f8b..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/crypto/NoKeyBundleException.java
+++ /dev/null
@@ -1,9 +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.sync.crypto;
-
-public class NoKeyBundleException extends CryptoException {
-  private static final long serialVersionUID = -6627154503154040915L;
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/crypto/PBKDF2.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/crypto/PBKDF2.java
deleted file mode 100644
index 636b210..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/crypto/PBKDF2.java
+++ /dev/null
@@ -1,78 +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.sync.crypto;
-
-import java.security.GeneralSecurityException;
-import java.util.Arrays;
-
-import javax.crypto.Mac;
-import javax.crypto.ShortBufferException;
-import javax.crypto.spec.SecretKeySpec;
-
-public class PBKDF2 {
-  public static byte[] pbkdf2SHA256(byte[] password, byte[] salt, int c, int dkLen)
-      throws GeneralSecurityException {
-    final String algorithm = "HmacSHA256";
-    SecretKeySpec keyspec = new SecretKeySpec(password, algorithm);
-    Mac prf = Mac.getInstance(algorithm);
-    prf.init(keyspec);
-
-    int hLen = prf.getMacLength();
-
-    byte U_r[] = new byte[hLen];
-    byte U_i[] = new byte[salt.length + 4];
-    byte scratch[] = new byte[hLen];
-
-    int l = Math.max(dkLen, hLen);
-    int r = dkLen - (l - 1) * hLen;
-    byte T[] = new byte[l * hLen];
-    int ti_offset = 0;
-    for (int i = 1; i <= l; i++) {
-      Arrays.fill(U_r, (byte) 0);
-      F(T, ti_offset, prf, salt, c, i, U_r, U_i, scratch);
-      ti_offset += hLen;
-    }
-
-    if (r < hLen) {
-      // Incomplete last block.
-      byte DK[] = new byte[dkLen];
-      System.arraycopy(T, 0, DK, 0, dkLen);
-      return DK;
-    }
-
-    return T;
-  }
-
-  private static void F(byte[] dest, int offset, Mac prf, byte[] S, int c, int blockIndex, byte U_r[], byte U_i[], byte[] scratch)
-      throws ShortBufferException, IllegalStateException {
-    final int hLen = prf.getMacLength();
-
-    // U0 = S || INT (i);
-    System.arraycopy(S, 0, U_i, 0, S.length);
-    INT(U_i, S.length, blockIndex);
-
-    for (int i = 0; i < c; i++) {
-      prf.update(U_i);
-      prf.doFinal(scratch, 0);
-      U_i = scratch;
-      xor(U_r, U_i);
-    }
-
-    System.arraycopy(U_r, 0, dest, offset, hLen);
-  }
-
-  private static void xor(byte[] dest, byte[] src) {
-    for (int i = 0; i < dest.length; i++) {
-      dest[i] ^= src[i];
-    }
-  }
-
-  private static void INT(byte[] dest, int offset, int i) {
-    dest[offset + 0] = (byte) (i / (256 * 256 * 256));
-    dest[offset + 1] = (byte) (i / (256 * 256));
-    dest[offset + 2] = (byte) (i / (256));
-    dest[offset + 3] = (byte) (i);
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/crypto/PersistedCrypto5Keys.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/crypto/PersistedCrypto5Keys.java
deleted file mode 100644
index 4dba4f2..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/crypto/PersistedCrypto5Keys.java
+++ /dev/null
@@ -1,103 +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.sync.crypto;
-
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.sync.CollectionKeys;
-import org.mozilla.gecko.sync.CryptoRecord;
-
-import android.content.SharedPreferences;
-
-public class PersistedCrypto5Keys {
-  public static final String LOG_TAG = "PersistedC5Keys";
-
-  public static final String CRYPTO5_KEYS_SERVER_RESPONSE_BODY = "crypto5KeysServerResponseBody";
-  public static final String CRYPTO5_KEYS_LAST_MODIFIED        = "crypto5KeysLastModified";
-
-  protected SharedPreferences prefs;
-  protected KeyBundle syncKeyBundle;
-
-  public PersistedCrypto5Keys(SharedPreferences prefs, KeyBundle syncKeyBundle) {
-    if (syncKeyBundle == null) {
-      throw new IllegalArgumentException("Null syncKeyBundle passed in to PersistedCrypto5Keys constructor.");
-    }
-    this.prefs = prefs;
-    this.syncKeyBundle = syncKeyBundle;
-  }
-
-  /**
-   * Get persisted crypto/keys.
-   * <p>
-   * crypto/keys is fetched from an encrypted JSON-encoded <code>CryptoRecord</code>.
-   *
-   * @return A <code>CollectionKeys</code> instance or <code>null</code> if none
-   *         is currently persisted.
-   */
-  public CollectionKeys keys() {
-    String keysJSON = prefs.getString(CRYPTO5_KEYS_SERVER_RESPONSE_BODY, null);
-    if (keysJSON == null) {
-      return null;
-    }
-    try {
-      CryptoRecord cryptoRecord = CryptoRecord.fromJSONRecord(keysJSON);
-      CollectionKeys keys = new CollectionKeys();
-      keys.setKeyPairsFromWBO(cryptoRecord, syncKeyBundle);
-      return keys;
-    } catch (Exception e) {
-      Logger.warn(LOG_TAG, "Got exception decrypting persisted crypto/keys.", e);
-      return null;
-    }
-  }
-
-  /**
-   * Persist crypto/keys.
-   * <p>
-   * crypto/keys is stored as an encrypted JSON-encoded <code>CryptoRecord</code>.
-   *
-   * @param keys
-   *          The <code>CollectionKeys</code> object to persist, which should
-   *          have the same default key bundle as the sync key bundle.
-   */
-  public void persistKeys(CollectionKeys keys) {
-    if (keys == null) {
-      Logger.debug(LOG_TAG, "Clearing persisted crypto/keys.");
-      prefs.edit().remove(CRYPTO5_KEYS_SERVER_RESPONSE_BODY).commit();
-      return;
-    }
-    try {
-      CryptoRecord cryptoRecord = keys.asCryptoRecord();
-      cryptoRecord.keyBundle = syncKeyBundle;
-      cryptoRecord.encrypt();
-      String keysJSON = cryptoRecord.toJSONString();
-      Logger.debug(LOG_TAG, "Persisting crypto/keys.");
-      prefs.edit().putString(CRYPTO5_KEYS_SERVER_RESPONSE_BODY, keysJSON).commit();
-    } catch (Exception e) {
-      Logger.warn(LOG_TAG, "Got exception encrypting while persisting crypto/keys.", e);
-    }
-  }
-
-  public boolean persistedKeysExist() {
-    return lastModified() > 0;
-  }
-
-  public long lastModified() {
-    return prefs.getLong(CRYPTO5_KEYS_LAST_MODIFIED, -1);
-  }
-
-  public void persistLastModified(long lastModified) {
-    if (lastModified <= 0) {
-      Logger.debug(LOG_TAG, "Clearing persisted crypto/keys last modified timestamp.");
-      prefs.edit().remove(CRYPTO5_KEYS_LAST_MODIFIED).commit();
-      return;
-    }
-    Logger.debug(LOG_TAG, "Persisting crypto/keys last modified timestamp " + lastModified + ".");
-    prefs.edit().putLong(CRYPTO5_KEYS_LAST_MODIFIED, lastModified).commit();
-  }
-
-  public void purge() {
-    persistLastModified(-1);
-    persistKeys(null);
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/delegates/ClientsDataDelegate.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/delegates/ClientsDataDelegate.java
deleted file mode 100644
index 07e9179..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/delegates/ClientsDataDelegate.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.sync.delegates;
-
-public interface ClientsDataDelegate {
-  public String getAccountGUID();
-  public String getDefaultClientName();
-  public void setClientName(String clientName, long now);
-  public String getClientName();
-  public void setClientsCount(int clientsCount);
-  public int getClientsCount();
-  public boolean isLocalGUID(String guid);
-  public String getFormFactor();
-
-  /**
-   * The last time the client's data was modified in a way that should be
-   * reflected remotely.
-   * <p>
-   * Changing the client's name should be reflected remotely, while changing the
-   * clients count should not (since that data is only used to inform local
-   * policy.)
-   *
-   * @return timestamp in milliseconds.
-   */
-  public long getLastModifiedTimestamp();
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/delegates/FreshStartDelegate.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/delegates/FreshStartDelegate.java
deleted file mode 100644
index 2e53470..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/delegates/FreshStartDelegate.java
+++ /dev/null
@@ -1,10 +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.sync.delegates;
-
-public interface FreshStartDelegate {
-  void onFreshStart();
-  void onFreshStartFailed(Exception e);
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/delegates/GlobalSessionCallback.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/delegates/GlobalSessionCallback.java
deleted file mode 100644
index 9829f5b..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/delegates/GlobalSessionCallback.java
+++ /dev/null
@@ -1,49 +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.sync.delegates;
-
-import java.net.URI;
-
-import org.mozilla.gecko.sync.GlobalSession;
-import org.mozilla.gecko.sync.stage.GlobalSyncStage.Stage;
-
-public interface GlobalSessionCallback {
-  /**
-   * Request that no further syncs occur within the next `backoff` milliseconds.
-   * @param backoff a duration in milliseconds.
-   */
-  void requestBackoff(long backoff);
-
-  /**
-   * Called on a 401 HTTP response.
-   */
-  void informUnauthorizedResponse(GlobalSession globalSession, URI oldClusterURL);
-
-
-  /**
-   * Called when an HTTP failure indicates that a software upgrade is required.
-   */
-  void informUpgradeRequiredResponse(GlobalSession session);
-
-  /**
-   * Called when a migration sentinel has been found and processed successfully.
-   * <p>
-   * This account should stop syncing immediately, and arrange to delete itself.
-   */
-  void informMigrated(GlobalSession session);
-
-  void handleAborted(GlobalSession globalSession, String reason);
-  void handleError(GlobalSession globalSession, Exception ex);
-  void handleSuccess(GlobalSession globalSession);
-  void handleStageCompleted(Stage currentState, GlobalSession globalSession);
-
-  /**
-   * Called when a {@link GlobalSession} wants to know if it should continue
-   * to make storage requests.
-   *
-   * @return false if the session should make no further requests.
-   */
-  boolean shouldBackOffStorage();
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/delegates/JSONRecordFetchDelegate.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/delegates/JSONRecordFetchDelegate.java
deleted file mode 100644
index 90b73a3..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/delegates/JSONRecordFetchDelegate.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.sync.delegates;
-
-import org.mozilla.gecko.sync.ExtendedJSONObject;
-import org.mozilla.gecko.sync.net.SyncStorageResponse;
-
-/**
- * A fairly generic delegate to handle fetches of single JSON object blobs, as
- * provided by <code>info/configuration</code>, <code>info/collections</code>
- * and <code>info/collection_counts</code>.
- */
-public interface JSONRecordFetchDelegate {
-  public void handleSuccess(ExtendedJSONObject body);
-  public void handleFailure(SyncStorageResponse response);
-  public void handleError(Exception e);
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/delegates/KeyUploadDelegate.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/delegates/KeyUploadDelegate.java
deleted file mode 100644
index 0cd5ec7..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/delegates/KeyUploadDelegate.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.sync.delegates;
-
-public interface KeyUploadDelegate {
-  /**
-   * Called when keys have been successfully uploaded to the server.
-   * <p>
-   * The uploaded keys are intentionally not exposed. It is possible for two
-   * clients to simultaneously upload keys and for each client to conclude that
-   * its keys are current (since the server returned 200 on upload). To shorten
-   * the window wherein two such clients can race, all clients should upload and
-   * then immediately re-download the fetched keys.
-   * <p>
-   * See Bug 692700, Bug 693893.
-   */
-  void onKeysUploaded();
-  void onKeyUploadFailed(Exception e);
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/delegates/MetaGlobalDelegate.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/delegates/MetaGlobalDelegate.java
deleted file mode 100644
index 13854cb..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/delegates/MetaGlobalDelegate.java
+++ /dev/null
@@ -1,15 +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.sync.delegates;
-
-import org.mozilla.gecko.sync.MetaGlobal;
-import org.mozilla.gecko.sync.net.SyncStorageResponse;
-
-public interface MetaGlobalDelegate {
-  public void handleSuccess(MetaGlobal global, SyncStorageResponse response);
-  public void handleMissing(MetaGlobal global, SyncStorageResponse response);
-  public void handleFailure(SyncStorageResponse response);
-  public void handleError(Exception e);
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/delegates/WipeServerDelegate.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/delegates/WipeServerDelegate.java
deleted file mode 100644
index ef35658..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/delegates/WipeServerDelegate.java
+++ /dev/null
@@ -1,10 +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.sync.delegates;
-
-public interface WipeServerDelegate {
-  public void onWiped(long timestamp);
-  public void onWipeFailed(Exception e);
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/middleware/Crypto5MiddlewareRepository.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/middleware/Crypto5MiddlewareRepository.java
deleted file mode 100644
index 79319af..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/middleware/Crypto5MiddlewareRepository.java
+++ /dev/null
@@ -1,76 +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.sync.middleware;
-
-import org.mozilla.gecko.sync.crypto.KeyBundle;
-import org.mozilla.gecko.sync.repositories.IdentityRecordFactory;
-import org.mozilla.gecko.sync.repositories.RecordFactory;
-import org.mozilla.gecko.sync.repositories.Repository;
-import org.mozilla.gecko.sync.repositories.RepositorySession;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionCleanDelegate;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionCreationDelegate;
-
-import android.content.Context;
-
-/**
- * Wrap an existing repository in middleware that encrypts and decrypts records
- * passing through.
- *
- * @author rnewman
- *
- */
-public class Crypto5MiddlewareRepository extends MiddlewareRepository {
-
-  public RecordFactory recordFactory = new IdentityRecordFactory();
-
-  public class Crypto5MiddlewareRepositorySessionCreationDelegate extends MiddlewareRepository.SessionCreationDelegate {
-    private final Crypto5MiddlewareRepository repository;
-    private final RepositorySessionCreationDelegate outerDelegate;
-
-    public Crypto5MiddlewareRepositorySessionCreationDelegate(Crypto5MiddlewareRepository repository, RepositorySessionCreationDelegate outerDelegate) {
-      this.repository = repository;
-      this.outerDelegate = outerDelegate;
-    }
-
-    @Override
-    public void onSessionCreateFailed(Exception ex) {
-      this.outerDelegate.onSessionCreateFailed(ex);
-    }
-
-    @Override
-    public void onSessionCreated(RepositorySession session) {
-      // Do some work, then report success with the wrapping session.
-      Crypto5MiddlewareRepositorySession cryptoSession;
-      try {
-        // Synchronous, baby.
-        cryptoSession = new Crypto5MiddlewareRepositorySession(session, this.repository, recordFactory);
-      } catch (Exception ex) {
-        this.outerDelegate.onSessionCreateFailed(ex);
-        return;
-      }
-      this.outerDelegate.onSessionCreated(cryptoSession);
-    }
-  }
-
-  public KeyBundle keyBundle;
-  private final Repository inner;
-
-  public Crypto5MiddlewareRepository(Repository inner, KeyBundle keys) {
-    super();
-    this.inner = inner;
-    this.keyBundle = keys;
-  }
-  @Override
-  public void createSession(RepositorySessionCreationDelegate delegate, Context context) {
-    Crypto5MiddlewareRepositorySessionCreationDelegate delegateWrapper = new Crypto5MiddlewareRepositorySessionCreationDelegate(this, delegate);
-    inner.createSession(delegateWrapper, context);
-  }
-
-  @Override
-  public void clean(boolean success, RepositorySessionCleanDelegate delegate,
-                    Context context) {
-    this.inner.clean(success, delegate, context);
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/middleware/Crypto5MiddlewareRepositorySession.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/middleware/Crypto5MiddlewareRepositorySession.java
deleted file mode 100644
index 46de7a2..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/middleware/Crypto5MiddlewareRepositorySession.java
+++ /dev/null
@@ -1,172 +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.sync.middleware;
-
-import java.io.UnsupportedEncodingException;
-import java.util.concurrent.ExecutorService;
-
-import org.mozilla.gecko.sync.CryptoRecord;
-import org.mozilla.gecko.sync.crypto.CryptoException;
-import org.mozilla.gecko.sync.crypto.KeyBundle;
-import org.mozilla.gecko.sync.repositories.InactiveSessionException;
-import org.mozilla.gecko.sync.repositories.NoStoreDelegateException;
-import org.mozilla.gecko.sync.repositories.RecordFactory;
-import org.mozilla.gecko.sync.repositories.RepositorySession;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionFetchRecordsDelegate;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionStoreDelegate;
-import org.mozilla.gecko.sync.repositories.domain.Record;
-
-/**
- * It's a RepositorySession that accepts Records as input, producing CryptoRecords
- * for submission to a remote service.
- * Takes a RecordFactory as a parameter. This is in charge of taking decrypted CryptoRecords
- * as input and producing some expected kind of Record as output for local use.
- *
- *
-
-
-
-                 +------------------------------------+
-                 |    Server11RepositorySession       |
-                 +-------------------------+----------+
-                           ^               |
-                           |               |
-                        Encrypted CryptoRecords
-                           |               |
-                           |               v
-                 +---------+--------------------------+
-                 | Crypto5MiddlewareRepositorySession |
-                 +------------------------------------+
-                           ^               |
-                           |               |  Decrypted CryptoRecords
-                           |               |
-                           |            +---------------+
-                           |            | RecordFactory |
-                           |            +--+------------+
-                           |               |
-                          Local Record instances
-                           |               |
-                           |               v
-                 +---------+--------------------------+
-                 |  Local RepositorySession instance  |
-                 +------------------------------------+
-
-
- * @author rnewman
- *
- */
-public class Crypto5MiddlewareRepositorySession extends MiddlewareRepositorySession {
-  private final KeyBundle keyBundle;
-  private final RecordFactory recordFactory;
-
-  public Crypto5MiddlewareRepositorySession(RepositorySession session, Crypto5MiddlewareRepository repository, RecordFactory recordFactory) {
-    super(session, repository);
-    this.keyBundle = repository.keyBundle;
-    this.recordFactory = recordFactory;
-  }
-
-  public class DecryptingTransformingFetchDelegate implements RepositorySessionFetchRecordsDelegate {
-    private final RepositorySessionFetchRecordsDelegate next;
-    private final KeyBundle keyBundle;
-    private final RecordFactory recordFactory;
-
-    DecryptingTransformingFetchDelegate(RepositorySessionFetchRecordsDelegate next, KeyBundle bundle, RecordFactory recordFactory) {
-      this.next = next;
-      this.keyBundle = bundle;
-      this.recordFactory = recordFactory;
-    }
-
-    @Override
-    public void onFetchFailed(Exception ex, Record record) {
-      next.onFetchFailed(ex, record);
-    }
-
-    @Override
-    public void onFetchedRecord(Record record) {
-      CryptoRecord r;
-      try {
-        r = (CryptoRecord) record;
-      } catch (ClassCastException e) {
-        next.onFetchFailed(e, record);
-        return;
-      }
-      r.keyBundle = keyBundle;
-      try {
-        r.decrypt();
-      } catch (Exception e) {
-        next.onFetchFailed(e, r);
-        return;
-      }
-      Record transformed;
-      try {
-        transformed = this.recordFactory.createRecord(r);
-      } catch (Exception e) {
-        next.onFetchFailed(e, r);
-        return;
-      }
-      next.onFetchedRecord(transformed);
-    }
-
-    @Override
-    public void onFetchCompleted(final long fetchEnd) {
-      next.onFetchCompleted(fetchEnd);
-    }
-
-    @Override
-    public RepositorySessionFetchRecordsDelegate deferredFetchDelegate(ExecutorService executor) {
-      // Synchronously perform *our* work, passing through appropriately.
-      RepositorySessionFetchRecordsDelegate deferredNext = next.deferredFetchDelegate(executor);
-      return new DecryptingTransformingFetchDelegate(deferredNext, keyBundle, recordFactory);
-    }
-  }
-
-  private DecryptingTransformingFetchDelegate makeUnwrappingDelegate(RepositorySessionFetchRecordsDelegate inner) {
-    if (inner == null) {
-      throw new IllegalArgumentException("Inner delegate cannot be null!");
-    }
-    return new DecryptingTransformingFetchDelegate(inner, this.keyBundle, this.recordFactory);
-  }
-
-  @Override
-  public void fetchSince(long timestamp,
-                         RepositorySessionFetchRecordsDelegate delegate) {
-    inner.fetchSince(timestamp, makeUnwrappingDelegate(delegate));
-  }
-
-  @Override
-  public void fetch(String[] guids,
-                    RepositorySessionFetchRecordsDelegate delegate) throws InactiveSessionException {
-    inner.fetch(guids, makeUnwrappingDelegate(delegate));
-  }
-
-  @Override
-  public void fetchAll(RepositorySessionFetchRecordsDelegate delegate) {
-    inner.fetchAll(makeUnwrappingDelegate(delegate));
-  }
-
-  @Override
-  public void setStoreDelegate(RepositorySessionStoreDelegate delegate) {
-    // TODO: it remains to be seen how this will work.
-    inner.setStoreDelegate(delegate);
-    this.delegate = delegate;             // So we can handle errors without involving inner.
-  }
-
-  @Override
-  public void store(Record record) throws NoStoreDelegateException {
-    if (delegate == null) {
-      throw new NoStoreDelegateException();
-    }
-    CryptoRecord rec = record.getEnvelope();
-    rec.keyBundle = this.keyBundle;
-    try {
-      rec.encrypt();
-    } catch (UnsupportedEncodingException | CryptoException e) {
-      delegate.onRecordStoreFailed(e, record.guid);
-      return;
-    }
-    // Allow the inner session to do delegate handling.
-    inner.store(rec);
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/middleware/MiddlewareRepository.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/middleware/MiddlewareRepository.java
deleted file mode 100644
index d807aa5..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/middleware/MiddlewareRepository.java
+++ /dev/null
@@ -1,22 +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.sync.middleware;
-
-import org.mozilla.gecko.sync.repositories.Repository;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionCreationDelegate;
-
-public abstract class MiddlewareRepository extends Repository {
-
-  public abstract class SessionCreationDelegate implements
-      RepositorySessionCreationDelegate {
-
-    // We call through to our inner repository, so we don't need our own
-    // deferral scheme.
-    @Override
-    public RepositorySessionCreationDelegate deferredCreationDelegate() {
-      return this;
-    }
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/middleware/MiddlewareRepositorySession.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/middleware/MiddlewareRepositorySession.java
deleted file mode 100644
index e14ef52..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/middleware/MiddlewareRepositorySession.java
+++ /dev/null
@@ -1,185 +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.sync.middleware;
-
-import java.util.concurrent.ExecutorService;
-
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.sync.repositories.InactiveSessionException;
-import org.mozilla.gecko.sync.repositories.InvalidSessionTransitionException;
-import org.mozilla.gecko.sync.repositories.RepositorySession;
-import org.mozilla.gecko.sync.repositories.RepositorySessionBundle;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionBeginDelegate;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionFinishDelegate;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionGuidsSinceDelegate;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionWipeDelegate;
-
-public abstract class MiddlewareRepositorySession extends RepositorySession {
-  private static final String LOG_TAG = "MiddlewareSession";
-  protected final RepositorySession inner;
-
-  public MiddlewareRepositorySession(RepositorySession innerSession, MiddlewareRepository repository) {
-    super(repository);
-    this.inner = innerSession;
-  }
-
-  @Override
-  public void wipe(RepositorySessionWipeDelegate delegate) {
-    inner.wipe(delegate);
-  }
-
-  public class MiddlewareRepositorySessionBeginDelegate implements RepositorySessionBeginDelegate {
-
-    private final MiddlewareRepositorySession outerSession;
-    private final RepositorySessionBeginDelegate next;
-
-    public MiddlewareRepositorySessionBeginDelegate(MiddlewareRepositorySession outerSession, RepositorySessionBeginDelegate next) {
-      this.outerSession = outerSession;
-      this.next = next;
-    }
-
-    @Override
-    public void onBeginFailed(Exception ex) {
-      next.onBeginFailed(ex);
-    }
-
-    @Override
-    public void onBeginSucceeded(RepositorySession session) {
-      next.onBeginSucceeded(outerSession);
-    }
-
-    @Override
-    public RepositorySessionBeginDelegate deferredBeginDelegate(ExecutorService executor) {
-      final RepositorySessionBeginDelegate deferred = next.deferredBeginDelegate(executor);
-      return new RepositorySessionBeginDelegate() {
-        @Override
-        public void onBeginSucceeded(RepositorySession session) {
-          if (inner != session) {
-            Logger.warn(LOG_TAG, "Got onBeginSucceeded for session " + session + ", not our inner session!");
-          }
-          deferred.onBeginSucceeded(outerSession);
-        }
-
-        @Override
-        public void onBeginFailed(Exception ex) {
-          deferred.onBeginFailed(ex);
-        }
-
-        @Override
-        public RepositorySessionBeginDelegate deferredBeginDelegate(ExecutorService executor) {
-          return this;
-        }
-      };
-    }
-  }
-
-  @Override
-  public void begin(RepositorySessionBeginDelegate delegate) throws InvalidSessionTransitionException {
-    inner.begin(new MiddlewareRepositorySessionBeginDelegate(this, delegate));
-  }
-
-  public class MiddlewareRepositorySessionFinishDelegate implements RepositorySessionFinishDelegate {
-    private final MiddlewareRepositorySession outerSession;
-    private final RepositorySessionFinishDelegate next;
-
-    public MiddlewareRepositorySessionFinishDelegate(MiddlewareRepositorySession outerSession, RepositorySessionFinishDelegate next) {
-      this.outerSession = outerSession;
-      this.next = next;
-    }
-
-    @Override
-    public void onFinishFailed(Exception ex) {
-      next.onFinishFailed(ex);
-    }
-
-    @Override
-    public void onFinishSucceeded(RepositorySession session, RepositorySessionBundle bundle) {
-      next.onFinishSucceeded(outerSession, bundle);
-    }
-
-    @Override
-    public RepositorySessionFinishDelegate deferredFinishDelegate(ExecutorService executor) {
-      return this;
-    }
-  }
-
-  @Override
-  public void finish(RepositorySessionFinishDelegate delegate) throws InactiveSessionException {
-    inner.finish(new MiddlewareRepositorySessionFinishDelegate(this, delegate));
-  }
-
-
-  @Override
-  public synchronized void ensureActive() throws InactiveSessionException {
-    inner.ensureActive();
-  }
-
-  @Override
-  public synchronized boolean isActive() {
-    return inner.isActive();
-  }
-
-  @Override
-  public synchronized SessionStatus getStatus() {
-    return inner.getStatus();
-  }
-
-  @Override
-  public synchronized void setStatus(SessionStatus status) {
-    inner.setStatus(status);
-  }
-
-  @Override
-  public synchronized void transitionFrom(SessionStatus from, SessionStatus to)
-      throws InvalidSessionTransitionException {
-    inner.transitionFrom(from, to);
-  }
-
-  @Override
-  public void abort() {
-    inner.abort();
-  }
-
-  @Override
-  public void abort(RepositorySessionFinishDelegate delegate) {
-    inner.abort(new MiddlewareRepositorySessionFinishDelegate(this, delegate));
-  }
-
-  @Override
-  public void guidsSince(long timestamp, RepositorySessionGuidsSinceDelegate delegate) {
-    // TODO: need to do anything here?
-    inner.guidsSince(timestamp, delegate);
-  }
-
-  @Override
-  public void storeDone() {
-    inner.storeDone();
-  }
-
-  @Override
-  public void storeDone(long storeEnd) {
-    inner.storeDone(storeEnd);
-  }
-
-  @Override
-  public boolean shouldSkip() {
-    return inner.shouldSkip();
-  }
-
-  @Override
-  public boolean dataAvailable() {
-    return inner.dataAvailable();
-  }
-
-  @Override
-  public void unbundle(RepositorySessionBundle bundle) {
-    inner.unbundle(bundle);
-  }
-
-  @Override
-  public long getLastSyncTimestamp() {
-    return inner.getLastSyncTimestamp();
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/AbstractBearerTokenAuthHeaderProvider.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/AbstractBearerTokenAuthHeaderProvider.java
deleted file mode 100644
index e3b4f25..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/AbstractBearerTokenAuthHeaderProvider.java
+++ /dev/null
@@ -1,34 +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.sync.net;
-
-import ch.boye.httpclientandroidlib.Header;
-import ch.boye.httpclientandroidlib.client.methods.HttpRequestBase;
-import ch.boye.httpclientandroidlib.impl.client.DefaultHttpClient;
-import ch.boye.httpclientandroidlib.message.BasicHeader;
-import ch.boye.httpclientandroidlib.protocol.BasicHttpContext;
-
-/**
- * An <code>AuthHeaderProvider</code> that returns an Authorization header for
- * bearer tokens, adding a simple prefix.
- */
-public abstract class AbstractBearerTokenAuthHeaderProvider implements AuthHeaderProvider {
-  protected final String header;
-
-  public AbstractBearerTokenAuthHeaderProvider(String token) {
-    if (token == null) {
-      throw new IllegalArgumentException("token must not be null.");
-    }
-
-    this.header = getPrefix() + " " + token;
-  }
-
-  protected abstract String getPrefix();
-
-  @Override
-  public Header getAuthHeader(HttpRequestBase request, BasicHttpContext context, DefaultHttpClient client) {
-    return new BasicHeader("Authorization", header);
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/AuthHeaderProvider.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/AuthHeaderProvider.java
deleted file mode 100644
index 7be6fef..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/AuthHeaderProvider.java
+++ /dev/null
@@ -1,30 +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.sync.net;
-
-import java.security.GeneralSecurityException;
-
-import ch.boye.httpclientandroidlib.Header;
-import ch.boye.httpclientandroidlib.client.methods.HttpRequestBase;
-import ch.boye.httpclientandroidlib.impl.client.DefaultHttpClient;
-import ch.boye.httpclientandroidlib.protocol.BasicHttpContext;
-
-/**
- * An <code>AuthHeaderProvider</code> generates HTTP Authorization headers for
- * HTTP requests.
- */
-public interface AuthHeaderProvider {
-  /**
-   * Generate an HTTP Authorization header.
-   *
-   * @param request HTTP request.
-   * @param context HTTP context.
-   * @param client HTTP client.
-   * @return HTTP Authorization header.
-   * @throws GeneralSecurityException usually wrapping a more specific exception.
-   */
-  Header getAuthHeader(HttpRequestBase request, BasicHttpContext context, DefaultHttpClient client)
-    throws GeneralSecurityException;
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/BaseResource.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/BaseResource.java
deleted file mode 100644
index 60bbc86..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/BaseResource.java
+++ /dev/null
@@ -1,565 +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.sync.net;
-
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.UnsupportedEncodingException;
-import java.lang.ref.WeakReference;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.security.GeneralSecurityException;
-import java.security.KeyManagementException;
-import java.security.NoSuchAlgorithmException;
-import java.security.SecureRandom;
-import java.util.concurrent.CopyOnWriteArrayList;
-
-import javax.net.ssl.SSLContext;
-
-import org.json.simple.JSONArray;
-import org.json.simple.JSONObject;
-import org.mozilla.gecko.background.common.GlobalConstants;
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.sync.ExtendedJSONObject;
-
-import ch.boye.httpclientandroidlib.Header;
-import ch.boye.httpclientandroidlib.HttpEntity;
-import ch.boye.httpclientandroidlib.HttpResponse;
-import ch.boye.httpclientandroidlib.HttpVersion;
-import ch.boye.httpclientandroidlib.client.AuthCache;
-import ch.boye.httpclientandroidlib.client.ClientProtocolException;
-import ch.boye.httpclientandroidlib.client.entity.GzipCompressingEntity;
-import ch.boye.httpclientandroidlib.client.methods.HttpDelete;
-import ch.boye.httpclientandroidlib.client.methods.HttpGet;
-import ch.boye.httpclientandroidlib.client.methods.HttpPatch;
-import ch.boye.httpclientandroidlib.client.methods.HttpPost;
-import ch.boye.httpclientandroidlib.client.methods.HttpPut;
-import ch.boye.httpclientandroidlib.client.methods.HttpRequestBase;
-import ch.boye.httpclientandroidlib.client.methods.HttpUriRequest;
-import ch.boye.httpclientandroidlib.client.protocol.ClientContext;
-import ch.boye.httpclientandroidlib.conn.ClientConnectionManager;
-import ch.boye.httpclientandroidlib.conn.scheme.PlainSocketFactory;
-import ch.boye.httpclientandroidlib.conn.scheme.Scheme;
-import ch.boye.httpclientandroidlib.conn.scheme.SchemeRegistry;
-import ch.boye.httpclientandroidlib.conn.ssl.SSLSocketFactory;
-import ch.boye.httpclientandroidlib.entity.StringEntity;
-import ch.boye.httpclientandroidlib.impl.client.BasicAuthCache;
-import ch.boye.httpclientandroidlib.impl.client.DefaultHttpClient;
-import ch.boye.httpclientandroidlib.impl.conn.tsccm.ThreadSafeClientConnManager;
-import ch.boye.httpclientandroidlib.params.HttpConnectionParams;
-import ch.boye.httpclientandroidlib.params.HttpParams;
-import ch.boye.httpclientandroidlib.params.HttpProtocolParams;
-import ch.boye.httpclientandroidlib.protocol.BasicHttpContext;
-import ch.boye.httpclientandroidlib.protocol.HttpContext;
-import ch.boye.httpclientandroidlib.util.EntityUtils;
-
-/**
- * Provide simple HTTP access to a Sync server or similar.
- * Implements Basic Auth by asking its delegate for credentials.
- * Communicates with a ResourceDelegate to asynchronously return responses and errors.
- * Exposes simple get/post/put/delete methods.
- */
- at SuppressWarnings("deprecation")
-public class BaseResource implements Resource {
-  private static final String ANDROID_LOOPBACK_IP = "10.0.2.2";
-
-  private static final int MAX_TOTAL_CONNECTIONS     = 20;
-  private static final int MAX_CONNECTIONS_PER_ROUTE = 10;
-
-  private boolean retryOnFailedRequest = true;
-
-  public static boolean rewriteLocalhost = true;
-
-  private static final String LOG_TAG = "BaseResource";
-
-  protected final URI uri;
-  protected BasicHttpContext context;
-  protected DefaultHttpClient client;
-  public    ResourceDelegate delegate;
-  protected HttpRequestBase request;
-  public final String charset = "utf-8";
-
-  private boolean shouldGzipCompress = false;
-  // A hint whether uploaded payloads are chunked. Default true to use GzipCompressingEntity, which is built-in functionality.
-  private boolean shouldChunkUploadsHint = true;
-
-  /**
-   * We have very few writes (observers tend to be installed around sync
-   * sessions) and many iterations (every HTTP request iterates observers), so
-   * CopyOnWriteArrayList is a reasonable choice.
-   */
-  protected static final CopyOnWriteArrayList<WeakReference<HttpResponseObserver>>
-    httpResponseObservers = new CopyOnWriteArrayList<>();
-
-  public BaseResource(String uri) throws URISyntaxException {
-    this(uri, rewriteLocalhost);
-  }
-
-  public BaseResource(URI uri) {
-    this(uri, rewriteLocalhost);
-  }
-
-  public BaseResource(String uri, boolean rewrite) throws URISyntaxException {
-    this(new URI(uri), rewrite);
-  }
-
-  public BaseResource(URI uri, boolean rewrite) {
-    if (uri == null) {
-      throw new IllegalArgumentException("uri must not be null");
-    }
-    if (rewrite && "localhost".equals(uri.getHost())) {
-      // Rewrite localhost URIs to refer to the special Android emulator loopback passthrough interface.
-      Logger.debug(LOG_TAG, "Rewriting " + uri + " to point to " + ANDROID_LOOPBACK_IP + ".");
-      try {
-        this.uri = new URI(uri.getScheme(), uri.getUserInfo(), ANDROID_LOOPBACK_IP, uri.getPort(), uri.getPath(), uri.getQuery(), uri.getFragment());
-      } catch (URISyntaxException e) {
-        Logger.error(LOG_TAG, "Got error rewriting URI for Android emulator.", e);
-        throw new IllegalArgumentException("Invalid URI", e);
-      }
-    } else {
-      this.uri = uri;
-    }
-  }
-
-  public static void addHttpResponseObserver(HttpResponseObserver newHttpResponseObserver) {
-    if (newHttpResponseObserver == null) {
-      return;
-    }
-    httpResponseObservers.add(new WeakReference<HttpResponseObserver>(newHttpResponseObserver));
-  }
-
-  public static boolean isHttpResponseObserver(HttpResponseObserver httpResponseObserver) {
-    for (WeakReference<HttpResponseObserver> weakReference : httpResponseObservers) {
-      HttpResponseObserver innerHttpResponseObserver = weakReference.get();
-      if (innerHttpResponseObserver == httpResponseObserver) {
-        return true;
-      }
-    }
-    return false;
-  }
-
-  public static boolean removeHttpResponseObserver(HttpResponseObserver httpResponseObserver) {
-    for (WeakReference<HttpResponseObserver> weakReference : httpResponseObservers) {
-      HttpResponseObserver innerHttpResponseObserver = weakReference.get();
-      if (innerHttpResponseObserver == httpResponseObserver) {
-        // It's safe to mutate the observers while iterating.
-        httpResponseObservers.remove(weakReference);
-        return true;
-      }
-    }
-    return false;
-  }
-
-  @Override
-  public URI getURI() {
-    return this.uri;
-  }
-
-  @Override
-  public String getURIString() {
-    return this.uri.toString();
-  }
-
-  @Override
-  public String getHostname() {
-    return this.getURI().getHost();
-  }
-
-  /**
-   * Causes the Resource to compress the uploaded entity payload in requests with payloads (e.g. post, put)
-   * @param shouldCompress true if the entity should be compressed, false otherwise
-   */
-  public void setShouldCompressUploadedEntity(final boolean shouldCompress) {
-    shouldGzipCompress = shouldCompress;
-  }
-
-  /**
-   * Causes the Resource to chunk the uploaded entity payload in requests with payloads (e.g. post, put).
-   * Note: this flag is only a hint - chunking is not guaranteed.
-   *
-   * Chunking is currently supported with gzip compression.
-   *
-   * @param shouldChunk true if the transfer should be chunked, false otherwise
-   */
-  public void setShouldChunkUploadsHint(final boolean shouldChunk) {
-    shouldChunkUploadsHint = shouldChunk;
-  }
-
-  private HttpEntity getMaybeCompressedEntity(final HttpEntity entity) {
-    if (!shouldGzipCompress) {
-      return entity;
-    }
-
-    return shouldChunkUploadsHint ? new GzipCompressingEntity(entity) : new GzipNonChunkedCompressingEntity(entity);
-  }
-
-  /**
-   * This shuts up HttpClient, which will otherwise debug log about there
-   * being no auth cache in the context.
-   */
-  private static void addAuthCacheToContext(HttpUriRequest request, HttpContext context) {
-    AuthCache authCache = new BasicAuthCache();                // Not thread safe.
-    context.setAttribute(ClientContext.AUTH_CACHE, authCache);
-  }
-
-  /**
-   * Invoke this after delegate and request have been set.
-   * @throws NoSuchAlgorithmException
-   * @throws KeyManagementException
-   */
-  protected void prepareClient() throws KeyManagementException, NoSuchAlgorithmException, GeneralSecurityException {
-    context = new BasicHttpContext();
-
-    // We could reuse these client instances, except that we mess around
-    // with their parameters… so we'd need a pool of some kind.
-    client = new DefaultHttpClient(getConnectionManager());
-
-    // TODO: Eventually we should use Apache HttpAsyncClient. It's not out of alpha yet.
-    // Until then, we synchronously make the request, then invoke our delegate's callback.
-    AuthHeaderProvider authHeaderProvider = delegate.getAuthHeaderProvider();
-    if (authHeaderProvider != null) {
-      Header authHeader = authHeaderProvider.getAuthHeader(request, context, client);
-      if (authHeader != null) {
-        request.addHeader(authHeader);
-        Logger.debug(LOG_TAG, "Added auth header.");
-      }
-    }
-
-    addAuthCacheToContext(request, context);
-
-    HttpParams params = client.getParams();
-    HttpConnectionParams.setConnectionTimeout(params, delegate.connectionTimeout());
-    HttpConnectionParams.setSoTimeout(params, delegate.socketTimeout());
-    HttpConnectionParams.setStaleCheckingEnabled(params, false);
-    HttpProtocolParams.setContentCharset(params, charset);
-    HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
-    final String userAgent = delegate.getUserAgent();
-    if (userAgent != null) {
-      HttpProtocolParams.setUserAgent(params, userAgent);
-    }
-    delegate.addHeaders(request, client);
-  }
-
-  private static final Object connManagerMonitor = new Object();
-  private static ClientConnectionManager connManager;
-
-  // Call within a synchronized block on connManagerMonitor.
-  private static ClientConnectionManager enableTLSConnectionManager() throws KeyManagementException, NoSuchAlgorithmException  {
-    SSLContext sslContext = SSLContext.getInstance("TLS");
-    sslContext.init(null, null, new SecureRandom());
-
-    Logger.debug(LOG_TAG, "Using protocols and cipher suites for Android API " + android.os.Build.VERSION.SDK_INT);
-    SSLSocketFactory sf = new SSLSocketFactory(sslContext, GlobalConstants.DEFAULT_PROTOCOLS, GlobalConstants.DEFAULT_CIPHER_SUITES, null);
-    SchemeRegistry schemeRegistry = new SchemeRegistry();
-    schemeRegistry.register(new Scheme("https", 443, sf));
-    schemeRegistry.register(new Scheme("http", 80, new PlainSocketFactory()));
-    ThreadSafeClientConnManager cm = new ThreadSafeClientConnManager(schemeRegistry);
-
-    cm.setMaxTotal(MAX_TOTAL_CONNECTIONS);
-    cm.setDefaultMaxPerRoute(MAX_CONNECTIONS_PER_ROUTE);
-    connManager = cm;
-    return cm;
-  }
-
-  public static ClientConnectionManager getConnectionManager() throws KeyManagementException, NoSuchAlgorithmException
-                                                         {
-    // TODO: shutdown.
-    synchronized (connManagerMonitor) {
-      if (connManager != null) {
-        return connManager;
-      }
-      return enableTLSConnectionManager();
-    }
-  }
-
-  /**
-   * Do some cleanup, so we don't need the stale connection check.
-   */
-  public static void closeExpiredConnections() {
-    ClientConnectionManager connectionManager;
-    synchronized (connManagerMonitor) {
-      connectionManager = connManager;
-    }
-    if (connectionManager == null) {
-      return;
-    }
-    Logger.trace(LOG_TAG, "Closing expired connections.");
-    connectionManager.closeExpiredConnections();
-  }
-
-  public static void shutdownConnectionManager() {
-    ClientConnectionManager connectionManager;
-    synchronized (connManagerMonitor) {
-      connectionManager = connManager;
-      connManager = null;
-    }
-    if (connectionManager == null) {
-      return;
-    }
-    Logger.debug(LOG_TAG, "Shutting down connection manager.");
-    connectionManager.shutdown();
-  }
-
-  private void execute() {
-    HttpResponse response;
-    try {
-      response = client.execute(request, context);
-      Logger.debug(LOG_TAG, "Response: " + response.getStatusLine().toString());
-    } catch (ClientProtocolException e) {
-      delegate.handleHttpProtocolException(e);
-      return;
-    } catch (IOException e) {
-      Logger.debug(LOG_TAG, "I/O exception returned from execute.");
-      if (!retryOnFailedRequest) {
-        delegate.handleHttpIOException(e);
-      } else {
-        retryRequest();
-      }
-      return;
-    } catch (Exception e) {
-      // Bug 740731: Don't let an exception fall through. Wrapping isn't
-      // optimal, but often the exception is treated as an Exception anyway.
-      if (!retryOnFailedRequest) {
-        // Bug 769671: IOException(Throwable cause) was added only in API level 9.
-        final IOException ex = new IOException();
-        ex.initCause(e);
-        delegate.handleHttpIOException(ex);
-      } else {
-        retryRequest();
-      }
-      return;
-    }
-
-    // Don't retry if the observer or delegate throws!
-    for (WeakReference<HttpResponseObserver> weakReference : httpResponseObservers) {
-      HttpResponseObserver observer = weakReference.get();
-      if (observer != null) {
-        observer.observeHttpResponse(request, response);
-      }
-    }
-    delegate.handleHttpResponse(response);
-  }
-
-  private void retryRequest() {
-    // Only retry once.
-    retryOnFailedRequest = false;
-    Logger.debug(LOG_TAG, "Retrying request...");
-    this.execute();
-  }
-
-  private void go(HttpRequestBase request) {
-   if (delegate == null) {
-      throw new IllegalArgumentException("No delegate provided.");
-    }
-    this.request = request;
-    try {
-      this.prepareClient();
-    } catch (KeyManagementException e) {
-      Logger.error(LOG_TAG, "Couldn't prepare client.", e);
-      delegate.handleTransportException(e);
-      return;
-    } catch (GeneralSecurityException e) {
-      Logger.error(LOG_TAG, "Couldn't prepare client.", e);
-      delegate.handleTransportException(e);
-      return;
-    } catch (Exception e) {
-      // Bug 740731: Don't let an exception fall through. Wrapping isn't
-      // optimal, but often the exception is treated as an Exception anyway.
-      delegate.handleTransportException(new GeneralSecurityException(e));
-      return;
-    }
-    this.execute();
-  }
-
-  @Override
-  public void get() {
-    Logger.debug(LOG_TAG, "HTTP GET " + this.uri.toASCIIString());
-    this.go(new HttpGet(this.uri));
-  }
-
-  /**
-   * Perform an HTTP GET as with {@link BaseResource#get()}, returning only
-   * after callbacks have been invoked.
-   */
-  public void getBlocking() {
-    // Until we use the asynchronous Apache HttpClient, we can simply call
-    // through.
-    this.get();
-  }
-
-  @Override
-  public void delete() {
-    Logger.debug(LOG_TAG, "HTTP DELETE " + this.uri.toASCIIString());
-    this.go(new HttpDelete(this.uri));
-  }
-
-  @Override
-  public void post(HttpEntity body) {
-    Logger.debug(LOG_TAG, "HTTP POST " + this.uri.toASCIIString());
-    body = getMaybeCompressedEntity(body);
-    HttpPost request = new HttpPost(this.uri);
-    request.setEntity(body);
-    this.go(request);
-  }
-
-  @Override
-  public void patch(HttpEntity body) {
-    Logger.debug(LOG_TAG, "HTTP PATCH " + this.uri.toASCIIString());
-    body = getMaybeCompressedEntity(body);
-    HttpPatch request = new HttpPatch(this.uri);
-    request.setEntity(body);
-    this.go(request);
-  }
-
-  @Override
-  public void put(HttpEntity body) {
-    Logger.debug(LOG_TAG, "HTTP PUT " + this.uri.toASCIIString());
-    body = getMaybeCompressedEntity(body);
-    HttpPut request = new HttpPut(this.uri);
-    request.setEntity(body);
-    this.go(request);
-  }
-
-  protected static StringEntity stringEntityWithContentTypeApplicationJSON(String s) {
-    StringEntity e = new StringEntity(s, "UTF-8");
-    e.setContentType("application/json");
-    return e;
-  }
-
-  /**
-   * Helper for turning a JSON object into a payload.
-   * @throws UnsupportedEncodingException
-   */
-  protected static StringEntity jsonEntity(JSONObject body) {
-    return stringEntityWithContentTypeApplicationJSON(body.toJSONString());
-  }
-
-  /**
-   * Helper for turning an extended JSON object into a payload.
-   * @throws UnsupportedEncodingException
-   */
-  protected static StringEntity jsonEntity(ExtendedJSONObject body) {
-    return stringEntityWithContentTypeApplicationJSON(body.toJSONString());
-  }
-
-  /**
-   * Helper for turning a JSON array into a payload.
-   * @throws UnsupportedEncodingException
-   */
-  protected static HttpEntity jsonEntity(JSONArray toPOST) throws UnsupportedEncodingException {
-    return stringEntityWithContentTypeApplicationJSON(toPOST.toJSONString());
-  }
-
-  /**
-   * Best-effort attempt to ensure that the entity has been fully consumed and
-   * that the underlying stream has been closed.
-   *
-   * This releases the connection back to the connection pool.
-   *
-   * @param entity The HttpEntity to be consumed.
-   */
-  public static void consumeEntity(HttpEntity entity) {
-    try {
-      EntityUtils.consume(entity);
-    } catch (IOException e) {
-      // Doesn't matter.
-    }
-  }
-
-  /**
-   * Best-effort attempt to ensure that the entity corresponding to the given
-   * HTTP response has been fully consumed and that the underlying stream has
-   * been closed.
-   *
-   * This releases the connection back to the connection pool.
-   *
-   * @param response
-   *          The HttpResponse to be consumed.
-   */
-  public static void consumeEntity(HttpResponse response) {
-    if (response == null) {
-      return;
-    }
-    try {
-      EntityUtils.consume(response.getEntity());
-    } catch (IOException e) {
-    }
-  }
-
-  /**
-   * Best-effort attempt to ensure that the entity corresponding to the given
-   * Sync storage response has been fully consumed and that the underlying
-   * stream has been closed.
-   *
-   * This releases the connection back to the connection pool.
-   *
-   * @param response
-   *          The SyncStorageResponse to be consumed.
-   */
-  public static void consumeEntity(SyncStorageResponse response) {
-    if (response.httpResponse() == null) {
-      return;
-    }
-    consumeEntity(response.httpResponse());
-  }
-
-  /**
-   * Best-effort attempt to ensure that the reader has been fully consumed, so
-   * that the underlying stream will be closed.
-   *
-   * This should allow the connection to be released back to the connection pool.
-   *
-   * @param reader The BufferedReader to be consumed.
-   */
-  public static void consumeReader(BufferedReader reader) {
-    try {
-      reader.close();
-    } catch (IOException e) {
-      // Do nothing.
-    }
-  }
-
-  public void post(JSONArray jsonArray) throws UnsupportedEncodingException {
-    post(jsonEntity(jsonArray));
-  }
-
-  public void put(JSONObject jsonObject) throws UnsupportedEncodingException {
-    put(jsonEntity(jsonObject));
-  }
-
-  public void put(ExtendedJSONObject o) {
-    put(jsonEntity(o));
-  }
-
-  public void post(ExtendedJSONObject o) {
-    post(jsonEntity(o));
-  }
-
-  /**
-   * Perform an HTTP POST as with {@link BaseResource#post(ExtendedJSONObject)}, returning only
-   * after callbacks have been invoked.
-   */
-  public void postBlocking(final ExtendedJSONObject o) {
-    // Until we use the asynchronous Apache HttpClient, we can simply call
-    // through.
-    post(jsonEntity(o));
-  }
-
-  public void post(JSONObject jsonObject) throws UnsupportedEncodingException {
-    post(jsonEntity(jsonObject));
-  }
-
-  public void patch(JSONArray jsonArray) throws UnsupportedEncodingException {
-    patch(jsonEntity(jsonArray));
-  }
-
-  public void patch(ExtendedJSONObject o) {
-    patch(jsonEntity(o));
-  }
-
-  public void patch(JSONObject jsonObject) throws UnsupportedEncodingException {
-    patch(jsonEntity(jsonObject));
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/BaseResourceDelegate.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/BaseResourceDelegate.java
deleted file mode 100644
index 84ae7a3..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/BaseResourceDelegate.java
+++ /dev/null
@@ -1,44 +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.sync.net;
-
-import ch.boye.httpclientandroidlib.client.methods.HttpRequestBase;
-import ch.boye.httpclientandroidlib.impl.client.DefaultHttpClient;
-
-/**
- * Shared abstract class for resource delegate that use the same timeouts
- * and no credentials.
- *
- * @author rnewman
- *
- */
-public abstract class BaseResourceDelegate implements ResourceDelegate {
-  public static int connectionTimeoutInMillis = 1000 * 30;     // Wait 30s for a connection to open.
-  public static int socketTimeoutInMillis     = 1000 * 2 * 60; // Wait 2 minutes for data.
-
-  protected Resource resource;
-  public BaseResourceDelegate(Resource resource) {
-    this.resource = resource;
-  }
-
-  @Override
-  public int connectionTimeout() {
-    return connectionTimeoutInMillis;
-  }
-
-  @Override
-  public int socketTimeout() {
-    return socketTimeoutInMillis;
-  }
-
-  @Override
-  public AuthHeaderProvider getAuthHeaderProvider() {
-    return null;
-  }
-
-  @Override
-  public void addHeaders(HttpRequestBase request, DefaultHttpClient client) {
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/BasicAuthHeaderProvider.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/BasicAuthHeaderProvider.java
deleted file mode 100644
index d8a371d..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/BasicAuthHeaderProvider.java
+++ /dev/null
@@ -1,51 +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.sync.net;
-
-import ch.boye.httpclientandroidlib.Header;
-import ch.boye.httpclientandroidlib.auth.Credentials;
-import ch.boye.httpclientandroidlib.auth.UsernamePasswordCredentials;
-import ch.boye.httpclientandroidlib.client.methods.HttpRequestBase;
-import ch.boye.httpclientandroidlib.impl.auth.BasicScheme;
-import ch.boye.httpclientandroidlib.impl.client.DefaultHttpClient;
-import ch.boye.httpclientandroidlib.protocol.BasicHttpContext;
-
-/**
- * An <code>AuthHeaderProvider</code> that returns an HTTP Basic auth header.
- */
-public class BasicAuthHeaderProvider implements AuthHeaderProvider {
-  protected final String credentials;
-
-  /**
-   * Constructor.
-   *
-   * @param credentials string in form "user:pass".
-   */
-  public BasicAuthHeaderProvider(String credentials) {
-    this.credentials = credentials;
-  }
-
-  /**
-   * Constructor.
-   *
-   * @param user username.
-   * @param pass password.
-   */
-  public BasicAuthHeaderProvider(String user, String pass) {
-    this(user + ":" + pass);
-  }
-
-  /**
-   * Return a Header object representing an Authentication header for HTTP
-   * Basic.
-   */
-  @Override
-  public Header getAuthHeader(HttpRequestBase request, BasicHttpContext context, DefaultHttpClient client) {
-    Credentials creds = new UsernamePasswordCredentials(credentials);
-
-    // This must be UTF-8 to generate the same Basic Auth headers as desktop for non-ASCII passwords.
-    return BasicScheme.authenticate(creds, "UTF-8", false);
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/BearerAuthHeaderProvider.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/BearerAuthHeaderProvider.java
deleted file mode 100644
index d142d50..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/BearerAuthHeaderProvider.java
+++ /dev/null
@@ -1,22 +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.sync.net;
-
-/**
- * An <code>AuthHeaderProvider</code> that returns an Authorization header for
- * Bearer tokens in the format expected by a Mozilla Firefox Accounts Profile Server.
- * <p>
- * See <a href="https://github.com/mozilla/fxa-profile-server/blob/master/docs/API.md">https://github.com/mozilla/fxa-profile-server/blob/master/docs/API.md</a>.
- */
-public class BearerAuthHeaderProvider extends AbstractBearerTokenAuthHeaderProvider {
-  public BearerAuthHeaderProvider(String token) {
-    super(token);
-  }
-
-  @Override
-  protected String getPrefix() {
-    return "Bearer";
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/BrowserIDAuthHeaderProvider.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/BrowserIDAuthHeaderProvider.java
deleted file mode 100644
index 5004673..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/BrowserIDAuthHeaderProvider.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.sync.net;
-
-/**
- * An <code>AuthHeaderProvider</code> that returns an Authorization header for
- * BrowserID assertions in the format expected by a Mozilla Services Token
- * Server.
- * <p>
- * See <a href="http://docs.services.mozilla.com/token/apis.html">http://docs.services.mozilla.com/token/apis.html</a>.
- */
-public class BrowserIDAuthHeaderProvider extends AbstractBearerTokenAuthHeaderProvider {
-  public BrowserIDAuthHeaderProvider(String assertion) {
-    super(assertion);
-  }
-
-  @Override
-  protected String getPrefix() {
-    return "BrowserID";
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/ConnectionMonitorThread.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/ConnectionMonitorThread.java
deleted file mode 100644
index 1a20117..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/ConnectionMonitorThread.java
+++ /dev/null
@@ -1,44 +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.sync.net;
-
-import org.mozilla.gecko.background.common.log.Logger;
-
-/**
- * Every <code>REAP_INTERVAL</code> milliseconds, wake up
- * and expire any connections that need cleaning up.
- *
- * When we're told to shut down, take the connection manager
- * with us.
- */
-public class ConnectionMonitorThread extends Thread {
-  private static final long REAP_INTERVAL = 5000;     // 5 seconds.
-  private static final String LOG_TAG = "ConnectionMonitorThread";
-
-  private volatile boolean stopping;
-
-  @Override
-  public void run() {
-    try {
-      while (!stopping) {
-        synchronized (this) {
-          wait(REAP_INTERVAL);
-          BaseResource.closeExpiredConnections();
-        }
-      }
-    } catch (InterruptedException e) {
-      Logger.trace(LOG_TAG, "Interrupted.");
-    }
-    BaseResource.shutdownConnectionManager();
-  }
-
-  public void shutdown() {
-    Logger.debug(LOG_TAG, "ConnectionMonitorThread told to shut down.");
-    stopping = true;
-    synchronized (this) {
-      notifyAll();
-    }
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/GzipNonChunkedCompressingEntity.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/GzipNonChunkedCompressingEntity.java
deleted file mode 100644
index 1e238c0..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/GzipNonChunkedCompressingEntity.java
+++ /dev/null
@@ -1,92 +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.sync.net;
-
-import ch.boye.httpclientandroidlib.HttpEntity;
-import ch.boye.httpclientandroidlib.client.entity.GzipCompressingEntity;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-
-/**
- * Wrapping entity that compresses content when {@link #writeTo writing}.
- *
- * This differs from {@link GzipCompressingEntity} in that it does not chunk
- * the sent data, therefore replacing the "Transfer-Encoding" HTTP header with
- * the "Content-Length" header required by some servers.
- *
- * However, to measure the content length, the gzipped content will be temporarily
- * stored in memory so be careful what content you send!
- */
-public class GzipNonChunkedCompressingEntity extends GzipCompressingEntity {
-    final int MAX_BUFFER_SIZE_BYTES = 10 * 1000 * 1000; // 10 MB.
-
-    private byte[] gzippedContent;
-
-    public GzipNonChunkedCompressingEntity(final HttpEntity entity) {
-        super(entity);
-    }
-
-    /**
-     * @return content length for gzipped content or -1 if there is an error
-     */
-    @Override
-    public long getContentLength() {
-        try {
-            initBuffer();
-        } catch (final IOException e) {
-            // GzipCompressingEntity always returns -1 in which case a 'Content-Length' header is omitted.
-            // Presumably, without it the request will fail (either client-side or server-side).
-            return -1;
-        }
-        return gzippedContent.length;
-    }
-
-    @Override
-    public boolean isChunked() {
-        // "Content-Length" & chunked encoding are mutually exclusive:
-        //   https://en.wikipedia.org/wiki/Chunked_transfer_encoding
-        return false;
-    }
-
-    @Override
-    public InputStream getContent() throws IOException {
-        initBuffer();
-        return new ByteArrayInputStream(gzippedContent);
-    }
-
-    @Override
-    public void writeTo(final OutputStream outstream) throws IOException {
-        initBuffer();
-        outstream.write(gzippedContent);
-    }
-
-    private void initBuffer() throws IOException {
-        if (gzippedContent != null) {
-            return;
-        }
-
-        final long unzippedContentLength = wrappedEntity.getContentLength();
-        if (unzippedContentLength > MAX_BUFFER_SIZE_BYTES) {
-            throw new IOException(
-                    "Wrapped entity content length, " + unzippedContentLength + " bytes, exceeds max: " + MAX_BUFFER_SIZE_BYTES);
-        }
-
-        // The buffer size needed by the gzipped content should be smaller than this,
-        // but it's more efficient just to allocate one larger buffer than allocate
-        // twice if the gzipped content is too large for the default buffer.
-        final ByteArrayOutputStream s = new ByteArrayOutputStream((int) unzippedContentLength);
-        try {
-            super.writeTo(s);
-        } finally {
-            s.close();
-        }
-
-        gzippedContent = s.toByteArray();
-    }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/HMACAuthHeaderProvider.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/HMACAuthHeaderProvider.java
deleted file mode 100644
index 5314d34..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/HMACAuthHeaderProvider.java
+++ /dev/null
@@ -1,257 +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.sync.net;
-
-import java.io.UnsupportedEncodingException;
-import java.net.URI;
-import java.security.GeneralSecurityException;
-import java.security.InvalidKeyException;
-import java.security.NoSuchAlgorithmException;
-
-import javax.crypto.Mac;
-import javax.crypto.spec.SecretKeySpec;
-
-import org.mozilla.apache.commons.codec.binary.Base64;
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.sync.Utils;
-
-import ch.boye.httpclientandroidlib.Header;
-import ch.boye.httpclientandroidlib.client.methods.HttpRequestBase;
-import ch.boye.httpclientandroidlib.client.methods.HttpUriRequest;
-import ch.boye.httpclientandroidlib.impl.client.DefaultHttpClient;
-import ch.boye.httpclientandroidlib.message.BasicHeader;
-import ch.boye.httpclientandroidlib.protocol.BasicHttpContext;
-
-/**
- * An <code>AuthHeaderProvider</code> that returns an Authorization header for
- * HMAC-SHA1-signed requests in the format expected by Mozilla Services
- * identity-attached services and specified by the MAC Authentication spec, available at
- * <a href="https://tools.ietf.org/html/draft-ietf-oauth-v2-http-mac">https://tools.ietf.org/html/draft-ietf-oauth-v2-http-mac</a>.
- * <p>
- * See <a href="https://wiki.mozilla.org/Services/Sagrada/ServiceClientFlow#Access">https://wiki.mozilla.org/Services/Sagrada/ServiceClientFlow#Access</a>.
- */
-public class HMACAuthHeaderProvider implements AuthHeaderProvider {
-  public static final String LOG_TAG = "HMACAuthHeaderProvider";
-
-  public static final int NONCE_LENGTH_IN_BYTES = 8;
-
-  public static final String HMAC_SHA1_ALGORITHM = "hmacSHA1";
-
-  public final String identifier;
-  public final String key;
-
-  public HMACAuthHeaderProvider(String identifier, String key) {
-    // Validate identifier string.  From the MAC Authentication spec:
-    // id             = "id" "=" string-value
-    // string-value   = ( <"> plain-string <"> ) / plain-string
-    // plain-string   = 1*( %x20-21 / %x23-5B / %x5D-7E )
-    // We add quotes around the id string, so input identifier must be a plain-string.
-    if (identifier == null) {
-      throw new IllegalArgumentException("identifier must not be null.");
-    }
-    if (!isPlainString(identifier)) {
-      throw new IllegalArgumentException("identifier must be a plain-string.");
-    }
-
-    if (key == null) {
-      throw new IllegalArgumentException("key must not be null.");
-    }
-
-    this.identifier = identifier;
-    this.key = key;
-  }
-
-  @Override
-  public Header getAuthHeader(HttpRequestBase request, BasicHttpContext context, DefaultHttpClient client) throws GeneralSecurityException {
-    long timestamp = System.currentTimeMillis() / 1000;
-    String nonce = Base64.encodeBase64String(Utils.generateRandomBytes(NONCE_LENGTH_IN_BYTES));
-    String extra = "";
-
-    try {
-      return getAuthHeader(request, context, client, timestamp, nonce, extra);
-    } catch (InvalidKeyException | NoSuchAlgorithmException | UnsupportedEncodingException e) {
-      // We lie a little and make every exception a GeneralSecurityException.
-      throw new GeneralSecurityException(e);
-    }
-  }
-
-  /**
-   * Test if input is a <code>plain-string</code>.
-   * <p>
-   * A plain-string is defined by the MAC Authentication spec as
-   * <code>plain-string   = 1*( %x20-21 / %x23-5B / %x5D-7E )</code>.
-   *
-   * @param input
-   *          as a String of "US-ASCII" bytes.
-   * @return true if input is a <code>plain-string</code>; false otherwise.
-   * @throws UnsupportedEncodingException
-   */
-  protected static boolean isPlainString(String input) {
-    if (input == null || input.length() == 0) {
-      return false;
-    }
-
-    byte[] bytes;
-    try {
-      bytes = input.getBytes("US-ASCII");
-    } catch (UnsupportedEncodingException e) {
-      // Should never happen.
-      Logger.warn(LOG_TAG, "Got exception in isPlainString; returning false.", e);
-      return false;
-    }
-
-    for (byte b : bytes) {
-      if ((0x20 <= b && b <= 0x21) || (0x23 <= b && b <= 0x5B) || (0x5D <= b && b <= 0x7E)) {
-        continue;
-      }
-      return false;
-    }
-
-    return true;
-  }
-
-  /**
-   * Helper function that generates an HTTP Authorization header given
-   * additional MAC Authentication specific data.
-   *
-   * @throws UnsupportedEncodingException
-   * @throws NoSuchAlgorithmException 
-   * @throws InvalidKeyException 
-   */
-  protected Header getAuthHeader(HttpRequestBase request, BasicHttpContext context, DefaultHttpClient client,
-      long timestamp, String nonce, String extra)
-      throws UnsupportedEncodingException, InvalidKeyException, NoSuchAlgorithmException {
-    // Validate timestamp.  From the MAC Authentication spec:
-    // timestamp      = 1*DIGIT
-    // This is equivalent to timestamp >= 0.
-    if (timestamp < 0) {
-      throw new IllegalArgumentException("timestamp must contain only [0-9].");
-    }
-
-    // Validate nonce string.  From the MAC Authentication spec:
-    // nonce          = "nonce" "=" string-value
-    // string-value   = ( <"> plain-string <"> ) / plain-string
-    // plain-string   = 1*( %x20-21 / %x23-5B / %x5D-7E )
-    // We add quotes around the nonce string, so input nonce must be a plain-string.
-    if (nonce == null) {
-      throw new IllegalArgumentException("nonce must not be null.");
-    }
-    if (nonce.length() == 0) {
-      throw new IllegalArgumentException("nonce must not be empty.");
-    }
-    if (!isPlainString(nonce)) {
-      throw new IllegalArgumentException("nonce must be a plain-string.");
-    }
-
-    // Validate extra string.  From the MAC Authentication spec:
-    // ext            = "ext" "=" string-value
-    // string-value   = ( <"> plain-string <"> ) / plain-string
-    // plain-string   = 1*( %x20-21 / %x23-5B / %x5D-7E )
-    // We add quotes around the extra string, so input extra must be a plain-string.
-    // We break the spec by allowing ext to be an empty string, i.e. to match 0*(...).
-    if (extra == null) {
-      throw new IllegalArgumentException("extra must not be null.");
-    }
-    if (extra.length() > 0 && !isPlainString(extra)) {
-      throw new IllegalArgumentException("extra must be a plain-string.");
-    }
-
-    String requestString = getRequestString(request, timestamp, nonce, extra);
-    String macString = getSignature(requestString, this.key);
-
-    String h = "MAC id=\"" + this.identifier + "\", " +
-               "ts=\""     + timestamp       + "\", " +
-               "nonce=\""  + nonce           + "\", " +
-               "mac=\""    + macString       + "\"";
-
-    if (extra != null) {
-      h += ", ext=\"" + extra + "\"";
-    }
-
-    Header header = new BasicHeader("Authorization", h);
-
-    return header;
-  }
-
-  protected static byte[] sha1(byte[] message, byte[] key)
-      throws NoSuchAlgorithmException, InvalidKeyException {
-
-    SecretKeySpec keySpec = new SecretKeySpec(key, HMAC_SHA1_ALGORITHM);
-
-    Mac hasher = Mac.getInstance(HMAC_SHA1_ALGORITHM);
-    hasher.init(keySpec);
-    hasher.update(message);
-
-    byte[] hmac = hasher.doFinal();
-
-    return hmac;
-  }
-
-  /**
-   * Sign an HMAC request string.
-   *
-   * @param requestString to sign.
-   * @param key as <code>String</code>.
-   * @return signature as base-64 encoded string.
-   * @throws InvalidKeyException
-   * @throws NoSuchAlgorithmException
-   * @throws UnsupportedEncodingException
-   */
-  protected static String getSignature(String requestString, String key)
-      throws InvalidKeyException, NoSuchAlgorithmException, UnsupportedEncodingException {
-    String macString = Base64.encodeBase64String(sha1(requestString.getBytes("UTF-8"), key.getBytes("UTF-8")));
-
-    return macString;
-  }
-
-  /**
-   * Generate an HMAC request string.
-   * <p>
-   * This method trusts its inputs to be valid as per the MAC Authentication spec.
-   *
-   * @param request HTTP request.
-   * @param timestamp to use.
-   * @param nonce to use.
-   * @param extra to use.
-   * @return request string.
-   */
-  protected static String getRequestString(HttpUriRequest request, long timestamp, String nonce, String extra) {
-    String method = request.getMethod().toUpperCase();
-
-    URI uri = request.getURI();
-    String host = uri.getHost();
-
-    String path = uri.getRawPath();
-    if (uri.getRawQuery() != null) {
-      path += "?";
-      path += uri.getRawQuery();
-    }
-    if (uri.getRawFragment() != null) {
-      path += "#";
-      path += uri.getRawFragment();
-    }
-
-    int port = uri.getPort();
-    String scheme = uri.getScheme();
-    if (port != -1) {
-    } else if ("http".equalsIgnoreCase(scheme)) {
-      port = 80;
-    } else if ("https".equalsIgnoreCase(scheme)) {
-      port = 443;
-    } else {
-      throw new IllegalArgumentException("Unsupported URI scheme: " + scheme + ".");
-    }
-
-    String requestString = timestamp + "\n" +
-        nonce       + "\n" +
-        method      + "\n" +
-        path        + "\n" +
-        host        + "\n" +
-        port        + "\n" +
-        extra       + "\n";
-
-    return requestString;
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/HandleProgressException.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/HandleProgressException.java
deleted file mode 100644
index 27ec74b..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/HandleProgressException.java
+++ /dev/null
@@ -1,15 +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.sync.net;
-
-import org.mozilla.gecko.sync.SyncException;
-
-public class HandleProgressException extends SyncException {
-  private static final long serialVersionUID = -4444933937013161059L;
-
-  public HandleProgressException(Exception ex) {
-    super(ex);
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/HawkAuthHeaderProvider.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/HawkAuthHeaderProvider.java
deleted file mode 100644
index 2bdd560..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/HawkAuthHeaderProvider.java
+++ /dev/null
@@ -1,403 +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.sync.net;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.UnsupportedEncodingException;
-import java.net.URI;
-import java.security.GeneralSecurityException;
-import java.security.InvalidKeyException;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.util.Locale;
-
-import javax.crypto.Mac;
-import javax.crypto.spec.SecretKeySpec;
-
-import org.mozilla.apache.commons.codec.binary.Base64;
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.sync.Utils;
-
-import ch.boye.httpclientandroidlib.Header;
-import ch.boye.httpclientandroidlib.HttpEntity;
-import ch.boye.httpclientandroidlib.HttpEntityEnclosingRequest;
-import ch.boye.httpclientandroidlib.client.methods.HttpRequestBase;
-import ch.boye.httpclientandroidlib.client.methods.HttpUriRequest;
-import ch.boye.httpclientandroidlib.impl.client.DefaultHttpClient;
-import ch.boye.httpclientandroidlib.message.BasicHeader;
-import ch.boye.httpclientandroidlib.protocol.BasicHttpContext;
-
-/**
- * An <code>AuthHeaderProvider</code> that returns an Authorization header for
- * Hawk: <a href="https://github.com/hueniverse/hawk">https://github.com/hueniverse/hawk</a>.
- *
- * Hawk is an HTTP authentication scheme using a message authentication code
- * (MAC) algorithm to provide partial HTTP request cryptographic verification.
- * Hawk is the successor to the HMAC authentication scheme.
- */
-public class HawkAuthHeaderProvider implements AuthHeaderProvider {
-  public static final String LOG_TAG = HawkAuthHeaderProvider.class.getSimpleName();
-
-  public static final int HAWK_HEADER_VERSION = 1;
-
-  protected static final int NONCE_LENGTH_IN_BYTES = 8;
-  protected static final String HMAC_SHA256_ALGORITHM = "hmacSHA256";
-
-  protected final String id;
-  protected final byte[] key;
-  protected final boolean includePayloadHash;
-  protected final long skewSeconds;
-
-  /**
-   * Create a Hawk Authorization header provider.
-   * <p>
-   * Hawk specifies no mechanism by which a client receives an
-   * identifier-and-key pair from the server.
-   * <p>
-   * Hawk requests can include a payload verification hash with requests that
-   * enclose an entity (PATCH, POST, and PUT requests).  <b>You should default
-   * to including the payload verification hash<b> unless you have a good reason
-   * not to -- the server can always ignore payload verification hashes provided
-   * by the client.
-   *
-   * @param id
-   *          to name requests with.
-   * @param key
-   *          to sign request with.
-   *
-   * @param includePayloadHash
-   *          true if payload verification hash should be included in signed
-   *          request header. See <a href="https://github.com/hueniverse/hawk#payload-validation">https://github.com/hueniverse/hawk#payload-validation</a>.
-   *
-   * @param skewSeconds
-   *          a number of seconds by which to skew the current time when
-   *          computing a header.
-   */
-  public HawkAuthHeaderProvider(String id, byte[] key, boolean includePayloadHash, long skewSeconds) {
-    if (id == null) {
-      throw new IllegalArgumentException("id must not be null");
-    }
-    if (key == null) {
-      throw new IllegalArgumentException("key must not be null");
-    }
-    this.id = id;
-    this.key = key;
-    this.includePayloadHash = includePayloadHash;
-    this.skewSeconds = skewSeconds;
-  }
-
-  /**
-   * @return the current time in milliseconds.
-   */
-  @SuppressWarnings("static-method")
-  protected long now() {
-    return System.currentTimeMillis();
-  }
-
-  /**
-   * @return the current time in seconds, adjusted for skew. This should
-   *         approximate the server's timestamp.
-   */
-  protected long getTimestampSeconds() {
-    return (now() / 1000) + skewSeconds;
-  }
-
-  @Override
-  public Header getAuthHeader(HttpRequestBase request, BasicHttpContext context, DefaultHttpClient client) throws GeneralSecurityException {
-    long timestamp = getTimestampSeconds();
-    String nonce = Base64.encodeBase64String(Utils.generateRandomBytes(NONCE_LENGTH_IN_BYTES));
-    String extra = "";
-
-    try {
-      return getAuthHeader(request, context, client, timestamp, nonce, extra, this.includePayloadHash);
-    } catch (Exception e) {
-      // We lie a little and make every exception a GeneralSecurityException.
-      throw new GeneralSecurityException(e);
-    }
-  }
-
-  /**
-   * Helper function that generates an HTTP Authorization: Hawk header given
-   * additional Hawk specific data.
-   *
-   * @throws NoSuchAlgorithmException
-   * @throws InvalidKeyException
-   * @throws IOException
-   */
-  protected Header getAuthHeader(HttpRequestBase request, BasicHttpContext context, DefaultHttpClient client,
-      long timestamp, String nonce, String extra, boolean includePayloadHash)
-          throws InvalidKeyException, NoSuchAlgorithmException, IOException {
-    if (timestamp < 0) {
-      throw new IllegalArgumentException("timestamp must contain only [0-9].");
-    }
-
-    if (nonce == null) {
-      throw new IllegalArgumentException("nonce must not be null.");
-    }
-    if (nonce.length() == 0) {
-      throw new IllegalArgumentException("nonce must not be empty.");
-    }
-
-    String payloadHash = null;
-    if (includePayloadHash) {
-      payloadHash = getPayloadHashString(request);
-    } else {
-      Logger.debug(LOG_TAG, "Configured to not include payload hash for this request.");
-    }
-
-    String app = null;
-    String dlg = null;
-    String requestString = getRequestString(request, "header", timestamp, nonce, payloadHash, extra, app, dlg);
-    String macString = getSignature(requestString.getBytes("UTF-8"), this.key);
-
-    StringBuilder sb = new StringBuilder();
-    sb.append("Hawk id=\"");
-    sb.append(this.id);
-    sb.append("\", ");
-    sb.append("ts=\"");
-    sb.append(timestamp);
-    sb.append("\", ");
-    sb.append("nonce=\"");
-    sb.append(nonce);
-    sb.append("\", ");
-    if (payloadHash != null) {
-      sb.append("hash=\"");
-      sb.append(payloadHash);
-      sb.append("\", ");
-    }
-    if (extra != null && extra.length() > 0) {
-      sb.append("ext=\"");
-      sb.append(escapeExtraHeaderAttribute(extra));
-      sb.append("\", ");
-    }
-    sb.append("mac=\"");
-    sb.append(macString);
-    sb.append("\"");
-
-    return new BasicHeader("Authorization", sb.toString());
-  }
-
-  /**
-   * Get the payload verification hash for the given request, if possible.
-   * <p>
-   * Returns null if the request does not enclose an entity (is not an HTTP
-   * PATCH, POST, or PUT). Throws if the payload verification hash cannot be
-   * computed.
-   *
-   * @param request
-   *          to compute hash for.
-   * @return verification hash, or null if the request does not enclose an entity.
-   * @throws IllegalArgumentException if the request does not enclose a valid non-null entity.
-   * @throws UnsupportedEncodingException
-   * @throws NoSuchAlgorithmException
-   * @throws IOException
-   */
-  protected static String getPayloadHashString(HttpRequestBase request)
-      throws UnsupportedEncodingException, NoSuchAlgorithmException, IOException, IllegalArgumentException {
-    final boolean shouldComputePayloadHash = request instanceof HttpEntityEnclosingRequest;
-    if (!shouldComputePayloadHash) {
-      Logger.debug(LOG_TAG, "Not computing payload verification hash for non-enclosing request.");
-      return null;
-    }
-    if (!(request instanceof HttpEntityEnclosingRequest)) {
-      throw new IllegalArgumentException("Cannot compute payload verification hash for enclosing request without an entity");
-    }
-    final HttpEntity entity = ((HttpEntityEnclosingRequest) request).getEntity();
-    if (entity == null) {
-      throw new IllegalArgumentException("Cannot compute payload verification hash for enclosing request with a null entity");
-    }
-    return Base64.encodeBase64String(getPayloadHash(entity));
-  }
-
-  /**
-   * Escape the user-provided extra string for the ext="" header attribute.
-   * <p>
-   * Hawk escapes the header ext="" attribute differently than it does the extra
-   * line in the normalized request string.
-   * <p>
-   * See <a href="https://github.com/hueniverse/hawk/blob/871cc597973110900467bd3dfb84a3c892f678fb/lib/browser.js#L385">https://github.com/hueniverse/hawk/blob/871cc597973110900467bd3dfb84a3c892f678fb/lib/browser.js#L385</a>.
-   *
-   * @param extra to escape.
-   * @return extra escaped for the ext="" header attribute.
-   */
-  protected static String escapeExtraHeaderAttribute(String extra) {
-    return extra.replaceAll("\\\\", "\\\\").replaceAll("\"", "\\\"");
-  }
-
-  /**
-   * Escape the user-provided extra string for inserting into the normalized
-   * request string.
-   * <p>
-   * Hawk escapes the header ext="" attribute differently than it does the extra
-   * line in the normalized request string.
-   * <p>
-   * See <a href="https://github.com/hueniverse/hawk/blob/871cc597973110900467bd3dfb84a3c892f678fb/lib/crypto.js#L67">https://github.com/hueniverse/hawk/blob/871cc597973110900467bd3dfb84a3c892f678fb/lib/crypto.js#L67</a>.
-   *
-   * @param extra to escape.
-   * @return extra escaped for the normalized request string.
-   */
-  protected static String escapeExtraString(String extra) {
-    return extra.replaceAll("\\\\", "\\\\").replaceAll("\n", "\\n");
-  }
-
-  /**
-   * Return the content type with no parameters (pieces following ;).
-   *
-   * @param contentTypeHeader to interrogate.
-   * @return base content type.
-   */
-  protected static String getBaseContentType(Header contentTypeHeader) {
-    if (contentTypeHeader == null) {
-      throw new IllegalArgumentException("contentTypeHeader must not be null.");
-    }
-    String contentType = contentTypeHeader.getValue();
-    if (contentType == null) {
-      throw new IllegalArgumentException("contentTypeHeader value must not be null.");
-    }
-    int index = contentType.indexOf(";");
-    if (index < 0) {
-      return contentType.trim();
-    }
-    return contentType.substring(0, index).trim();
-  }
-
-  /**
-   * Generate the SHA-256 hash of a normalized Hawk payload generated from an
-   * HTTP entity.
-   * <p>
-   * <b>Warning:</b> the entity <b>must</b> be repeatable.  If it is not, this
-   * code throws an <code>IllegalArgumentException</code>.
-   * <p>
-   * This is under-specified; the code here was reverse engineered from the code
-   * at
-   * <a href="https://github.com/hueniverse/hawk/blob/871cc597973110900467bd3dfb84a3c892f678fb/lib/crypto.js#L81">https://github.com/hueniverse/hawk/blob/871cc597973110900467bd3dfb84a3c892f678fb/lib/crypto.js#L81</a>.
-   * @param entity to normalize and hash.
-   * @return hash.
-   * @throws IllegalArgumentException if entity is not repeatable.
-   */
-  protected static byte[] getPayloadHash(HttpEntity entity) throws UnsupportedEncodingException, IOException, NoSuchAlgorithmException {
-    if (!entity.isRepeatable()) {
-      throw new IllegalArgumentException("entity must be repeatable");
-    }
-    final MessageDigest digest = MessageDigest.getInstance("SHA-256");
-    digest.update(("hawk." + HAWK_HEADER_VERSION + ".payload\n").getBytes("UTF-8"));
-    digest.update(getBaseContentType(entity.getContentType()).getBytes("UTF-8"));
-    digest.update("\n".getBytes("UTF-8"));
-    InputStream stream = entity.getContent();
-    try {
-      int numRead;
-      byte[] buffer = new byte[4096];
-      while (-1 != (numRead = stream.read(buffer))) {
-        if (numRead > 0) {
-          digest.update(buffer, 0, numRead);
-        }
-      }
-      digest.update("\n".getBytes("UTF-8")); // Trailing newline is specified by Hawk.
-      return digest.digest();
-    } finally {
-      stream.close();
-    }
-  }
-
-  /**
-   * Generate a normalized Hawk request string. This is under-specified; the
-   * code here was reverse engineered from the code at
-   * <a href="https://github.com/hueniverse/hawk/blob/871cc597973110900467bd3dfb84a3c892f678fb/lib/crypto.js#L55">https://github.com/hueniverse/hawk/blob/871cc597973110900467bd3dfb84a3c892f678fb/lib/crypto.js#L55</a>.
-   * <p>
-   * This method trusts its inputs to be valid.
-   */
-  protected static String getRequestString(HttpUriRequest request, String type, long timestamp, String nonce, String hash, String extra, String app, String dlg) {
-    String method = request.getMethod().toUpperCase(Locale.US);
-
-    URI uri = request.getURI();
-    String host = uri.getHost();
-
-    String path = uri.getRawPath();
-    if (uri.getRawQuery() != null) {
-      path += "?";
-      path += uri.getRawQuery();
-    }
-    if (uri.getRawFragment() != null) {
-      path += "#";
-      path += uri.getRawFragment();
-    }
-
-    int port = uri.getPort();
-    String scheme = uri.getScheme();
-    if (port != -1) {
-    } else if ("http".equalsIgnoreCase(scheme)) {
-      port = 80;
-    } else if ("https".equalsIgnoreCase(scheme)) {
-      port = 443;
-    } else {
-      throw new IllegalArgumentException("Unsupported URI scheme: " + scheme + ".");
-    }
-
-    StringBuilder sb = new StringBuilder();
-    sb.append("hawk.");
-    sb.append(HAWK_HEADER_VERSION);
-    sb.append('.');
-    sb.append(type);
-    sb.append('\n');
-    sb.append(timestamp);
-    sb.append('\n');
-    sb.append(nonce);
-    sb.append('\n');
-    sb.append(method);
-    sb.append('\n');
-    sb.append(path);
-    sb.append('\n');
-    sb.append(host);
-    sb.append('\n');
-    sb.append(port);
-    sb.append('\n');
-    if (hash != null) {
-      sb.append(hash);
-    }
-    sb.append("\n");
-    if (extra != null && extra.length() > 0) {
-      sb.append(escapeExtraString(extra));
-    }
-    sb.append("\n");
-    if (app != null) {
-      sb.append(app);
-      sb.append("\n");
-      if (dlg != null) {
-        sb.append(dlg);
-      }
-      sb.append("\n");
-    }
-
-    return sb.toString();
-  }
-
-  protected static byte[] hmacSha256(byte[] message, byte[] key)
-      throws NoSuchAlgorithmException, InvalidKeyException {
-
-    SecretKeySpec keySpec = new SecretKeySpec(key, HMAC_SHA256_ALGORITHM);
-
-    Mac hasher = Mac.getInstance(HMAC_SHA256_ALGORITHM);
-    hasher.init(keySpec);
-    hasher.update(message);
-
-    return hasher.doFinal();
-  }
-
-  /**
-   * Sign a Hawk request string.
-   *
-   * @param requestString to sign.
-   * @param key as <code>String</code>.
-   * @return signature as base-64 encoded string.
-   * @throws InvalidKeyException
-   * @throws NoSuchAlgorithmException
-   * @throws UnsupportedEncodingException
-   */
-  protected static String getSignature(byte[] requestString, byte[] key)
-      throws InvalidKeyException, NoSuchAlgorithmException, UnsupportedEncodingException {
-    return Base64.encodeBase64String(hmacSha256(requestString, key));
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/HttpResponseObserver.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/HttpResponseObserver.java
deleted file mode 100644
index 24b37a0..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/HttpResponseObserver.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.sync.net;
-
-import ch.boye.httpclientandroidlib.HttpResponse;
-import ch.boye.httpclientandroidlib.client.methods.HttpUriRequest;
-
-public interface HttpResponseObserver {
-  /**
-   * Observe an HTTP response.
-   * @param request
-   *          The <code>HttpUriRequest<code> that elicited the response.
-   *
-   * @param response
-   *          The <code>HttpResponse</code> to observe.
-   */
-  public void observeHttpResponse(HttpUriRequest request, HttpResponse response);
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/MozResponse.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/MozResponse.java
deleted file mode 100644
index 3f76f92..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/MozResponse.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.sync.net;
-
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.Reader;
-import java.util.Scanner;
-
-import org.json.simple.JSONArray;
-import org.json.simple.parser.JSONParser;
-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.NonArrayJSONException;
-import org.mozilla.gecko.sync.NonObjectJSONException;
-
-import ch.boye.httpclientandroidlib.Header;
-import ch.boye.httpclientandroidlib.HttpEntity;
-import ch.boye.httpclientandroidlib.HttpResponse;
-import ch.boye.httpclientandroidlib.HttpStatus;
-import ch.boye.httpclientandroidlib.impl.cookie.DateParseException;
-import ch.boye.httpclientandroidlib.impl.cookie.DateUtils;
-
-public class MozResponse {
-  private static final String LOG_TAG = "MozResponse";
-
-  private static final String HEADER_RETRY_AFTER = "retry-after";
-
-  protected HttpResponse response;
-  private String body = null;
-
-  public HttpResponse httpResponse() {
-    return this.response;
-  }
-
-  public int getStatusCode() {
-    return this.response.getStatusLine().getStatusCode();
-  }
-
-  public boolean wasSuccessful() {
-    return this.getStatusCode() == 200;
-  }
-
-  public boolean isInvalidAuthentication() {
-    return this.getStatusCode() == HttpStatus.SC_UNAUTHORIZED;
-  }
-
-  /**
-   * Fetch the content type of the HTTP response body.
-   *
-   * @return a <code>Header</code> instance, or <code>null</code> if there was
-   *         no body or no valid Content-Type.
-   */
-  public Header getContentType() {
-    HttpEntity entity = this.response.getEntity();
-    if (entity == null) {
-      return null;
-    }
-    return entity.getContentType();
-  }
-
-  private static boolean missingHeader(String value) {
-    return value == null ||
-           value.trim().length() == 0;
-  }
-
-  public String body() throws IllegalStateException, IOException {
-    if (body != null) {
-      return body;
-    }
-    final HttpEntity entity = this.response.getEntity();
-    if (entity == null) {
-      body = null;
-      return null;
-    }
-
-    InputStreamReader is = new InputStreamReader(entity.getContent());
-    // Oh, Java, you are so evil.
-    body = new Scanner(is).useDelimiter("\\A").next();
-    return body;
-  }
-
-  /**
-   * Return the body as a <b>non-null</b> <code>ExtendedJSONObject</code>.
-   *
-   * @return A non-null <code>ExtendedJSONObject</code>.
-   *
-   * @throws IllegalStateException
-   * @throws IOException
-   * @throws NonObjectJSONException
-   */
-  public ExtendedJSONObject jsonObjectBody() throws IllegalStateException, IOException, NonObjectJSONException {
-    if (body != null) {
-      // Do it from the cached String.
-      return new ExtendedJSONObject(body);
-    }
-
-    HttpEntity entity = this.response.getEntity();
-    if (entity == null) {
-      throw new IOException("no entity");
-    }
-
-    InputStream content = entity.getContent();
-    try {
-      Reader in = new BufferedReader(new InputStreamReader(content, "UTF-8"));
-      return new ExtendedJSONObject(in);
-    } finally {
-      content.close();
-    }
-  }
-
-  public JSONArray jsonArrayBody() throws NonArrayJSONException, IOException {
-    final JSONParser parser = new JSONParser();
-    try {
-      if (body != null) {
-        // Do it from the cached String.
-        return (JSONArray) parser.parse(body);
-      }
-
-      final HttpEntity entity = this.response.getEntity();
-      if (entity == null) {
-        throw new IOException("no entity");
-      }
-
-      final InputStream content = entity.getContent();
-      final Reader in = new BufferedReader(new InputStreamReader(content, "UTF-8"));
-      try {
-        return (JSONArray) parser.parse(in);
-      } finally {
-        in.close();
-      }
-    } catch (ClassCastException | ParseException e) {
-      NonArrayJSONException exception = new NonArrayJSONException("value must be a json array");
-      exception.initCause(e);
-      throw exception;
-    }
-  }
-
-  protected boolean hasHeader(String h) {
-    return this.response.containsHeader(h);
-  }
-
-  public MozResponse(HttpResponse res) {
-    response = res;
-  }
-
-  protected String getNonMissingHeader(String h) {
-    if (!this.hasHeader(h)) {
-      return null;
-    }
-
-    final Header header = this.response.getFirstHeader(h);
-    final String value = header.getValue();
-    if (missingHeader(value)) {
-      Logger.warn(LOG_TAG, h + " header present but empty.");
-      return null;
-    }
-    return value;
-  }
-
-  protected long getLongHeader(String h) throws NumberFormatException {
-    final String value = getNonMissingHeader(h);
-    if (value == null) {
-      return -1L;
-    }
-    return Long.parseLong(value, 10);
-  }
-
-  protected int getIntegerHeader(String h) throws NumberFormatException {
-    final String value = getNonMissingHeader(h);
-    if (value == null) {
-      return -1;
-    }
-    return Integer.parseInt(value, 10);
-  }
-
-  /**
-   * @return A number of seconds, or -1 if the 'Retry-After' header was not present.
-   */
-  public int retryAfterInSeconds() throws NumberFormatException {
-    final String retryAfter = getNonMissingHeader(HEADER_RETRY_AFTER);
-    if (retryAfter == null) {
-      return -1;
-    }
-
-    try {
-      return Integer.parseInt(retryAfter, 10);
-    } catch (NumberFormatException e) {
-      // Fall through to try date format.
-    }
-
-    try {
-      final long then = DateUtils.parseDate(retryAfter).getTime();
-      final long now  = System.currentTimeMillis();
-      return (int)((then - now) / 1000);     // Convert milliseconds to seconds.
-    } catch (DateParseException e) {
-      Logger.warn(LOG_TAG, "Retry-After header neither integer nor date: " + retryAfter);
-      return -1;
-    }
-  }
-
-  /**
-   * @return A number of seconds, or -1 if the 'Backoff' header was not
-   *         present.
-   */
-  public int backoffInSeconds() throws NumberFormatException {
-    return this.getIntegerHeader("backoff");
-  }
-
-  public void logResponseBody(final String logTag) {
-    if (!Logger.LOG_PERSONAL_INFORMATION) {
-      return;
-    }
-    try {
-      Logger.pii(logTag, "Response body: " + body());
-    } catch (Throwable e) {
-      Logger.debug(logTag, "No response body.");
-    }
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/Resource.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/Resource.java
deleted file mode 100644
index ab7b98a..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/Resource.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.sync.net;
-
-import java.net.URI;
-
-import ch.boye.httpclientandroidlib.HttpEntity;
-
-public interface Resource {
-  public abstract URI getURI();
-  public abstract String getURIString();
-  public abstract String getHostname();
-  public abstract void get();
-  public abstract void delete();
-  public abstract void post(HttpEntity body);
-  public abstract void patch(HttpEntity body);
-  public abstract void put(HttpEntity body);
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/ResourceDelegate.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/ResourceDelegate.java
deleted file mode 100644
index 0dea943..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/ResourceDelegate.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.sync.net;
-
-import java.io.IOException;
-import java.security.GeneralSecurityException;
-
-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;
-
-/**
- * ResourceDelegate implementers must ensure that HTTP responses
- * are fully consumed to ensure that connections are returned to
- * the pool:
- *
- *          EntityUtils.consume(entity);
- * @author rnewman
- *
- */
-public interface ResourceDelegate {
-  // Request augmentation.
-  AuthHeaderProvider getAuthHeaderProvider();
-  void addHeaders(HttpRequestBase request, DefaultHttpClient client);
-
-  /**
-   * The value of the User-Agent header to include with the request.
-   *
-   * @return User-Agent header value; null means do not set User-Agent header.
-   */
-  public String getUserAgent();
-
-  // Response handling.
-
-  /**
-   * Override this to handle an HttpResponse.
-   *
-   * ResourceDelegate implementers <b>must</b> ensure that HTTP responses are
-   * fully consumed to ensure that connections are returned to the pool, for
-   * example by calling <code>EntityUtils.consume(response.getEntity())</code>.
-   */
-  void handleHttpResponse(HttpResponse response);
-  void handleHttpProtocolException(ClientProtocolException e);
-  void handleHttpIOException(IOException e);
-
-  // During preparation.
-  void handleTransportException(GeneralSecurityException e);
-
-  // Connection parameters.
-  int connectionTimeout();
-  int socketTimeout();
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/SRPConstants.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/SRPConstants.java
deleted file mode 100644
index 5dfe660..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/SRPConstants.java
+++ /dev/null
@@ -1,174 +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.sync.net;
-
-import java.math.BigInteger;
-
-/**
- * SRP Group Parameters from
- * <a href="http://tools.ietf.org/html/rfc5054#appendix-A">Appendix A of RFC 5054</a>.
- *
- * The 1024-, 1536-, and 2048-bit groups are taken from software
- * developed by Tom Wu and Eugene Jhong for the Stanford SRP
- * distribution, and subsequently proven to be prime.  The larger primes
- * are taken from [MODP], but generators have been calculated that are
- * primitive roots of N, unlike the generators in [MODP].
- *
- * The 1024-bit and 1536-bit groups <b>MUST</b> be supported.
- */
-public class SRPConstants {
-  public static class Parameters {
-    public final BigInteger N;
-    public final BigInteger g;
-    public final int bitLength;
-    public final int byteLength;
-    public final int hexLength;
-
-    protected Parameters(String N, long g) {
-      if (N == null) {
-        throw new IllegalArgumentException("N must not be null");
-      }
-      this.N = new BigInteger(N.replaceAll(" ", ""), 16); // Hex.
-      this.g = BigInteger.valueOf(g);
-      this.hexLength = this.N.toString(16).length();
-      this.byteLength = hexLength / 2;
-      this.bitLength = this.byteLength * 8;
-    }
-  }
-
-  public static final Parameters _1024 = new Parameters("" +
-      "EEAF0AB9 ADB38DD6 9C33F80A FA8FC5E8 60726187 75FF3C0B 9EA2314C" +
-      "9C256576 D674DF74 96EA81D3 383B4813 D692C6E0 E0D5D8E2 50B98BE4" +
-      "8E495C1D 6089DAD1 5DC7D7B4 6154D6B6 CE8EF4AD 69B15D49 82559B29" +
-      "7BCF1885 C529F566 660E57EC 68EDBC3C 05726CC0 2FD4CBF4 976EAA9A" +
-      "FD5138FE 8376435B 9FC61D2F C0EB06E3", 2L);
-
-  public static final Parameters _1536 = new Parameters("" +
-      "9DEF3CAF B939277A B1F12A86 17A47BBB DBA51DF4 99AC4C80 BEEEA961" +
-      "4B19CC4D 5F4F5F55 6E27CBDE 51C6A94B E4607A29 1558903B A0D0F843" +
-      "80B655BB 9A22E8DC DF028A7C EC67F0D0 8134B1C8 B9798914 9B609E0B" +
-      "E3BAB63D 47548381 DBC5B1FC 764E3F4B 53DD9DA1 158BFD3E 2B9C8CF5" +
-      "6EDF0195 39349627 DB2FD53D 24B7C486 65772E43 7D6C7F8C E442734A" +
-      "F7CCB7AE 837C264A E3A9BEB8 7F8A2FE9 B8B5292E 5A021FFF 5E91479E" +
-      "8CE7A28C 2442C6F3 15180F93 499A234D CF76E3FE D135F9BB", 2L);
-
-  public static final Parameters _2048 = new Parameters("" +
-      "AC6BDB41 324A9A9B F166DE5E 1389582F AF72B665 1987EE07 FC319294" +
-      "3DB56050 A37329CB B4A099ED 8193E075 7767A13D D52312AB 4B03310D" +
-      "CD7F48A9 DA04FD50 E8083969 EDB767B0 CF609517 9A163AB3 661A05FB" +
-      "D5FAAAE8 2918A996 2F0B93B8 55F97993 EC975EEA A80D740A DBF4FF74" +
-      "7359D041 D5C33EA7 1D281E44 6B14773B CA97B43A 23FB8016 76BD207A" +
-      "436C6481 F1D2B907 8717461A 5B9D32E6 88F87748 544523B5 24B0D57D" +
-      "5EA77A27 75D2ECFA 032CFBDB F52FB378 61602790 04E57AE6 AF874E73" +
-      "03CE5329 9CCC041C 7BC308D8 2A5698F3 A8D0C382 71AE35F8 E9DBFBB6" +
-      "94B5C803 D89F7AE4 35DE236D 525F5475 9B65E372 FCD68EF2 0FA7111F" +
-      "9E4AFF73", 2L);
-
-  public static final Parameters _3072 = new Parameters("" +
-      "FFFFFFFF FFFFFFFF C90FDAA2 2168C234 C4C6628B 80DC1CD1 29024E08" +
-      "8A67CC74 020BBEA6 3B139B22 514A0879 8E3404DD EF9519B3 CD3A431B" +
-      "302B0A6D F25F1437 4FE1356D 6D51C245 E485B576 625E7EC6 F44C42E9" +
-      "A637ED6B 0BFF5CB6 F406B7ED EE386BFB 5A899FA5 AE9F2411 7C4B1FE6" +
-      "49286651 ECE45B3D C2007CB8 A163BF05 98DA4836 1C55D39A 69163FA8" +
-      "FD24CF5F 83655D23 DCA3AD96 1C62F356 208552BB 9ED52907 7096966D" +
-      "670C354E 4ABC9804 F1746C08 CA18217C 32905E46 2E36CE3B E39E772C" +
-      "180E8603 9B2783A2 EC07A28F B5C55DF0 6F4C52C9 DE2BCBF6 95581718" +
-      "3995497C EA956AE5 15D22618 98FA0510 15728E5A 8AAAC42D AD33170D" +
-      "04507A33 A85521AB DF1CBA64 ECFB8504 58DBEF0A 8AEA7157 5D060C7D" +
-      "B3970F85 A6E1E4C7 ABF5AE8C DB0933D7 1E8C94E0 4A25619D CEE3D226" +
-      "1AD2EE6B F12FFA06 D98A0864 D8760273 3EC86A64 521F2B18 177B200C" +
-      "BBE11757 7A615D6C 770988C0 BAD946E2 08E24FA0 74E5AB31 43DB5BFC" +
-      "E0FD108E 4B82D120 A93AD2CA FFFFFFFF FFFFFFFF", 5L);
-
-  public static final Parameters _4096 = new Parameters("" +
-      "FFFFFFFF FFFFFFFF C90FDAA2 2168C234 C4C6628B 80DC1CD1 29024E08" +
-      "8A67CC74 020BBEA6 3B139B22 514A0879 8E3404DD EF9519B3 CD3A431B" +
-      "302B0A6D F25F1437 4FE1356D 6D51C245 E485B576 625E7EC6 F44C42E9" +
-      "A637ED6B 0BFF5CB6 F406B7ED EE386BFB 5A899FA5 AE9F2411 7C4B1FE6" +
-      "49286651 ECE45B3D C2007CB8 A163BF05 98DA4836 1C55D39A 69163FA8" +
-      "FD24CF5F 83655D23 DCA3AD96 1C62F356 208552BB 9ED52907 7096966D" +
-      "670C354E 4ABC9804 F1746C08 CA18217C 32905E46 2E36CE3B E39E772C" +
-      "180E8603 9B2783A2 EC07A28F B5C55DF0 6F4C52C9 DE2BCBF6 95581718" +
-      "3995497C EA956AE5 15D22618 98FA0510 15728E5A 8AAAC42D AD33170D" +
-      "04507A33 A85521AB DF1CBA64 ECFB8504 58DBEF0A 8AEA7157 5D060C7D" +
-      "B3970F85 A6E1E4C7 ABF5AE8C DB0933D7 1E8C94E0 4A25619D CEE3D226" +
-      "1AD2EE6B F12FFA06 D98A0864 D8760273 3EC86A64 521F2B18 177B200C" +
-      "BBE11757 7A615D6C 770988C0 BAD946E2 08E24FA0 74E5AB31 43DB5BFC" +
-      "E0FD108E 4B82D120 A9210801 1A723C12 A787E6D7 88719A10 BDBA5B26" +
-      "99C32718 6AF4E23C 1A946834 B6150BDA 2583E9CA 2AD44CE8 DBBBC2DB" +
-      "04DE8EF9 2E8EFC14 1FBECAA6 287C5947 4E6BC05D 99B2964F A090C3A2" +
-      "233BA186 515BE7ED 1F612970 CEE2D7AF B81BDD76 2170481C D0069127" +
-      "D5B05AA9 93B4EA98 8D8FDDC1 86FFB7DC 90A6C08F 4DF435C9 34063199" +
-      "FFFFFFFF FFFFFFFF", 5L);
-
-  public static final Parameters _6144 = new Parameters("" +
-      "FFFFFFFF FFFFFFFF C90FDAA2 2168C234 C4C6628B 80DC1CD1 29024E08" +
-      "8A67CC74 020BBEA6 3B139B22 514A0879 8E3404DD EF9519B3 CD3A431B" +
-      "302B0A6D F25F1437 4FE1356D 6D51C245 E485B576 625E7EC6 F44C42E9" +
-      "A637ED6B 0BFF5CB6 F406B7ED EE386BFB 5A899FA5 AE9F2411 7C4B1FE6" +
-      "49286651 ECE45B3D C2007CB8 A163BF05 98DA4836 1C55D39A 69163FA8" +
-      "FD24CF5F 83655D23 DCA3AD96 1C62F356 208552BB 9ED52907 7096966D" +
-      "670C354E 4ABC9804 F1746C08 CA18217C 32905E46 2E36CE3B E39E772C" +
-      "180E8603 9B2783A2 EC07A28F B5C55DF0 6F4C52C9 DE2BCBF6 95581718" +
-      "3995497C EA956AE5 15D22618 98FA0510 15728E5A 8AAAC42D AD33170D" +
-      "04507A33 A85521AB DF1CBA64 ECFB8504 58DBEF0A 8AEA7157 5D060C7D" +
-      "B3970F85 A6E1E4C7 ABF5AE8C DB0933D7 1E8C94E0 4A25619D CEE3D226" +
-      "1AD2EE6B F12FFA06 D98A0864 D8760273 3EC86A64 521F2B18 177B200C" +
-      "BBE11757 7A615D6C 770988C0 BAD946E2 08E24FA0 74E5AB31 43DB5BFC" +
-      "E0FD108E 4B82D120 A9210801 1A723C12 A787E6D7 88719A10 BDBA5B26" +
-      "99C32718 6AF4E23C 1A946834 B6150BDA 2583E9CA 2AD44CE8 DBBBC2DB" +
-      "04DE8EF9 2E8EFC14 1FBECAA6 287C5947 4E6BC05D 99B2964F A090C3A2" +
-      "233BA186 515BE7ED 1F612970 CEE2D7AF B81BDD76 2170481C D0069127" +
-      "D5B05AA9 93B4EA98 8D8FDDC1 86FFB7DC 90A6C08F 4DF435C9 34028492" +
-      "36C3FAB4 D27C7026 C1D4DCB2 602646DE C9751E76 3DBA37BD F8FF9406" +
-      "AD9E530E E5DB382F 413001AE B06A53ED 9027D831 179727B0 865A8918" +
-      "DA3EDBEB CF9B14ED 44CE6CBA CED4BB1B DB7F1447 E6CC254B 33205151" +
-      "2BD7AF42 6FB8F401 378CD2BF 5983CA01 C64B92EC F032EA15 D1721D03" +
-      "F482D7CE 6E74FEF6 D55E702F 46980C82 B5A84031 900B1C9E 59E7C97F" +
-      "BEC7E8F3 23A97A7E 36CC88BE 0F1D45B7 FF585AC5 4BD407B2 2B4154AA" +
-      "CC8F6D7E BF48E1D8 14CC5ED2 0F8037E0 A79715EE F29BE328 06A1D58B" +
-      "B7C5DA76 F550AA3D 8A1FBFF0 EB19CCB1 A313D55C DA56C9EC 2EF29632" +
-      "387FE8D7 6E3C0468 043E8F66 3F4860EE 12BF2D5B 0B7474D6 E694F91E" +
-      "6DCC4024 FFFFFFFF FFFFFFFF", 5L);
-
-  public static final Parameters _8192 = new Parameters("" +
-      "FFFFFFFF FFFFFFFF C90FDAA2 2168C234 C4C6628B 80DC1CD1 29024E08" +
-      "8A67CC74 020BBEA6 3B139B22 514A0879 8E3404DD EF9519B3 CD3A431B" +
-      "302B0A6D F25F1437 4FE1356D 6D51C245 E485B576 625E7EC6 F44C42E9" +
-      "A637ED6B 0BFF5CB6 F406B7ED EE386BFB 5A899FA5 AE9F2411 7C4B1FE6" +
-      "49286651 ECE45B3D C2007CB8 A163BF05 98DA4836 1C55D39A 69163FA8" +
-      "FD24CF5F 83655D23 DCA3AD96 1C62F356 208552BB 9ED52907 7096966D" +
-      "670C354E 4ABC9804 F1746C08 CA18217C 32905E46 2E36CE3B E39E772C" +
-      "180E8603 9B2783A2 EC07A28F B5C55DF0 6F4C52C9 DE2BCBF6 95581718" +
-      "3995497C EA956AE5 15D22618 98FA0510 15728E5A 8AAAC42D AD33170D" +
-      "04507A33 A85521AB DF1CBA64 ECFB8504 58DBEF0A 8AEA7157 5D060C7D" +
-      "B3970F85 A6E1E4C7 ABF5AE8C DB0933D7 1E8C94E0 4A25619D CEE3D226" +
-      "1AD2EE6B F12FFA06 D98A0864 D8760273 3EC86A64 521F2B18 177B200C" +
-      "BBE11757 7A615D6C 770988C0 BAD946E2 08E24FA0 74E5AB31 43DB5BFC" +
-      "E0FD108E 4B82D120 A9210801 1A723C12 A787E6D7 88719A10 BDBA5B26" +
-      "99C32718 6AF4E23C 1A946834 B6150BDA 2583E9CA 2AD44CE8 DBBBC2DB" +
-      "04DE8EF9 2E8EFC14 1FBECAA6 287C5947 4E6BC05D 99B2964F A090C3A2" +
-      "233BA186 515BE7ED 1F612970 CEE2D7AF B81BDD76 2170481C D0069127" +
-      "D5B05AA9 93B4EA98 8D8FDDC1 86FFB7DC 90A6C08F 4DF435C9 34028492" +
-      "36C3FAB4 D27C7026 C1D4DCB2 602646DE C9751E76 3DBA37BD F8FF9406" +
-      "AD9E530E E5DB382F 413001AE B06A53ED 9027D831 179727B0 865A8918" +
-      "DA3EDBEB CF9B14ED 44CE6CBA CED4BB1B DB7F1447 E6CC254B 33205151" +
-      "2BD7AF42 6FB8F401 378CD2BF 5983CA01 C64B92EC F032EA15 D1721D03" +
-      "F482D7CE 6E74FEF6 D55E702F 46980C82 B5A84031 900B1C9E 59E7C97F" +
-      "BEC7E8F3 23A97A7E 36CC88BE 0F1D45B7 FF585AC5 4BD407B2 2B4154AA" +
-      "CC8F6D7E BF48E1D8 14CC5ED2 0F8037E0 A79715EE F29BE328 06A1D58B" +
-      "B7C5DA76 F550AA3D 8A1FBFF0 EB19CCB1 A313D55C DA56C9EC 2EF29632" +
-      "387FE8D7 6E3C0468 043E8F66 3F4860EE 12BF2D5B 0B7474D6 E694F91E" +
-      "6DBE1159 74A3926F 12FEE5E4 38777CB6 A932DF8C D8BEC4D0 73B931BA" +
-      "3BC832B6 8D9DD300 741FA7BF 8AFC47ED 2576F693 6BA42466 3AAB639C" +
-      "5AE4F568 3423B474 2BF1C978 238F16CB E39D652D E3FDB8BE FC848AD9" +
-      "22222E04 A4037C07 13EB57A8 1A23F0C7 3473FC64 6CEA306B 4BCBC886" +
-      "2F8385DD FA9D4B7F A2C087E8 79683303 ED5BDD3A 062B3CF5 B3A278A6" +
-      "6D2A13F8 3F44F82D DF310EE0 74AB6A36 4597E899 A0255DC1 64F31CC5" +
-      "0846851D F9AB4819 5DED7EA1 B1D510BD 7EE74D73 FAF36BC3 1ECFA268" +
-      "359046F4 EB879F92 4009438B 481C6CD7 889A002E D5EE382B C9190DA6" +
-      "FC026E47 9558E447 5677E9AA 9E3050E2 765694DF C81F56E8 80B96E71" +
-      "60C980DD 98EDD3DF FFFFFFFF FFFFFFFF", 19L);
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/SyncResponse.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/SyncResponse.java
deleted file mode 100644
index 177d7aa..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/SyncResponse.java
+++ /dev/null
@@ -1,157 +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.sync.net;
-
-import android.support.annotation.Nullable;
-
-import org.mozilla.gecko.sync.Utils;
-
-import ch.boye.httpclientandroidlib.HttpResponse;
-
-public class SyncResponse extends MozResponse {
-  public static final String X_WEAVE_BACKOFF = "x-weave-backoff";
-  public static final String X_BACKOFF = "x-backoff";
-  public static final String X_LAST_MODIFIED = "x-last-modified";
-  public static final String X_WEAVE_TIMESTAMP = "x-weave-timestamp";
-  public static final String X_WEAVE_RECORDS = "x-weave-records";
-  public static final String X_WEAVE_QUOTA_REMAINING = "x-weave-quota-remaining";
-  public static final String X_WEAVE_ALERT = "x-weave-alert";
-  public static final String X_WEAVE_NEXT_OFFSET = "x-weave-next-offset";
-
-  public SyncResponse(HttpResponse res) {
-    super(res);
-  }
-
-  /**
-   * @return A number of seconds, or -1 if the 'X-Weave-Backoff' header was not
-   *         present.
-   */
-  public int weaveBackoffInSeconds() throws NumberFormatException {
-    return this.getIntegerHeader(X_WEAVE_BACKOFF);
-  }
-
-  /**
-   * @return A number of seconds, or -1 if the 'X-Backoff' header was not
-   *         present.
-   */
-  public int xBackoffInSeconds() throws NumberFormatException {
-    return this.getIntegerHeader(X_BACKOFF);
-  }
-
-  /**
-   * Extract a number of seconds, or -1 if none of the specified headers were present.
-   *
-   * @param includeRetryAfter
-   *          if <code>true</code>, the Retry-After header is excluded. This is
-   *          useful for processing non-error responses where a Retry-After
-   *          header would be unexpected.
-   * @return the maximum of the three possible backoff headers, in seconds.
-   */
-  public int totalBackoffInSeconds(boolean includeRetryAfter) {
-    int retryAfterInSeconds = -1;
-    if (includeRetryAfter) {
-      try {
-        retryAfterInSeconds = retryAfterInSeconds();
-      } catch (NumberFormatException e) {
-      }
-    }
-
-    int weaveBackoffInSeconds = -1;
-    try {
-      weaveBackoffInSeconds = weaveBackoffInSeconds();
-    } catch (NumberFormatException e) {
-    }
-
-    int backoffInSeconds = -1;
-    try {
-      backoffInSeconds = xBackoffInSeconds();
-    } catch (NumberFormatException e) {
-    }
-
-    int totalBackoff = Math.max(retryAfterInSeconds, Math.max(backoffInSeconds, weaveBackoffInSeconds));
-    if (totalBackoff < 0) {
-      return -1;
-    } else {
-      return totalBackoff;
-    }
-  }
-
-  /**
-   * @return A number of milliseconds, or -1 if neither the 'Retry-After',
-   *         'X-Backoff', or 'X-Weave-Backoff' header were present.
-   */
-  public long totalBackoffInMilliseconds() {
-    long totalBackoff = totalBackoffInSeconds(true);
-    if (totalBackoff < 0) {
-      return -1;
-    } else {
-      return 1000 * totalBackoff;
-    }
-  }
-
-  public long normalizedWeaveTimestamp() {
-    return normalizedTimestampForHeader(X_WEAVE_TIMESTAMP);
-  }
-
-  /**
-   * Timestamps returned from a Sync server are decimal numbers of seconds,
-   * e.g., 1323393518.04.
-   *
-   * We want milliseconds since epoch.
-   *
-   * @return milliseconds since the epoch, as a long, or -1 if the header
-   *         was missing or invalid.
-   */
-  public long normalizedTimestampForHeader(String header) {
-    if (!this.hasHeader(header)) {
-      return -1;
-    }
-
-    return Utils.decimalSecondsToMilliseconds(
-            this.response.getFirstHeader(header).getValue()
-    );
-  }
-
-  public int weaveRecords() throws NumberFormatException {
-    return this.getIntegerHeader(X_WEAVE_RECORDS);
-  }
-
-  public int weaveQuotaRemaining() throws NumberFormatException {
-    return this.getIntegerHeader(X_WEAVE_QUOTA_REMAINING);
-  }
-
-  public String weaveAlert() {
-    return this.getNonMissingHeader(X_WEAVE_ALERT);
-  }
-
-  /**
-   * This header may be sent back with multi-record responses where the request included a limit parameter.
-   * Its presence indicates that the number of available records exceeded the given limit.
-   * The value from this header can be passed back in the offset parameter to retrieve additional records.
-   * The value of this header will always be a string of characters from the urlsafe-base64 alphabet.
-   * The specific contents of the string are an implementation detail of the server,
-   * so clients should treat it as an opaque token.
-   *
-   * @return the offset header
-   */
-  public String weaveOffset() {
-    return this.getNonMissingHeader(X_WEAVE_NEXT_OFFSET);
-  }
-
-  /**
-   * This header gives the last-modified time of the target resource as seen during processing of the request,
-   * and will be included in all success responses (200, 201, 204).
-   * When given in response to a write request, this will be equal to the server’s current time and
-   * to the new last-modified time of any BSOs created or changed by the request.
-   * It is similar to the standard HTTP Last-Modified header,
-   * but the value is a decimal timestamp rather than a HTTP-format date.
-   *
-   * @return the last modified header
-   */
-  @Nullable
-  public String lastModified() {
-    return this.getNonMissingHeader(X_LAST_MODIFIED);
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/SyncStorageCollectionRequest.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/SyncStorageCollectionRequest.java
deleted file mode 100644
index 3ae672f..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/SyncStorageCollectionRequest.java
+++ /dev/null
@@ -1,145 +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.sync.net;
-
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.net.URI;
-
-import org.mozilla.gecko.background.common.log.Logger;
-
-import ch.boye.httpclientandroidlib.Header;
-import ch.boye.httpclientandroidlib.HttpEntity;
-import ch.boye.httpclientandroidlib.HttpResponse;
-import ch.boye.httpclientandroidlib.client.methods.HttpRequestBase;
-import ch.boye.httpclientandroidlib.impl.client.DefaultHttpClient;
-
-/**
- * A request class that handles line-by-line responses. Eventually this will
- * handle real stream processing; for now, just parse the returned body
- * line-by-line.
- *
- * @author rnewman
- *
- */
-public class SyncStorageCollectionRequest extends SyncStorageRequest {
-  private static final String LOG_TAG = "CollectionRequest";
-
-  public SyncStorageCollectionRequest(URI uri) {
-    super(uri);
-  }
-
-  protected volatile boolean aborting = false;
-
-  /**
-   * Instruct the request that it should process no more records,
-   * and decline to notify any more delegate callbacks.
-   */
-  public void abort() {
-    aborting = true;
-    try {
-      this.resource.request.abort();
-    } catch (Exception e) {
-      // Just in case.
-      Logger.warn(LOG_TAG, "Got exception in abort: " + e);
-    }
-  }
-
-  @Override
-  protected BaseResourceDelegate makeResourceDelegate(SyncStorageRequest request) {
-    return new SyncCollectionResourceDelegate((SyncStorageCollectionRequest) request);
-  }
-
-  // TODO: this is awful.
-  public class SyncCollectionResourceDelegate extends
-      SyncStorageResourceDelegate {
-
-    private static final String CONTENT_TYPE_INCREMENTAL = "application/newlines";
-    private static final int FETCH_BUFFER_SIZE = 16 * 1024;   // 16K chars.
-
-    SyncCollectionResourceDelegate(SyncStorageCollectionRequest request) {
-      super(request);
-    }
-
-    @Override
-    public void addHeaders(HttpRequestBase request, DefaultHttpClient client) {
-      super.addHeaders(request, client);
-      request.setHeader("Accept", CONTENT_TYPE_INCREMENTAL);
-      // Caller is responsible for setting full=1.
-    }
-
-    @Override
-    public void handleHttpResponse(HttpResponse response) {
-      if (aborting) {
-        return;
-      }
-
-      if (response.getStatusLine().getStatusCode() != 200) {
-        super.handleHttpResponse(response);
-        return;
-      }
-
-      HttpEntity entity = response.getEntity();
-      Header contentType = entity.getContentType();
-      if (!contentType.getValue().startsWith(CONTENT_TYPE_INCREMENTAL)) {
-        // Not incremental!
-        super.handleHttpResponse(response);
-        return;
-      }
-
-      // TODO: at this point we can access X-Weave-Timestamp, compare
-      // that to our local timestamp, and compute an estimate of clock
-      // skew. We can provide this to the incremental delegate, which
-      // will allow it to seamlessly correct timestamps on the records
-      // it processes. Bug 721887.
-
-      // Line-by-line processing, then invoke success.
-      SyncStorageCollectionRequestDelegate delegate = (SyncStorageCollectionRequestDelegate) this.request.delegate;
-      InputStream content = null;
-      BufferedReader br = null;
-      try {
-        content = entity.getContent();
-        br = new BufferedReader(new InputStreamReader(content), FETCH_BUFFER_SIZE);
-        String line;
-
-        // This relies on connection timeouts at the HTTP layer.
-        while (!aborting &&
-               null != (line = br.readLine())) {
-          try {
-            delegate.handleRequestProgress(line);
-          } catch (Exception ex) {
-            delegate.handleRequestError(new HandleProgressException(ex));
-            BaseResource.consumeEntity(entity);
-            return;
-          }
-        }
-        if (aborting) {
-          // So we don't hit the success case below.
-          return;
-        }
-      } catch (IOException ex) {
-        if (!aborting) {
-          delegate.handleRequestError(ex);
-        }
-        BaseResource.consumeEntity(entity);
-        return;
-      } finally {
-        // Attempt to close the stream and reader.
-        if (br != null) {
-          try {
-            br.close();
-          } catch (IOException e) {
-            // We don't care if this fails.
-          }
-        }
-      }
-      // We're done processing the entity. Don't let fetching the body succeed!
-      BaseResource.consumeEntity(entity);
-      delegate.handleRequestSuccess(new SyncStorageResponse(response));
-    }
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/SyncStorageCollectionRequestDelegate.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/SyncStorageCollectionRequestDelegate.java
deleted file mode 100644
index ddf5200..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/SyncStorageCollectionRequestDelegate.java
+++ /dev/null
@@ -1,9 +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.sync.net;
-
-public abstract class SyncStorageCollectionRequestDelegate implements
-    SyncStorageRequestIncrementalDelegate, SyncStorageRequestDelegate {
-}
\ No newline at end of file
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/SyncStorageRecordRequest.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/SyncStorageRecordRequest.java
deleted file mode 100644
index c18c4fe..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/SyncStorageRecordRequest.java
+++ /dev/null
@@ -1,95 +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.sync.net;
-
-import org.json.simple.JSONArray;
-import org.json.simple.JSONObject;
-import org.mozilla.gecko.sync.CryptoRecord;
-
-import java.io.UnsupportedEncodingException;
-import java.net.URI;
-import java.net.URISyntaxException;
-
-/**
- * Resource class that implements expected headers and processing for Sync.
- * Accepts a simplified delegate.
- *
- * Includes:
- * * Basic Auth headers (via Resource)
- * * Error responses:
- *   * 401
- *   * 503
- * * Headers:
- *   * Retry-After
- *   * X-Weave-Backoff
- *   * X-Backoff
- *   * X-Weave-Records?
- *   * ...
- * * Timeouts
- * * Network errors
- * * application/newlines
- * * JSON parsing
- * * Content-Type and Content-Length validation.
- */
-public class SyncStorageRecordRequest extends SyncStorageRequest {
-
-  public class SyncStorageRecordResourceDelegate extends SyncStorageResourceDelegate {
-    SyncStorageRecordResourceDelegate(SyncStorageRequest request) {
-      super(request);
-    }
-  }
-
-  public SyncStorageRecordRequest(URI uri) {
-    super(uri);
-  }
-
-  public SyncStorageRecordRequest(String url) throws URISyntaxException {
-    this(new URI(url));
-  }
-
-  @Override
-  protected BaseResourceDelegate makeResourceDelegate(SyncStorageRequest request) {
-    return new SyncStorageRecordResourceDelegate(request);
-  }
-
-  @SuppressWarnings("unchecked")
-  public void post(JSONObject body) {
-    // Let's do this the trivial way for now.
-    // Note that POSTs should be an array, so we wrap here.
-    final JSONArray toPOST = new JSONArray();
-    toPOST.add(body);
-    try {
-      this.resource.post(toPOST);
-    } catch (UnsupportedEncodingException e) {
-      this.delegate.handleRequestError(e);
-    }
-  }
-
-  public void post(JSONArray body) {
-    // Let's do this the trivial way for now.
-    try {
-      this.resource.post(body);
-    } catch (UnsupportedEncodingException e) {
-      this.delegate.handleRequestError(e);
-    }
-  }
-
-  public void put(JSONObject body) {
-    // Let's do this the trivial way for now.
-    try {
-      this.resource.put(body);
-    } catch (UnsupportedEncodingException e) {
-      this.delegate.handleRequestError(e);
-    }
-  }
-
-  public void post(CryptoRecord record) {
-    this.post(record.toJSONObject());
-  }
-
-  public void put(CryptoRecord record) {
-    this.put(record.toJSONObject());
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/SyncStorageRequest.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/SyncStorageRequest.java
deleted file mode 100644
index 3ede9cd..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/SyncStorageRequest.java
+++ /dev/null
@@ -1,204 +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.sync.net;
-
-import java.io.IOException;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.security.GeneralSecurityException;
-import java.util.HashMap;
-
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.sync.SyncConstants;
-
-import ch.boye.httpclientandroidlib.HttpEntity;
-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;
-
-public class SyncStorageRequest implements Resource {
-  public static HashMap<String, String> SERVER_ERROR_MESSAGES;
-  static {
-    HashMap<String, String> errors = new HashMap<String, String>();
-
-    // Sync protocol errors.
-    errors.put("1", "Illegal method/protocol");
-    errors.put("2", "Incorrect/missing CAPTCHA");
-    errors.put("3", "Invalid/missing username");
-    errors.put("4", "Attempt to overwrite data that can't be overwritten (such as creating a user ID that already exists)");
-    errors.put("5", "User ID does not match account in path");
-    errors.put("6", "JSON parse failure");
-    errors.put("7", "Missing password field");
-    errors.put("8", "Invalid Weave Basic Object");
-    errors.put("9", "Requested password not strong enough");
-    errors.put("10", "Invalid/missing password reset code");
-    errors.put("11", "Unsupported function");
-    errors.put("12", "No email address on file");
-    errors.put("13", "Invalid collection");
-    errors.put("14", "User over quota");
-    errors.put("15", "The email does not match the username");
-    errors.put("16", "Client upgrade required");
-    errors.put("255", "An unexpected server error occurred: pool is empty.");
-
-    // Infrastructure-generated errors.
-    errors.put("\"server issue: getVS failed\"",                         "server issue: getVS failed");
-    errors.put("\"server issue: prefix not set\"",                       "server issue: prefix not set");
-    errors.put("\"server issue: host header not received from client\"", "server issue: host header not received from client");
-    errors.put("\"server issue: database lookup failed\"",               "server issue: database lookup failed");
-    errors.put("\"server issue: database is not healthy\"",              "server issue: database is not healthy");
-    errors.put("\"server issue: database not in pool\"",                 "server issue: database not in pool");
-    errors.put("\"server issue: database marked as down\"",              "server issue: database marked as down");
-    SERVER_ERROR_MESSAGES = errors;
-  }
-  public static String getServerErrorMessage(String body) {
-    if (SERVER_ERROR_MESSAGES.containsKey(body)) {
-      return SERVER_ERROR_MESSAGES.get(body);
-    }
-    return body;
-  }
-
-  /**
-   * @param uri
-   * @throws URISyntaxException
-   */
-  public SyncStorageRequest(String uri) throws URISyntaxException {
-    this(new URI(uri));
-  }
-
-  /**
-   * @param uri
-   */
-  public SyncStorageRequest(URI uri) {
-    this.resource = new BaseResource(uri);
-    this.resourceDelegate = this.makeResourceDelegate(this);
-    this.resource.delegate = this.resourceDelegate;
-  }
-
-  @Override
-  public URI getURI() {
-    return this.resource.getURI();
-  }
-
-  @Override
-  public String getURIString() {
-    return this.resource.getURIString();
-  }
-
-  @Override
-  public String getHostname() {
-    return this.resource.getHostname();
-  }
-
-  /**
-   * A ResourceDelegate that mediates between Resource-level notifications and the SyncStorageRequest.
-   */
-  public class SyncStorageResourceDelegate extends BaseResourceDelegate {
-    private static final String LOG_TAG = "SSResourceDelegate";
-    protected SyncStorageRequest request;
-
-    SyncStorageResourceDelegate(SyncStorageRequest request) {
-      super(request);
-      this.request = request;
-    }
-
-    @Override
-    public AuthHeaderProvider getAuthHeaderProvider() {
-      return request.delegate.getAuthHeaderProvider();
-    }
-
-    @Override
-    public String getUserAgent() {
-      return SyncConstants.USER_AGENT;
-    }
-
-    @Override
-    public void handleHttpResponse(HttpResponse response) {
-      Logger.debug(LOG_TAG, "SyncStorageResourceDelegate handling response: " + response.getStatusLine() + ".");
-      SyncStorageRequestDelegate d = this.request.delegate;
-      SyncStorageResponse res = new SyncStorageResponse(response);
-      // It is the responsibility of the delegate handlers to completely consume the response.
-      // In context of a Sync storage response, success is either a 200 OK or 202 Accepted.
-      // 202 is returned during uploads of data in a batching mode, indicating that more is expected.
-      if (res.getStatusCode() == 200 || res.getStatusCode() == 202) {
-        d.handleRequestSuccess(res);
-      } else {
-        Logger.warn(LOG_TAG, "HTTP request failed.");
-        try {
-          Logger.warn(LOG_TAG, "HTTP response body: " + res.getErrorMessage());
-        } catch (Exception e) {
-          Logger.error(LOG_TAG, "Can't fetch HTTP response body.", e);
-        }
-        d.handleRequestFailure(res);
-      }
-    }
-
-    @Override
-    public void handleHttpProtocolException(ClientProtocolException e) {
-      this.request.delegate.handleRequestError(e);
-    }
-
-    @Override
-    public void handleHttpIOException(IOException e) {
-      this.request.delegate.handleRequestError(e);
-    }
-
-    @Override
-    public void handleTransportException(GeneralSecurityException e) {
-      this.request.delegate.handleRequestError(e);
-    }
-
-    @Override
-    public void addHeaders(HttpRequestBase request, DefaultHttpClient client) {
-      // Clients can use their delegate interface to specify X-If-Unmodified-Since.
-      String ifUnmodifiedSince = this.request.delegate.ifUnmodifiedSince();
-      if (ifUnmodifiedSince != null) {
-        Logger.debug(LOG_TAG, "Making request with X-If-Unmodified-Since = " + ifUnmodifiedSince);
-        request.setHeader("x-if-unmodified-since", ifUnmodifiedSince);
-      }
-      if (request.getMethod().equalsIgnoreCase("DELETE")) {
-        request.addHeader("x-confirm-delete", "1");
-      }
-    }
-  }
-
-  protected BaseResourceDelegate resourceDelegate;
-  public SyncStorageRequestDelegate delegate;
-  protected BaseResource resource;
-
-  public SyncStorageRequest() {
-    super();
-  }
-
-  // Default implementation. Override this.
-  protected BaseResourceDelegate makeResourceDelegate(SyncStorageRequest request) {
-    return new SyncStorageResourceDelegate(request);
-  }
-
-  @Override
-  public void get() {
-    this.resource.get();
-  }
-
-  @Override
-  public void delete() {
-    this.resource.delete();
-  }
-
-  @Override
-  public void post(HttpEntity body) {
-    this.resource.post(body);
-  }
-
-  @Override
-  public void patch(HttpEntity body) {
-    this.resource.patch(body);
-  }
-
-  @Override
-  public void put(HttpEntity body) {
-    this.resource.put(body);
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/SyncStorageRequestDelegate.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/SyncStorageRequestDelegate.java
deleted file mode 100644
index 29f42cc..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/SyncStorageRequestDelegate.java
+++ /dev/null
@@ -1,38 +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.sync.net;
-
-public interface SyncStorageRequestDelegate {
-  public AuthHeaderProvider getAuthHeaderProvider();
-
-  String ifUnmodifiedSince();
-
-  // TODO: at this point we can access X-Weave-Timestamp, compare
-  // that to our local timestamp, and compute an estimate of clock
-  // skew. Bug 721887.
-
-  /**
-   * Override this to handle a successful SyncStorageRequest.
-   *
-   * SyncStorageResourceDelegate implementers <b>must</b> ensure that the HTTP
-   * responses underlying SyncStorageResponses are fully consumed to ensure that
-   * connections are returned to the pool, for example by calling
-   * <code>BaseResource.consumeEntity(response)</code>.
-   */
-  void handleRequestSuccess(SyncStorageResponse response);
-
-  /**
-   * Override this to handle a failed SyncStorageRequest.
-   *
-   *
-   * SyncStorageResourceDelegate implementers <b>must</b> ensure that the HTTP
-   * responses underlying SyncStorageResponses are fully consumed to ensure that
-   * connections are returned to the pool, for example by calling
-   * <code>BaseResource.consumeEntity(response)</code>.
-   */
-  void handleRequestFailure(SyncStorageResponse response);
-
-  void handleRequestError(Exception ex);
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/SyncStorageRequestIncrementalDelegate.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/SyncStorageRequestIncrementalDelegate.java
deleted file mode 100644
index aa5d735..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/SyncStorageRequestIncrementalDelegate.java
+++ /dev/null
@@ -1,9 +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.sync.net;
-
-public interface SyncStorageRequestIncrementalDelegate {
-  void handleRequestProgress(String progress);  // For line-by-line.
-}
\ No newline at end of file
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/SyncStorageResponse.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/SyncStorageResponse.java
deleted file mode 100644
index 644df31..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/SyncStorageResponse.java
+++ /dev/null
@@ -1,85 +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.sync.net;
-
-import java.io.IOException;
-import java.util.HashMap;
-
-import org.mozilla.gecko.background.common.log.Logger;
-
-import ch.boye.httpclientandroidlib.HttpResponse;
-
-public class SyncStorageResponse extends SyncResponse {
-  private static final String LOG_TAG = "SyncStorageResponse";
-
-  // Responses that are actionable get constant status codes.
-  public static final String RESPONSE_CLIENT_UPGRADE_REQUIRED = "16";
-
-  public static HashMap<String, String> SERVER_ERROR_MESSAGES;
-  static {
-    HashMap<String, String> errors = new HashMap<String, String>();
-
-    // Sync protocol errors.
-    errors.put("1", "Illegal method/protocol");
-    errors.put("2", "Incorrect/missing CAPTCHA");
-    errors.put("3", "Invalid/missing username");
-    errors.put("4", "Attempt to overwrite data that can't be overwritten (such as creating a user ID that already exists)");
-    errors.put("5", "User ID does not match account in path");
-    errors.put("6", "JSON parse failure");
-    errors.put("7", "Missing password field");
-    errors.put("8", "Invalid Weave Basic Object");
-    errors.put("9", "Requested password not strong enough");
-    errors.put("10", "Invalid/missing password reset code");
-    errors.put("11", "Unsupported function");
-    errors.put("12", "No email address on file");
-    errors.put("13", "Invalid collection");
-    errors.put("14", "User over quota");
-    errors.put("15", "The email does not match the username");
-    errors.put(RESPONSE_CLIENT_UPGRADE_REQUIRED, "Client upgrade required");
-    errors.put("255", "An unexpected server error occurred: pool is empty.");
-
-    // Infrastructure-generated errors.
-    errors.put("\"server issue: getVS failed\"",                         "server issue: getVS failed");
-    errors.put("\"server issue: prefix not set\"",                       "server issue: prefix not set");
-    errors.put("\"server issue: host header not received from client\"", "server issue: host header not received from client");
-    errors.put("\"server issue: database lookup failed\"",               "server issue: database lookup failed");
-    errors.put("\"server issue: database is not healthy\"",              "server issue: database is not healthy");
-    errors.put("\"server issue: database not in pool\"",                 "server issue: database not in pool");
-    errors.put("\"server issue: database marked as down\"",              "server issue: database marked as down");
-    SERVER_ERROR_MESSAGES = errors;
-  }
-  public static String getServerErrorMessage(String body) {
-    Logger.debug(LOG_TAG, "Looking up message for body \"" + body + "\"");
-    if (SERVER_ERROR_MESSAGES.containsKey(body)) {
-      return SERVER_ERROR_MESSAGES.get(body);
-    }
-    return body;
-  }
-
-
-  public SyncStorageResponse(HttpResponse res) {
-    super(res);
-  }
-
-  public String getErrorMessage() throws IllegalStateException, IOException {
-    return SyncStorageResponse.getServerErrorMessage(this.body().trim());
-  }
-
-  /**
-   * This header gives the last-modified time of the target resource as seen during processing of
-   * the request, and will be included in all success responses (200, 201, 204).
-   * When given in response to a write request, this will be equal to the server’s current time and
-   * to the new last-modified time of any BSOs created or changed by the request.
-   */
-  public String getLastModified() {
-    if (!response.containsHeader(X_LAST_MODIFIED)) {
-      return null;
-    }
-    return response.getFirstHeader(X_LAST_MODIFIED).getValue();
-  }
-
-  // TODO: Content-Type and Content-Length validation.
-
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/TLSSocketFactory.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/TLSSocketFactory.java
deleted file mode 100644
index dd68c05..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/TLSSocketFactory.java
+++ /dev/null
@@ -1,62 +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.sync.net;
-
-import java.io.IOException;
-import java.net.Socket;
-
-import javax.net.ssl.SSLContext;
-import javax.net.ssl.SSLSocket;
-
-import org.mozilla.gecko.background.common.GlobalConstants;
-import org.mozilla.gecko.background.common.log.Logger;
-
-import ch.boye.httpclientandroidlib.conn.ssl.SSLSocketFactory;
-import ch.boye.httpclientandroidlib.params.HttpParams;
-
-public class TLSSocketFactory extends SSLSocketFactory {
-  private static final String LOG_TAG = "TLSSocketFactory";
-
-  // Guarded by `this`.
-  private static String[] cipherSuites = GlobalConstants.DEFAULT_CIPHER_SUITES;
-
-  public TLSSocketFactory(SSLContext sslContext) {
-    super(sslContext);
-  }
-
-  /**
-   * Attempt to specify the cipher suites to use for a connection. If
-   * setting fails (as it will on Android 2.2, because the wrong names
-   * are in use to specify ciphers), attempt to set the defaults.
-   *
-   * We store the list of cipher suites in `cipherSuites`, which
-   * avoids this fallback handling having to be executed more than once.
-   *
-   * This method is synchronized to ensure correct use of that member.
-   *
-   * See Bug 717691 for more details.
-   *
-   * @param socket
-   *        The SSLSocket on which to operate.
-   */
-  public static synchronized void setEnabledCipherSuites(SSLSocket socket) {
-    try {
-      socket.setEnabledCipherSuites(cipherSuites);
-    } catch (IllegalArgumentException e) {
-      cipherSuites = socket.getSupportedCipherSuites();
-      Logger.warn(LOG_TAG, "Setting enabled cipher suites failed: " + e.getMessage());
-      Logger.warn(LOG_TAG, "Using " + cipherSuites.length + " supported suites.");
-      socket.setEnabledCipherSuites(cipherSuites);
-    }
-  }
-
-  @Override
-  public Socket createSocket(HttpParams params) throws IOException {
-    SSLSocket socket = (SSLSocket) super.createSocket(params);
-    socket.setEnabledProtocols(GlobalConstants.DEFAULT_PROTOCOLS);
-    setEnabledCipherSuites(socket);
-    return socket;
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/WBOCollectionRequestDelegate.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/WBOCollectionRequestDelegate.java
deleted file mode 100644
index 2e26f04..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/WBOCollectionRequestDelegate.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.sync.net;
-
-import org.mozilla.gecko.sync.crypto.KeyBundle;
-import org.mozilla.gecko.sync.CryptoRecord;
-import org.mozilla.gecko.sync.KeyBundleProvider;
-
-/**
- * Subclass this to handle collection fetches.
- * @author rnewman
- *
- */
-public abstract class WBOCollectionRequestDelegate
-extends SyncStorageCollectionRequestDelegate
-implements KeyBundleProvider {
-
-  @Override
-  public abstract KeyBundle keyBundle();
-  public abstract void handleWBO(CryptoRecord record);
-
-  @Override
-  public void handleRequestProgress(String progress) {
-    try {
-      CryptoRecord record = CryptoRecord.fromJSONRecord(progress);
-      record.keyBundle = this.keyBundle();
-      this.handleWBO(record);
-    } catch (Exception e) {
-      this.handleRequestError(e);
-      // TODO: abort?! Allow exception to propagate to fail?
-    }
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/WBORequestDelegate.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/WBORequestDelegate.java
deleted file mode 100644
index 8a09e0c..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/WBORequestDelegate.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.sync.net;
-
-import org.mozilla.gecko.sync.KeyBundleProvider;
-import org.mozilla.gecko.sync.crypto.KeyBundle;
-
-public abstract class WBORequestDelegate
-implements SyncStorageRequestDelegate, KeyBundleProvider {
-  @Override
-  public abstract KeyBundle keyBundle();
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/BookmarkNeedsReparentingException.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/BookmarkNeedsReparentingException.java
deleted file mode 100644
index 5fe3dc9..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/BookmarkNeedsReparentingException.java
+++ /dev/null
@@ -1,17 +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.sync.repositories;
-
-import org.mozilla.gecko.sync.SyncException;
-
-public class BookmarkNeedsReparentingException extends SyncException {
-
-  private static final long serialVersionUID = -7018336108709392800L;
-
-  public BookmarkNeedsReparentingException(Exception ex) {
-    super(ex);
-  }
-
-}
\ No newline at end of file
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/BookmarksRepository.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/BookmarksRepository.java
deleted file mode 100644
index 289fc48..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/BookmarksRepository.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.sync.repositories;
-
-/**
- * Shared interface for repositories that consume and produce
- * bookmark records.
- *
- * @author rnewman
- *
- */
-public interface BookmarksRepository {
-
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/ConstrainedServer11Repository.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/ConstrainedServer11Repository.java
deleted file mode 100644
index a6dc3f6..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/ConstrainedServer11Repository.java
+++ /dev/null
@@ -1,51 +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.sync.repositories;
-
-import java.net.URISyntaxException;
-
-import org.mozilla.gecko.sync.InfoCollections;
-import org.mozilla.gecko.sync.InfoConfiguration;
-import org.mozilla.gecko.sync.net.AuthHeaderProvider;
-
-/**
- * A kind of Server11Repository that supports explicit setting of total fetch limit, per-batch fetch limit, and a sort order.
- *
- * @author rnewman
- *
- */
-public class ConstrainedServer11Repository extends Server11Repository {
-
-  private final String sort;
-  private final long batchLimit;
-  private final long totalLimit;
-
-  public ConstrainedServer11Repository(String collection, String storageURL,
-                                       AuthHeaderProvider authHeaderProvider,
-                                       InfoCollections infoCollections,
-                                       InfoConfiguration infoConfiguration,
-                                       long batchLimit, long totalLimit, String sort)
-          throws URISyntaxException {
-    super(collection, storageURL, authHeaderProvider, infoCollections, infoConfiguration);
-    this.batchLimit = batchLimit;
-    this.totalLimit = totalLimit;
-    this.sort  = sort;
-  }
-
-  @Override
-  public String getDefaultSort() {
-    return sort;
-  }
-
-  @Override
-  public long getDefaultBatchLimit() {
-    return batchLimit;
-  }
-
-  @Override
-  public long getDefaultTotalLimit() {
-    return totalLimit;
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/FetchFailedException.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/FetchFailedException.java
deleted file mode 100644
index 8b29a37..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/FetchFailedException.java
+++ /dev/null
@@ -1,11 +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.sync.repositories;
-
-import org.mozilla.gecko.sync.SyncException;
-
-public class FetchFailedException extends SyncException {
-  private static final long serialVersionUID = -7533105300182522946L;
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/HashSetStoreTracker.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/HashSetStoreTracker.java
deleted file mode 100644
index 3b6facc..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/HashSetStoreTracker.java
+++ /dev/null
@@ -1,61 +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.sync.repositories;
-
-import java.util.HashSet;
-import java.util.Iterator;
-
-import org.mozilla.gecko.sync.repositories.domain.Record;
-
-public class HashSetStoreTracker implements StoreTracker {
-
-  // Guarded by `this`.
-  // Used to store GUIDs that were not locally modified but
-  // have been modified by a call to `store`, and thus
-  // should not be returned by a subsequent fetch.
-  private final HashSet<String> guids;
-
-  public HashSetStoreTracker() {
-    guids = new HashSet<String>();
-  }
-
-  @Override
-  public String toString() {
-    return "#<Tracker: " + guids.size() + " guids tracked.>";
-  }
-
-  @Override
-  public synchronized boolean trackRecordForExclusion(String guid) {
-    return (guid != null) && guids.add(guid);
-  }
-
-  @Override
-  public synchronized boolean isTrackedForExclusion(String guid) {
-    return (guid != null) && guids.contains(guid);
-  }
-
-  @Override
-  public synchronized boolean untrackStoredForExclusion(String guid) {
-    return (guid != null) && guids.remove(guid);
-  }
-
-  @Override
-  public RecordFilter getFilter() {
-    if (guids.size() == 0) {
-      return null;
-    }
-    return new RecordFilter() {
-      @Override
-      public boolean excludeRecord(Record r) {
-        return isTrackedForExclusion(r.guid);
-      }
-    };
-  }
-
-  @Override
-  public Iterator<String> recordsTrackedForExclusion() {
-    return this.guids.iterator();
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/HistoryRepository.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/HistoryRepository.java
deleted file mode 100644
index eddc321..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/HistoryRepository.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.sync.repositories;
-
-/**
- * Shared interface for repositories that consume and produce
- * history records.
- *
- * @author rnewman
- *
- */
-public interface HistoryRepository {
-
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/IdentityRecordFactory.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/IdentityRecordFactory.java
deleted file mode 100644
index acedc66..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/IdentityRecordFactory.java
+++ /dev/null
@@ -1,15 +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.sync.repositories;
-
-import org.mozilla.gecko.sync.repositories.domain.Record;
-
-public class IdentityRecordFactory extends RecordFactory {
-
-  @Override
-  public Record createRecord(Record record) {
-    return record;
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/InactiveSessionException.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/InactiveSessionException.java
deleted file mode 100644
index 185f0d7..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/InactiveSessionException.java
+++ /dev/null
@@ -1,17 +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.sync.repositories;
-
-import org.mozilla.gecko.sync.SyncException;
-
-public class InactiveSessionException extends SyncException {
-
-  private static final long serialVersionUID = 537241160815940991L;
-
-  public InactiveSessionException(Exception ex) {
-    super(ex);
-  }
-
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/InvalidBookmarkTypeException.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/InvalidBookmarkTypeException.java
deleted file mode 100644
index 3597276..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/InvalidBookmarkTypeException.java
+++ /dev/null
@@ -1,17 +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.sync.repositories;
-
-import org.mozilla.gecko.sync.SyncException;
-
-public class InvalidBookmarkTypeException extends SyncException {
-
-  private static final long serialVersionUID = -6098516814844387449L;
-
-  public InvalidBookmarkTypeException(Exception e) {
-    super(e);
-  }
-
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/InvalidRequestException.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/InvalidRequestException.java
deleted file mode 100644
index 3f761e5..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/InvalidRequestException.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.sync.repositories;
-
-import org.mozilla.gecko.sync.SyncException;
-
-public class InvalidRequestException extends SyncException {
-
-  private static final long serialVersionUID = 4502951350743608243L;
-
-  public InvalidRequestException(Exception ex) {
-    super(ex);
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/InvalidSessionTransitionException.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/InvalidSessionTransitionException.java
deleted file mode 100644
index 0963892..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/InvalidSessionTransitionException.java
+++ /dev/null
@@ -1,17 +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.sync.repositories;
-
-import org.mozilla.gecko.sync.SyncException;
-
-public class InvalidSessionTransitionException extends SyncException {
-
-  private static final long serialVersionUID = 4157729859314427281L;
-
-  public InvalidSessionTransitionException(Exception ex) {
-    super(ex);
-  }
-
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/MultipleRecordsForGuidException.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/MultipleRecordsForGuidException.java
deleted file mode 100644
index 58cca4a..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/MultipleRecordsForGuidException.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.sync.repositories;
-
-import org.mozilla.gecko.sync.SyncException;
-
-public class MultipleRecordsForGuidException extends SyncException {
-
-  private static final long serialVersionUID = 7426987323485324741L;
-
-  public MultipleRecordsForGuidException(Exception ex) {
-    super(ex);
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/NoContentProviderException.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/NoContentProviderException.java
deleted file mode 100644
index 85d119a..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/NoContentProviderException.java
+++ /dev/null
@@ -1,25 +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.sync.repositories;
-
-import org.mozilla.gecko.sync.SyncException;
-
-import android.net.Uri;
-
-/**
- * Raised when a Content Provider cannot be retrieved.
- *
- * @author rnewman
- *
- */
-public class NoContentProviderException extends SyncException {
-  private static final long serialVersionUID = 1L;
-
-  public final Uri requestedProvider;
-  public NoContentProviderException(Uri requested) {
-    super();
-    this.requestedProvider = requested;
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/NoGuidForIdException.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/NoGuidForIdException.java
deleted file mode 100644
index 3681def..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/NoGuidForIdException.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.sync.repositories;
-
-import org.mozilla.gecko.sync.SyncException;
-
-public class NoGuidForIdException extends SyncException {
-
-  private static final long serialVersionUID = -675614284405829041L;
-
-  public NoGuidForIdException(Exception ex) {
-    super(ex);
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/NoStoreDelegateException.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/NoStoreDelegateException.java
deleted file mode 100644
index 5747039..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/NoStoreDelegateException.java
+++ /dev/null
@@ -1,11 +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.sync.repositories;
-
-import org.mozilla.gecko.sync.SyncException;
-
-public class NoStoreDelegateException extends SyncException {
-  private static final long serialVersionUID = 6631689468978422074L;
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/NullCursorException.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/NullCursorException.java
deleted file mode 100644
index 4d90579..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/NullCursorException.java
+++ /dev/null
@@ -1,17 +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.sync.repositories;
-
-import org.mozilla.gecko.sync.SyncException;
-
-public class NullCursorException extends SyncException {
-
-  private static final long serialVersionUID = 3146506225701104661L;
-
-  public NullCursorException(Exception e) {
-    super(e);
-  }
-
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/ParentNotFoundException.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/ParentNotFoundException.java
deleted file mode 100644
index 991fd74..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/ParentNotFoundException.java
+++ /dev/null
@@ -1,17 +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.sync.repositories;
-
-import org.mozilla.gecko.sync.SyncException;
-
-public class ParentNotFoundException extends SyncException {
-  
-  private static final long serialVersionUID = -2687003621705922982L;
-
-  public ParentNotFoundException(Exception ex) {
-    super(ex);
-  }
-
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/ProfileDatabaseException.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/ProfileDatabaseException.java
deleted file mode 100644
index 0f80751..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/ProfileDatabaseException.java
+++ /dev/null
@@ -1,17 +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.sync.repositories;
-
-import org.mozilla.gecko.sync.SyncException;
-
-public class ProfileDatabaseException extends SyncException {
-
-  private static final long serialVersionUID = -4916908502042261602L;
-
-  public ProfileDatabaseException(Exception ex) {
-    super(ex);
-  }
-
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/RecordFactory.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/RecordFactory.java
deleted file mode 100644
index 6a8d81a..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/RecordFactory.java
+++ /dev/null
@@ -1,13 +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.sync.repositories;
-
-import org.mozilla.gecko.sync.repositories.domain.Record;
-
-// Take a record retrieved from some middleware, producing
-// some concrete record type for application to some local repository.
-public abstract class RecordFactory {
-  public abstract Record createRecord(Record record);
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/RecordFilter.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/RecordFilter.java
deleted file mode 100644
index 733448d..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/RecordFilter.java
+++ /dev/null
@@ -1,11 +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.sync.repositories;
-
-import org.mozilla.gecko.sync.repositories.domain.Record;
-
-public interface RecordFilter {
-  public boolean excludeRecord(Record r);
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/Repository.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/Repository.java
deleted file mode 100644
index 3dd3fd2..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/Repository.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.sync.repositories;
-
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionCleanDelegate;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionCreationDelegate;
-
-import android.content.Context;
-
-public abstract class Repository {
-  public abstract void createSession(RepositorySessionCreationDelegate delegate, Context context);
-
-  public void clean(boolean success, RepositorySessionCleanDelegate delegate, Context context) {
-    delegate.onCleaned(this);
-  }
-}
\ No newline at end of file
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/RepositorySession.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/RepositorySession.java
deleted file mode 100644
index 84fca13..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/RepositorySession.java
+++ /dev/null
@@ -1,384 +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.sync.repositories;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Iterator;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionBeginDelegate;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionFetchRecordsDelegate;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionFinishDelegate;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionGuidsSinceDelegate;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionStoreDelegate;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionWipeDelegate;
-import org.mozilla.gecko.sync.repositories.domain.Record;
-
-/**
- * A <code>RepositorySession</code> is created and used thusly:
- *
- *<ul>
- * <li>Construct, with a reference to its parent {@link Repository}, by calling
- *   {@link Repository#createSession(RepositorySessionCreationDelegate, android.content.Context)}.</li>
- * <li>Populate with saved information by calling {@link #unbundle(RepositorySessionBundle)}.</li>
- * <li>Begin a sync by calling {@link #begin(RepositorySessionBeginDelegate)}. <code>begin()</code>
- *   is an appropriate place to initialize expensive resources.</li>
- * <li>Perform operations such as {@link #fetchSince(long, RepositorySessionFetchRecordsDelegate)} and
- *   {@link #store(Record)}.</li>
- * <li>Finish by calling {@link #finish(RepositorySessionFinishDelegate)}, retrieving and storing
- *   the current bundle.</li>
- *</ul>
- *
- * If <code>finish()</code> is not called, {@link #abort()} must be called. These calls must
- * <em>always</em> be paired with <code>begin()</code>.
- *
- */
-public abstract class RepositorySession {
-
-  public enum SessionStatus {
-    UNSTARTED,
-    ACTIVE,
-    ABORTED,
-    DONE
-  }
-
-  private static final String LOG_TAG = "RepositorySession";
-
-  protected static void trace(String message) {
-    Logger.trace(LOG_TAG, message);
-  }
-
-  private SessionStatus status = SessionStatus.UNSTARTED;
-  protected Repository repository;
-  protected RepositorySessionStoreDelegate delegate;
-
-  /**
-   * A queue of Runnables which call out into delegates.
-   */
-  protected ExecutorService delegateQueue  = Executors.newSingleThreadExecutor();
-
-  /**
-   * A queue of Runnables which effect storing.
-   * This includes actual store work, and also the consequences of storeDone.
-   * This provides strict ordering.
-   */
-  protected ExecutorService storeWorkQueue = Executors.newSingleThreadExecutor();
-
-  // The time that the last sync on this collection completed, in milliseconds since epoch.
-  private long lastSyncTimestamp = 0;
-
-  public long getLastSyncTimestamp() {
-    return lastSyncTimestamp;
-  }
-
-  public static long now() {
-    return System.currentTimeMillis();
-  }
-
-  public RepositorySession(Repository repository) {
-    this.repository = repository;
-  }
-
-  public abstract void guidsSince(long timestamp, RepositorySessionGuidsSinceDelegate delegate);
-  public abstract void fetchSince(long timestamp, RepositorySessionFetchRecordsDelegate delegate);
-  public abstract void fetch(String[] guids, RepositorySessionFetchRecordsDelegate delegate) throws InactiveSessionException;
-  public abstract void fetchAll(RepositorySessionFetchRecordsDelegate delegate);
-
-  /**
-   * Override this if you wish to short-circuit a sync when you know --
-   * e.g., by inspecting the database or info/collections -- that no new
-   * data are available.
-   *
-   * @return true if a sync should proceed.
-   */
-  public boolean dataAvailable() {
-    return true;
-  }
-
-  /**
-   * @return true if we cannot safely sync from this <code>RepositorySession</code>.
-   */
-  public boolean shouldSkip() {
-    return false;
-  }
-
-  /*
-   * Store operations proceed thusly:
-   *
-   * * Set a delegate
-   * * Store an arbitrary number of records. At any time the delegate can be
-   *   notified of an error.
-   * * Call storeDone to notify the session that no more items are forthcoming.
-   * * The store delegate will be notified of error or completion.
-   *
-   * This arrangement of calls allows for batching at the session level.
-   *
-   * Store success calls are not guaranteed.
-   */
-  public void setStoreDelegate(RepositorySessionStoreDelegate delegate) {
-    Logger.debug(LOG_TAG, "Setting store delegate to " + delegate);
-    this.delegate = delegate;
-  }
-  public abstract void store(Record record) throws NoStoreDelegateException;
-
-  public void storeDone() {
-    // Our default behavior will be to assume that the Runnable is
-    // executed as soon as all the stores synchronously finish, so
-    // our end timestamp can just be… now.
-    storeDone(now());
-  }
-
-  public void storeDone(final long end) {
-    Logger.debug(LOG_TAG, "Scheduling onStoreCompleted for after storing is done: " + end);
-    Runnable command = new Runnable() {
-      @Override
-      public void run() {
-        delegate.onStoreCompleted(end);
-      }
-    };
-    storeWorkQueue.execute(command);
-  }
-
-  public abstract void wipe(RepositorySessionWipeDelegate delegate);
-
-  /**
-   * Synchronously perform the shared work of beginning. Throws on failure.
-   * @throws InvalidSessionTransitionException
-   *
-   */
-  protected void sharedBegin() throws InvalidSessionTransitionException {
-    Logger.debug(LOG_TAG, "Shared begin.");
-    if (delegateQueue.isShutdown()) {
-      throw new InvalidSessionTransitionException(null);
-    }
-    if (storeWorkQueue.isShutdown()) {
-      throw new InvalidSessionTransitionException(null);
-    }
-    this.transitionFrom(SessionStatus.UNSTARTED, SessionStatus.ACTIVE);
-  }
-
-  /**
-   * Start the session. This is an appropriate place to initialize
-   * data access components such as database handles.
-   *
-   * @param delegate
-   * @throws InvalidSessionTransitionException
-   */
-  public void begin(RepositorySessionBeginDelegate delegate) throws InvalidSessionTransitionException {
-    sharedBegin();
-    delegate.deferredBeginDelegate(delegateQueue).onBeginSucceeded(this);
-  }
-
-  public void unbundle(RepositorySessionBundle bundle) {
-    this.lastSyncTimestamp = bundle == null ? 0 : bundle.getTimestamp();
-  }
-
-  /**
-   * Override this in your subclasses to return values to save between sessions.
-   * Note that RepositorySession automatically bumps the timestamp to the time
-   * the last sync began. If unbundled but not begun, this will be the same as the
-   * value in the input bundle.
-   *
-   * The Synchronizer most likely wants to bump the bundle timestamp to be a value
-   * return from a fetch call.
-   */
-  protected RepositorySessionBundle getBundle() {
-    // Why don't we just persist the old bundle?
-    long timestamp = getLastSyncTimestamp();
-    RepositorySessionBundle bundle = new RepositorySessionBundle(timestamp);
-    Logger.debug(LOG_TAG, "Setting bundle timestamp to " + timestamp + ".");
-
-    return bundle;
-  }
-
-  /**
-   * Just like finish(), but doesn't do any work that should only be performed
-   * at the end of a successful sync, and can be called any time.
-   */
-  public void abort(RepositorySessionFinishDelegate delegate) {
-    this.abort();
-    delegate.deferredFinishDelegate(delegateQueue).onFinishSucceeded(this, this.getBundle());
-  }
-
-  /**
-   * Abnormally terminate the repository session, freeing or closing
-   * any resources that were opened during the lifetime of the session.
-   */
-  public void abort() {
-    // TODO: do something here.
-    this.setStatus(SessionStatus.ABORTED);
-    try {
-      storeWorkQueue.shutdownNow();
-    } catch (Exception e) {
-      Logger.error(LOG_TAG, "Caught exception shutting down store work queue.", e);
-    }
-    try {
-      delegateQueue.shutdown();
-    } catch (Exception e) {
-      Logger.error(LOG_TAG, "Caught exception shutting down delegate queue.", e);
-    }
-  }
-
-  /**
-   * End the repository session, freeing or closing any resources
-   * that were opened during the lifetime of the session.
-   *
-   * @param delegate notified of success or failure.
-   * @throws InactiveSessionException
-   */
-  public void finish(final RepositorySessionFinishDelegate delegate) throws InactiveSessionException {
-    try {
-      this.transitionFrom(SessionStatus.ACTIVE, SessionStatus.DONE);
-      delegate.deferredFinishDelegate(delegateQueue).onFinishSucceeded(this, this.getBundle());
-    } catch (InvalidSessionTransitionException e) {
-      Logger.error(LOG_TAG, "Tried to finish() an unstarted or already finished session");
-      throw new InactiveSessionException(e);
-    }
-
-    Logger.trace(LOG_TAG, "Shutting down work queues.");
-    storeWorkQueue.shutdown();
-    delegateQueue.shutdown();
-  }
-
-  /**
-   * Run the provided command if we're active and our delegate queue
-   * is not shut down.
-   */
-  protected synchronized void executeDelegateCommand(Runnable command)
-      throws InactiveSessionException {
-    if (!isActive() || delegateQueue.isShutdown()) {
-      throw new InactiveSessionException(null);
-    }
-    delegateQueue.execute(command);
-  }
-
-  public synchronized void ensureActive() throws InactiveSessionException {
-    if (!isActive()) {
-      throw new InactiveSessionException(null);
-    }
-  }
-
-  public synchronized boolean isActive() {
-    return status == SessionStatus.ACTIVE;
-  }
-
-  public synchronized SessionStatus getStatus() {
-    return status;
-  }
-
-  public synchronized void setStatus(SessionStatus status) {
-    this.status = status;
-  }
-
-  public synchronized void transitionFrom(SessionStatus from, SessionStatus to) throws InvalidSessionTransitionException {
-    if (from == null || this.status == from) {
-      Logger.trace(LOG_TAG, "Successfully transitioning from " + this.status + " to " + to);
-
-      this.status = to;
-      return;
-    }
-    Logger.warn(LOG_TAG, "Wanted to transition from " + from + " but in state " + this.status);
-    throw new InvalidSessionTransitionException(null);
-  }
-
-  /**
-   * Produce a record that is some combination of the remote and local records
-   * provided.
-   *
-   * The returned record must be produced without mutating either remoteRecord
-   * or localRecord. It is acceptable to return either remoteRecord or localRecord
-   * if no modifications are to be propagated.
-   *
-   * The returned record *should* have the local androidID and the remote GUID,
-   * and some optional merge of data from the two records.
-   *
-   * This method can be called with records that are identical, or differ in
-   * any regard.
-   *
-   * This method will not be called if:
-   *
-   * * either record is marked as deleted, or
-   * * there is no local mapping for a new remote record.
-   *
-   * Otherwise, it will be called precisely once.
-   *
-   * Side-effects (e.g., for transactional storage) can be hooked in here.
-   *
-   * @param remoteRecord
-   *        The record retrieved from upstream, already adjusted for clock skew.
-   * @param localRecord
-   *        The record retrieved from local storage.
-   * @param lastRemoteRetrieval
-   *        The timestamp of the last retrieved set of remote records, adjusted for
-   *        clock skew.
-   * @param lastLocalRetrieval
-   *        The timestamp of the last retrieved set of local records.
-   * @return
-   *        A Record instance to apply, or null to apply nothing.
-   */
-  protected Record reconcileRecords(final Record remoteRecord,
-                                    final Record localRecord,
-                                    final long lastRemoteRetrieval,
-                                    final long lastLocalRetrieval) {
-    Logger.debug(LOG_TAG, "Reconciling remote " + remoteRecord.guid + " against local " + localRecord.guid);
-
-    if (localRecord.equalPayloads(remoteRecord)) {
-      if (remoteRecord.lastModified > localRecord.lastModified) {
-        Logger.debug(LOG_TAG, "Records are equal. No record application needed.");
-        return null;
-      }
-
-      // Local wins.
-      return null;
-    }
-
-    // TODO: Decide what to do based on:
-    // * Which of the two records is modified;
-    // * Whether they are equal or congruent;
-    // * The modified times of each record (interpreted through the lens of clock skew);
-    // * ...
-    boolean localIsMoreRecent = localRecord.lastModified > remoteRecord.lastModified;
-    Logger.debug(LOG_TAG, "Local record is more recent? " + localIsMoreRecent);
-    Record donor = localIsMoreRecent ? localRecord : remoteRecord;
-
-    // Modify the local record to match the remote record's GUID and values.
-    // Preserve the local Android ID, and merge data where possible.
-    // It sure would be nice if copyWithIDs didn't give a shit about androidID, mm?
-    Record out = donor.copyWithIDs(remoteRecord.guid, localRecord.androidID);
-
-    // We don't want to upload the record if the remote record was
-    // applied without changes.
-    // This logic will become more complicated as reconciling becomes smarter.
-    if (!localIsMoreRecent) {
-      trackGUID(out.guid);
-    }
-    return out;
-  }
-
-  /**
-   * Depending on the RepositorySession implementation, track
-   * that a record — most likely a brand-new record that has been
-   * applied unmodified — should be tracked so as to not be uploaded
-   * redundantly.
-   *
-   * The default implementations do nothing.
-   */
-  protected void trackGUID(String guid) {
-  }
-
-  protected synchronized void untrackGUIDs(Collection<String> guids) {
-  }
-
-  protected void untrackGUID(String guid) {
-  }
-
-  // Ah, Java. You wretched creature.
-  public Iterator<String> getTrackedRecordIDs() {
-    return new ArrayList<String>().iterator();
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/RepositorySessionBundle.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/RepositorySessionBundle.java
deleted file mode 100644
index 7908ec7..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/RepositorySessionBundle.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.sync.repositories;
-
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.sync.ExtendedJSONObject;
-import org.mozilla.gecko.sync.NonObjectJSONException;
-
-import java.io.IOException;
-
-public class RepositorySessionBundle {
-  public static final String LOG_TAG = RepositorySessionBundle.class.getSimpleName();
-
-  protected static final String JSON_KEY_TIMESTAMP = "timestamp";
-
-  protected final ExtendedJSONObject object;
-
-  public RepositorySessionBundle(String jsonString) throws IOException, NonObjectJSONException {
-
-    object = new ExtendedJSONObject(jsonString);
-  }
-
-  public RepositorySessionBundle(long lastSyncTimestamp) {
-    object = new ExtendedJSONObject();
-    this.setTimestamp(lastSyncTimestamp);
-  }
-
-  public long getTimestamp() {
-    if (object.containsKey(JSON_KEY_TIMESTAMP)) {
-      return object.getLong(JSON_KEY_TIMESTAMP);
-    }
-
-    return -1;
-  }
-
-  public void setTimestamp(long timestamp) {
-    Logger.debug(LOG_TAG, "Setting timestamp to " + timestamp + ".");
-    object.put(JSON_KEY_TIMESTAMP, timestamp);
-  }
-
-  public void bumpTimestamp(long timestamp) {
-    long existing = this.getTimestamp();
-    if (timestamp > existing) {
-      this.setTimestamp(timestamp);
-    } else {
-      Logger.debug(LOG_TAG, "Timestamp " + timestamp + " not greater than " + existing + "; not bumping.");
-    }
-  }
-
-  public String toJSONString() {
-    return object.toJSONString();
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/Server11Repository.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/Server11Repository.java
deleted file mode 100644
index 4404fda..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/Server11Repository.java
+++ /dev/null
@@ -1,144 +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.sync.repositories;
-
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.util.ArrayList;
-
-import org.mozilla.gecko.sync.InfoCollections;
-import org.mozilla.gecko.sync.InfoConfiguration;
-import org.mozilla.gecko.sync.Utils;
-import org.mozilla.gecko.sync.net.AuthHeaderProvider;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionCreationDelegate;
-
-import android.content.Context;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-
-/**
- * A Server11Repository implements fetching and storing against the Sync 1.1 API.
- * It doesn't do crypto: that's the job of the middleware.
- *
- * @author rnewman
- */
-public class Server11Repository extends Repository {
-  protected String collection;
-  protected URI collectionURI;
-  protected final AuthHeaderProvider authHeaderProvider;
-  protected final InfoCollections infoCollections;
-
-  private final InfoConfiguration infoConfiguration;
-
-  /**
-   * Construct a new repository that fetches and stores against the Sync 1.1. API.
-   *
-   * @param collection name.
-   * @param storageURL full URL to storage endpoint.
-   * @param authHeaderProvider to use in requests; may be null.
-   * @param infoCollections instance; must not be null.
-   * @throws URISyntaxException
-   */
-  public Server11Repository(@NonNull String collection, @NonNull String storageURL, AuthHeaderProvider authHeaderProvider, @NonNull InfoCollections infoCollections, @NonNull InfoConfiguration infoConfiguration) throws URISyntaxException {
-    if (collection == null) {
-      throw new IllegalArgumentException("collection must not be null");
-    }
-    if (storageURL == null) {
-      throw new IllegalArgumentException("storageURL must not be null");
-    }
-    if (infoCollections == null) {
-      throw new IllegalArgumentException("infoCollections must not be null");
-    }
-    this.collection = collection;
-    this.collectionURI = new URI(storageURL + (storageURL.endsWith("/") ? collection : "/" + collection));
-    this.authHeaderProvider = authHeaderProvider;
-    this.infoCollections = infoCollections;
-    this.infoConfiguration = infoConfiguration;
-  }
-
-  @Override
-  public void createSession(RepositorySessionCreationDelegate delegate,
-                            Context context) {
-    delegate.onSessionCreated(new Server11RepositorySession(this));
-  }
-
-  public URI collectionURI() {
-    return this.collectionURI;
-  }
-
-  public URI collectionURI(boolean full, long newer, long limit, String sort, String ids, String offset) throws URISyntaxException {
-    ArrayList<String> params = new ArrayList<String>();
-    if (full) {
-      params.add("full=1");
-    }
-    if (newer >= 0) {
-      // Translate local millisecond timestamps into server decimal seconds.
-      String newerString = Utils.millisecondsToDecimalSecondsString(newer);
-      params.add("newer=" + newerString);
-    }
-    if (limit > 0) {
-      params.add("limit=" + limit);
-    }
-    if (sort != null) {
-      params.add("sort=" + sort);       // We trust these values.
-    }
-    if (ids != null) {
-      params.add("ids=" + ids);         // We trust these values.
-    }
-    if (offset != null) {
-      // Offset comes straight out of HTTP headers and it is the responsibility of the caller to URI-escape it.
-      params.add("offset=" + offset);
-    }
-    if (params.size() == 0) {
-      return this.collectionURI;
-    }
-
-    StringBuilder out = new StringBuilder();
-    char indicator = '?';
-    for (String param : params) {
-      out.append(indicator);
-      indicator = '&';
-      out.append(param);
-    }
-    String uri = this.collectionURI + out.toString();
-    return new URI(uri);
-  }
-
-  public URI wboURI(String id) throws URISyntaxException {
-    return new URI(this.collectionURI + "/" + id);
-  }
-
-  // Override these.
-  @SuppressWarnings("static-method")
-  public long getDefaultBatchLimit() {
-    return -1;
-  }
-
-  @SuppressWarnings("static-method")
-  public String getDefaultSort() {
-    return null;
-  }
-
-  public long getDefaultTotalLimit() {
-    return -1;
-  }
-
-  public AuthHeaderProvider getAuthHeaderProvider() {
-    return authHeaderProvider;
-  }
-
-  public boolean updateNeeded(long lastSyncTimestamp) {
-    return infoCollections.updateNeeded(collection, lastSyncTimestamp);
-  }
-
-  @Nullable
-  public Long getCollectionLastModified() {
-    return infoCollections.getTimestamp(collection);
-  }
-
-  public InfoConfiguration getInfoConfiguration() {
-    return infoConfiguration;
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/Server11RepositorySession.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/Server11RepositorySession.java
deleted file mode 100644
index 20c735a..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/Server11RepositorySession.java
+++ /dev/null
@@ -1,104 +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.sync.repositories;
-
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionFetchRecordsDelegate;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionGuidsSinceDelegate;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionStoreDelegate;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionWipeDelegate;
-import org.mozilla.gecko.sync.repositories.domain.Record;
-import org.mozilla.gecko.sync.repositories.downloaders.BatchingDownloader;
-import org.mozilla.gecko.sync.repositories.uploaders.BatchingUploader;
-
-public class Server11RepositorySession extends RepositorySession {
-  public static final String LOG_TAG = "Server11Session";
-
-  Server11Repository serverRepository;
-  private BatchingUploader uploader;
-  private final BatchingDownloader downloader;
-
-  public Server11RepositorySession(Repository repository) {
-    super(repository);
-    serverRepository = (Server11Repository) repository;
-    this.downloader = new BatchingDownloader(serverRepository, this);
-  }
-
-  public Server11Repository getServerRepository() {
-    return serverRepository;
-  }
-
-  @Override
-  public void setStoreDelegate(RepositorySessionStoreDelegate delegate) {
-    this.delegate = delegate;
-
-    // Now that we have the delegate, we can initialize our uploader.
-    this.uploader = new BatchingUploader(this, storeWorkQueue, delegate);
-  }
-
-  @Override
-  public void guidsSince(long timestamp,
-                         RepositorySessionGuidsSinceDelegate delegate) {
-    // TODO Auto-generated method stub
-
-  }
-
-  @Override
-  public void fetchSince(long timestamp,
-                         RepositorySessionFetchRecordsDelegate delegate) {
-    this.downloader.fetchSince(timestamp, delegate);
-  }
-
-  @Override
-  public void fetchAll(RepositorySessionFetchRecordsDelegate delegate) {
-    this.fetchSince(-1, delegate);
-  }
-
-  @Override
-  public void fetch(String[] guids,
-                    RepositorySessionFetchRecordsDelegate delegate) {
-    this.downloader.fetch(guids, delegate);
-  }
-
-  @Override
-  public void wipe(RepositorySessionWipeDelegate delegate) {
-    if (!isActive()) {
-      delegate.onWipeFailed(new InactiveSessionException(null));
-      return;
-    }
-    // TODO: implement wipe.
-  }
-
-  @Override
-  public void store(Record record) throws NoStoreDelegateException {
-    if (delegate == null) {
-      throw new NoStoreDelegateException();
-    }
-
-    // If delegate was set, this shouldn't happen.
-    if (uploader == null) {
-      throw new IllegalStateException("Uploader haven't been initialized");
-    }
-
-    uploader.process(record);
-  }
-
-  @Override
-  public void storeDone() {
-    Logger.debug(LOG_TAG, "storeDone().");
-
-    // If delegate was set, this shouldn't happen.
-    if (uploader == null) {
-      throw new IllegalStateException("Uploader haven't been initialized");
-    }
-
-    uploader.noMoreRecordsToUpload();
-  }
-
-  @Override
-  public boolean dataAvailable() {
-    return serverRepository.updateNeeded(getLastSyncTimestamp());
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/StoreFailedException.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/StoreFailedException.java
deleted file mode 100644
index fcb09e3..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/StoreFailedException.java
+++ /dev/null
@@ -1,11 +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.sync.repositories;
-
-import org.mozilla.gecko.sync.SyncException;
-
-public class StoreFailedException extends SyncException {
-  private static final long serialVersionUID = 6080340122855859752L;
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/StoreTracker.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/StoreTracker.java
deleted file mode 100644
index b6a3071..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/StoreTracker.java
+++ /dev/null
@@ -1,82 +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.sync.repositories;
-
-import java.util.Iterator;
-
-/**
- * Our hacky version of transactional semantics. The goal is to prevent
- * the following situation:
- *
- * * AAA is not modified locally.
- * * A modified AAA is downloaded during the storing phase. Its local
- *   timestamp is advanced.
- * * The direction of syncing changes, and AAA is now uploaded to the server.
- *
- * The following situation should still be supported:
- *
- * * AAA is not modified locally.
- * * A modified AAA is downloaded and merged with the local AAA.
- * * The merged AAA is uploaded to the server.
- *
- * As should:
- *
- * * AAA is modified locally.
- * * A modified AAA is downloaded, and discarded or merged.
- * * The current version of AAA is uploaded to the server.
- *
- * We achieve this by tracking GUIDs during the storing phase. If we
- * apply a record such that the local copy is substantially the same
- * as the record we just downloaded, we add it to a list of records
- * to avoid uploading. The definition of "substantially the same"
- * depends on the particular repository. The only consideration is "do we
- * want to upload this record in this sync?".
- *
- * Note that items are removed from this list when a fetch that
- * considers them for upload completes successfully. The entire list
- * is discarded when the session is completed.
- *
- * This interface exposes methods to:
- *
- * * During a store, recording that a record has been stored, and should
- *   thus not be returned in subsequent fetches;
- * * During a fetch, checking whether a record should be returned.
- *
- * In the future this might also grow self-persistence.
- *
- * See also RepositorySession.trackRecord.
- *
- * @author rnewman
- *
- */
-public interface StoreTracker {
-
-  /**
-   * @param guid
-   *        The GUID of the item to track.
-   * @return
-   *        Whether the GUID was a newly tracked value.
-   */
-  public boolean trackRecordForExclusion(String guid);
-
-  /**
-   * @param guid
-   *        The GUID of the item to check.
-   * @return
-   *        true if the item is already tracked.
-   */
-  public boolean isTrackedForExclusion(String guid);
-
-  /**
-  *
-  * @param guid
-  * @return true if the specified GUID was removed from the tracked set.
-  */
-  public boolean untrackStoredForExclusion(String guid);
-
-  public RecordFilter getFilter();
-
-  public Iterator<String> recordsTrackedForExclusion();
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/StoreTrackingRepositorySession.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/StoreTrackingRepositorySession.java
deleted file mode 100644
index 1a5c1e9..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/StoreTrackingRepositorySession.java
+++ /dev/null
@@ -1,102 +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.sync.repositories;
-
-import java.util.Collection;
-import java.util.Iterator;
-
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionBeginDelegate;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionFinishDelegate;
-import org.mozilla.gecko.sync.repositories.domain.Record;
-
-public abstract class StoreTrackingRepositorySession extends RepositorySession {
-  private static final String LOG_TAG = "StoreTrackSession";
-  protected StoreTracker storeTracker;
-
-  protected static StoreTracker createStoreTracker() {
-    return new HashSetStoreTracker();
-  }
-
-  public StoreTrackingRepositorySession(Repository repository) {
-    super(repository);
-  }
-
-  @Override
-  public void begin(RepositorySessionBeginDelegate delegate) throws InvalidSessionTransitionException {
-    RepositorySessionBeginDelegate deferredDelegate = delegate.deferredBeginDelegate(delegateQueue);
-    try {
-      super.sharedBegin();
-    } catch (InvalidSessionTransitionException e) {
-      deferredDelegate.onBeginFailed(e);
-      return;
-    }
-    // Or do this in your own subclass.
-    storeTracker = createStoreTracker();
-    deferredDelegate.onBeginSucceeded(this);
-  }
-
-  @Override
-  protected synchronized void trackGUID(String guid) {
-    if (this.storeTracker == null) {
-      throw new IllegalStateException("Store tracker not yet initialized!");
-    }
-    this.storeTracker.trackRecordForExclusion(guid);
-  }
-
-  @Override
-  protected synchronized void untrackGUID(String guid) {
-    if (this.storeTracker == null) {
-      throw new IllegalStateException("Store tracker not yet initialized!");
-    }
-    this.storeTracker.untrackStoredForExclusion(guid);
-  }
-
-  @Override
-  protected synchronized void untrackGUIDs(Collection<String> guids) {
-    if (this.storeTracker == null) {
-      throw new IllegalStateException("Store tracker not yet initialized!");
-    }
-    if (guids == null) {
-      return;
-    }
-    for (String guid : guids) {
-      this.storeTracker.untrackStoredForExclusion(guid);
-    }
-  }
-
-  protected void trackRecord(Record record) {
-
-    Logger.debug(LOG_TAG, "Tracking record " + record.guid +
-                           " (" + record.lastModified + ") to avoid re-upload.");
-    // Future: we care about the timestamp…
-    trackGUID(record.guid);
-  }
-
-  protected void untrackRecord(Record record) {
-    Logger.debug(LOG_TAG, "Un-tracking record " + record.guid + ".");
-    untrackGUID(record.guid);
-  }
-
-  @Override
-  public Iterator<String> getTrackedRecordIDs() {
-    if (this.storeTracker == null) {
-      throw new IllegalStateException("Store tracker not yet initialized!");
-    }
-    return this.storeTracker.recordsTrackedForExclusion();
-  }
-
-  @Override
-  public void abort(RepositorySessionFinishDelegate delegate) {
-    this.storeTracker = null;
-    super.abort(delegate);
-  }
-
-  @Override
-  public void finish(RepositorySessionFinishDelegate delegate) throws InactiveSessionException {
-    super.finish(delegate);
-    this.storeTracker = null;
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/AndroidBrowserBookmarksDataAccessor.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/AndroidBrowserBookmarksDataAccessor.java
deleted file mode 100644
index fd3c35d..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/AndroidBrowserBookmarksDataAccessor.java
+++ /dev/null
@@ -1,326 +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.sync.repositories.android;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.Map;
-
-import org.json.simple.JSONArray;
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.db.BrowserContract;
-import org.mozilla.gecko.sync.repositories.NullCursorException;
-import org.mozilla.gecko.sync.repositories.domain.BookmarkRecord;
-import org.mozilla.gecko.sync.repositories.domain.Record;
-
-import android.content.ContentUris;
-import android.content.ContentValues;
-import android.content.Context;
-import android.database.Cursor;
-import android.net.Uri;
-
-public class AndroidBrowserBookmarksDataAccessor extends AndroidBrowserRepositoryDataAccessor {
-
-  private static final String LOG_TAG = "BookmarksDataAccessor";
-
-  /*
-   * Fragments of SQL to make our lives easier.
-   */
-  private static final String BOOKMARK_IS_FOLDER = BrowserContract.Bookmarks.TYPE + " = " +
-                                                   BrowserContract.Bookmarks.TYPE_FOLDER;
-
-  // SQL fragment to retrieve GUIDs whose ID mappings should be tracked by this session.
-  // Exclude folders we don't want to sync.
-  private static final String GUID_SHOULD_TRACK = BrowserContract.SyncColumns.GUID + " NOT IN ('" +
-                                                  BrowserContract.Bookmarks.TAGS_FOLDER_GUID + "', '" +
-                                                  BrowserContract.Bookmarks.PLACES_FOLDER_GUID + "', '" +
-                                                  BrowserContract.Bookmarks.PINNED_FOLDER_GUID + "')";
-
-  private static final String EXCLUDE_SPECIAL_GUIDS_WHERE_CLAUSE;
-  static {
-    if (AndroidBrowserBookmarksRepositorySession.SPECIAL_GUIDS.length > 0) {
-      StringBuilder b = new StringBuilder(BrowserContract.SyncColumns.GUID + " NOT IN (");
-
-      int remaining = AndroidBrowserBookmarksRepositorySession.SPECIAL_GUIDS.length - 1;
-      for (String specialGuid : AndroidBrowserBookmarksRepositorySession.SPECIAL_GUIDS) {
-        b.append('"');
-        b.append(specialGuid);
-        b.append('"');
-        if (remaining-- > 0) {
-          b.append(", ");
-        }
-      }
-      b.append(')');
-      EXCLUDE_SPECIAL_GUIDS_WHERE_CLAUSE = b.toString();
-    } else {
-      EXCLUDE_SPECIAL_GUIDS_WHERE_CLAUSE = null;       // null is a valid WHERE clause.
-    }
-  }
-
-  public static final String TYPE_FOLDER = "folder";
-  public static final String TYPE_BOOKMARK = "bookmark";
-
-  private final RepoUtils.QueryHelper queryHelper;
-
-  public AndroidBrowserBookmarksDataAccessor(Context context) {
-    super(context);
-    this.queryHelper = new RepoUtils.QueryHelper(context, getUri(), LOG_TAG);
-  }
-
-  @Override
-  protected Uri getUri() {
-    return BrowserContractHelpers.BOOKMARKS_CONTENT_URI;
-  }
-
-  protected static Uri getPositionsUri() {
-    return BrowserContractHelpers.BOOKMARKS_POSITIONS_CONTENT_URI;
-  }
-
-  @Override
-  public void wipe() {
-    Uri uri = getUri();
-    Logger.info(LOG_TAG, "wiping (except for special guids): " + uri);
-    context.getContentResolver().delete(uri, EXCLUDE_SPECIAL_GUIDS_WHERE_CLAUSE, null);
-  }
-
-  private final String[] GUID_AND_ID = new String[] { BrowserContract.Bookmarks.GUID,
-                                                BrowserContract.Bookmarks._ID };
-
-  protected Cursor getGuidsIDsForFolders() throws NullCursorException {
-    // Exclude items that we don't want to sync (pinned items, reading list, 
-    // tags, the places root), in case they've ended up in the DB.
-    String where = BOOKMARK_IS_FOLDER + " AND " + GUID_SHOULD_TRACK;
-    return queryHelper.safeQuery(".getGuidsIDsForFolders", GUID_AND_ID, where, null, null);
-  }
-
-  /**
-   * Issue a request to the Content Provider to update the positions of the
-   * records named by the provided GUIDs to the index of their GUID in the
-   * provided array.
-   *
-   * @param childArray
-   *        A sequence of GUID strings.
-   */
-  public int updatePositions(ArrayList<String> childArray) {
-    final int size = childArray.size();
-    if (size == 0) {
-      return 0;
-    }
-
-    Logger.debug(LOG_TAG, "Updating positions for " + size + " items.");
-    String[] args = childArray.toArray(new String[size]);
-    return context.getContentResolver().update(getPositionsUri(), new ContentValues(), null, args);
-  }
-
-  public int bumpModifiedByGUID(Collection<String> ids, long modified) {
-    final int size = ids.size();
-    if (size == 0) {
-      return 0;
-    }
-
-    Logger.debug(LOG_TAG, "Bumping modified for " + size + " items to " + modified);
-    String where = RepoUtils.computeSQLInClause(size, BrowserContract.Bookmarks.GUID);
-    String[] selectionArgs = ids.toArray(new String[size]);
-    ContentValues values = new ContentValues();
-    values.put(BrowserContract.Bookmarks.DATE_MODIFIED, modified);
-
-    return context.getContentResolver().update(getUri(), values, where, selectionArgs);
-  }
-
-  /**
-   * Bump the modified time of a record by ID.
-   */
-  public int bumpModified(long id, long modified) {
-    Logger.debug(LOG_TAG, "Bumping modified for " + id + " to " + modified);
-    String where = BrowserContract.Bookmarks._ID + " = ?";
-    String[] selectionArgs = new String[] { String.valueOf(id) };
-    ContentValues values = new ContentValues();
-    values.put(BrowserContract.Bookmarks.DATE_MODIFIED, modified);
-
-    return context.getContentResolver().update(getUri(), values, where, selectionArgs);
-  }
-
-  protected void updateParentAndPosition(String guid, long newParentId, long position) {
-    ContentValues cv = new ContentValues();
-    cv.put(BrowserContract.Bookmarks.PARENT, newParentId);
-    if (position >= 0) {
-      cv.put(BrowserContract.Bookmarks.POSITION, position);
-    }
-    updateByGuid(guid, cv);
-  }
-
-  protected Map<String, Long> idsForGUIDs(String[] guids) throws NullCursorException {
-    final String where = RepoUtils.computeSQLInClause(guids.length, BrowserContract.Bookmarks.GUID);
-    Cursor c = queryHelper.safeQuery(".idsForGUIDs", GUID_AND_ID, where, guids, null);
-    try {
-      HashMap<String, Long> out = new HashMap<String, Long>();
-      if (!c.moveToFirst()) {
-        return out;
-      }
-      final int guidIndex = c.getColumnIndexOrThrow(BrowserContract.Bookmarks.GUID);
-      final int idIndex = c.getColumnIndexOrThrow(BrowserContract.Bookmarks._ID);
-      while (!c.isAfterLast()) {
-        out.put(c.getString(guidIndex), c.getLong(idIndex));
-        c.moveToNext();
-      }
-      return out;
-    } finally {
-      c.close();
-    }
-  }
-
-  /**
-   * Move the children of each source folder to the destination folder.
-   * Bump the modified time of each child.
-   * The caller should bump the modified time of the destination if desired.
-   *
-   * @param fromIDs the Android IDs of the source folders.
-   * @param to the Android ID of the destination folder.
-   * @return the number of updated rows.
-   */
-  protected int moveChildren(String[] fromIDs, long to) {
-    long now = System.currentTimeMillis();
-    long pos = -1;
-
-    ContentValues cv = new ContentValues();
-    cv.put(BrowserContract.Bookmarks.PARENT, to);
-    cv.put(BrowserContract.Bookmarks.DATE_MODIFIED, now);
-    cv.put(BrowserContract.Bookmarks.POSITION, pos);
-
-    final String where = RepoUtils.computeSQLInClause(fromIDs.length, BrowserContract.Bookmarks.PARENT);
-    return context.getContentResolver().update(getUri(), cv, where, fromIDs);
-  }
-  
-  /*
-   * Verify that all special GUIDs are present and that they aren't marked as deleted.
-   * Insert them if they aren't there.
-   */
-  public void checkAndBuildSpecialGuids() throws NullCursorException {
-    final String[] specialGUIDs = AndroidBrowserBookmarksRepositorySession.SPECIAL_GUIDS;
-    Cursor cur = fetch(specialGUIDs);
-    long placesRoot = 0;
-
-    // Map from GUID to whether deleted. Non-presence implies just that.
-    HashMap<String, Boolean> statuses = new HashMap<String, Boolean>(specialGUIDs.length);
-    try {
-      if (cur.moveToFirst()) {
-        while (!cur.isAfterLast()) {
-          String guid = RepoUtils.getStringFromCursor(cur, BrowserContract.SyncColumns.GUID);
-          if ("places".equals(guid)) {
-            placesRoot = RepoUtils.getLongFromCursor(cur, BrowserContract.CommonColumns._ID);
-          }
-          // Make sure none of these folders are marked as deleted.
-          boolean deleted = RepoUtils.getLongFromCursor(cur, BrowserContract.SyncColumns.IS_DELETED) == 1;
-          statuses.put(guid, deleted);
-          cur.moveToNext();
-        }
-      }
-    } finally {
-      cur.close();
-    }
-
-    // Insert or undelete them if missing.
-    for (String guid : specialGUIDs) {
-      if (statuses.containsKey(guid)) {
-        if (statuses.get(guid)) {
-          // Undelete.
-          Logger.info(LOG_TAG, "Undeleting special GUID " + guid);
-          ContentValues cv = new ContentValues();
-          cv.put(BrowserContract.SyncColumns.IS_DELETED, 0);
-          updateByGuid(guid, cv);
-        }
-      } else {
-        // Insert.
-        if (guid.equals("places")) {
-          // This is awkward.
-          Logger.info(LOG_TAG, "No places root. Inserting one.");
-          placesRoot = insertSpecialFolder("places", 0);
-        } else if (guid.equals("mobile")) {
-          Logger.info(LOG_TAG, "No mobile folder. Inserting one under the places root.");
-          insertSpecialFolder("mobile", placesRoot);
-        } else {
-          // unfiled, menu, toolbar.
-          Logger.info(LOG_TAG, "No " + guid + " root. Inserting one under places (" + placesRoot + ").");
-          insertSpecialFolder(guid, placesRoot);
-        }
-      }
-    }
-  }
-
-  private long insertSpecialFolder(String guid, long parentId) {
-    BookmarkRecord record = new BookmarkRecord(guid);
-    record.title = AndroidBrowserBookmarksRepositorySession.SPECIAL_GUIDS_MAP.get(guid);
-    record.type = "folder";
-    record.androidParentID = parentId;
-    return ContentUris.parseId(insert(record));
-  }
-
-  @Override
-  protected ContentValues getContentValues(Record record) {
-    BookmarkRecord rec = (BookmarkRecord) record;
-
-    if (rec.deleted) {
-      ContentValues cv = new ContentValues();
-      cv.put(BrowserContract.SyncColumns.GUID,     rec.guid);
-      cv.put(BrowserContract.Bookmarks.IS_DELETED, 1);
-      return cv;
-    }
-
-    final int recordType = BrowserContractHelpers.typeCodeForString(rec.type);
-    if (recordType == -1) {
-      throw new IllegalStateException("Unexpected record type " + rec.type);
-    }
-
-    ContentValues cv = new ContentValues();
-    cv.put(BrowserContract.SyncColumns.GUID,      rec.guid);
-    cv.put(BrowserContract.Bookmarks.TYPE,        recordType);
-    cv.put(BrowserContract.Bookmarks.TITLE,       rec.title);
-    cv.put(BrowserContract.Bookmarks.URL,         rec.bookmarkURI);
-    cv.put(BrowserContract.Bookmarks.DESCRIPTION, rec.description);
-    if (rec.tags == null) {
-      rec.tags = new JSONArray();
-    }
-    cv.put(BrowserContract.Bookmarks.TAGS,        rec.tags.toJSONString());
-    cv.put(BrowserContract.Bookmarks.KEYWORD,     rec.keyword);
-    cv.put(BrowserContract.Bookmarks.PARENT,      rec.androidParentID);
-    cv.put(BrowserContract.Bookmarks.POSITION,    rec.androidPosition);
-
-    // Note that we don't set the modified timestamp: we allow the
-    // content provider to do that for us.
-    return cv;
-  }
-
-  /**
-   * Returns a cursor over non-deleted records that list the given androidID as a parent.
-   */
-  public Cursor getChildren(long androidID) throws NullCursorException {
-    return getChildren(androidID, false);
-  }
-
-  /**
-   * Returns a cursor with any records that list the given androidID as a parent.
-   * Excludes 'places', and optionally any deleted records.
-   */
-  public Cursor getChildren(long androidID, boolean includeDeleted) throws NullCursorException {
-    final String where = BrowserContract.Bookmarks.PARENT + " = ? AND " +
-                         BrowserContract.SyncColumns.GUID + " <> ? " +
-                         (!includeDeleted ? ("AND " + BrowserContract.SyncColumns.IS_DELETED + " = 0") : "");
-
-    final String[] args = new String[] { String.valueOf(androidID), "places" };
-
-    // Order by position, falling back on creation date and ID.
-    final String order = BrowserContract.Bookmarks.POSITION + ", " +
-                         BrowserContract.SyncColumns.DATE_CREATED + ", " +
-                         BrowserContract.Bookmarks._ID;
-    return queryHelper.safeQuery(".getChildren", getAllColumns(), where, args, order);
-  }
-
-  
-  @Override
-  protected String[] getAllColumns() {
-    return BrowserContractHelpers.BookmarkColumns;
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/AndroidBrowserBookmarksRepository.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/AndroidBrowserBookmarksRepository.java
deleted file mode 100644
index 38520fd..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/AndroidBrowserBookmarksRepository.java
+++ /dev/null
@@ -1,25 +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.sync.repositories.android;
-
-import org.mozilla.gecko.sync.repositories.BookmarksRepository;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionCreationDelegate;
-
-import android.content.Context;
-
-public class AndroidBrowserBookmarksRepository extends AndroidBrowserRepository implements BookmarksRepository {
-
-  @Override
-  protected void sessionCreator(RepositorySessionCreationDelegate delegate, Context context) {
-    AndroidBrowserBookmarksRepositorySession session = new AndroidBrowserBookmarksRepositorySession(AndroidBrowserBookmarksRepository.this, context);
-    final RepositorySessionCreationDelegate deferredCreationDelegate = delegate.deferredCreationDelegate();
-    deferredCreationDelegate.onSessionCreated(session);
-  }
-
-  @Override
-  protected AndroidBrowserRepositoryDataAccessor getDataAccessor(Context context) {
-    return new AndroidBrowserBookmarksDataAccessor(context);
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/AndroidBrowserBookmarksRepositorySession.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/AndroidBrowserBookmarksRepositorySession.java
deleted file mode 100644
index fb79901..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/AndroidBrowserBookmarksRepositorySession.java
+++ /dev/null
@@ -1,1107 +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.sync.repositories.android;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.TreeMap;
-
-import org.json.simple.JSONArray;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.db.BrowserContract;
-import org.mozilla.gecko.sync.Utils;
-import org.mozilla.gecko.sync.repositories.InactiveSessionException;
-import org.mozilla.gecko.sync.repositories.InvalidSessionTransitionException;
-import org.mozilla.gecko.sync.repositories.NoGuidForIdException;
-import org.mozilla.gecko.sync.repositories.NullCursorException;
-import org.mozilla.gecko.sync.repositories.ParentNotFoundException;
-import org.mozilla.gecko.sync.repositories.Repository;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionBeginDelegate;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionFinishDelegate;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionStoreDelegate;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionWipeDelegate;
-import org.mozilla.gecko.sync.repositories.domain.BookmarkRecord;
-import org.mozilla.gecko.sync.repositories.domain.Record;
-
-import android.content.ContentUris;
-import android.content.Context;
-import android.database.Cursor;
-import android.net.Uri;
-
-public class AndroidBrowserBookmarksRepositorySession extends AndroidBrowserRepositorySession
-  implements BookmarksInsertionManager.BookmarkInserter {
-
-  public static final int DEFAULT_DELETION_FLUSH_THRESHOLD = 50;
-  public static final int DEFAULT_INSERTION_FLUSH_THRESHOLD = 50;
-
-  // TODO: synchronization for these.
-  private final HashMap<String, Long> parentGuidToIDMap = new HashMap<String, Long>();
-  private final HashMap<Long, String> parentIDToGuidMap = new HashMap<Long, String>();
-
-  /**
-   * Some notes on reparenting/reordering.
-   *
-   * Fennec stores new items with a high-negative position, because it doesn't care.
-   * On the other hand, it also doesn't give us any help managing positions.
-   *
-   * We can process records and folders in any order, though we'll usually see folders
-   * first because their sortindex is larger.
-   *
-   * We can also see folders that refer to children we haven't seen, and children we
-   * won't see (perhaps due to a TTL, perhaps due to a limit on our fetch).
-   *
-   * And of course folders can refer to local children (including ones that might
-   * be reconciled into oblivion!), or local children in other folders. And the local
-   * version of a folder -- which might be a reconciling target, or might not -- can
-   * have local additions or removals. (That causes complications with on-the-fly
-   * reordering: we don't know in advance which records will even exist by the end
-   * of the sync.)
-   *
-   * We opt to leave records in a reasonable state as we go, applying reordering/
-   * reparenting operations whenever possible. A final sequence is applied after all
-   * incoming records have been handled.
-   *
-   * As such, we need to track a bunch of stuff as we go:
-   *
-   * • For each downloaded folder, the array of children. These will be server GUIDs,
-   *   but not necessarily identical to the remote list: if we download a record and
-   *   it's been locally moved, it must be removed from this child array.
-   *
-   *   This mapping can be discarded when final reordering has occurred, either on
-   *   store completion or when every child has been seen within this session.
-   *
-   * • A list of orphans: records whose parent folder does not yet exist. This can be
-   *   trimmed as orphans are reparented.
-   *
-   * • Mappings from folder GUIDs to folder IDs, so that we can parent items without
-   *   having to look in the DB. Of course, this must be kept up-to-date as we
-   *   reconcile.
-   *
-   * Reordering also needs to occur during fetch. That is, a folder might have been
-   * created locally, or modified locally without any remote changes. An order must
-   * be generated for the folder's children array, and it must be persisted into the
-   * database to act as a starting point for future changes. But of course we don't
-   * want to incur a database write if the children already have a satisfactory order.
-   *
-   * Do we also need a list of "adopters", parents that are still waiting for children?
-   * As items get picked out of the orphans list, we can do on-the-fly ordering, until
-   * we're left with lonely records at the end.
-   *
-   * As we modify local folders, perhaps by moving children out of their purview, we
-   * must bump their modification time so as to cause them to be uploaded on the next
-   * stage of syncing. The same applies to simple reordering.
-   */
-
-  // TODO: can we guarantee serial access to these?
-  private final HashMap<String, ArrayList<String>> missingParentToChildren = new HashMap<String, ArrayList<String>>();
-  private final HashMap<String, JSONArray>         parentToChildArray      = new HashMap<String, JSONArray>();
-  private int needsReparenting = 0;
-
-  private final AndroidBrowserBookmarksDataAccessor dataAccessor;
-
-  protected BookmarksDeletionManager deletionManager;
-  protected BookmarksInsertionManager insertionManager;
-
-  /**
-   * An array of known-special GUIDs.
-   */
-  public static final String[] SPECIAL_GUIDS = new String[] {
-    // Mobile and desktop places roots have to come first.
-    "places",
-    "mobile",
-    "toolbar",
-    "menu",
-    "unfiled"
-  };
-
-  /**
-   * = A note about folder mapping =
-   *
-   * Note that _none_ of Places's folders actually have a special GUID. They're all
-   * randomly generated. Special folders are indicated by membership in the
-   * moz_bookmarks_roots table, and by having the parent `1`.
-   *
-   * Additionally, the mobile root is annotated. In Firefox Sync, PlacesUtils is
-   * used to find the IDs of these special folders.
-   *
-   * We need to consume records with these various GUIDs, producing a local
-   * representation which we are able to stably map upstream.
-   *
-   * Android Sync skips over the contents of some special GUIDs -- `places`, `tags`,
-   * etc. -- when finding IDs.
-   * Some of these special GUIDs are part of desktop structure (places, tags). Some
-   * are part of Fennec's custom data (readinglist, pinned).
-   *
-   * We don't want to upload or apply these records.
-   *
-   * That is:
-   *
-   * * We should not upload a `places`,`tags`, `readinglist`, or `pinned` record.
-   * * We can stably _store_ menu/toolbar/unfiled/mobile as special GUIDs, and set
-     * their parent ID as appropriate on upload.
-   *
-   * Fortunately, Fennec stores our representation of the data, not Places: that is,
-   * there's a "places" root, containing "mobile", "menu", "toolbar", etc.
-   *
-   * These are guaranteed to exist when the database is created.
-   *
-   * = Places folders =
-   *
-   * guid        root_name   folder_id   parent
-   * ----------  ----------  ----------  ----------
-   * ?           places      1           0
-   * ?           menu        2           1
-   * ?           toolbar     3           1
-   * ?           tags        4           1
-   * ?           unfiled     5           1
-   *
-   * ?           mobile*     474         1
-   *
-   *
-   * = Fennec folders =
-   *
-   * guid        folder_id   parent
-   * ----------  ----------  ----------
-   * places      0           0
-   * mobile      1           0
-   * menu        2           0
-   * etc.
-   *
-  */
-  public static final Map<String, String> SPECIAL_GUID_PARENTS;
-  static {
-    HashMap<String, String> m = new HashMap<String, String>();
-    m.put("places",  null);
-    m.put("menu",    "places");
-    m.put("toolbar", "places");
-    m.put("tags",    "places");
-    m.put("unfiled", "places");
-    m.put("mobile",  "places");
-    SPECIAL_GUID_PARENTS = Collections.unmodifiableMap(m);
-  }
-
-
-  /**
-   * A map of guids to their localized name strings.
-   */
-  // Oh, if only we could make this final and initialize it in the static initializer.
-  public static Map<String, String> SPECIAL_GUIDS_MAP;
-
-  /**
-   * Return true if the provided record GUID should be skipped
-   * in child lists or fetch results.
-   *
-   * @param recordGUID the GUID of the record to check.
-   * @return true if the record should be skipped.
-   */
-  public static boolean forbiddenGUID(final String recordGUID) {
-    return recordGUID == null ||
-           BrowserContract.Bookmarks.PINNED_FOLDER_GUID.equals(recordGUID) ||
-           BrowserContract.Bookmarks.PLACES_FOLDER_GUID.equals(recordGUID) ||
-           BrowserContract.Bookmarks.TAGS_FOLDER_GUID.equals(recordGUID);
-  }
-
-  /**
-   * Return true if the provided parent GUID's children should
-   * be skipped in child lists or fetch results.
-   * This differs from {@link #forbiddenGUID(String)} in that we're skipping
-   * part of the hierarchy.
-   *
-   * @param parentGUID the GUID of parent of the record to check.
-   * @return true if the record should be skipped.
-   */
-  public static boolean forbiddenParent(final String parentGUID) {
-    return parentGUID == null ||
-           BrowserContract.Bookmarks.PINNED_FOLDER_GUID.equals(parentGUID);
-  }
-
-  public AndroidBrowserBookmarksRepositorySession(Repository repository, Context context) {
-    super(repository);
-
-    if (SPECIAL_GUIDS_MAP == null) {
-      HashMap<String, String> m = new HashMap<String, String>();
-
-      // Note that we always use the literal name "mobile" for the Mobile Bookmarks
-      // folder, regardless of its actual name in the database or the Fennec UI.
-      // This is to match desktop (working around Bug 747699) and to avoid a similar
-      // issue locally. See Bug 748898.
-      m.put("mobile",  "mobile");
-
-      // Other folders use their contextualized names, and we simply rely on
-      // these not changing, matching desktop, and such to avoid issues.
-      m.put("menu",    context.getString(R.string.bookmarks_folder_menu));
-      m.put("places",  context.getString(R.string.bookmarks_folder_places));
-      m.put("toolbar", context.getString(R.string.bookmarks_folder_toolbar));
-      m.put("unfiled", context.getString(R.string.bookmarks_folder_unfiled));
-
-      SPECIAL_GUIDS_MAP = Collections.unmodifiableMap(m);
-    }
-
-    dbHelper = new AndroidBrowserBookmarksDataAccessor(context);
-    dataAccessor = (AndroidBrowserBookmarksDataAccessor) dbHelper;
-  }
-
-  private static int getTypeFromCursor(Cursor cur) {
-    return RepoUtils.getIntFromCursor(cur, BrowserContract.Bookmarks.TYPE);
-  }
-
-  private static boolean rowIsFolder(Cursor cur) {
-    return getTypeFromCursor(cur) == BrowserContract.Bookmarks.TYPE_FOLDER;
-  }
-
-  private String getGUIDForID(long androidID) {
-    String guid = parentIDToGuidMap.get(androidID);
-    trace("  " + androidID + " => " + guid);
-    return guid;
-  }
-
-  private long getIDForGUID(String guid) {
-    Long id = parentGuidToIDMap.get(guid);
-    if (id == null) {
-      Logger.warn(LOG_TAG, "Couldn't find local ID for GUID " + guid);
-      return -1;
-    }
-    return id;
-  }
-
-  private String getGUID(Cursor cur) {
-    return RepoUtils.getStringFromCursor(cur, "guid");
-  }
-
-  private long getParentID(Cursor cur) {
-    return RepoUtils.getLongFromCursor(cur, BrowserContract.Bookmarks.PARENT);
-  }
-
-  // More efficient for bulk operations.
-  private long getPosition(Cursor cur, int positionIndex) {
-    return cur.getLong(positionIndex);
-  }
-  private long getPosition(Cursor cur) {
-    return RepoUtils.getLongFromCursor(cur, BrowserContract.Bookmarks.POSITION);
-  }
-
-  private String getParentName(String parentGUID) throws ParentNotFoundException, NullCursorException {
-    if (parentGUID == null) {
-      return "";
-    }
-    if (SPECIAL_GUIDS_MAP.containsKey(parentGUID)) {
-      return SPECIAL_GUIDS_MAP.get(parentGUID);
-    }
-
-    // Get parent name from database.
-    String parentName = "";
-    Cursor name = dataAccessor.fetch(new String[] { parentGUID });
-    try {
-      name.moveToFirst();
-      if (!name.isAfterLast()) {
-        parentName = RepoUtils.getStringFromCursor(name, BrowserContract.Bookmarks.TITLE);
-      }
-      else {
-        Logger.error(LOG_TAG, "Couldn't find record with guid '" + parentGUID + "' when looking for parent name.");
-        throw new ParentNotFoundException(null);
-      }
-    } finally {
-      name.close();
-    }
-    return parentName;
-  }
-
-  /**
-   * Retrieve the child array for a record, repositioning and updating the database as necessary.
-   *
-   * @param folderID
-   *        The database ID of the folder.
-   * @param persist
-   *        True if generated positions should be written to the database. The modified
-   *        time of the parent folder is only bumped if this is true.
-   * @param childArray
-   *        A new, empty JSONArray which will be populated with an array of GUIDs.
-   * @return
-   *        True if the resulting array is "clean" (i.e., reflects the content of the database).
-   * @throws NullCursorException
-   */
-  @SuppressWarnings("unchecked")
-  private boolean getChildrenArray(long folderID, boolean persist, JSONArray childArray) throws NullCursorException {
-    trace("Calling getChildren for androidID " + folderID);
-    Cursor children = dataAccessor.getChildren(folderID);
-    try {
-      if (!children.moveToFirst()) {
-        trace("No children: empty cursor.");
-        return true;
-      }
-      final int positionIndex = children.getColumnIndex(BrowserContract.Bookmarks.POSITION);
-      final int count = children.getCount();
-      Logger.debug(LOG_TAG, "Expecting " + count + " children.");
-
-      // Sorted by requested position.
-      TreeMap<Long, ArrayList<String>> guids = new TreeMap<Long, ArrayList<String>>();
-
-      while (!children.isAfterLast()) {
-        final String childGuid   = getGUID(children);
-        final long childPosition = getPosition(children, positionIndex);
-        trace("  Child GUID: " + childGuid);
-        trace("  Child position: " + childPosition);
-        Utils.addToIndexBucketMap(guids, Math.abs(childPosition), childGuid);
-        children.moveToNext();
-      }
-
-      // This will suffice for taking a jumble of records and indices and
-      // producing a sorted sequence that preserves some kind of order --
-      // from the abs of the position, falling back on cursor order (that
-      // is, creation time and ID).
-      // Note that this code is not intended to merge values from two sources!
-      boolean changed = false;
-      int i = 0;
-      for (Entry<Long, ArrayList<String>> entry : guids.entrySet()) {
-        long pos = entry.getKey();
-        int atPos = entry.getValue().size();
-
-        // If every element has a different index, and the indices are
-        // in strict natural order, then changed will be false.
-        if (atPos > 1 || pos != i) {
-          changed = true;
-        }
-
-        ++i;
-
-        for (String guid : entry.getValue()) {
-          if (!forbiddenGUID(guid)) {
-            childArray.add(guid);
-          }
-        }
-      }
-
-      if (Logger.shouldLogVerbose(LOG_TAG)) {
-        // Don't JSON-encode unless we're logging.
-        Logger.trace(LOG_TAG, "Output child array: " + childArray.toJSONString());
-      }
-
-      if (!changed) {
-        Logger.debug(LOG_TAG, "Nothing moved! Database reflects child array.");
-        return true;
-      }
-
-      if (!persist) {
-        Logger.debug(LOG_TAG, "Returned array does not match database, and not persisting.");
-        return false;
-      }
-
-      Logger.debug(LOG_TAG, "Generating child array required moving records. Updating DB.");
-      final long time = now();
-      if (0 < dataAccessor.updatePositions(childArray)) {
-        Logger.debug(LOG_TAG, "Bumping parent time to " + time + ".");
-        dataAccessor.bumpModified(folderID, time);
-      }
-      return true;
-    } finally {
-      children.close();
-    }
-  }
-
-  protected static boolean isDeleted(Cursor cur) {
-    return RepoUtils.getLongFromCursor(cur, BrowserContract.SyncColumns.IS_DELETED) != 0;
-  }
-
-  @Override
-  protected Record retrieveDuringStore(Cursor cur) throws NoGuidForIdException, NullCursorException, ParentNotFoundException {
-    // During storing of a retrieved record, we never care about the children
-    // array that's already present in the database -- we don't use it for
-    // reconciling. Skip all that effort for now.
-    return retrieveRecord(cur, false);
-  }
-
-  @Override
-  protected Record retrieveDuringFetch(Cursor cur) throws NoGuidForIdException, NullCursorException, ParentNotFoundException {
-    return retrieveRecord(cur, true);
-  }
-
-  /**
-   * Build a record from a cursor, with a flag to dictate whether the
-   * children array should be computed and written back into the database.
-   */
-  protected BookmarkRecord retrieveRecord(Cursor cur, boolean computeAndPersistChildren) throws NoGuidForIdException, NullCursorException, ParentNotFoundException {
-    String recordGUID = getGUID(cur);
-    Logger.trace(LOG_TAG, "Record from mirror cursor: " + recordGUID);
-
-    if (forbiddenGUID(recordGUID)) {
-      Logger.debug(LOG_TAG, "Ignoring " + recordGUID + " record in recordFromMirrorCursor.");
-      return null;
-    }
-
-    // Short-cut for deleted items.
-    if (isDeleted(cur)) {
-      return AndroidBrowserBookmarksRepositorySession.bookmarkFromMirrorCursor(cur, null, null, null);
-    }
-
-    long androidParentID = getParentID(cur);
-
-    // Ensure special folders stay in the right place.
-    String androidParentGUID = SPECIAL_GUID_PARENTS.get(recordGUID);
-    if (androidParentGUID == null) {
-      androidParentGUID = getGUIDForID(androidParentID);
-    }
-
-    boolean needsReparenting = false;
-
-    if (androidParentGUID == null) {
-      Logger.debug(LOG_TAG, "No parent GUID for record " + recordGUID + " with parent " + androidParentID);
-      // If the parent has been stored and somehow has a null GUID, throw an error.
-      if (parentIDToGuidMap.containsKey(androidParentID)) {
-        Logger.error(LOG_TAG, "Have the parent android ID for the record but the parent's GUID wasn't found.");
-        throw new NoGuidForIdException(null);
-      }
-
-      // We have a parent ID but it's wrong. If the record is deleted,
-      // we'll just say that it was in the Unsorted Bookmarks folder.
-      // If not, we'll move it into Mobile Bookmarks.
-      needsReparenting = true;
-    }
-
-    // If record is a folder, and we want to see children at this time, then build out the children array.
-    final JSONArray childArray;
-    if (computeAndPersistChildren) {
-      childArray = getChildrenArrayForRecordCursor(cur, recordGUID, true);
-    } else {
-      childArray = null;
-    }
-    String parentName = getParentName(androidParentGUID);
-    BookmarkRecord bookmark = AndroidBrowserBookmarksRepositorySession.bookmarkFromMirrorCursor(cur, androidParentGUID, parentName, childArray);
-
-    if (bookmark == null) {
-      Logger.warn(LOG_TAG, "Unable to extract bookmark from cursor. Record GUID " + recordGUID +
-                           ", parent " + androidParentGUID + "/" + androidParentID);
-      return null;
-    }
-
-    if (needsReparenting) {
-      Logger.warn(LOG_TAG, "Bookmark record " + recordGUID + " has a bad parent pointer. Reparenting now.");
-
-      String destination = bookmark.deleted ? "unfiled" : "mobile";
-      bookmark.androidParentID = getIDForGUID(destination);
-      bookmark.androidPosition = getPosition(cur);
-      bookmark.parentID        = destination;
-      bookmark.parentName      = getParentName(destination);
-      if (!bookmark.deleted) {
-        // Actually move it.
-        // TODO: compute position. Persist.
-        relocateBookmark(bookmark);
-      }
-    }
-
-    return bookmark;
-  }
-
-  /**
-   * Ensure that the local database row for the provided bookmark
-   * reflects this record's parent information.
-   *
-   * @param bookmark
-   */
-  private void relocateBookmark(BookmarkRecord bookmark) {
-    dataAccessor.updateParentAndPosition(bookmark.guid, bookmark.androidParentID, bookmark.androidPosition);
-  }
-
-  protected JSONArray getChildrenArrayForRecordCursor(Cursor cur, String recordGUID, boolean persist) throws NullCursorException {
-    boolean isFolder = rowIsFolder(cur);
-    if (!isFolder) {
-      return null;
-    }
-
-    long androidID = parentGuidToIDMap.get(recordGUID);
-    JSONArray childArray = new JSONArray();
-    getChildrenArray(androidID, persist, childArray);
-
-    Logger.debug(LOG_TAG, "Fetched " + childArray.size() + " children for " + recordGUID);
-    return childArray;
-  }
-
-  @Override
-  public boolean shouldIgnore(Record record) {
-    if (!(record instanceof BookmarkRecord)) {
-      return true;
-    }
-    if (record.deleted) {
-      return false;
-    }
-
-    BookmarkRecord bmk = (BookmarkRecord) record;
-
-    if (forbiddenGUID(bmk.guid)) {
-      Logger.debug(LOG_TAG, "Ignoring forbidden record with guid: " + bmk.guid);
-      return true;
-    }
-
-    if (forbiddenParent(bmk.parentID)) {
-      Logger.debug(LOG_TAG,  "Ignoring child " + bmk.guid + " of forbidden parent folder " + bmk.parentID);
-      return true;
-    }
-
-    if (BrowserContractHelpers.isSupportedType(bmk.type)) {
-      return false;
-    }
-
-    Logger.debug(LOG_TAG, "Ignoring record with guid: " + bmk.guid + " and type: " + bmk.type);
-    return true;
-  }
-
-  @Override
-  public void begin(RepositorySessionBeginDelegate delegate) throws InvalidSessionTransitionException {
-    // Check for the existence of special folders
-    // and insert them if they don't exist.
-    Cursor cur;
-    try {
-      Logger.debug(LOG_TAG, "Check and build special GUIDs.");
-      dataAccessor.checkAndBuildSpecialGuids();
-      cur = dataAccessor.getGuidsIDsForFolders();
-      Logger.debug(LOG_TAG, "Got GUIDs for folders.");
-    } catch (android.database.sqlite.SQLiteConstraintException e) {
-      Logger.error(LOG_TAG, "Got sqlite constraint exception working with Fennec bookmark DB.", e);
-      delegate.onBeginFailed(e);
-      return;
-    } catch (Exception e) {
-      delegate.onBeginFailed(e);
-      return;
-    }
-
-    // To deal with parent mapping of bookmarks we have to do some
-    // hairy stuff. Here's the setup for it.
-
-    Logger.debug(LOG_TAG, "Preparing folder ID mappings.");
-
-    // Fake our root.
-    Logger.debug(LOG_TAG, "Tracking places root as ID 0.");
-    parentIDToGuidMap.put(0L, "places");
-    parentGuidToIDMap.put("places", 0L);
-    try {
-      cur.moveToFirst();
-      while (!cur.isAfterLast()) {
-        String guid = getGUID(cur);
-        long id = RepoUtils.getLongFromCursor(cur, BrowserContract.Bookmarks._ID);
-        parentGuidToIDMap.put(guid, id);
-        parentIDToGuidMap.put(id, guid);
-        Logger.debug(LOG_TAG, "GUID " + guid + " maps to " + id);
-        cur.moveToNext();
-      }
-    } finally {
-      cur.close();
-    }
-    deletionManager = new BookmarksDeletionManager(dataAccessor, DEFAULT_DELETION_FLUSH_THRESHOLD);
-
-    // We just crawled the database enumerating all folders; we'll start the
-    // insertion manager with exactly these folders as the known parents (the
-    // collection is copied) in the manager constructor.
-    insertionManager = new BookmarksInsertionManager(DEFAULT_INSERTION_FLUSH_THRESHOLD, parentGuidToIDMap.keySet(), this);
-
-    Logger.debug(LOG_TAG, "Done with initial setup of bookmarks session.");
-    super.begin(delegate);
-  }
-
-  /**
-   * Implement method of BookmarksInsertionManager.BookmarkInserter.
-   */
-  @Override
-  public boolean insertFolder(BookmarkRecord record) {
-    // A folder that is *not* deleted needs its androidID updated, so that
-    // updateBookkeeping can re-parent, etc.
-    Record toStore = prepareRecord(record);
-    try {
-      Uri recordURI = dbHelper.insert(toStore);
-      if (recordURI == null) {
-        delegate.onRecordStoreFailed(new RuntimeException("Got null URI inserting folder with guid " + toStore.guid + "."), record.guid);
-        return false;
-      }
-      toStore.androidID = ContentUris.parseId(recordURI);
-      Logger.debug(LOG_TAG, "Inserted folder with guid " + toStore.guid + " as androidID " + toStore.androidID);
-
-      updateBookkeeping(toStore);
-    } catch (Exception e) {
-      delegate.onRecordStoreFailed(e, record.guid);
-      return false;
-    }
-    trackRecord(toStore);
-    delegate.onRecordStoreSucceeded(record.guid);
-    return true;
-  }
-
-  /**
-   * Implement method of BookmarksInsertionManager.BookmarkInserter.
-   */
-  @Override
-  public void bulkInsertNonFolders(Collection<BookmarkRecord> records) {
-    // All of these records are *not* deleted and *not* folders, so we don't
-    // need to update androidID at all!
-    // TODO: persist records that fail to insert for later retry.
-    ArrayList<Record> toStores = new ArrayList<Record>(records.size());
-    for (Record record : records) {
-      toStores.add(prepareRecord(record));
-    }
-
-    try {
-      int stored = dataAccessor.bulkInsert(toStores);
-      if (stored != toStores.size()) {
-        // Something failed; most pessimistic action is to declare that all insertions failed.
-        // TODO: perform the bulkInsert in a transaction and rollback unless all insertions succeed?
-        for (Record failed : toStores) {
-          delegate.onRecordStoreFailed(new RuntimeException("Possibly failed to bulkInsert non-folder with guid " + failed.guid + "."), failed.guid);
-        }
-        return;
-      }
-    } catch (NullCursorException e) {
-      for (Record failed : toStores) {
-        delegate.onRecordStoreFailed(e, failed.guid);
-      }
-      return;
-    }
-
-    // Success For All!
-    for (Record succeeded : toStores) {
-      try {
-        updateBookkeeping(succeeded);
-      } catch (Exception e) {
-        Logger.warn(LOG_TAG, "Got exception updating bookkeeping of non-folder with guid " + succeeded.guid + ".", e);
-      }
-      trackRecord(succeeded);
-      delegate.onRecordStoreSucceeded(succeeded.guid);
-    }
-  }
-
-  @Override
-  public void finish(RepositorySessionFinishDelegate delegate) throws InactiveSessionException {
-    // Allow these to be GCed.
-    deletionManager = null;
-    insertionManager = null;
-
-    // Override finish to do this check; make sure all records
-    // needing re-parenting have been re-parented.
-    if (needsReparenting != 0) {
-      Logger.error(LOG_TAG, "Finish called but " + needsReparenting +
-                            " bookmark(s) have been placed in unsorted bookmarks and not been reparented.");
-
-      // TODO: handling of failed reparenting.
-      // E.g., delegate.onFinishFailed(new BookmarkNeedsReparentingException(null));
-    }
-    super.finish(delegate);
-  };
-
-  @Override
-  public void setStoreDelegate(RepositorySessionStoreDelegate delegate) {
-    super.setStoreDelegate(delegate);
-
-    if (deletionManager != null) {
-      deletionManager.setDelegate(delegate);
-    }
-  }
-
-  @Override
-  protected Record reconcileRecords(Record remoteRecord, Record localRecord,
-                                    long lastRemoteRetrieval,
-                                    long lastLocalRetrieval) {
-
-    BookmarkRecord reconciled = (BookmarkRecord) super.reconcileRecords(remoteRecord, localRecord,
-                                                                        lastRemoteRetrieval,
-                                                                        lastLocalRetrieval);
-
-    // For now we *always* use the remote record's children array as a starting point.
-    // We won't write it into the database yet; we'll record it and process as we go.
-    reconciled.children = ((BookmarkRecord) remoteRecord).children;
-
-    // *Always* track folders, though: if we decide we need to reposition items, we'll
-    // untrack later.
-    if (reconciled.isFolder()) {
-      trackRecord(reconciled);
-    }
-    return reconciled;
-  }
-
-  /**
-   * Rename mobile folders to "mobile", both in and out. The other half of
-   * this logic lives in {@link #computeParentFields(BookmarkRecord, String, String)}, where
-   * the parent name of a record is set from {@link #SPECIAL_GUIDS_MAP} rather than
-   * from source data.
-   *
-   * Apply this approach generally for symmetry.
-   */
-  @Override
-  protected void fixupRecord(Record record) {
-    final BookmarkRecord r = (BookmarkRecord) record;
-    final String parentName = SPECIAL_GUIDS_MAP.get(r.parentID);
-    if (parentName == null) {
-      return;
-    }
-    if (Logger.shouldLogVerbose(LOG_TAG)) {
-      Logger.trace(LOG_TAG, "Replacing parent name \"" + r.parentName + "\" with \"" + parentName + "\".");
-    }
-    r.parentName = parentName;
-  }
-
-  @Override
-  protected Record prepareRecord(Record record) {
-    if (record.deleted) {
-      Logger.debug(LOG_TAG, "No need to prepare deleted record " + record.guid);
-      return record;
-    }
-
-    BookmarkRecord bmk = (BookmarkRecord) record;
-
-    if (!isSpecialRecord(record)) {
-      // We never want to reparent special records.
-      handleParenting(bmk);
-    }
-
-    if (Logger.LOG_PERSONAL_INFORMATION) {
-      if (bmk.isFolder()) {
-        Logger.pii(LOG_TAG, "Inserting folder " + bmk.guid + ", " + bmk.title +
-                            " with parent " + bmk.androidParentID +
-                            " (" + bmk.parentID + ", " + bmk.parentName +
-                            ", " + bmk.androidPosition + ")");
-      } else {
-        Logger.pii(LOG_TAG, "Inserting bookmark " + bmk.guid + ", " + bmk.title + ", " +
-                            bmk.bookmarkURI + " with parent " + bmk.androidParentID +
-                            " (" + bmk.parentID + ", " + bmk.parentName +
-                            ", " + bmk.androidPosition + ")");
-      }
-    } else {
-      if (bmk.isFolder()) {
-        Logger.debug(LOG_TAG, "Inserting folder " + bmk.guid +  ", parent " +
-                              bmk.androidParentID +
-                              " (" + bmk.parentID + ", " + bmk.androidPosition + ")");
-      } else {
-        Logger.debug(LOG_TAG, "Inserting bookmark " + bmk.guid + " with parent " +
-                              bmk.androidParentID +
-                              " (" + bmk.parentID + ", " + ", " + bmk.androidPosition + ")");
-      }
-    }
-    return bmk;
-  }
-
-  /**
-   * If the provided record doesn't have correct parent information,
-   * update appropriate bookkeeping to improve the situation.
-   *
-   * @param bmk
-   */
-  private void handleParenting(BookmarkRecord bmk) {
-    if (parentGuidToIDMap.containsKey(bmk.parentID)) {
-      bmk.androidParentID = parentGuidToIDMap.get(bmk.parentID);
-
-      // Might as well set a basic position from the downloaded children array.
-      JSONArray children = parentToChildArray.get(bmk.parentID);
-      if (children != null) {
-        int index = children.indexOf(bmk.guid);
-        if (index >= 0) {
-          bmk.androidPosition = index;
-        }
-      }
-    }
-    else {
-      bmk.androidParentID = parentGuidToIDMap.get("unfiled");
-      ArrayList<String> children;
-      if (missingParentToChildren.containsKey(bmk.parentID)) {
-        children = missingParentToChildren.get(bmk.parentID);
-      } else {
-        children = new ArrayList<String>();
-      }
-      children.add(bmk.guid);
-      needsReparenting++;
-      missingParentToChildren.put(bmk.parentID, children);
-    }
-  }
-
-  private boolean isSpecialRecord(Record record) {
-    return SPECIAL_GUID_PARENTS.containsKey(record.guid);
-  }
-
-  @Override
-  protected void updateBookkeeping(Record record) throws NoGuidForIdException,
-                                                         NullCursorException,
-                                                         ParentNotFoundException {
-    super.updateBookkeeping(record);
-    BookmarkRecord bmk = (BookmarkRecord) record;
-
-    // If record is folder, update maps and re-parent children if necessary.
-    if (!bmk.isFolder()) {
-      Logger.debug(LOG_TAG, "Not a folder. No bookkeeping.");
-      return;
-    }
-
-    Logger.debug(LOG_TAG, "Updating bookkeeping for folder " + record.guid);
-
-    // Mappings between ID and GUID.
-    // TODO: update our persisted children arrays!
-    // TODO: if our Android ID just changed, replace parents for all of our children.
-    parentGuidToIDMap.put(bmk.guid,      bmk.androidID);
-    parentIDToGuidMap.put(bmk.androidID, bmk.guid);
-
-    JSONArray childArray = bmk.children;
-
-    if (Logger.shouldLogVerbose(LOG_TAG)) {
-      Logger.trace(LOG_TAG, bmk.guid + " has children " + childArray.toJSONString());
-    }
-    parentToChildArray.put(bmk.guid, childArray);
-
-    // Re-parent.
-    if (missingParentToChildren.containsKey(bmk.guid)) {
-      for (String child : missingParentToChildren.get(bmk.guid)) {
-        // This might return -1; that's OK, the bookmark will
-        // be properly repositioned later.
-        long position = childArray.indexOf(child);
-        dataAccessor.updateParentAndPosition(child, bmk.androidID, position);
-        needsReparenting--;
-      }
-      missingParentToChildren.remove(bmk.guid);
-    }
-  }
-
-  @Override
-  protected void insert(Record record) throws NoGuidForIdException, NullCursorException, ParentNotFoundException {
-    try {
-      insertionManager.enqueueRecord((BookmarkRecord) record);
-    } catch (Exception e) {
-      throw new NullCursorException(e);
-    }
-  }
-
-  @Override
-  protected void storeRecordDeletion(final Record record, final Record existingRecord) {
-    if (SPECIAL_GUIDS_MAP.containsKey(record.guid)) {
-      Logger.debug(LOG_TAG, "Told to delete record " + record.guid + ". Ignoring.");
-      return;
-    }
-    final BookmarkRecord bookmarkRecord = (BookmarkRecord) record;
-    final BookmarkRecord existingBookmark = (BookmarkRecord) existingRecord;
-    final boolean isFolder = existingBookmark.isFolder();
-    final String parentGUID = existingBookmark.parentID;
-    deletionManager.deleteRecord(bookmarkRecord.guid, isFolder, parentGUID);
-  }
-
-  protected void flushQueues() {
-    long now = now();
-    Logger.debug(LOG_TAG, "Applying remaining insertions.");
-    try {
-      insertionManager.finishUp();
-      Logger.debug(LOG_TAG, "Done applying remaining insertions.");
-    } catch (Exception e) {
-      Logger.warn(LOG_TAG, "Unable to apply remaining insertions.", e);
-    }
-
-    Logger.debug(LOG_TAG, "Applying deletions.");
-    try {
-      untrackGUIDs(deletionManager.flushAll(getIDForGUID("unfiled"), now));
-      Logger.debug(LOG_TAG, "Done applying deletions.");
-    } catch (Exception e) {
-      Logger.error(LOG_TAG, "Unable to apply deletions.", e);
-    }
-  }
-
-  @SuppressWarnings("unchecked")
-  private void finishUp() {
-    try {
-      flushQueues();
-      Logger.debug(LOG_TAG, "Have " + parentToChildArray.size() + " folders whose children might need repositioning.");
-      for (Entry<String, JSONArray> entry : parentToChildArray.entrySet()) {
-        String guid = entry.getKey();
-        JSONArray onServer = entry.getValue();
-        try {
-          final long folderID = getIDForGUID(guid);
-          final JSONArray inDB = new JSONArray();
-          final boolean clean = getChildrenArray(folderID, false, inDB);
-          final boolean sameArrays = Utils.sameArrays(onServer, inDB);
-
-          // If the local children and the remote children are already
-          // the same, then we don't need to bump the modified time of the
-          // parent: we wouldn't upload a different record, so avoid the cycle.
-          if (!sameArrays) {
-            int added = 0;
-            for (Object o : inDB) {
-              if (!onServer.contains(o)) {
-                onServer.add(o);
-                added++;
-              }
-            }
-            Logger.debug(LOG_TAG, "Added " + added + " items locally.");
-            Logger.debug(LOG_TAG, "Untracking and bumping " + guid + "(" + folderID + ")");
-            dataAccessor.bumpModified(folderID, now());
-            untrackGUID(guid);
-          }
-
-          // If the arrays are different, or they're the same but not flushed to disk,
-          // write them out now.
-          if (!sameArrays || !clean) {
-            dataAccessor.updatePositions(new ArrayList<String>(onServer));
-          }
-        } catch (Exception e) {
-          Logger.warn(LOG_TAG, "Error repositioning children for " + guid, e);
-        }
-      }
-    } finally {
-      super.storeDone();
-    }
-  }
-
-  /**
-   * Hook into the deletion manager on wipe.
-   */
-  class BookmarkWipeRunnable extends WipeRunnable {
-    public BookmarkWipeRunnable(RepositorySessionWipeDelegate delegate) {
-      super(delegate);
-    }
-
-    @Override
-    public void run() {
-      try {
-        // Clear our queued deletions.
-        deletionManager.clear();
-        insertionManager.clear();
-        super.run();
-      } catch (Exception ex) {
-        delegate.onWipeFailed(ex);
-        return;
-      }
-    }
-  }
-
-  @Override
-  protected WipeRunnable getWipeRunnable(RepositorySessionWipeDelegate delegate) {
-    return new BookmarkWipeRunnable(delegate);
-  }
-
-  @Override
-  public void storeDone() {
-    Runnable command = new Runnable() {
-      @Override
-      public void run() {
-        finishUp();
-      }
-    };
-    storeWorkQueue.execute(command);
-  }
-
-  @Override
-  protected String buildRecordString(Record record) {
-    BookmarkRecord bmk = (BookmarkRecord) record;
-    String parent = bmk.parentName + "/";
-    if (bmk.isBookmark()) {
-      return "b" + parent + bmk.bookmarkURI + ":" + bmk.title;
-    }
-    if (bmk.isFolder()) {
-      return "f" + parent + bmk.title;
-    }
-    if (bmk.isSeparator()) {
-      return "s" + parent + bmk.androidPosition;
-    }
-    if (bmk.isQuery()) {
-      return "q" + parent + bmk.bookmarkURI;
-    }
-    return null;
-  }
-
-  public static BookmarkRecord computeParentFields(BookmarkRecord rec, String suggestedParentGUID, String suggestedParentName) {
-    final String guid = rec.guid;
-    if (guid == null) {
-      // Oh dear.
-      Logger.error(LOG_TAG, "No guid in computeParentFields!");
-      return null;
-    }
-
-    String realParent = SPECIAL_GUID_PARENTS.get(guid);
-    if (realParent == null) {
-      // No magic parent. Use whatever the caller suggests.
-      realParent = suggestedParentGUID;
-    } else {
-      Logger.debug(LOG_TAG, "Ignoring suggested parent ID " + suggestedParentGUID +
-                            " for " + guid + "; using " + realParent);
-    }
-
-    if (realParent == null) {
-      // Oh dear.
-      Logger.error(LOG_TAG, "No parent for record " + guid);
-      return null;
-    }
-
-    // Always set the parent name for special folders back to default.
-    String parentName = SPECIAL_GUIDS_MAP.get(realParent);
-    if (parentName == null) {
-      parentName = suggestedParentName;
-    }
-
-    rec.parentID = realParent;
-    rec.parentName = parentName;
-    return rec;
-  }
-
-  private static BookmarkRecord logBookmark(BookmarkRecord rec) {
-    try {
-      Logger.debug(LOG_TAG, "Returning " + (rec.deleted ? "deleted " : "") +
-                            "bookmark record " + rec.guid + " (" + rec.androidID +
-                           ", parent " + rec.parentID + ")");
-      if (!rec.deleted && Logger.LOG_PERSONAL_INFORMATION) {
-        Logger.pii(LOG_TAG, "> Parent name:      " + rec.parentName);
-        Logger.pii(LOG_TAG, "> Title:            " + rec.title);
-        Logger.pii(LOG_TAG, "> Type:             " + rec.type);
-        Logger.pii(LOG_TAG, "> URI:              " + rec.bookmarkURI);
-        Logger.pii(LOG_TAG, "> Position:         " + rec.androidPosition);
-        if (rec.isFolder()) {
-          Logger.pii(LOG_TAG, "FOLDER: Children are " +
-                             (rec.children == null ?
-                                 "null" :
-                                 rec.children.toJSONString()));
-        }
-      }
-    } catch (Exception e) {
-      Logger.debug(LOG_TAG, "Exception logging bookmark record " + rec, e);
-    }
-    return rec;
-  }
-
-  // Create a BookmarkRecord object from a cursor on a row containing a Fennec bookmark.
-  public static BookmarkRecord bookmarkFromMirrorCursor(Cursor cur, String parentGUID, String parentName, JSONArray children) {
-    final String collection = "bookmarks";
-    final String guid       = RepoUtils.getStringFromCursor(cur, BrowserContract.SyncColumns.GUID);
-    final long lastModified = RepoUtils.getLongFromCursor(cur,   BrowserContract.SyncColumns.DATE_MODIFIED);
-    final boolean deleted   = isDeleted(cur);
-    BookmarkRecord rec = new BookmarkRecord(guid, collection, lastModified, deleted);
-
-    // No point in populating it.
-    if (deleted) {
-      return logBookmark(rec);
-    }
-
-    int rowType = getTypeFromCursor(cur);
-    String typeString = BrowserContractHelpers.typeStringForCode(rowType);
-
-    if (typeString == null) {
-      Logger.warn(LOG_TAG, "Unsupported type code " + rowType);
-      return null;
-    }
-
-    Logger.trace(LOG_TAG, "Record " + guid + " has type " + typeString);
-
-    rec.type = typeString;
-    rec.title = RepoUtils.getStringFromCursor(cur, BrowserContract.Bookmarks.TITLE);
-    rec.bookmarkURI = RepoUtils.getStringFromCursor(cur, BrowserContract.Bookmarks.URL);
-    rec.description = RepoUtils.getStringFromCursor(cur, BrowserContract.Bookmarks.DESCRIPTION);
-    rec.tags = RepoUtils.getJSONArrayFromCursor(cur, BrowserContract.Bookmarks.TAGS);
-    rec.keyword = RepoUtils.getStringFromCursor(cur, BrowserContract.Bookmarks.KEYWORD);
-
-    rec.androidID = RepoUtils.getLongFromCursor(cur, BrowserContract.Bookmarks._ID);
-    rec.androidPosition = RepoUtils.getLongFromCursor(cur, BrowserContract.Bookmarks.POSITION);
-    rec.children = children;
-
-    // Need to restore the parentId since it isn't stored in content provider.
-    // We also take this opportunity to fix up parents for special folders,
-    // allowing us to map between the hierarchies used by Fennec and Places.
-    BookmarkRecord withParentFields = computeParentFields(rec, parentGUID, parentName);
-    if (withParentFields == null) {
-      // Oh dear. Something went wrong.
-      return null;
-    }
-    return logBookmark(withParentFields);
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/AndroidBrowserHistoryDataAccessor.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/AndroidBrowserHistoryDataAccessor.java
deleted file mode 100644
index c09d647..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/AndroidBrowserHistoryDataAccessor.java
+++ /dev/null
@@ -1,188 +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.sync.repositories.android;
-
-import java.util.ArrayList;
-
-import org.json.simple.JSONArray;
-import org.json.simple.JSONObject;
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.db.BrowserContract;
-import org.mozilla.gecko.sync.repositories.NullCursorException;
-import org.mozilla.gecko.sync.repositories.domain.HistoryRecord;
-import org.mozilla.gecko.sync.repositories.domain.Record;
-
-import android.content.ContentValues;
-import android.content.Context;
-import android.net.Uri;
-
-public class AndroidBrowserHistoryDataAccessor extends
-    AndroidBrowserRepositoryDataAccessor {
-
-  public AndroidBrowserHistoryDataAccessor(Context context) {
-    super(context);
-  }
-
-  @Override
-  protected Uri getUri() {
-    return BrowserContractHelpers.HISTORY_CONTENT_URI;
-  }
-
-  @Override
-  protected ContentValues getContentValues(Record record) {
-    ContentValues cv = new ContentValues();
-    HistoryRecord rec = (HistoryRecord) record;
-    cv.put(BrowserContract.History.GUID, rec.guid);
-    cv.put(BrowserContract.History.TITLE, rec.title);
-    cv.put(BrowserContract.History.URL, rec.histURI);
-    if (rec.visits != null) {
-      JSONArray visits = rec.visits;
-      long mostRecent = getLastVisited(visits);
-
-      // Fennec stores history timestamps in milliseconds, and visit timestamps in microseconds.
-      // The rest of Sync works in microseconds. This is the conversion point for records coming form Sync.
-      cv.put(BrowserContract.History.DATE_LAST_VISITED, mostRecent / 1000);
-      cv.put(BrowserContract.History.REMOTE_DATE_LAST_VISITED, mostRecent / 1000);
-      cv.put(BrowserContract.History.VISITS, Long.toString(visits.size()));
-    }
-    return cv;
-  }
-
-  @Override
-  protected String[] getAllColumns() {
-    return BrowserContractHelpers.HistoryColumns;
-  }
-
-  @Override
-  public Uri insert(Record record) {
-    HistoryRecord rec = (HistoryRecord) record;
-
-    Logger.debug(LOG_TAG, "Storing record " + record.guid);
-    Uri newRecordUri = super.insert(record);
-
-    Logger.debug(LOG_TAG, "Storing visits for " + record.guid);
-    context.getContentResolver().bulkInsert(
-            BrowserContract.Visits.CONTENT_URI,
-            VisitsHelper.getVisitsContentValues(rec.guid, rec.visits)
-    );
-
-    return newRecordUri;
-  }
-
-  /**
-   * Given oldGUID, first updates corresponding history record with new values (super operation),
-   * and then inserts visits from the new record.
-   * Existing visits from the old record are updated on database level to point to new GUID if necessary.
-   *
-   * @param oldGUID GUID of old <code>HistoryRecord</code>
-   * @param newRecord new <code>HistoryRecord</code> to replace old one with, and insert visits from
-   */
-  @Override
-  public void update(String oldGUID, Record newRecord) {
-    // First, update existing history records with new values. This might involve changing history GUID,
-    // and thanks to ON UPDATE CASCADE clause on Visits.HISTORY_GUID foreign key, visits will be "ported over"
-    // to the new GUID.
-    super.update(oldGUID, newRecord);
-
-    // Now we need to insert any visits from the new record
-    HistoryRecord rec = (HistoryRecord) newRecord;
-    String newGUID = newRecord.guid;
-    Logger.debug(LOG_TAG, "Storing visits for " + newGUID + ", replacing " + oldGUID);
-
-    context.getContentResolver().bulkInsert(
-            BrowserContract.Visits.CONTENT_URI,
-            VisitsHelper.getVisitsContentValues(newGUID, rec.visits)
-    );
-  }
-
-  /**
-   * Insert records.
-   * <p>
-   * This inserts all the records (using <code>ContentProvider.bulkInsert</code>),
-   * then inserts all the visit information (also using <code>ContentProvider.bulkInsert</code>).
-   *
-   * @param records
-   *          the records to insert.
-   * @return
-   *          the number of records actually inserted.
-   * @throws NullCursorException
-   */
-  public int bulkInsert(ArrayList<HistoryRecord> records) throws NullCursorException {
-    if (records.isEmpty()) {
-      Logger.debug(LOG_TAG, "No records to insert, returning.");
-    }
-
-    int size = records.size();
-    ContentValues[] cvs = new ContentValues[size];
-    int index = 0;
-    for (Record record : records) {
-      if (record.guid == null) {
-        throw new IllegalArgumentException("Record with null GUID passed in to bulkInsert.");
-      }
-      cvs[index] = getContentValues(record);
-      index += 1;
-    }
-
-    // First update the history records.
-    int inserted = context.getContentResolver().bulkInsert(getUri(), cvs);
-    if (inserted == size) {
-      Logger.debug(LOG_TAG, "Inserted " + inserted + " records, as expected.");
-    } else {
-      Logger.debug(LOG_TAG, "Inserted " +
-                   inserted + " records but expected " +
-                   size     + " records; continuing to update visits.");
-    }
-
-    final ContentValues remoteVisitAggregateValues = new ContentValues();
-    final Uri historyIncrementRemoteAggregateUri = getUri().buildUpon()
-            .appendQueryParameter(BrowserContract.PARAM_INCREMENT_REMOTE_AGGREGATES, "true")
-            .build();
-    for (Record record : records) {
-      HistoryRecord rec = (HistoryRecord) record;
-      if (rec.visits != null && rec.visits.size() != 0) {
-        int remoteVisitsInserted = context.getContentResolver().bulkInsert(
-                BrowserContract.Visits.CONTENT_URI,
-                VisitsHelper.getVisitsContentValues(rec.guid, rec.visits)
-        );
-
-        // If we just inserted any visits, update remote visit aggregate values.
-        // While inserting visits, we might not insert all of rec.visits - if we already have a local
-        // visit record with matching (guid,date), we will skip that visit.
-        // Remote visits aggregate value will be incremented by number of visits inserted.
-        // Note that we don't need to set REMOTE_DATE_LAST_VISITED, because it already gets set above.
-        if (remoteVisitsInserted > 0) {
-          // Note that REMOTE_VISITS must be set before calling cr.update(...) with a URI
-          // that has PARAM_INCREMENT_REMOTE_AGGREGATES=true.
-          remoteVisitAggregateValues.put(BrowserContract.History.REMOTE_VISITS, remoteVisitsInserted);
-          context.getContentResolver().update(
-                  historyIncrementRemoteAggregateUri,
-                  remoteVisitAggregateValues,
-                  BrowserContract.History.GUID + " = ?", new String[] {rec.guid}
-          );
-        }
-      }
-    }
-
-    return inserted;
-  }
-
-  /**
-   * Helper method used to find largest <code>VisitsHelper.SYNC_DATE_KEY</code> value in a provided JSONArray.
-   *
-   * @param visits Array of objects which will be searched.
-   * @return largest value of <code>VisitsHelper.SYNC_DATE_KEY</code>.
-     */
-  private long getLastVisited(JSONArray visits) {
-    long mostRecent = 0;
-    for (int i = 0; i < visits.size(); i++) {
-      final JSONObject visit = (JSONObject) visits.get(i);
-      long visitDate = (Long) visit.get(VisitsHelper.SYNC_DATE_KEY);
-      if (visitDate > mostRecent) {
-        mostRecent = visitDate;
-      }
-    }
-    return mostRecent;
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/AndroidBrowserHistoryRepository.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/AndroidBrowserHistoryRepository.java
deleted file mode 100644
index bd2b5d3..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/AndroidBrowserHistoryRepository.java
+++ /dev/null
@@ -1,25 +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.sync.repositories.android;
-
-import org.mozilla.gecko.sync.repositories.HistoryRepository;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionCreationDelegate;
-
-import android.content.Context;
-
-public class AndroidBrowserHistoryRepository extends AndroidBrowserRepository implements HistoryRepository {
-
-  @Override
-  protected void sessionCreator(RepositorySessionCreationDelegate delegate, Context context) {
-    AndroidBrowserHistoryRepositorySession session = new AndroidBrowserHistoryRepositorySession(AndroidBrowserHistoryRepository.this, context);
-    delegate.onSessionCreated(session);
-  }
-
-  @Override
-  protected AndroidBrowserRepositoryDataAccessor getDataAccessor(Context context) {
-    return new AndroidBrowserHistoryDataAccessor(context);
-  }
-
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/AndroidBrowserHistoryRepositorySession.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/AndroidBrowserHistoryRepositorySession.java
deleted file mode 100644
index 7c462ab..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/AndroidBrowserHistoryRepositorySession.java
+++ /dev/null
@@ -1,208 +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.sync.repositories.android;
-
-import java.util.ArrayList;
-
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.db.BrowserContract;
-import org.mozilla.gecko.sync.repositories.InvalidSessionTransitionException;
-import org.mozilla.gecko.sync.repositories.NoGuidForIdException;
-import org.mozilla.gecko.sync.repositories.NullCursorException;
-import org.mozilla.gecko.sync.repositories.ParentNotFoundException;
-import org.mozilla.gecko.sync.repositories.Repository;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionBeginDelegate;
-import org.mozilla.gecko.sync.repositories.domain.HistoryRecord;
-import org.mozilla.gecko.sync.repositories.domain.Record;
-
-import android.content.ContentProviderClient;
-import android.content.Context;
-import android.database.Cursor;
-import android.os.RemoteException;
-
-public class AndroidBrowserHistoryRepositorySession extends AndroidBrowserRepositorySession {
-  public static final String LOG_TAG = "ABHistoryRepoSess";
-
-  /**
-   * The number of records to queue for insertion before writing to databases.
-   */
-  public static final int INSERT_RECORD_THRESHOLD = 50;
-  public static final int RECENT_VISITS_LIMIT = 20;
-
-  public AndroidBrowserHistoryRepositorySession(Repository repository, Context context) {
-    super(repository);
-    dbHelper = new AndroidBrowserHistoryDataAccessor(context);
-  }
-
-  @Override
-  public void begin(RepositorySessionBeginDelegate delegate) throws InvalidSessionTransitionException {
-    // HACK: Fennec creates history records without a GUID. Mercilessly drop
-    // them on the floor. See Bug 739514.
-    try {
-      dbHelper.delete(BrowserContract.History.GUID + " IS NULL", null);
-    } catch (Exception e) {
-      // Ignore.
-    }
-    super.begin(delegate);
-  }
-
-  @Override
-  protected Record retrieveDuringStore(Cursor cur) {
-    return RepoUtils.historyFromMirrorCursor(cur);
-  }
-
-  @Override
-  protected Record retrieveDuringFetch(Cursor cur) {
-    return RepoUtils.historyFromMirrorCursor(cur);
-  }
-
-  @Override
-  protected String buildRecordString(Record record) {
-    HistoryRecord hist = (HistoryRecord) record;
-    return hist.histURI;
-  }
-
-  @Override
-  public boolean shouldIgnore(Record record) {
-    if (super.shouldIgnore(record)) {
-      return true;
-    }
-    if (!(record instanceof HistoryRecord)) {
-      return true;
-    }
-    HistoryRecord r = (HistoryRecord) record;
-    return !RepoUtils.isValidHistoryURI(r.histURI);
-  }
-
-  @Override
-  protected Record transformRecord(Record record) throws NullCursorException {
-    return addVisitsToRecord(record);
-  }
-
-  private Record addVisitsToRecord(Record record) throws NullCursorException {
-    Logger.debug(LOG_TAG, "Adding visits for GUID " + record.guid);
-
-    // Sync is an object store, so what we attach here will replace what's already present on the Sync servers.
-    // We upload just a recent subset of visits for each history record for space and bandwidth reasons.
-    // We chose 20 to be conservative.  See Bug 1164660 for details.
-    ContentProviderClient visitsClient = dbHelper.context.getContentResolver().acquireContentProviderClient(BrowserContractHelpers.VISITS_CONTENT_URI);
-    if (visitsClient == null) {
-      throw new IllegalStateException("Could not obtain a ContentProviderClient for Visits URI");
-    }
-
-    try {
-      ((HistoryRecord) record).visits = VisitsHelper.getRecentHistoryVisitsForGUID(
-              visitsClient, record.guid, RECENT_VISITS_LIMIT);
-    } catch (RemoteException e) {
-      throw new IllegalStateException("Error while obtaining visits for a record", e);
-    } finally {
-      visitsClient.release();
-    }
-
-    return record;
-  }
-
-  @Override
-  protected Record prepareRecord(Record record) {
-    return record;
-  }
-
-  protected final Object recordsBufferMonitor = new Object();
-  protected ArrayList<HistoryRecord> recordsBuffer = new ArrayList<HistoryRecord>();
-
-  /**
-   * Queue record for insertion, possibly flushing the queue.
-   * <p>
-   * Must be called on <code>storeWorkQueue</code> thread! But this is only
-   * called from <code>store</code>, which is called on the queue thread.
-   *
-   * @param record
-   *          A <code>Record</code> with a GUID that is not present locally.
-   */
-  @Override
-  protected void insert(Record record) throws NoGuidForIdException, NullCursorException, ParentNotFoundException {
-    enqueueNewRecord((HistoryRecord) prepareRecord(record));
-  }
-
-  /**
-   * Batch incoming records until some reasonable threshold is hit or storeDone
-   * is received.
-   * <p>
-   * Must be called on <code>storeWorkQueue</code> thread!
-   *
-   * @param record A <code>Record</code> with a GUID that is not present locally.
-   * @throws NullCursorException
-   */
-  protected void enqueueNewRecord(HistoryRecord record) throws NullCursorException {
-    synchronized (recordsBufferMonitor) {
-      if (recordsBuffer.size() >= INSERT_RECORD_THRESHOLD) {
-        flushNewRecords();
-      }
-      Logger.debug(LOG_TAG, "Enqueuing new record with GUID " + record.guid);
-      recordsBuffer.add(record);
-    }
-  }
-
-  /**
-   * Flush queue of incoming records to database.
-   * <p>
-   * Must be called on <code>storeWorkQueue</code> thread!
-   * <p>
-   * Must be locked by recordsBufferMonitor!
-   * @throws NullCursorException
-   */
-  protected void flushNewRecords() throws NullCursorException {
-    if (recordsBuffer.size() < 1) {
-      Logger.debug(LOG_TAG, "No records to flush, returning.");
-      return;
-    }
-
-    final ArrayList<HistoryRecord> outgoing = recordsBuffer;
-    recordsBuffer = new ArrayList<HistoryRecord>();
-    Logger.debug(LOG_TAG, "Flushing " + outgoing.size() + " records to database.");
-    // TODO: move bulkInsert to AndroidBrowserDataAccessor?
-    int inserted = ((AndroidBrowserHistoryDataAccessor) dbHelper).bulkInsert(outgoing);
-    if (inserted != outgoing.size()) {
-      // Something failed; most pessimistic action is to declare that all insertions failed.
-      // TODO: perform the bulkInsert in a transaction and rollback unless all insertions succeed?
-      for (HistoryRecord failed : outgoing) {
-        delegate.onRecordStoreFailed(new RuntimeException("Failed to insert history item with guid " + failed.guid + "."), failed.guid);
-      }
-      return;
-    }
-
-    // All good, everybody succeeded.
-    for (HistoryRecord succeeded : outgoing) {
-      try {
-        // Does not use androidID -- just GUID -> String map.
-        updateBookkeeping(succeeded);
-      } catch (NoGuidForIdException | ParentNotFoundException e) {
-        // Should not happen.
-        throw new NullCursorException(e);
-      } catch (NullCursorException e) {
-        throw e;
-      }
-      trackRecord(succeeded);
-      delegate.onRecordStoreSucceeded(succeeded.guid); // At this point, we are really inserted.
-    }
-  }
-
-  @Override
-  public void storeDone() {
-    storeWorkQueue.execute(new Runnable() {
-      @Override
-      public void run() {
-        synchronized (recordsBufferMonitor) {
-          try {
-            flushNewRecords();
-          } catch (Exception e) {
-            Logger.warn(LOG_TAG, "Error flushing records to database.", e);
-          }
-        }
-        storeDone(System.currentTimeMillis());
-      }
-    });
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/AndroidBrowserRepository.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/AndroidBrowserRepository.java
deleted file mode 100644
index 6c5c661..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/AndroidBrowserRepository.java
+++ /dev/null
@@ -1,74 +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.sync.repositories.android;
-
-import org.mozilla.gecko.sync.repositories.NullCursorException;
-import org.mozilla.gecko.sync.repositories.Repository;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionCleanDelegate;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionCreationDelegate;
-
-import android.content.Context;
-
-public abstract class AndroidBrowserRepository extends Repository {
-
-  @Override
-  public void createSession(RepositorySessionCreationDelegate delegate, Context context) {
-    new CreateSessionThread(delegate, context).start();
-  }
-
-  @Override
-  public void clean(boolean success, RepositorySessionCleanDelegate delegate, Context context) {
-    // Only clean deleted records if success
-    if (success) {
-      new CleanThread(delegate, context).start();
-    }
-  }
-
-  class CleanThread extends Thread {
-    private final RepositorySessionCleanDelegate delegate;
-    private final Context context;
-
-    public CleanThread(RepositorySessionCleanDelegate delegate, Context context) {
-      if (context == null) {
-        throw new IllegalArgumentException("context is null");
-      }
-      this.delegate = delegate;
-      this.context = context;
-    }
-
-    @Override
-    public void run() {
-      try {
-        getDataAccessor(context).purgeDeleted();
-      } catch (Exception e) {
-        delegate.onCleanFailed(AndroidBrowserRepository.this, e);
-        return;
-      }
-      delegate.onCleaned(AndroidBrowserRepository.this);
-    }
-  }
-
-  protected abstract AndroidBrowserRepositoryDataAccessor getDataAccessor(Context context);
-  protected abstract void sessionCreator(RepositorySessionCreationDelegate delegate, Context context);
-
-  class CreateSessionThread extends Thread {
-    private final RepositorySessionCreationDelegate delegate;
-    private final Context context;
-
-    public CreateSessionThread(RepositorySessionCreationDelegate delegate, Context context) {
-      if (context == null) {
-        throw new IllegalArgumentException("context is null.");
-      }
-      this.delegate = delegate;
-      this.context = context;
-    }
-
-    @Override
-    public void run() {
-      sessionCreator(delegate, context);
-    }
-  }
-
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/AndroidBrowserRepositoryDataAccessor.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/AndroidBrowserRepositoryDataAccessor.java
deleted file mode 100644
index 138d63d..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/AndroidBrowserRepositoryDataAccessor.java
+++ /dev/null
@@ -1,232 +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.sync.repositories.android;
-
-import java.util.List;
-
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.background.db.CursorDumper;
-import org.mozilla.gecko.db.BrowserContract;
-import org.mozilla.gecko.sync.repositories.NullCursorException;
-import org.mozilla.gecko.sync.repositories.domain.Record;
-
-import android.content.ContentValues;
-import android.content.Context;
-import android.database.Cursor;
-import android.net.Uri;
-
-public abstract class AndroidBrowserRepositoryDataAccessor {
-
-  private static final String[] GUID_COLUMNS = new String[] { BrowserContract.SyncColumns.GUID };
-  protected Context context;
-  protected static String LOG_TAG = "BrowserDataAccessor";
-  protected final RepoUtils.QueryHelper queryHelper;
-
-  public AndroidBrowserRepositoryDataAccessor(Context context) {
-    this.context = context;
-    this.queryHelper = new RepoUtils.QueryHelper(context, getUri(), LOG_TAG);
-  }
-
-  protected abstract String[] getAllColumns();
-
-  /**
-   * Produce a <code>ContentValues</code> instance that represents the provided <code>Record</code>.
-   *
-   * @param record The <code>Record</code> to be converted.
-   * @return The <code>ContentValues</code> corresponding to <code>record</code>.
-   */
-  protected abstract ContentValues getContentValues(Record record);
-
-  protected abstract Uri getUri();
-
-  /**
-   * Dump all the records in raw format.
-   */
-  public void dumpDB() {
-    Cursor cur = null;
-    try {
-      cur = queryHelper.safeQuery(".dumpDB", null, null, null, null);
-      CursorDumper.dumpCursor(cur);
-    } catch (NullCursorException e) {
-    } finally {
-      if (cur != null) {
-        cur.close();
-      }
-    }
-  }
-
-  public String dateModifiedWhere(long timestamp) {
-    return BrowserContract.SyncColumns.DATE_MODIFIED + " >= " + Long.toString(timestamp);
-  }
-
-  public void delete(String where, String[] args) {
-    Uri uri = getUri();
-    context.getContentResolver().delete(uri, where, args);
-  }
-
-  public void wipe() {
-    Logger.debug(LOG_TAG, "Wiping.");
-    delete(null, null);
-  }
-
-  public void purgeDeleted() throws NullCursorException {
-    String where = BrowserContract.SyncColumns.IS_DELETED + "= 1";
-    Uri uri = getUri();
-    Logger.info(LOG_TAG, "Purging deleted from: " + uri);
-    context.getContentResolver().delete(uri, where, null);
-  }
-
-  /**
-   * Remove matching records from the database entirely, i.e., do not set a
-   * deleted flag, delete entirely.
-   *
-   * @param guid
-   *          The GUID of the record to be deleted.
-   * @return The number of records deleted.
-   */
-  public int purgeGuid(String guid) {
-    String where  = BrowserContract.SyncColumns.GUID + " = ?";
-    String[] args = new String[] { guid };
-
-    int deleted = context.getContentResolver().delete(getUri(), where, args);
-    if (deleted != 1) {
-      Logger.warn(LOG_TAG, "Unexpectedly deleted " + deleted + " records for guid " + guid);
-    }
-    return deleted;
-  }
-
-  public void update(String guid, Record newRecord) {
-    String where  = BrowserContract.SyncColumns.GUID + " = ?";
-    String[] args = new String[] { guid };
-    ContentValues cv = getContentValues(newRecord);
-    int updated = context.getContentResolver().update(getUri(), cv, where, args);
-    if (updated != 1) {
-      Logger.warn(LOG_TAG, "Unexpectedly updated " + updated + " rows for guid " + guid);
-    }
-  }
-
-  public Uri insert(Record record) {
-    ContentValues cv = getContentValues(record);
-    return context.getContentResolver().insert(getUri(), cv);
-  }
-
-  /**
-   * Fetch all records.
-   * <p>
-   * The caller is responsible for closing the cursor.
-   *
-   * @return A cursor. You </b>must</b> close this when you're done with it.
-   * @throws NullCursorException
-   */
-  public Cursor fetchAll() throws NullCursorException {
-    return queryHelper.safeQuery(".fetchAll", getAllColumns(), null, null, null);
-  }
-
-  /**
-   * Fetch GUIDs for records modified since the provided timestamp.
-   * <p>
-   * The caller is responsible for closing the cursor.
-   *
-   * @param timestamp A timestamp in milliseconds.
-   * @return A cursor. You <b>must</b> close this when you're done with it.
-   * @throws NullCursorException
-   */
-  public Cursor getGUIDsSince(long timestamp) throws NullCursorException {
-    return queryHelper.safeQuery(".getGUIDsSince",
-                                 GUID_COLUMNS,
-                                 dateModifiedWhere(timestamp),
-                                 null, null);
-  }
-
-  /**
-   * Fetch records modified since the provided timestamp.
-   * <p>
-   * The caller is responsible for closing the cursor.
-   *
-   * @param timestamp A timestamp in milliseconds.
-   * @return A cursor. You <b>must</b> close this when you're done with it.
-   * @throws NullCursorException
-   */
-  public Cursor fetchSince(long timestamp) throws NullCursorException {
-    return queryHelper.safeQuery(".fetchSince",
-                                 getAllColumns(),
-                                 dateModifiedWhere(timestamp),
-                                 null, null);
-  }
-
-  /**
-   * Fetch records for the provided GUIDs.
-   * <p>
-   * The caller is responsible for closing the cursor.
-   *
-   * @param guids The GUIDs of the records to fetch.
-   * @return A cursor. You <b>must</b> close this when you're done with it.
-   * @throws NullCursorException
-   */
-  public Cursor fetch(String guids[]) throws NullCursorException {
-    String where = RepoUtils.computeSQLInClause(guids.length, "guid");
-    return queryHelper.safeQuery(".fetch", getAllColumns(), where, guids, null);
-  }
-
-  public void updateByGuid(String guid, ContentValues cv) {
-    String where  = BrowserContract.SyncColumns.GUID + " = ?";
-    String[] args = new String[] { guid };
-
-    int updated = context.getContentResolver().update(getUri(), cv, where, args);
-    if (updated == 1) {
-      return;
-    }
-    Logger.warn(LOG_TAG, "Unexpectedly updated " + updated + " rows for guid " + guid);
-  }
-
-  /**
-   * Insert records.
-   * <p>
-   * This inserts all the records (using <code>ContentProvider.bulkInsert</code>),
-   * but does <b>not</b> update the <code>androidID</code> of each record.
-   *
-   * @param records
-   *          the records to insert.
-   * @return
-   *          the number of records actually inserted.
-   * @throws NullCursorException
-   */
-  public int bulkInsert(List<Record> records) throws NullCursorException {
-    if (records.isEmpty()) {
-      Logger.debug(LOG_TAG, "No records to insert, returning.");
-    }
-
-    int size = records.size();
-    ContentValues[] cvs = new ContentValues[size];
-    int index = 0;
-    for (Record record : records) {
-      try {
-        cvs[index] = getContentValues(record);
-        index += 1;
-      } catch (Exception e) {
-        Logger.warn(LOG_TAG, "Got exception in getContentValues for record with guid " + record.guid, e);
-      }
-    }
-
-    if (index != size) {
-      // bulkInsert treats null ContentValues as blank rows, which we don't want
-      // to insert into the database.
-      // We expect exceptions in getContentValues to be exceedingly rare, so we
-      // re-allocate in the (rare) error case and maintain a fast path for the
-      // success case.
-      size = index;
-    }
-
-    int inserted = context.getContentResolver().bulkInsert(getUri(), cvs);
-    if (inserted == size) {
-      Logger.debug(LOG_TAG, "Inserted " + inserted + " records, as expected.");
-    } else {
-      Logger.debug(LOG_TAG, "Inserted " +
-                   inserted + " records but expected " +
-                   size     + " records.");
-    }
-    return inserted;
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/AndroidBrowserRepositorySession.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/AndroidBrowserRepositorySession.java
deleted file mode 100644
index 4f0da0b..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/AndroidBrowserRepositorySession.java
+++ /dev/null
@@ -1,792 +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.sync.repositories.android;
-
-import java.util.ArrayList;
-
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.sync.repositories.InactiveSessionException;
-import org.mozilla.gecko.sync.repositories.InvalidRequestException;
-import org.mozilla.gecko.sync.repositories.InvalidSessionTransitionException;
-import org.mozilla.gecko.sync.repositories.MultipleRecordsForGuidException;
-import org.mozilla.gecko.sync.repositories.NoGuidForIdException;
-import org.mozilla.gecko.sync.repositories.NoStoreDelegateException;
-import org.mozilla.gecko.sync.repositories.NullCursorException;
-import org.mozilla.gecko.sync.repositories.ParentNotFoundException;
-import org.mozilla.gecko.sync.repositories.ProfileDatabaseException;
-import org.mozilla.gecko.sync.repositories.RecordFilter;
-import org.mozilla.gecko.sync.repositories.Repository;
-import org.mozilla.gecko.sync.repositories.StoreTrackingRepositorySession;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionBeginDelegate;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionFetchRecordsDelegate;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionFinishDelegate;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionGuidsSinceDelegate;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionWipeDelegate;
-import org.mozilla.gecko.sync.repositories.domain.Record;
-
-import android.content.ContentUris;
-import android.database.Cursor;
-import android.net.Uri;
-import android.util.SparseArray;
-
-/**
- * You'll notice that all delegate calls *either*:
- *
- * - request a deferred delegate with the appropriate work queue, then
- *   make the appropriate call, or
- * - create a Runnable which makes the appropriate call, and pushes it
- *   directly into the appropriate work queue.
- *
- * This is to ensure that all delegate callbacks happen off the current
- * thread. This provides lock safety (we don't enter another method that
- * might try to take a lock already taken in our caller), and ensures
- * that operations take place off the main thread.
- *
- * Don't do both -- the two approaches are equivalent -- and certainly
- * don't do neither unless you know what you're doing!
- *
- * Similarly, all store calls go through the appropriate store queue. This
- * ensures that store() and storeDone() consequences occur before-after.
- *
- * @author rnewman
- *
- */
-public abstract class AndroidBrowserRepositorySession extends StoreTrackingRepositorySession {
-  public static final String LOG_TAG = "BrowserRepoSession";
-
-  protected AndroidBrowserRepositoryDataAccessor dbHelper;
-
-  /**
-   * In order to reconcile the "same record" with two *different* GUIDs (for
-   * example, the same bookmark created by two different clients), we maintain a
-   * mapping for each local record from a "record string" to
-   * "local record GUID".
-   * <p>
-   * The "record string" above is a "record identifying unique key" produced by
-   * <code>buildRecordString</code>.
-   * <p>
-   * Since we hash each "record string", this map may produce a false positive.
-   * In this case, we search the database for a matching record explicitly using
-   * <code>findByRecordString</code>.
-   */
-  protected SparseArray<String> recordToGuid;
-
-  public AndroidBrowserRepositorySession(Repository repository) {
-    super(repository);
-  }
-
-  /**
-   * Retrieve a record from a cursor. Act as if we don't know the final contents of
-   * the record: for example, a folder's child array might change.
-   *
-   * Return null if this record should not be processed.
-   *
-   * @throws NoGuidForIdException
-   * @throws NullCursorException
-   * @throws ParentNotFoundException
-   */
-  protected abstract Record retrieveDuringStore(Cursor cur) throws NoGuidForIdException, NullCursorException, ParentNotFoundException;
-
-  /**
-   * Retrieve a record from a cursor. Ensure that the contents of the database are
-   * updated to match the record that we're constructing: for example, the children
-   * of a folder might be repositioned as we generate the folder's record.
-   *
-   * @throws NoGuidForIdException
-   * @throws NullCursorException
-   * @throws ParentNotFoundException
-   */
-  protected abstract Record retrieveDuringFetch(Cursor cur) throws NoGuidForIdException, NullCursorException, ParentNotFoundException;
-
-  /**
-   * Override this to allow records to be skipped during insertion.
-   *
-   * For example, a session subclass might skip records of an unsupported type.
-   */
-  @SuppressWarnings("static-method")
-  public boolean shouldIgnore(Record record) {
-    return false;
-  }
-
-  /**
-   * Perform any necessary transformation of a record prior to searching by
-   * any field other than GUID.
-   *
-   * Example: translating remote folder names into local names.
-   */
-  @SuppressWarnings("static-method")
-  protected void fixupRecord(Record record) {
-    return;
-  }
-
-  /**
-   * Override in subclass to implement record extension.
-   *
-   * Populate any fields of the record that are expensive to calculate,
-   * prior to reconciling.
-   *
-   * Example: computing children arrays.
-   *
-   * Return null if this record should not be processed.
-   *
-   * @param record
-   *        The record to transform. Can be null.
-   * @return The transformed record. Can be null.
-   * @throws NullCursorException
-   */
-  @SuppressWarnings("static-method")
-  protected Record transformRecord(Record record) throws NullCursorException {
-    return record;
-  }
-
-  @Override
-  public void begin(RepositorySessionBeginDelegate delegate) throws InvalidSessionTransitionException {
-    RepositorySessionBeginDelegate deferredDelegate = delegate.deferredBeginDelegate(delegateQueue);
-    super.sharedBegin();
-
-    try {
-      // We do this check here even though it results in one extra call to the DB
-      // because if we didn't, we have to do a check on every other call since there
-      // is no way of knowing which call would be hit first.
-      checkDatabase();
-    } catch (ProfileDatabaseException e) {
-      Logger.error(LOG_TAG, "ProfileDatabaseException from begin. Fennec must be launched once until this error is fixed");
-      deferredDelegate.onBeginFailed(e);
-      return;
-    } catch (Exception e) {
-      deferredDelegate.onBeginFailed(e);
-      return;
-    }
-    storeTracker = createStoreTracker();
-    deferredDelegate.onBeginSucceeded(this);
-  }
-
-  @Override
-  public void finish(RepositorySessionFinishDelegate delegate) throws InactiveSessionException {
-    dbHelper = null;
-    recordToGuid = null;
-    super.finish(delegate);
-  }
-
-  /**
-   * Produce a "record string" (record identifying unique key).
-   *
-   * @param record
-   *          the <code>Record</code> to identify.
-   * @return a <code>String</code> instance.
-   */
-  protected abstract String buildRecordString(Record record);
-
-  protected void checkDatabase() throws ProfileDatabaseException, NullCursorException {
-    Logger.debug(LOG_TAG, "BEGIN: checking database.");
-    try {
-      dbHelper.fetch(new String[] { "none" }).close();
-      Logger.debug(LOG_TAG, "END: checking database.");
-    } catch (NullPointerException e) {
-      throw new ProfileDatabaseException(e);
-    }
-  }
-
-  @Override
-  public void guidsSince(long timestamp, RepositorySessionGuidsSinceDelegate delegate) {
-    GuidsSinceRunnable command = new GuidsSinceRunnable(timestamp, delegate);
-    delegateQueue.execute(command);
-  }
-
-  class GuidsSinceRunnable implements Runnable {
-
-    private final RepositorySessionGuidsSinceDelegate delegate;
-    private final long                                timestamp;
-
-    public GuidsSinceRunnable(long timestamp,
-                              RepositorySessionGuidsSinceDelegate delegate) {
-      this.timestamp = timestamp;
-      this.delegate = delegate;
-    }
-
-    @Override
-    public void run() {
-      if (!isActive()) {
-        delegate.onGuidsSinceFailed(new InactiveSessionException(null));
-        return;
-      }
-
-      Cursor cur;
-      try {
-        cur = dbHelper.getGUIDsSince(timestamp);
-      } catch (Exception e) {
-        delegate.onGuidsSinceFailed(e);
-        return;
-      }
-
-      ArrayList<String> guids;
-      try {
-        if (!cur.moveToFirst()) {
-          delegate.onGuidsSinceSucceeded(new String[] {});
-          return;
-        }
-        guids = new ArrayList<String>();
-        while (!cur.isAfterLast()) {
-          guids.add(RepoUtils.getStringFromCursor(cur, "guid"));
-          cur.moveToNext();
-        }
-      } finally {
-        Logger.debug(LOG_TAG, "Closing cursor after guidsSince.");
-        cur.close();
-      }
-
-      String guidsArray[] = new String[guids.size()];
-      guids.toArray(guidsArray);
-      delegate.onGuidsSinceSucceeded(guidsArray);
-    }
-  }
-
-  @Override
-  public void fetch(String[] guids,
-                    RepositorySessionFetchRecordsDelegate delegate) throws InactiveSessionException {
-    FetchRunnable command = new FetchRunnable(guids, now(), null, delegate);
-    executeDelegateCommand(command);
-  }
-
-  abstract class FetchingRunnable implements Runnable {
-    protected final RepositorySessionFetchRecordsDelegate delegate;
-
-    public FetchingRunnable(RepositorySessionFetchRecordsDelegate delegate) {
-      this.delegate = delegate;
-    }
-
-    protected void fetchFromCursor(Cursor cursor, RecordFilter filter, long end) {
-      Logger.debug(LOG_TAG, "Fetch from cursor:");
-      try {
-        try {
-          if (!cursor.moveToFirst()) {
-            delegate.onFetchCompleted(end);
-            return;
-          }
-          while (!cursor.isAfterLast()) {
-            Record r = retrieveDuringFetch(cursor);
-            if (r != null) {
-              if (filter == null || !filter.excludeRecord(r)) {
-                Logger.trace(LOG_TAG, "Processing record " + r.guid);
-                delegate.onFetchedRecord(transformRecord(r));
-              } else {
-                Logger.debug(LOG_TAG, "Skipping filtered record " + r.guid);
-              }
-            }
-            cursor.moveToNext();
-          }
-          delegate.onFetchCompleted(end);
-        } catch (NoGuidForIdException e) {
-          Logger.warn(LOG_TAG, "No GUID for ID.", e);
-          delegate.onFetchFailed(e, null);
-        } catch (Exception e) {
-          Logger.warn(LOG_TAG, "Exception in fetchFromCursor.", e);
-          delegate.onFetchFailed(e, null);
-          return;
-        }
-      } finally {
-        Logger.trace(LOG_TAG, "Closing cursor after fetch.");
-        cursor.close();
-      }
-    }
-  }
-
-  public class FetchRunnable extends FetchingRunnable {
-    private final String[] guids;
-    private final long     end;
-    private final RecordFilter filter;
-
-    public FetchRunnable(String[] guids,
-                         long end,
-                         RecordFilter filter,
-                         RepositorySessionFetchRecordsDelegate delegate) {
-      super(delegate);
-      this.guids  = guids;
-      this.end    = end;
-      this.filter = filter;
-    }
-
-    @Override
-    public void run() {
-      if (!isActive()) {
-        delegate.onFetchFailed(new InactiveSessionException(null), null);
-        return;
-      }
-
-      if (guids == null || guids.length < 1) {
-        Logger.error(LOG_TAG, "No guids sent to fetch");
-        delegate.onFetchFailed(new InvalidRequestException(null), null);
-        return;
-      }
-
-      try {
-        Cursor cursor = dbHelper.fetch(guids);
-        this.fetchFromCursor(cursor, filter, end);
-      } catch (NullCursorException e) {
-        delegate.onFetchFailed(e, null);
-      }
-    }
-  }
-
-  @Override
-  public void fetchSince(long timestamp,
-                         RepositorySessionFetchRecordsDelegate delegate) {
-    if (this.storeTracker == null) {
-      throw new IllegalStateException("Store tracker not yet initialized!");
-    }
-
-    Logger.debug(LOG_TAG, "Running fetchSince(" + timestamp + ").");
-    FetchSinceRunnable command = new FetchSinceRunnable(timestamp, now(), this.storeTracker.getFilter(), delegate);
-    delegateQueue.execute(command);
-  }
-
-  class FetchSinceRunnable extends FetchingRunnable {
-    private final long since;
-    private final long end;
-    private final RecordFilter filter;
-
-    public FetchSinceRunnable(long since,
-                              long end,
-                              RecordFilter filter,
-                              RepositorySessionFetchRecordsDelegate delegate) {
-      super(delegate);
-      this.since  = since;
-      this.end    = end;
-      this.filter = filter;
-    }
-
-    @Override
-    public void run() {
-      if (!isActive()) {
-        delegate.onFetchFailed(new InactiveSessionException(null), null);
-        return;
-      }
-
-      try {
-        Cursor cursor = dbHelper.fetchSince(since);
-        this.fetchFromCursor(cursor, filter, end);
-      } catch (NullCursorException e) {
-        delegate.onFetchFailed(e, null);
-        return;
-      }
-    }
-  }
-
-  @Override
-  public void fetchAll(RepositorySessionFetchRecordsDelegate delegate) {
-    this.fetchSince(0, delegate);
-  }
-
-  protected int storeCount = 0;
-
-  @Override
-  public void store(final Record record) throws NoStoreDelegateException {
-    if (delegate == null) {
-      throw new NoStoreDelegateException();
-    }
-    if (record == null) {
-      Logger.error(LOG_TAG, "Record sent to store was null");
-      throw new IllegalArgumentException("Null record passed to AndroidBrowserRepositorySession.store().");
-    }
-
-    storeCount += 1;
-    Logger.debug(LOG_TAG, "Storing record with GUID " + record.guid + " (stored " + storeCount + " records this session).");
-
-    // Store Runnables *must* complete synchronously. It's OK, they
-    // run on a background thread.
-    Runnable command = new Runnable() {
-
-      @Override
-      public void run() {
-        if (!isActive()) {
-          Logger.warn(LOG_TAG, "AndroidBrowserRepositorySession is inactive. Store failing.");
-          delegate.onRecordStoreFailed(new InactiveSessionException(null), record.guid);
-          return;
-        }
-
-        // Check that the record is a valid type.
-        // Fennec only supports bookmarks and folders. All other types of records,
-        // including livemarks and queries, are simply ignored.
-        // See Bug 708149. This might be resolved by Fennec changing its database
-        // schema, or by Sync storing non-applied records in its own private database.
-        if (shouldIgnore(record)) {
-          Logger.debug(LOG_TAG, "Ignoring record " + record.guid);
-
-          // Don't throw: we don't want to abort the entire sync when we get a livemark!
-          // delegate.onRecordStoreFailed(new InvalidBookmarkTypeException(null));
-          return;
-        }
-
-
-        // TODO: lift these into the session.
-        // Temporary: this matches prior syncing semantics, in which only
-        // the relationship between the local and remote record is considered.
-        // In the future we'll track these two timestamps and use them to
-        // determine which records have changed, and thus process incoming
-        // records more efficiently.
-        long lastLocalRetrieval  = 0;      // lastSyncTimestamp?
-        long lastRemoteRetrieval = 0;      // TODO: adjust for clock skew.
-        boolean remotelyModified = record.lastModified > lastRemoteRetrieval;
-
-        Record existingRecord;
-        try {
-          // GUID matching only: deleted records don't have a payload with which to search.
-          existingRecord = retrieveByGUIDDuringStore(record.guid);
-          if (record.deleted) {
-            if (existingRecord == null) {
-              // We're done. Don't bother with a callback. That can change later
-              // if we want it to.
-              trace("Incoming record " + record.guid + " is deleted, and no local version. Bye!");
-              return;
-            }
-
-            if (existingRecord.deleted) {
-              trace("Local record already deleted. Bye!");
-              return;
-            }
-
-            // Which one wins?
-            if (!remotelyModified) {
-              trace("Ignoring deleted record from the past.");
-              return;
-            }
-
-            boolean locallyModified = existingRecord.lastModified > lastLocalRetrieval;
-            if (!locallyModified) {
-              trace("Remote modified, local not. Deleting.");
-              storeRecordDeletion(record, existingRecord);
-              return;
-            }
-
-            trace("Both local and remote records have been modified.");
-            if (record.lastModified > existingRecord.lastModified) {
-              trace("Remote is newer, and deleted. Deleting local.");
-              storeRecordDeletion(record, existingRecord);
-              return;
-            }
-
-            trace("Remote is older, local is not deleted. Ignoring.");
-            return;
-          }
-          // End deletion logic.
-
-          // Now we're processing a non-deleted incoming record.
-          // Apply any changes we need in order to correctly find existing records.
-          fixupRecord(record);
-
-          if (existingRecord == null) {
-            trace("Looking up match for record " + record.guid);
-            existingRecord = findExistingRecord(record);
-          }
-
-          if (existingRecord == null) {
-            // The record is new.
-            trace("No match. Inserting.");
-            insert(record);
-            return;
-          }
-
-          // We found a local dupe.
-          trace("Incoming record " + record.guid + " dupes to local record " + existingRecord.guid);
-
-          // Populate more expensive fields prior to reconciling.
-          existingRecord = transformRecord(existingRecord);
-          Record toStore = reconcileRecords(record, existingRecord, lastRemoteRetrieval, lastLocalRetrieval);
-
-          if (toStore == null) {
-            Logger.debug(LOG_TAG, "Reconciling returned null. Not inserting a record.");
-            return;
-          }
-
-          // TODO: pass in timestamps?
-
-          // This section of code will only run if the incoming record is not
-          // marked as deleted, so we never want to just drop ours from the database:
-          // we need to upload it later.
-          // Allowing deleted items to propagate through `replace` allows normal
-          // logging and side-effects to occur, and is no more expensive than simply
-          // bumping the modified time.
-          Logger.debug(LOG_TAG, "Replacing existing " + existingRecord.guid +
-                       (toStore.deleted ? " with deleted record " : " with record ") +
-                       toStore.guid);
-          Record replaced = replace(toStore, existingRecord);
-
-          // Note that we don't track records here; deciding that is the job
-          // of reconcileRecords.
-          Logger.debug(LOG_TAG, "Calling delegate callback with guid " + replaced.guid +
-                                "(" + replaced.androidID + ")");
-          delegate.onRecordStoreSucceeded(replaced.guid);
-          return;
-
-        } catch (MultipleRecordsForGuidException e) {
-          Logger.error(LOG_TAG, "Multiple records returned for given guid: " + record.guid);
-          delegate.onRecordStoreFailed(e, record.guid);
-          return;
-        } catch (NoGuidForIdException e) {
-          Logger.error(LOG_TAG, "Store failed for " + record.guid, e);
-          delegate.onRecordStoreFailed(e, record.guid);
-          return;
-        } catch (Exception e) {
-          Logger.error(LOG_TAG, "Store failed for " + record.guid, e);
-          delegate.onRecordStoreFailed(e, record.guid);
-          return;
-        }
-      }
-    };
-    storeWorkQueue.execute(command);
-  }
-
-  /**
-   * Process a request for deletion of a record.
-   * Neither argument will ever be null.
-   *
-   * @param record the incoming record. This will be mostly blank, given that it's a deletion.
-   * @param existingRecord the existing record. Use this to decide how to process the deletion.
-   */
-  protected void storeRecordDeletion(final Record record, final Record existingRecord) {
-    // TODO: we ought to mark the record as deleted rather than purging it,
-    // in order to support syncing to multiple destinations. Bug 722607.
-    dbHelper.purgeGuid(record.guid);
-    delegate.onRecordStoreSucceeded(record.guid);
-  }
-
-  protected void insert(Record record) throws NoGuidForIdException, NullCursorException, ParentNotFoundException {
-    Record toStore = prepareRecord(record);
-    Uri recordURI = dbHelper.insert(toStore);
-    if (recordURI == null) {
-      throw new NullCursorException(new RuntimeException("Got null URI inserting record with guid " + record.guid));
-    }
-    toStore.androidID = ContentUris.parseId(recordURI);
-
-    updateBookkeeping(toStore);
-    trackRecord(toStore);
-    delegate.onRecordStoreSucceeded(toStore.guid);
-
-    Logger.debug(LOG_TAG, "Inserted record with guid " + toStore.guid + " as androidID " + toStore.androidID);
-  }
-
-  protected Record replace(Record newRecord, Record existingRecord) throws NoGuidForIdException, NullCursorException, ParentNotFoundException {
-    Record toStore = prepareRecord(newRecord);
-
-    // newRecord should already have suitable androidID and guid.
-    dbHelper.update(existingRecord.guid, toStore);
-    updateBookkeeping(toStore);
-    Logger.debug(LOG_TAG, "replace() returning record " + toStore.guid);
-    return toStore;
-  }
-
-  /**
-   * Retrieve a record from the store by GUID, without writing unnecessarily to the
-   * database.
-   *
-   * @throws NoGuidForIdException
-   * @throws NullCursorException
-   * @throws ParentNotFoundException
-   * @throws MultipleRecordsForGuidException
-   */
-  protected Record retrieveByGUIDDuringStore(String guid) throws
-                                             NoGuidForIdException,
-                                             NullCursorException,
-                                             ParentNotFoundException,
-                                             MultipleRecordsForGuidException {
-    Cursor cursor = dbHelper.fetch(new String[] { guid });
-    try {
-      if (!cursor.moveToFirst()) {
-        return null;
-      }
-
-      Record r = retrieveDuringStore(cursor);
-
-      cursor.moveToNext();
-      if (cursor.isAfterLast()) {
-        // Got one record!
-        return r; // Not transformed.
-      }
-
-      // More than one. Oh dear.
-      throw (new MultipleRecordsForGuidException(null));
-    } finally {
-      cursor.close();
-    }
-  }
-
-  /**
-   * Attempt to find an equivalent record through some means other than GUID.
-   *
-   * @param record
-   *        The record for which to search.
-   * @return
-   *        An equivalent Record object, or null if none is found.
-   *
-   * @throws MultipleRecordsForGuidException
-   * @throws NoGuidForIdException
-   * @throws NullCursorException
-   * @throws ParentNotFoundException
-   */
-  protected Record findExistingRecord(Record record) throws MultipleRecordsForGuidException,
-    NoGuidForIdException, NullCursorException, ParentNotFoundException {
-
-    Logger.debug(LOG_TAG, "Finding existing record for incoming record with GUID " + record.guid);
-    String recordString = buildRecordString(record);
-    if (recordString == null) {
-      Logger.debug(LOG_TAG, "No record string for incoming record " + record.guid);
-      return null;
-    }
-
-    if (Logger.LOG_PERSONAL_INFORMATION) {
-      Logger.pii(LOG_TAG, "Searching with record string " + recordString);
-    } else {
-      Logger.debug(LOG_TAG, "Searching with record string.");
-    }
-    String guid = getGuidForString(recordString);
-    if (guid == null) {
-      Logger.debug(LOG_TAG, "Failed to find existing record for " + record.guid);
-      return null;
-    }
-
-    // Our map contained a match, but it could be a false positive. Since
-    // computed record string is supposed to be a unique key, we can easily
-    // verify our positive.
-    Logger.debug(LOG_TAG, "Found one. Checking stored record.");
-    Record stored = retrieveByGUIDDuringStore(guid);
-    String storedRecordString = buildRecordString(record);
-    if (recordString.equals(storedRecordString)) {
-      Logger.debug(LOG_TAG, "Existing record matches incoming record.  Returning existing record.");
-      return stored;
-    }
-
-    // Oh no, we got a false positive! (This should be *very* rare --
-    // essentially, we got a hash collision.) Search the DB for this record
-    // explicitly by hand.
-    Logger.debug(LOG_TAG, "Existing record does not match incoming record.  Trying to find record by record string.");
-    return findByRecordString(recordString);
-  }
-
-  protected String getGuidForString(String recordString) throws NoGuidForIdException, NullCursorException, ParentNotFoundException {
-    if (recordToGuid == null) {
-      createRecordToGuidMap();
-    }
-    return recordToGuid.get(recordString.hashCode());
-  }
-
-  protected void createRecordToGuidMap() throws NoGuidForIdException, NullCursorException, ParentNotFoundException {
-    Logger.info(LOG_TAG, "BEGIN: creating record -> GUID map.");
-    recordToGuid = new SparseArray<String>();
-
-    // TODO: we should be able to do this entire thing with string concatenations within SQL.
-    // Also consider whether it's better to fetch and process every record in the DB into
-    // memory, or run a query per record to do the same thing.
-    Cursor cur = dbHelper.fetchAll();
-    try {
-      if (!cur.moveToFirst()) {
-        return;
-      }
-      while (!cur.isAfterLast()) {
-        Record record = retrieveDuringStore(cur);
-        if (record != null) {
-          final String recordString = buildRecordString(record);
-          if (recordString != null) {
-            recordToGuid.put(recordString.hashCode(), record.guid);
-          }
-        }
-        cur.moveToNext();
-      }
-    } finally {
-      cur.close();
-    }
-    Logger.info(LOG_TAG, "END: creating record -> GUID map.");
-  }
-
-  /**
-   * Search the local database for a record with the same "record string".
-   * <p>
-   * We expect to do this only in the unlikely event of a hash
-   * collision, so we iterate the database completely.  Since we want
-   * to include information about the parents of bookmarks, it is
-   * difficult to do better purely using the
-   * <code>ContentProvider</code> interface.
-   *
-   * @param recordString
-   *          the "record string" to search for; must be n
-   * @return a <code>Record</code> with the same "record string", or
-   *         <code>null</code> if none is present.
-   * @throws ParentNotFoundException
-   * @throws NullCursorException
-   * @throws NoGuidForIdException
-   */
-  protected Record findByRecordString(String recordString) throws NoGuidForIdException, NullCursorException, ParentNotFoundException {
-    Cursor cur = dbHelper.fetchAll();
-    try {
-      if (!cur.moveToFirst()) {
-        return null;
-      }
-      while (!cur.isAfterLast()) {
-        Record record = retrieveDuringStore(cur);
-        if (record != null) {
-          final String storedRecordString = buildRecordString(record);
-          if (recordString.equals(storedRecordString)) {
-            return record;
-          }
-        }
-        cur.moveToNext();
-      }
-      return null;
-    } finally {
-      cur.close();
-    }
-  }
-
-  public void putRecordToGuidMap(String recordString, String guid) throws NoGuidForIdException, NullCursorException, ParentNotFoundException {
-    if (recordString == null) {
-      return;
-    }
-
-    if (recordToGuid == null) {
-      createRecordToGuidMap();
-    }
-    recordToGuid.put(recordString.hashCode(), guid);
-  }
-
-  protected abstract Record prepareRecord(Record record);
-
-  protected void updateBookkeeping(Record record) throws NoGuidForIdException,
-                                                 NullCursorException,
-                                                 ParentNotFoundException {
-    putRecordToGuidMap(buildRecordString(record), record.guid);
-  }
-
-  protected WipeRunnable getWipeRunnable(RepositorySessionWipeDelegate delegate) {
-    return new WipeRunnable(delegate);
-  }
-
-  @Override
-  public void wipe(RepositorySessionWipeDelegate delegate) {
-    Runnable command = getWipeRunnable(delegate);
-    storeWorkQueue.execute(command);
-  }
-
-  class WipeRunnable implements Runnable {
-    protected RepositorySessionWipeDelegate delegate;
-
-    public WipeRunnable(RepositorySessionWipeDelegate delegate) {
-      this.delegate = delegate;
-    }
-
-    @Override
-    public void run() {
-      if (!isActive()) {
-        delegate.onWipeFailed(new InactiveSessionException(null));
-        return;
-      }
-      dbHelper.wipe();
-      delegate.onWipeSucceeded();
-    }
-  }
-
-  // For testing purposes.
-  public AndroidBrowserRepositoryDataAccessor getDBHelper() {
-    return dbHelper;
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/BookmarksDeletionManager.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/BookmarksDeletionManager.java
deleted file mode 100644
index d8d8756..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/BookmarksDeletionManager.java
+++ /dev/null
@@ -1,239 +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.sync.repositories.android;
-
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.db.BrowserContract;
-import org.mozilla.gecko.sync.repositories.NullCursorException;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionStoreDelegate;
-
-/**
- * Queue up deletions. Process them at the end.
- *
- * Algorithm:
- *
- * * Collect GUIDs as we go. For convenience we partition these into
- *   folders and non-folders.
- *
- * * Non-folders can be deleted in batches as we go.
- *
- * * At the end of the sync:
- *   * Delete all that aren't folders.
- *   * Move the remaining children of any that are folders to an "Orphans" folder.
- *     - We do this even for children that are _marked_ as deleted -- we still want
- *       to upload them, and their parent is irrelevant.
- *   * Delete all the folders.
- *
- * * Any outstanding records -- the ones we moved to "Orphans" -- are true orphans.
- *   These should be reuploaded (because their parent has changed), as should their
- *   new parent (because its children array has changed).
- *   We achieve the former by moving them without tracking (but we don't make any
- *   special effort here -- warning! Lurking bug!).
- *   We achieve the latter by bumping its mtime. The caller should take care of untracking it.
- *
- * Note that we make no particular effort to handle repositioning or reparenting:
- * batching deletes at the end should be handled seamlessly by existing code,
- * because the deleted records could have arrived in a batch at the end regardless.
- *
- * Note that this class is not thread safe. This should be fine: call it only
- * from within a store runnable.
- *
- */
-public class BookmarksDeletionManager {
-  private static final String LOG_TAG = "BookmarkDelete";
-
-  private final AndroidBrowserBookmarksDataAccessor dataAccessor;
-  private RepositorySessionStoreDelegate delegate;
-
-  private final int flushThreshold;
-
-  private final HashSet<String> folders    = new HashSet<String>();
-  private final HashSet<String> nonFolders = new HashSet<String>();
-  private int nonFolderCount = 0;
-
-  // Records that we need to touch once we've deleted the non-folders.
-  private HashSet<String> nonFolderParents = new HashSet<String>();
-  private HashSet<String> folderParents    = new HashSet<String>();
-
-  /**
-   * Create an instance to be used for tracking deletions in a bookmarks
-   * repository session.
-   *
-   * @param dataAccessor
-   *        Used to effect database changes.
-   *
-   * @param flushThreshold
-   *        When this many non-folder records have been stored for deletion,
-   *        an incremental flush occurs.
-   */
-  public BookmarksDeletionManager(AndroidBrowserBookmarksDataAccessor dataAccessor, int flushThreshold) {
-    this.dataAccessor = dataAccessor;
-    this.flushThreshold = flushThreshold;
-  }
-
-  /**
-   * Set the delegate to use for callbacks.
-   * If not invoked, no callbacks will be submitted.
-   *
-   * @param delegate a delegate, which should already be a delayed delegate.
-   */
-  public void setDelegate(RepositorySessionStoreDelegate delegate) {
-    this.delegate = delegate;
-  }
-
-  public void deleteRecord(String guid, boolean isFolder, String parentGUID) {
-    if (guid == null) {
-      Logger.warn(LOG_TAG, "Cannot queue deletion of record with no GUID.");
-      return;
-    }
-    Logger.debug(LOG_TAG, "Queuing deletion of " + guid);
-
-    if (isFolder) {
-      folders.add(guid);
-      if (!folders.contains(parentGUID)) {
-        // We're not going to delete its parent; will need to bump it.
-        folderParents.add(parentGUID);
-      }
-
-      nonFolderParents.remove(guid);
-      folderParents.remove(guid);
-      return;
-    }
-
-    if (!folders.contains(parentGUID)) {
-      // We're not going to delete its parent; will need to bump it.
-      nonFolderParents.add(parentGUID);
-    }
-
-    if (nonFolders.add(guid)) {
-      if (++nonFolderCount >= flushThreshold) {
-        deleteNonFolders();
-      }
-    }
-  }
-
-  /**
-   * Flush deletions that can be easily taken care of right now.
-   */
-  public void incrementalFlush() {
-    // Yes, this means we only bump when we finish, not during an incremental flush.
-    deleteNonFolders();
-  }
-
-  /**
-   * Apply all pending deletions and reset state for the next batch of stores.
-   *
-   * @param orphanDestination the ID of the folder to which orphaned children
-   *                          should be moved.
-   *
-   * @throws NullCursorException
-   * @return a set of IDs to untrack. Will not be null.
-   */
-  public Set<String> flushAll(long orphanDestination, long now) throws NullCursorException {
-    Logger.debug(LOG_TAG, "Doing complete flush of deleted items. Moving orphans to " + orphanDestination);
-    deleteNonFolders();
-
-    // Find out which parents *won't* be deleted, and thus need to have their
-    // modified times bumped.
-    nonFolderParents.removeAll(folders);
-
-    Logger.debug(LOG_TAG, "Bumping modified times for " + nonFolderParents.size() +
-                          " parents of deleted non-folders.");
-    dataAccessor.bumpModifiedByGUID(nonFolderParents, now);
-
-    if (folders.size() > 0) {
-      final String[] folderGUIDs = folders.toArray(new String[folders.size()]);
-      final String[] folderIDs = getIDs(folderGUIDs);   // Throws if any don't exist.
-      int moved = dataAccessor.moveChildren(folderIDs, orphanDestination);
-      if (moved > 0) {
-        dataAccessor.bumpModified(orphanDestination, now);
-      }
-
-      // We've deleted or moved anything that might be under these folders.
-      // Just delete them.
-      final String folderWhere = RepoUtils.computeSQLInClause(folders.size(), BrowserContract.Bookmarks.GUID);
-      dataAccessor.delete(folderWhere, folderGUIDs);
-      invokeCallbacks(delegate, folderGUIDs);
-
-      folderParents.removeAll(folders);
-      Logger.debug(LOG_TAG, "Bumping modified times for " + folderParents.size() +
-                            " parents of deleted folders.");
-      dataAccessor.bumpModifiedByGUID(folderParents, now);
-
-      // Clean up.
-      folders.clear();
-    }
-
-    HashSet<String> ret = nonFolderParents;
-    ret.addAll(folderParents);
-
-    nonFolderParents = new HashSet<String>();
-    folderParents    = new HashSet<String>();
-    return ret;
-  }
-
-  private String[] getIDs(String[] guids) throws NullCursorException {
-    // Convert GUIDs to numeric IDs.
-    String[] ids = new String[guids.length];
-    Map<String, Long> guidsToIDs = dataAccessor.idsForGUIDs(guids);
-    for (int i = 0; i < guids.length; ++i) {
-      String guid = guids[i];
-      Long id =  guidsToIDs.get(guid);
-      if (id == null) {
-        throw new IllegalArgumentException("Can't get ID for unknown record " + guid);
-      }
-      ids[i] = id.toString();
-    }
-    return ids;
-  }
-
-  /**
-   * Flush non-folder deletions. This can be called at any time.
-   */
-  private void deleteNonFolders() {
-    if (nonFolderCount == 0) {
-      Logger.debug(LOG_TAG, "No non-folders to delete.");
-      return;
-    }
-
-    Logger.debug(LOG_TAG, "Applying deletion of " + nonFolderCount + " non-folders.");
-    final String[] nonFolderGUIDs = nonFolders.toArray(new String[nonFolderCount]);
-    final String nonFolderWhere = RepoUtils.computeSQLInClause(nonFolderCount, BrowserContract.Bookmarks.GUID);
-    dataAccessor.delete(nonFolderWhere, nonFolderGUIDs);
-
-    invokeCallbacks(delegate, nonFolderGUIDs);
-
-    // Discard these.
-    // Note that we maintain folderParents and nonFolderParents; we need them later.
-    nonFolders.clear();
-    nonFolderCount = 0;
-  }
-
-  private void invokeCallbacks(RepositorySessionStoreDelegate delegate,
-                               String[] nonFolderGUIDs) {
-    if (delegate == null) {
-      return;
-    }
-    Logger.trace(LOG_TAG, "Invoking store callback for " + nonFolderGUIDs.length + " GUIDs.");
-    for (String guid : nonFolderGUIDs) {
-      delegate.onRecordStoreSucceeded(guid);
-    }
-  }
-
-  /**
-   * Clear state in case of redundancy (e.g., wipe).
-   */
-  public void clear() {
-    nonFolders.clear();
-    nonFolderCount = 0;
-    folders.clear();
-    nonFolderParents.clear();
-    folderParents.clear();
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/BookmarksInsertionManager.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/BookmarksInsertionManager.java
deleted file mode 100644
index 98670d3..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/BookmarksInsertionManager.java
+++ /dev/null
@@ -1,298 +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.sync.repositories.android;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.LinkedHashSet;
-import java.util.Map;
-import java.util.Set;
-
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.sync.Utils;
-import org.mozilla.gecko.sync.repositories.domain.BookmarkRecord;
-
-/**
- * Queue up insertions:
- * <ul>
- * <li>Folder inserts where the parent is known. Do these immediately, because
- * they allow other records to be inserted. Requires bookkeeping updates. On
- * insert, flush the next set.</li>
- * <li>Regular inserts where the parent is known. These can happen whenever.
- * Batch for speed.</li>
- * <li>Records where the parent is not known. These can be flushed out when the
- * parent is known, or entered as orphans. This can be a queue earlier in the
- * process, so they don't get assigned to Unsorted. Feed into the main batch
- * when the parent arrives.</li>
- * </ul>
- * <p>
- * Deletions are always done at the end so that orphaning is minimized, and
- * that's why we are batching folders and non-folders separately.
- * <p>
- * Updates are always applied as they arrive.
- * <p>
- * Note that this class is not thread safe. This should be fine: call it only
- * from within a store runnable.
- */
-public class BookmarksInsertionManager {
-  public static final String LOG_TAG = "BookmarkInsert";
-  public static boolean DEBUG = false;
-
-  protected final int flushThreshold;
-  protected final BookmarkInserter inserter;
-
-  /**
-   * Folders that have been successfully inserted.
-   */
-  private final Set<String> insertedFolders = new HashSet<String>();
-
-  /**
-   * Non-folders waiting for bulk insertion.
-   * <p>
-   * We write in insertion order to keep things easy to debug.
-   */
-  private final Set<BookmarkRecord> nonFoldersToWrite = new LinkedHashSet<BookmarkRecord>();
-
-  /**
-   * Map from parent folder GUID to child records (folders and non-folders)
-   * waiting to be enqueued after parent folder is inserted.
-   */
-  private final Map<String, Set<BookmarkRecord>> recordsWaitingForParent = new HashMap<String, Set<BookmarkRecord>>();
-
-  /**
-   * Create an instance to be used for tracking insertions in a bookmarks
-   * repository session.
-   *
-   * @param flushThreshold
-   *        When this many non-folder records have been stored for insertion,
-   *        an incremental flush occurs.
-   * @param insertedFolders
-   *        The GUIDs of all the folders already inserted into the database.
-   * @param inserter
-   *        The <code>BookmarkInsert</code> to use.
-   */
-  public BookmarksInsertionManager(int flushThreshold, Collection<String> insertedFolders, BookmarkInserter inserter) {
-    this.flushThreshold = flushThreshold;
-    this.insertedFolders.addAll(insertedFolders);
-    this.inserter = inserter;
-  }
-
-  protected void addRecordWithUnwrittenParent(BookmarkRecord record) {
-    Set<BookmarkRecord> destination = recordsWaitingForParent.get(record.parentID);
-    if (destination == null) {
-      destination = new LinkedHashSet<BookmarkRecord>();
-      recordsWaitingForParent.put(record.parentID, destination);
-    }
-    destination.add(record);
-  }
-
-  /**
-   * If <code>record</code> is a folder, insert it immediately; if it is a
-   * non-folder, enqueue it. Then do the same for any records waiting for this record.
-   *
-   * @param record
-   *          the <code>BookmarkRecord</code> to enqueue.
-   */
-  protected void recursivelyEnqueueRecordAndChildren(BookmarkRecord record) {
-    if (record.isFolder()) {
-      if (!inserter.insertFolder(record)) {
-        Logger.warn(LOG_TAG, "Folder with known parent with guid " + record.parentID + " failed to insert!");
-        return;
-      }
-      Logger.debug(LOG_TAG, "Folder with known parent with guid " + record.parentID + " inserted; adding to inserted folders.");
-      insertedFolders.add(record.guid);
-    } else {
-      Logger.debug(LOG_TAG, "Non-folder has known parent with guid " + record.parentID + "; adding to insertion queue.");
-      nonFoldersToWrite.add(record);
-    }
-
-    // Now process record's children.
-    Set<BookmarkRecord> waiting = recordsWaitingForParent.remove(record.guid);
-    if (waiting == null) {
-      return;
-    }
-    for (BookmarkRecord waiter : waiting) {
-      recursivelyEnqueueRecordAndChildren(waiter);
-    }
-  }
-
-  /**
-   * Enqueue a folder.
-   *
-   * @param record
-   *          the folder to enqueue.
-   */
-  protected void enqueueFolder(BookmarkRecord record) {
-    Logger.debug(LOG_TAG, "Inserting folder with guid " + record.guid);
-
-    if (!insertedFolders.contains(record.parentID)) {
-      Logger.debug(LOG_TAG, "Folder has unknown parent with guid " + record.parentID + "; keeping until we see the parent.");
-      addRecordWithUnwrittenParent(record);
-      return;
-    }
-
-    // Parent is known; add as much of the tree as this roots.
-    recursivelyEnqueueRecordAndChildren(record);
-    flushNonFoldersIfNecessary();
-  }
-
-  /**
-   * Enqueue a non-folder.
-   *
-   * @param record
-   *          the non-folder to enqueue.
-   */
-  protected void enqueueNonFolder(BookmarkRecord record) {
-    Logger.debug(LOG_TAG, "Inserting non-folder with guid " + record.guid);
-
-    if (!insertedFolders.contains(record.parentID)) {
-      Logger.debug(LOG_TAG, "Non-folder has unknown parent with guid " + record.parentID + "; keeping until we see the parent.");
-      addRecordWithUnwrittenParent(record);
-      return;
-    }
-
-    // Parent is known; add to insertion queue and maybe write.
-    Logger.debug(LOG_TAG, "Non-folder has known parent with guid " + record.parentID + "; adding to insertion queue.");
-    nonFoldersToWrite.add(record);
-    flushNonFoldersIfNecessary();
-  }
-
-  /**
-   * Enqueue a bookmark record for eventual insertion.
-   *
-   * @param record
-   *          the <code>BookmarkRecord</code> to enqueue.
-   */
-  public void enqueueRecord(BookmarkRecord record) {
-    if (record.isFolder()) {
-      enqueueFolder(record);
-    } else {
-      enqueueNonFolder(record);
-    }
-    if (DEBUG) {
-      dumpState();
-    }
-  }
-
-  /**
-   * Flush non-folders; empties the insertion queue entirely.
-   */
-  protected void flushNonFolders() {
-    inserter.bulkInsertNonFolders(nonFoldersToWrite); // All errors are handled in bulkInsertNonFolders.
-    nonFoldersToWrite.clear();
-  }
-
-  /**
-   * Flush non-folder insertions if there are many of them; empties the
-   * insertion queue entirely.
-   */
-  protected void flushNonFoldersIfNecessary() {
-    int num = nonFoldersToWrite.size();
-    if (num < flushThreshold) {
-      Logger.debug(LOG_TAG, "Incremental flush called with " + num + " < " + flushThreshold + " non-folders; not flushing.");
-      return;
-    }
-    Logger.debug(LOG_TAG, "Incremental flush called with " + num + " non-folders; flushing.");
-    flushNonFolders();
-  }
-
-  /**
-   * Insert all remaining folders followed by all remaining non-folders,
-   * regardless of whether parent records have been successfully inserted.
-   */
-  public void finishUp() {
-    // Iterate through all waiting records, writing the folders and collecting
-    // the non-folders for bulk insertion.
-    int numFolders = 0;
-    int numNonFolders = 0;
-    for (Set<BookmarkRecord> records : recordsWaitingForParent.values()) {
-      for (BookmarkRecord record : records) {
-        if (!record.isFolder()) {
-          numNonFolders += 1;
-          nonFoldersToWrite.add(record);
-          continue;
-        }
-
-        numFolders += 1;
-        if (!inserter.insertFolder(record)) {
-          Logger.warn(LOG_TAG, "Folder with known parent with guid " + record.parentID + " failed to insert!");
-          continue;
-        }
-
-        Logger.debug(LOG_TAG, "Folder with known parent with guid " + record.parentID + " inserted; adding to inserted folders.");
-        insertedFolders.add(record.guid);
-      }
-    }
-    recordsWaitingForParent.clear();
-    flushNonFolders();
-
-    Logger.debug(LOG_TAG, "finishUp inserted " +
-        numFolders + " folders without known parents and " +
-        numNonFolders + " non-folders without known parents.");
-    if (DEBUG) {
-      dumpState();
-    }
-  }
-
-  public void clear() {
-    this.insertedFolders.clear();
-    this.nonFoldersToWrite.clear();
-    this.recordsWaitingForParent.clear();
-  }
-
-  // For debugging.
-  public boolean isClear() {
-    return nonFoldersToWrite.isEmpty() && recordsWaitingForParent.isEmpty();
-  }
-
-  // For debugging.
-  public void dumpState() {
-    ArrayList<String> readies = new ArrayList<String>();
-    for (BookmarkRecord record : nonFoldersToWrite) {
-      readies.add(record.guid);
-    }
-    String ready = Utils.toCommaSeparatedString(new ArrayList<String>(readies));
-
-    ArrayList<String> waits = new ArrayList<String>();
-    for (Set<BookmarkRecord> recs : recordsWaitingForParent.values()) {
-      for (BookmarkRecord rec : recs) {
-        waits.add(rec.guid);
-      }
-    }
-    String waiting = Utils.toCommaSeparatedString(waits);
-    String known = Utils.toCommaSeparatedString(insertedFolders);
-
-    Logger.debug(LOG_TAG, "Q=(" + ready + "), W = (" + waiting + "), P=(" + known + ")");
-  }
-
-  public interface BookmarkInserter {
-    /**
-     * Insert a single folder.
-     * <p>
-     * All exceptions should be caught and all delegate callbacks invoked here.
-     *
-     * @param record
-     *          the record to insert.
-     * @return
-     *          <code>true</code> if the folder was inserted; <code>false</code> otherwise.
-     */
-    public boolean insertFolder(BookmarkRecord record);
-
-    /**
-     * Insert many non-folders. Each non-folder's parent was already present in
-     * the database before this <code>BookmarkInsertionsManager</code> was
-     * created, or had <code>insertFolder</code> called with it as argument (and
-     * possibly was not inserted).
-     * <p>
-     * All exceptions should be caught and all delegate callbacks invoked here.
-     *
-     * @param records
-     *          the records to insert.
-     */
-    public void bulkInsertNonFolders(Collection<BookmarkRecord> records);
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/BrowserContractHelpers.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/BrowserContractHelpers.java
deleted file mode 100644
index e83aea0..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/BrowserContractHelpers.java
+++ /dev/null
@@ -1,154 +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.sync.repositories.android;
-
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Map;
-
-import org.mozilla.gecko.db.BrowserContract;
-import org.mozilla.gecko.sync.setup.Constants;
-
-import android.net.Uri;
-
-public class BrowserContractHelpers extends BrowserContract {
-
-  protected static Uri withSyncAndDeletedAndProfile(Uri u) {
-    return u.buildUpon()
-            .appendQueryParameter(PARAM_PROFILE, Constants.DEFAULT_PROFILE)
-            .appendQueryParameter(PARAM_IS_SYNC, "true")
-            .appendQueryParameter(PARAM_SHOW_DELETED, "true")
-            .build();
-  }
-  protected static Uri withSyncAndProfile(Uri u) {
-    return u.buildUpon()
-        .appendQueryParameter(PARAM_PROFILE, Constants.DEFAULT_PROFILE)
-        .appendQueryParameter(PARAM_IS_SYNC, "true")
-        .build();
-  }
-
-  public static final Uri BOOKMARKS_CONTENT_URI            = withSyncAndDeletedAndProfile(Bookmarks.CONTENT_URI);
-  public static final Uri BOOKMARKS_PARENTS_CONTENT_URI    = withSyncAndDeletedAndProfile(Bookmarks.PARENTS_CONTENT_URI);
-  public static final Uri BOOKMARKS_POSITIONS_CONTENT_URI  = withSyncAndDeletedAndProfile(Bookmarks.POSITIONS_CONTENT_URI);
-  public static final Uri HISTORY_CONTENT_URI              = withSyncAndDeletedAndProfile(History.CONTENT_URI);
-  public static final Uri VISITS_CONTENT_URI               = withSyncAndDeletedAndProfile(Visits.CONTENT_URI);
-  public static final Uri SCHEMA_CONTENT_URI               = withSyncAndDeletedAndProfile(Schema.CONTENT_URI);
-  public static final Uri PASSWORDS_CONTENT_URI            = withSyncAndDeletedAndProfile(Passwords.CONTENT_URI);
-  public static final Uri DELETED_PASSWORDS_CONTENT_URI    = withSyncAndDeletedAndProfile(DeletedPasswords.CONTENT_URI);
-  public static final Uri FORM_HISTORY_CONTENT_URI         = withSyncAndProfile(FormHistory.CONTENT_URI);
-  public static final Uri DELETED_FORM_HISTORY_CONTENT_URI = withSyncAndProfile(DeletedFormHistory.CONTENT_URI);
-  public static final Uri TABS_CONTENT_URI                 = withSyncAndProfile(Tabs.CONTENT_URI);
-  public static final Uri CLIENTS_CONTENT_URI              = withSyncAndProfile(Clients.CONTENT_URI);
-  public static final Uri LOGINS_CONTENT_URI               = withSyncAndProfile(Logins.CONTENT_URI);
-
-  public static final String[] PasswordColumns = new String[] {
-    Passwords.ID,
-    Passwords.HOSTNAME,
-    Passwords.HTTP_REALM,
-    Passwords.FORM_SUBMIT_URL,
-    Passwords.USERNAME_FIELD,
-    Passwords.PASSWORD_FIELD,
-    Passwords.ENCRYPTED_USERNAME,
-    Passwords.ENCRYPTED_PASSWORD,
-    Passwords.ENC_TYPE,
-    Passwords.TIME_CREATED,
-    Passwords.TIME_LAST_USED,
-    Passwords.TIME_PASSWORD_CHANGED,
-    Passwords.TIMES_USED,
-    Passwords.GUID
-  };
-
-  public static final String[] HistoryColumns = new String[] {
-    CommonColumns._ID,
-    SyncColumns.GUID,
-    SyncColumns.DATE_CREATED,
-    SyncColumns.DATE_MODIFIED,
-    SyncColumns.IS_DELETED,
-    History.TITLE,
-    History.URL,
-    History.DATE_LAST_VISITED,
-    History.VISITS
-  };
-
-  public static final String[] BookmarkColumns = new String[] {
-    CommonColumns._ID,
-    SyncColumns.GUID,
-    SyncColumns.DATE_CREATED,
-    SyncColumns.DATE_MODIFIED,
-    SyncColumns.IS_DELETED,
-    Bookmarks.TITLE,
-    Bookmarks.URL,
-    Bookmarks.TYPE,
-    Bookmarks.PARENT,
-    Bookmarks.POSITION,
-    Bookmarks.TAGS,
-    Bookmarks.DESCRIPTION,
-    Bookmarks.KEYWORD
-  };
-
-  public static final String[] FormHistoryColumns = new String[] {
-    FormHistory.ID,
-    FormHistory.GUID,
-    FormHistory.FIELD_NAME,
-    FormHistory.VALUE,
-    FormHistory.TIMES_USED,
-    FormHistory.FIRST_USED,
-    FormHistory.LAST_USED
-  };
-
-  public static final String[] DeletedColumns = new String[] {
-    BrowserContract.DeletedColumns.ID,
-    BrowserContract.DeletedColumns.GUID,
-    BrowserContract.DeletedColumns.TIME_DELETED
-  };
-
-  // Mapping from Sync types to Fennec types.
-  public static final String[] BOOKMARK_TYPE_CODE_TO_STRING = {
-    // Observe omissions: "microsummary", "item".
-    "folder", "bookmark", "separator", "livemark", "query"
-  };
-  private static final int MAX_BOOKMARK_TYPE_CODE = BOOKMARK_TYPE_CODE_TO_STRING.length - 1;
-  public static final Map<String, Integer> BOOKMARK_TYPE_STRING_TO_CODE;
-  static {
-    HashMap<String, Integer> t = new HashMap<String, Integer>();
-    t.put("folder",    Bookmarks.TYPE_FOLDER);
-    t.put("bookmark",  Bookmarks.TYPE_BOOKMARK);
-    t.put("separator", Bookmarks.TYPE_SEPARATOR);
-    t.put("livemark",  Bookmarks.TYPE_LIVEMARK);
-    t.put("query",     Bookmarks.TYPE_QUERY);
-    BOOKMARK_TYPE_STRING_TO_CODE = Collections.unmodifiableMap(t);
-  }
-
-  /**
-   * Convert a database bookmark type code into the Sync string equivalent.
-   *
-   * @param code one of the <code>Bookmarks.TYPE_*</code> enumerations.
-   * @return the string equivalent, or null if not found.
-   */
-  public static String typeStringForCode(int code) {
-    if (0 <= code && code <= MAX_BOOKMARK_TYPE_CODE) {
-      return BOOKMARK_TYPE_CODE_TO_STRING[code];
-    }
-    return null;
-  }
-
-  /**
-   * Convert a Sync type string into a Fennec type code.
-   *
-   * @param type a type string, such as "livemark".
-   * @return the type code, or -1 if not found.
-   */
-  public static int typeCodeForString(String type) {
-    Integer found = BOOKMARK_TYPE_STRING_TO_CODE.get(type);
-    if (found == null) {
-      return -1;
-    }
-    return found;
-  }
-
-  public static boolean isSupportedType(String type) {
-    return BOOKMARK_TYPE_STRING_TO_CODE.containsKey(type);
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/CachedSQLiteOpenHelper.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/CachedSQLiteOpenHelper.java
deleted file mode 100644
index 5c17f9b..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/CachedSQLiteOpenHelper.java
+++ /dev/null
@@ -1,62 +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.sync.repositories.android;
-
-import android.content.Context;
-import android.database.sqlite.SQLiteDatabase;
-import android.database.sqlite.SQLiteDatabase.CursorFactory;
-import android.database.sqlite.SQLiteOpenHelper;
-
-public abstract class CachedSQLiteOpenHelper extends SQLiteOpenHelper {
-
-  public CachedSQLiteOpenHelper(Context context, String name, CursorFactory factory,
-      int version) {
-    super(context, name, factory, version);
-  }
-
-  // Cache these so we don't have to track them across cursors. Call `close`
-  // when you're done.
-  private SQLiteDatabase readableDatabase;
-  private SQLiteDatabase writableDatabase;
-
-  synchronized protected SQLiteDatabase getCachedReadableDatabase() {
-    if (readableDatabase == null) {
-      if (writableDatabase == null) {
-        readableDatabase = this.getReadableDatabase();
-        return readableDatabase;
-      } else {
-        return writableDatabase;
-      }
-    } else {
-      return readableDatabase;
-    }
-  }
-
-  synchronized protected SQLiteDatabase getCachedWritableDatabase() {
-    if (writableDatabase == null) {
-      writableDatabase = this.getWritableDatabase();
-    }
-    return writableDatabase;
-  }
-
-  @Override
-  synchronized public void close() {
-    if (readableDatabase != null) {
-      readableDatabase.close();
-      readableDatabase = null;
-    }
-    if (writableDatabase != null) {
-      writableDatabase.close();
-      writableDatabase = null;
-    }
-    super.close();
-  }
-
-  // Used for testing.
-  public boolean isClosed() {
-    return readableDatabase == null &&
-           writableDatabase == null;
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/ClientsDatabase.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/ClientsDatabase.java
deleted file mode 100644
index 4962a20..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/ClientsDatabase.java
+++ /dev/null
@@ -1,252 +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.sync.repositories.android;
-
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.sync.repositories.NullCursorException;
-import org.mozilla.gecko.sync.repositories.domain.ClientRecord;
-
-import android.content.ContentValues;
-import android.content.Context;
-import android.database.Cursor;
-import android.database.sqlite.SQLiteDatabase;
-
-public class ClientsDatabase extends CachedSQLiteOpenHelper {
-
-  public static final String LOG_TAG = "ClientsDatabase";
-
-  // Database Specifications.
-  protected static final String DB_NAME = "clients_database";
-  protected static final int SCHEMA_VERSION = 3;
-
-  // Clients Table.
-  public static final String TBL_CLIENTS      = "clients";
-  public static final String COL_ACCOUNT_GUID = "guid";
-  public static final String COL_PROFILE      = "profile";
-  public static final String COL_NAME         = "name";
-  public static final String COL_TYPE         = "device_type";
-
-  // Optional fields.
-  public static final String COL_FORMFACTOR = "formfactor";
-  public static final String COL_OS = "os";
-  public static final String COL_APPLICATION = "application";
-  public static final String COL_APP_PACKAGE = "appPackage";
-  public static final String COL_DEVICE = "device";
-
-  public static final String[] TBL_CLIENTS_COLUMNS = new String[] { COL_ACCOUNT_GUID, COL_PROFILE, COL_NAME, COL_TYPE,
-                                                                    COL_FORMFACTOR, COL_OS, COL_APPLICATION, COL_APP_PACKAGE, COL_DEVICE };
-  public static final String TBL_CLIENTS_KEY = COL_ACCOUNT_GUID + " = ? AND " +
-                                               COL_PROFILE + " = ?";
-
-  // Commands Table.
-  public static final String TBL_COMMANDS = "commands";
-  public static final String COL_COMMAND  = "command";
-  public static final String COL_ARGS     = "args";
-
-  public static final String[] TBL_COMMANDS_COLUMNS    = new String[] { COL_ACCOUNT_GUID, COL_COMMAND, COL_ARGS };
-  public static final String   TBL_COMMANDS_KEY        = COL_ACCOUNT_GUID + " = ? AND " +
-                                                         COL_COMMAND + " = ? AND " +
-                                                         COL_ARGS + " = ?";
-  public static final String   TBL_COMMANDS_GUID_QUERY = COL_ACCOUNT_GUID + " = ? ";
-
-  private final RepoUtils.QueryHelper queryHelper;
-
-  public ClientsDatabase(Context context) {
-    super(context, DB_NAME, null, SCHEMA_VERSION);
-    this.queryHelper = new RepoUtils.QueryHelper(context, null, LOG_TAG);
-    Logger.debug(LOG_TAG, "ClientsDatabase instantiated.");
-  }
-
-  @Override
-  public void onCreate(SQLiteDatabase db) {
-    Logger.debug(LOG_TAG, "ClientsDatabase.onCreate().");
-    createClientsTable(db);
-    createCommandsTable(db);
-  }
-
-  public static void createClientsTable(SQLiteDatabase db) {
-    Logger.debug(LOG_TAG, "ClientsDatabase.createClientsTable().");
-    String createClientsTableSql = "CREATE TABLE " + TBL_CLIENTS + " ("
-        + COL_ACCOUNT_GUID + " TEXT, "
-        + COL_PROFILE + " TEXT, "
-        + COL_NAME + " TEXT, "
-        + COL_TYPE + " TEXT, "
-        + COL_FORMFACTOR + " TEXT, "
-        + COL_OS + " TEXT, "
-        + COL_APPLICATION + " TEXT, "
-        + COL_APP_PACKAGE + " TEXT, "
-        + COL_DEVICE + " TEXT, "
-        + "PRIMARY KEY (" + COL_ACCOUNT_GUID + ", " + COL_PROFILE + "))";
-    db.execSQL(createClientsTableSql);
-  }
-
-  public static void createCommandsTable(SQLiteDatabase db) {
-    Logger.debug(LOG_TAG, "ClientsDatabase.createCommandsTable().");
-    String createCommandsTableSql = "CREATE TABLE " + TBL_COMMANDS + " ("
-        + COL_ACCOUNT_GUID + " TEXT, "
-        + COL_COMMAND + " TEXT, "
-        + COL_ARGS + " TEXT, "
-        + "PRIMARY KEY (" + COL_ACCOUNT_GUID + ", " + COL_COMMAND + ", " + COL_ARGS + "), "
-        + "FOREIGN KEY (" + COL_ACCOUNT_GUID + ") REFERENCES " + TBL_CLIENTS + " (" + COL_ACCOUNT_GUID + "))";
-    db.execSQL(createCommandsTableSql);
-  }
-
-  @Override
-  public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
-    Logger.debug(LOG_TAG, "ClientsDatabase.onUpgrade(" + oldVersion + ", " + newVersion + ").");
-    if (oldVersion < 2) {
-      // For now we'll just drop and recreate the tables.
-      db.execSQL("DROP TABLE IF EXISTS " + TBL_CLIENTS);
-      db.execSQL("DROP TABLE IF EXISTS " + TBL_COMMANDS);
-      onCreate(db);
-      return;
-    }
-
-    if (newVersion >= 3) {
-      // Add the optional columns to clients.
-      db.execSQL("ALTER TABLE " + TBL_CLIENTS + " ADD COLUMN " + COL_FORMFACTOR + " TEXT");
-      db.execSQL("ALTER TABLE " + TBL_CLIENTS + " ADD COLUMN " + COL_OS + " TEXT");
-      db.execSQL("ALTER TABLE " + TBL_CLIENTS + " ADD COLUMN " + COL_APPLICATION + " TEXT");
-      db.execSQL("ALTER TABLE " + TBL_CLIENTS + " ADD COLUMN " + COL_APP_PACKAGE + " TEXT");
-      db.execSQL("ALTER TABLE " + TBL_CLIENTS + " ADD COLUMN " + COL_DEVICE + " TEXT");
-    }
-  }
-
-  public void wipeDB() {
-    SQLiteDatabase db = this.getCachedWritableDatabase();
-    onUpgrade(db, 0, SCHEMA_VERSION);
-  }
-
-  public void wipeClientsTable() {
-    SQLiteDatabase db = this.getCachedWritableDatabase();
-    db.execSQL("DELETE FROM " + TBL_CLIENTS);
-  }
-
-  public void wipeCommandsTable() {
-    SQLiteDatabase db = this.getCachedWritableDatabase();
-    db.execSQL("DELETE FROM " + TBL_COMMANDS);
-  }
-
-  // If a record with given GUID exists, we'll update it,
-  // otherwise we'll insert it.
-  public void store(String profileId, ClientRecord record) {
-    SQLiteDatabase db = this.getCachedWritableDatabase();
-
-    ContentValues cv = new ContentValues();
-    cv.put(COL_ACCOUNT_GUID, record.guid);
-    cv.put(COL_PROFILE, profileId);
-    cv.put(COL_NAME, record.name);
-    cv.put(COL_TYPE, record.type);
-
-    if (record.formfactor != null) {
-      cv.put(COL_FORMFACTOR, record.formfactor);
-    }
-
-    if (record.os != null) {
-      cv.put(COL_OS, record.os);
-    }
-
-    if (record.application != null) {
-      cv.put(COL_APPLICATION, record.application);
-    }
-
-    if (record.appPackage != null) {
-      cv.put(COL_APP_PACKAGE, record.appPackage);
-    }
-
-    if (record.device != null) {
-      cv.put(COL_DEVICE, record.device);
-    }
-
-    String[] args = new String[] { record.guid, profileId };
-    int rowsUpdated = db.update(TBL_CLIENTS, cv, TBL_CLIENTS_KEY, args);
-
-    if (rowsUpdated >= 1) {
-      Logger.debug(LOG_TAG, "Replaced client record for row with accountGUID " + record.guid);
-    } else {
-      long rowId = db.insert(TBL_CLIENTS, null, cv);
-      Logger.debug(LOG_TAG, "Inserted client record into row: " + rowId);
-    }
-  }
-
-  /**
-   * Store a command in the commands database if it doesn't already exist.
-   *
-   * @param accountGUID
-   * @param command - The command type
-   * @param args - A JSON string of args
-   * @throws NullCursorException
-   */
-  public void store(String accountGUID, String command, String args) throws NullCursorException {
-    if (Logger.LOG_PERSONAL_INFORMATION) {
-      Logger.pii(LOG_TAG, "Storing command " + command + " with args " + args);
-    } else {
-      Logger.trace(LOG_TAG, "Storing command " + command + ".");
-    }
-    SQLiteDatabase db = this.getCachedWritableDatabase();
-
-    ContentValues cv = new ContentValues();
-    cv.put(COL_ACCOUNT_GUID, accountGUID);
-    cv.put(COL_COMMAND, command);
-    if (args == null) {
-      cv.put(COL_ARGS, "[]");
-    } else {
-      cv.put(COL_ARGS, args);
-    }
-
-    Cursor cur = this.fetchSpecificCommand(accountGUID, command, args);
-    try {
-      if (cur.moveToFirst()) {
-        Logger.debug(LOG_TAG, "Command already exists in database.");
-        return;
-      }
-    } finally {
-      cur.close();
-    }
-
-    long rowId = db.insert(TBL_COMMANDS, null, cv);
-    Logger.debug(LOG_TAG, "Inserted command into row: " + rowId);
-  }
-
-  public Cursor fetchClientsCursor(String accountGUID, String profileId) throws NullCursorException {
-    String[] args = new String[] { accountGUID, profileId };
-    SQLiteDatabase db = this.getCachedReadableDatabase();
-
-    return queryHelper.safeQuery(db, ".fetchClientsCursor", TBL_CLIENTS, TBL_CLIENTS_COLUMNS, TBL_CLIENTS_KEY, args);
-  }
-
-  public Cursor fetchSpecificCommand(String accountGUID, String command, String commandArgs) throws NullCursorException {
-    String[] args = new String[] { accountGUID, command, commandArgs };
-    SQLiteDatabase db = this.getCachedReadableDatabase();
-
-    return queryHelper.safeQuery(db, ".fetchSpecificCommand", TBL_COMMANDS, TBL_COMMANDS_COLUMNS, TBL_COMMANDS_KEY, args);
-  }
-
-  public Cursor fetchCommandsForClient(String accountGUID) throws NullCursorException {
-    String[] args = new String[] { accountGUID };
-    SQLiteDatabase db = this.getCachedReadableDatabase();
-
-    return queryHelper.safeQuery(db, ".fetchCommandsForClient", TBL_COMMANDS, TBL_COMMANDS_COLUMNS, TBL_COMMANDS_GUID_QUERY, args);
-  }
-
-  public Cursor fetchAllClients() throws NullCursorException {
-    SQLiteDatabase db = this.getCachedReadableDatabase();
-
-    return queryHelper.safeQuery(db, ".fetchAllClients", TBL_CLIENTS, TBL_CLIENTS_COLUMNS, null, null);
-  }
-
-  public Cursor fetchAllCommands() throws NullCursorException {
-    SQLiteDatabase db = this.getCachedReadableDatabase();
-
-    return queryHelper.safeQuery(db, ".fetchAllCommands", TBL_COMMANDS, TBL_COMMANDS_COLUMNS, null, null);
-  }
-
-  public void deleteClient(String accountGUID, String profileId) {
-    String[] args = new String[] { accountGUID, profileId };
-
-    SQLiteDatabase db = this.getCachedWritableDatabase();
-    db.delete(TBL_CLIENTS, TBL_CLIENTS_KEY, args);
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/ClientsDatabaseAccessor.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/ClientsDatabaseAccessor.java
deleted file mode 100644
index 4af84ce..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/ClientsDatabaseAccessor.java
+++ /dev/null
@@ -1,178 +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.sync.repositories.android;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-import org.json.simple.JSONArray;
-
-import org.mozilla.gecko.sync.CommandProcessor.Command;
-import org.mozilla.gecko.sync.repositories.NullCursorException;
-import org.mozilla.gecko.sync.repositories.domain.ClientRecord;
-import org.mozilla.gecko.sync.setup.Constants;
-
-import android.content.Context;
-import android.database.Cursor;
-
-public class ClientsDatabaseAccessor {
-
-  public static final String LOG_TAG = "ClientsDatabaseAccessor";
-
-  private ClientsDatabase db;
-
-  // Need this so we can properly stub out the class for testing.
-  public ClientsDatabaseAccessor() {}
-
-  public ClientsDatabaseAccessor(Context context) {
-    db = new ClientsDatabase(context);
-  }
-
-  public void store(ClientRecord record) {
-    db.store(getProfileId(), record);
-  }
-
-  public void store(Collection<ClientRecord> records) {
-    for (ClientRecord record : records) {
-      this.store(record);
-    }
-  }
-
-  public void store(String accountGUID, Command command) throws NullCursorException {
-    db.store(accountGUID, command.commandType, command.args.toJSONString());
-  }
-
-  public ClientRecord fetchClient(String accountGUID) throws NullCursorException {
-    final Cursor cur = db.fetchClientsCursor(accountGUID, getProfileId());
-    try {
-      if (!cur.moveToFirst()) {
-        return null;
-      }
-      return recordFromCursor(cur);
-    } finally {
-      cur.close();
-    }
-  }
-
-  public Map<String, ClientRecord> fetchAllClients() throws NullCursorException {
-    final HashMap<String, ClientRecord> map = new HashMap<String, ClientRecord>();
-    final Cursor cur = db.fetchAllClients();
-    try {
-      if (!cur.moveToFirst()) {
-        return Collections.unmodifiableMap(map);
-      }
-
-      while (!cur.isAfterLast()) {
-        ClientRecord clientRecord = recordFromCursor(cur);
-        map.put(clientRecord.guid, clientRecord);
-        cur.moveToNext();
-      }
-      return Collections.unmodifiableMap(map);
-    } finally {
-      cur.close();
-    }
-  }
-
-  public List<Command> fetchAllCommands() throws NullCursorException {
-    final List<Command> commands = new ArrayList<Command>();
-    final Cursor cur = db.fetchAllCommands();
-    try {
-      if (!cur.moveToFirst()) {
-        return Collections.unmodifiableList(commands);
-      }
-
-      while (!cur.isAfterLast()) {
-        Command command = commandFromCursor(cur);
-        commands.add(command);
-        cur.moveToNext();
-      }
-      return Collections.unmodifiableList(commands);
-    } finally {
-      cur.close();
-    }
-  }
-
-  public List<Command> fetchCommandsForClient(String accountGUID) throws NullCursorException {
-    final List<Command> commands = new ArrayList<Command>();
-    final Cursor cur = db.fetchCommandsForClient(accountGUID);
-    try {
-      if (!cur.moveToFirst()) {
-        return Collections.unmodifiableList(commands);
-      }
-
-      while(!cur.isAfterLast()) {
-        Command command = commandFromCursor(cur);
-        commands.add(command);
-        cur.moveToNext();
-      }
-      return Collections.unmodifiableList(commands);
-    } finally {
-      cur.close();
-    }
-  }
-
-  protected static ClientRecord recordFromCursor(Cursor cur) {
-    final String accountGUID = RepoUtils.getStringFromCursor(cur, ClientsDatabase.COL_ACCOUNT_GUID);
-    final String clientName = RepoUtils.getStringFromCursor(cur, ClientsDatabase.COL_NAME);
-    final String clientType = RepoUtils.getStringFromCursor(cur, ClientsDatabase.COL_TYPE);
-
-    final ClientRecord record = new ClientRecord(accountGUID);
-    record.name = clientName;
-    record.type = clientType;
-
-    // Optional fields. These will either be null or strings.
-    record.formfactor = RepoUtils.optStringFromCursor(cur, ClientsDatabase.COL_FORMFACTOR);
-    record.os = RepoUtils.optStringFromCursor(cur, ClientsDatabase.COL_OS);
-    record.device = RepoUtils.optStringFromCursor(cur, ClientsDatabase.COL_DEVICE);
-    record.appPackage = RepoUtils.optStringFromCursor(cur, ClientsDatabase.COL_APP_PACKAGE);
-    record.application = RepoUtils.optStringFromCursor(cur, ClientsDatabase.COL_APPLICATION);
-
-    return record;
-  }
-
-  protected static Command commandFromCursor(Cursor cur) {
-    String commandType = RepoUtils.getStringFromCursor(cur, ClientsDatabase.COL_COMMAND);
-    JSONArray commandArgs = RepoUtils.getJSONArrayFromCursor(cur, ClientsDatabase.COL_ARGS);
-    return new Command(commandType, commandArgs);
-  }
-
-  public int clientsCount() {
-    try {
-      final Cursor cur = db.fetchAllClients();
-      try {
-        return cur.getCount();
-      } finally {
-        cur.close();
-      }
-    } catch (NullCursorException e) {
-      return 0;
-    }
-
-  }
-
-  private String getProfileId() {
-    return Constants.DEFAULT_PROFILE;
-  }
-
-  public void wipeDB() {
-    db.wipeDB();
-  }
-
-  public void wipeClientsTable() {
-    db.wipeClientsTable();
-  }
-
-  public void wipeCommandsTable() {
-    db.wipeCommandsTable();
-  }
-
-  public void close() {
-    db.close();
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/FennecTabsRepository.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/FennecTabsRepository.java
deleted file mode 100644
index 720d856..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/FennecTabsRepository.java
+++ /dev/null
@@ -1,383 +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.sync.repositories.android;
-
-import java.util.ArrayList;
-
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.background.db.Tab;
-import org.mozilla.gecko.db.BrowserContract;
-import org.mozilla.gecko.db.BrowserContract.Clients;
-import org.mozilla.gecko.sync.delegates.ClientsDataDelegate;
-import org.mozilla.gecko.sync.repositories.InactiveSessionException;
-import org.mozilla.gecko.sync.repositories.NoContentProviderException;
-import org.mozilla.gecko.sync.repositories.NoStoreDelegateException;
-import org.mozilla.gecko.sync.repositories.Repository;
-import org.mozilla.gecko.sync.repositories.RepositorySession;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionCreationDelegate;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionFetchRecordsDelegate;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionFinishDelegate;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionGuidsSinceDelegate;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionWipeDelegate;
-import org.mozilla.gecko.sync.repositories.domain.ClientRecord;
-import org.mozilla.gecko.sync.repositories.domain.Record;
-import org.mozilla.gecko.sync.repositories.domain.TabsRecord;
-
-import android.content.ContentProviderClient;
-import android.content.ContentValues;
-import android.content.Context;
-import android.database.Cursor;
-import android.net.Uri;
-import android.os.RemoteException;
-
-public class FennecTabsRepository extends Repository {
-  private static final String LOG_TAG = "FennecTabsRepository";
-
-  protected final ClientsDataDelegate clientsDataDelegate;
-
-  public FennecTabsRepository(ClientsDataDelegate clientsDataDelegate) {
-    this.clientsDataDelegate = clientsDataDelegate;
-  }
-
-  /**
-   * Note that -- unlike most repositories -- this will only fetch Fennec's tabs,
-   * and only store tabs from other clients.
-   *
-   * It will never retrieve tabs from other clients, or store tabs for Fennec,
-   * unless you use {@link #fetch(String[], RepositorySessionFetchRecordsDelegate)}
-   * and specify an explicit GUID.
-   */
-  public class FennecTabsRepositorySession extends RepositorySession {
-    protected static final String LOG_TAG = "FennecTabsSession";
-
-    private final ContentProviderClient tabsProvider;
-    private final ContentProviderClient clientsProvider;
-
-    protected final RepoUtils.QueryHelper tabsHelper;
-
-    protected final ClientsDatabaseAccessor clientsDatabase;
-
-    protected ContentProviderClient getContentProvider(final Context context, final Uri uri) throws NoContentProviderException {
-      ContentProviderClient client = context.getContentResolver().acquireContentProviderClient(uri);
-      if (client == null) {
-        throw new NoContentProviderException(uri);
-      }
-      return client;
-    }
-
-    protected void releaseProviders() {
-      try {
-        clientsProvider.release();
-      } catch (Exception e) {}
-      try {
-        tabsProvider.release();
-      } catch (Exception e) {}
-      clientsDatabase.close();
-    }
-
-    public FennecTabsRepositorySession(Repository repository, Context context) throws NoContentProviderException {
-      super(repository);
-      clientsProvider = getContentProvider(context, BrowserContractHelpers.CLIENTS_CONTENT_URI);
-      try {
-        tabsProvider = getContentProvider(context, BrowserContractHelpers.TABS_CONTENT_URI);
-      } catch (NoContentProviderException e) {
-        clientsProvider.release();
-        throw e;
-      } catch (Exception e) {
-        clientsProvider.release();
-        // Oh, Java.
-        throw new RuntimeException(e);
-      }
-
-      tabsHelper = new RepoUtils.QueryHelper(context, BrowserContractHelpers.TABS_CONTENT_URI, LOG_TAG);
-      clientsDatabase = new ClientsDatabaseAccessor(context);
-    }
-
-    @Override
-    public void abort() {
-      releaseProviders();
-      super.abort();
-    }
-
-    @Override
-    public void finish(final RepositorySessionFinishDelegate delegate) throws InactiveSessionException {
-      releaseProviders();
-      super.finish(delegate);
-    }
-
-    // Default parameters for local data: local client has null GUID. Override
-    // these to test against non-live data.
-    protected String localClientSelection() {
-      return BrowserContract.Tabs.CLIENT_GUID + " IS NULL";
-    }
-
-    protected String[] localClientSelectionArgs() {
-      return null;
-    }
-
-    @Override
-    public void guidsSince(final long timestamp,
-                           final RepositorySessionGuidsSinceDelegate delegate) {
-      // Bug 783692: Now that Bug 730039 has landed, we could implement this,
-      // but it's not a priority since it's not used (yet).
-      Logger.warn(LOG_TAG, "Not returning anything from guidsSince.");
-      delegateQueue.execute(new Runnable() {
-        @Override
-        public void run() {
-          delegate.onGuidsSinceSucceeded(new String[] {});
-        }
-      });
-    }
-
-    @Override
-    public void fetchSince(final long timestamp,
-                           final RepositorySessionFetchRecordsDelegate delegate) {
-      if (tabsProvider == null) {
-        throw new IllegalArgumentException("tabsProvider was null.");
-      }
-      if (tabsHelper == null) {
-        throw new IllegalArgumentException("tabsHelper was null.");
-      }
-
-      final String positionAscending = BrowserContract.Tabs.POSITION + " ASC";
-
-      final String localClientSelection = localClientSelection();
-      final String[] localClientSelectionArgs = localClientSelectionArgs();
-
-      final Runnable command = new Runnable() {
-        @Override
-        public void run() {
-          // We fetch all local tabs (since the record must contain them all)
-          // but only process the record if the timestamp is sufficiently
-          // recent, or if the client data has been modified.
-          try {
-            final Cursor cursor = tabsHelper.safeQuery(tabsProvider, ".fetchSince()", null,
-                localClientSelection, localClientSelectionArgs, positionAscending);
-            try {
-              final String localClientGuid = clientsDataDelegate.getAccountGUID();
-              final String localClientName = clientsDataDelegate.getClientName();
-              final TabsRecord tabsRecord = FennecTabsRepository.tabsRecordFromCursor(cursor, localClientGuid, localClientName);
-
-              if (tabsRecord.lastModified >= timestamp ||
-                  clientsDataDelegate.getLastModifiedTimestamp() >= timestamp) {
-                delegate.onFetchedRecord(tabsRecord);
-              }
-            } finally {
-              cursor.close();
-            }
-          } catch (Exception e) {
-            delegate.onFetchFailed(e, null);
-            return;
-          }
-          delegate.onFetchCompleted(now());
-        }
-      };
-
-      delegateQueue.execute(command);
-    }
-
-    @Override
-    public void fetch(final String[] guids,
-                      final RepositorySessionFetchRecordsDelegate delegate) {
-      // Bug 783692: Now that Bug 730039 has landed, we could implement this,
-      // but it's not a priority since it's not used (yet).
-      Logger.warn(LOG_TAG, "Not returning anything from fetch");
-      delegateQueue.execute(new Runnable() {
-        @Override
-        public void run() {
-          delegate.onFetchCompleted(now());
-        }
-      });
-    }
-
-    @Override
-    public void fetchAll(final RepositorySessionFetchRecordsDelegate delegate) {
-      fetchSince(0, delegate);
-    }
-
-    private static final String TABS_CLIENT_GUID_IS = BrowserContract.Tabs.CLIENT_GUID + " = ?";
-    private static final String CLIENT_GUID_IS = BrowserContract.Clients.GUID + " = ?";
-
-    @Override
-    public void store(final Record record) throws NoStoreDelegateException {
-      if (delegate == null) {
-        Logger.warn(LOG_TAG, "No store delegate.");
-        throw new NoStoreDelegateException();
-      }
-      if (record == null) {
-        Logger.error(LOG_TAG, "Record sent to store was null");
-        throw new IllegalArgumentException("Null record passed to FennecTabsRepositorySession.store().");
-      }
-      if (!(record instanceof TabsRecord)) {
-        Logger.error(LOG_TAG, "Can't store anything but a TabsRecord");
-        throw new IllegalArgumentException("Non-TabsRecord passed to FennecTabsRepositorySession.store().");
-      }
-      final TabsRecord tabsRecord = (TabsRecord) record;
-
-      Runnable command = new Runnable() {
-        @Override
-        public void run() {
-          Logger.debug(LOG_TAG, "Storing tabs for client " + tabsRecord.guid);
-          if (!isActive()) {
-            delegate.onRecordStoreFailed(new InactiveSessionException(null), record.guid);
-            return;
-          }
-          if (tabsRecord.guid == null) {
-            delegate.onRecordStoreFailed(new RuntimeException("Can't store record with null GUID."), record.guid);
-            return;
-          }
-
-          try {
-            // This is nice and easy: we *always* store.
-            final String[] selectionArgs = new String[] { tabsRecord.guid };
-            if (tabsRecord.deleted) {
-              try {
-                Logger.debug(LOG_TAG, "Clearing entry for client " + tabsRecord.guid);
-                clientsProvider.delete(BrowserContractHelpers.CLIENTS_CONTENT_URI,
-                                       CLIENT_GUID_IS,
-                                       selectionArgs);
-                delegate.onRecordStoreSucceeded(record.guid);
-              } catch (Exception e) {
-                delegate.onRecordStoreFailed(e, record.guid);
-              }
-              return;
-            }
-
-            // If it exists, update the client record; otherwise insert.
-            final ContentValues clientsCV = tabsRecord.getClientsContentValues();
-
-            final ClientRecord clientRecord = clientsDatabase.fetchClient(tabsRecord.guid);
-            if (null != clientRecord) {
-                // Null is an acceptable device type.
-                clientsCV.put(Clients.DEVICE_TYPE, clientRecord.type);
-            }
-
-            Logger.debug(LOG_TAG, "Updating clients provider.");
-            final int updated = clientsProvider.update(BrowserContractHelpers.CLIENTS_CONTENT_URI,
-                clientsCV,
-                CLIENT_GUID_IS,
-                selectionArgs);
-            if (0 == updated) {
-              clientsProvider.insert(BrowserContractHelpers.CLIENTS_CONTENT_URI, clientsCV);
-            }
-
-            // Now insert tabs.
-            final ContentValues[] tabsArray = tabsRecord.getTabsContentValues();
-            Logger.debug(LOG_TAG, "Inserting " + tabsArray.length + " tabs for client " + tabsRecord.guid);
-
-            tabsProvider.delete(BrowserContractHelpers.TABS_CONTENT_URI, TABS_CLIENT_GUID_IS, selectionArgs);
-            final int inserted = tabsProvider.bulkInsert(BrowserContractHelpers.TABS_CONTENT_URI, tabsArray);
-            Logger.trace(LOG_TAG, "Inserted: " + inserted);
-
-            delegate.onRecordStoreSucceeded(record.guid);
-          } catch (Exception e) {
-            Logger.warn(LOG_TAG, "Error storing tabs.", e);
-            delegate.onRecordStoreFailed(e, record.guid);
-          }
-        }
-      };
-
-      storeWorkQueue.execute(command);
-    }
-
-    @Override
-    public void wipe(RepositorySessionWipeDelegate delegate) {
-      try {
-        tabsProvider.delete(BrowserContractHelpers.TABS_CONTENT_URI, null, null);
-        clientsProvider.delete(BrowserContractHelpers.CLIENTS_CONTENT_URI, null, null);
-      } catch (RemoteException e) {
-        Logger.warn(LOG_TAG, "Got RemoteException in wipe.", e);
-        delegate.onWipeFailed(e);
-        return;
-      }
-      delegate.onWipeSucceeded();
-    }
-  }
-
-  @Override
-  public void createSession(RepositorySessionCreationDelegate delegate,
-                            Context context) {
-    try {
-      final FennecTabsRepositorySession session = new FennecTabsRepositorySession(this, context);
-      delegate.onSessionCreated(session);
-    } catch (Exception e) {
-      delegate.onSessionCreateFailed(e);
-    }
-  }
-
-  /**
-   * Extract a <code>TabsRecord</code> from a cursor.
-   * <p>
-   * Caller is responsible for creating and closing cursor. Each row of the
-   * cursor should be an individual tab record.
-   * <p>
-   * The extracted tabs record has the given client GUID and client name.
-   *
-   * @param cursor
-   *          to inspect.
-   * @param clientGuid
-   *          returned tabs record will have this client GUID.
-   * @param clientName
-   *          returned tabs record will have this client name.
-   * @return <code>TabsRecord</code> instance.
-   */
-  public static TabsRecord tabsRecordFromCursor(final Cursor cursor, final String clientGuid, final String clientName) {
-    final String collection = "tabs";
-    final TabsRecord record = new TabsRecord(clientGuid, collection, 0, false);
-    record.tabs = new ArrayList<Tab>();
-    record.clientName = clientName;
-
-    record.androidID = -1;
-    record.deleted = false;
-
-    record.lastModified = 0;
-
-    int position = cursor.getPosition();
-    try {
-      cursor.moveToFirst();
-      while (!cursor.isAfterLast()) {
-        final Tab tab = Tab.fromCursor(cursor);
-        record.tabs.add(tab);
-
-        if (tab.lastUsed > record.lastModified) {
-          record.lastModified = tab.lastUsed;
-        }
-
-        cursor.moveToNext();
-      }
-    } finally {
-      cursor.moveToPosition(position);
-    }
-
-    return record;
-  }
-
-  /**
-   * Deletes all non-local clients and their associated remote tabs.
-   */
-  public static void deleteNonLocalClientsAndTabs(Context context) {
-    final String nonLocalClientSelection = BrowserContract.Clients.GUID + " IS NOT NULL";
-
-    ContentProviderClient clientsProvider = context.getContentResolver()
-            .acquireContentProviderClient(BrowserContractHelpers.CLIENTS_CONTENT_URI);
-    if (clientsProvider == null) {
-        Logger.warn(LOG_TAG, "Unable to create clientsProvider!");
-        return;
-    }
-
-    try {
-      Logger.info(LOG_TAG, "Clearing all non-local clients and their associated remote tabs for default profile.");
-      clientsProvider.delete(BrowserContractHelpers.CLIENTS_CONTENT_URI, nonLocalClientSelection, null);
-    } catch (RemoteException e) {
-      Logger.warn(LOG_TAG, "Error while deleting", e);
-    } finally {
-      try {
-        clientsProvider.release();
-      } catch (Exception e) {
-        Logger.warn(LOG_TAG, "Got exception releasing clientsProvider!", e);
-      }
-    }
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/FormHistoryRepositorySession.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/FormHistoryRepositorySession.java
deleted file mode 100644
index 9beafa7..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/FormHistoryRepositorySession.java
+++ /dev/null
@@ -1,723 +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.sync.repositories.android;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.concurrent.Callable;
-
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.db.BrowserContract;
-import org.mozilla.gecko.db.BrowserContract.DeletedFormHistory;
-import org.mozilla.gecko.db.BrowserContract.FormHistory;
-import org.mozilla.gecko.sync.repositories.InactiveSessionException;
-import org.mozilla.gecko.sync.repositories.NoContentProviderException;
-import org.mozilla.gecko.sync.repositories.NoStoreDelegateException;
-import org.mozilla.gecko.sync.repositories.NullCursorException;
-import org.mozilla.gecko.sync.repositories.RecordFilter;
-import org.mozilla.gecko.sync.repositories.Repository;
-import org.mozilla.gecko.sync.repositories.StoreTrackingRepositorySession;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionCreationDelegate;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionFetchRecordsDelegate;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionFinishDelegate;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionGuidsSinceDelegate;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionWipeDelegate;
-import org.mozilla.gecko.sync.repositories.domain.FormHistoryRecord;
-import org.mozilla.gecko.sync.repositories.domain.Record;
-
-import android.content.ContentProviderClient;
-import android.content.ContentValues;
-import android.content.Context;
-import android.database.Cursor;
-import android.net.Uri;
-import android.os.RemoteException;
-
-public class FormHistoryRepositorySession extends
-    StoreTrackingRepositorySession {
-  public static final String LOG_TAG = "FormHistoryRepoSess";
-
-  /**
-   * Number of records to insert in one batch.
-   */
-  public static final int INSERT_ITEM_THRESHOLD = 200;
-
-  private static final Uri FORM_HISTORY_CONTENT_URI = BrowserContractHelpers.FORM_HISTORY_CONTENT_URI;
-  private static final Uri DELETED_FORM_HISTORY_CONTENT_URI = BrowserContractHelpers.DELETED_FORM_HISTORY_CONTENT_URI;
-
-  public static class FormHistoryRepository extends Repository {
-
-    @Override
-    public void createSession(RepositorySessionCreationDelegate delegate,
-                              Context context) {
-      try {
-        final FormHistoryRepositorySession session = new FormHistoryRepositorySession(this, context);
-        delegate.onSessionCreated(session);
-      } catch (Exception e) {
-        delegate.onSessionCreateFailed(e);
-      }
-    }
-  }
-
-  protected final ContentProviderClient formsProvider;
-  protected final RepoUtils.QueryHelper regularHelper;
-  protected final RepoUtils.QueryHelper deletedHelper;
-
-  /**
-   * Acquire the content provider client.
-   * <p>
-   * The caller is responsible for releasing the client.
-   *
-   * @param context The application context.
-   * @return The <code>ContentProviderClient</code>.
-   * @throws NoContentProviderException
-   */
-  public static ContentProviderClient acquireContentProvider(final Context context)
-      throws NoContentProviderException {
-    Uri uri = BrowserContract.FORM_HISTORY_AUTHORITY_URI;
-    ContentProviderClient client = context.getContentResolver().acquireContentProviderClient(uri);
-    if (client == null) {
-      throw new NoContentProviderException(uri);
-    }
-    return client;
-  }
-
-  protected void releaseProviders() {
-    try {
-      if (formsProvider != null) {
-        formsProvider.release();
-      }
-    } catch (Exception e) {
-    }
-  }
-
-  // Only used for testing.
-  public ContentProviderClient getFormsProvider() {
-    return formsProvider;
-  }
-
-  public FormHistoryRepositorySession(Repository repository, Context context)
-      throws NoContentProviderException {
-    super(repository);
-    formsProvider = acquireContentProvider(context);
-    regularHelper = new RepoUtils.QueryHelper(context, BrowserContractHelpers.FORM_HISTORY_CONTENT_URI, LOG_TAG);
-    deletedHelper = new RepoUtils.QueryHelper(context, BrowserContractHelpers.DELETED_FORM_HISTORY_CONTENT_URI, LOG_TAG);
-  }
-
-  @Override
-  public void abort() {
-    releaseProviders();
-    super.abort();
-  }
-
-  @Override
-  public void finish(final RepositorySessionFinishDelegate delegate)
-      throws InactiveSessionException {
-    releaseProviders();
-    super.finish(delegate);
-  }
-
-  protected static final String[] GUID_COLUMNS = new String[] { FormHistory.GUID };
-
-  @Override
-  public void guidsSince(final long timestamp, final RepositorySessionGuidsSinceDelegate delegate) {
-    Runnable command = new Runnable() {
-      @Override
-      public void run() {
-        if (!isActive()) {
-          delegate.onGuidsSinceFailed(new InactiveSessionException(null));
-          return;
-        }
-
-        ArrayList<String> guids = new ArrayList<String>();
-
-        final long sharedEnd = now();
-        Cursor cur = null;
-        try {
-          cur = regularHelper.safeQuery(formsProvider, "", GUID_COLUMNS, regularBetween(timestamp, sharedEnd), null, null);
-          cur.moveToFirst();
-          while (!cur.isAfterLast()) {
-            guids.add(cur.getString(0));
-            cur.moveToNext();
-          }
-        } catch (RemoteException | NullCursorException e) {
-          delegate.onGuidsSinceFailed(e);
-          return;
-        } finally {
-          if (cur != null) {
-            cur.close();
-          }
-        }
-
-        try {
-          cur = deletedHelper.safeQuery(formsProvider, "", GUID_COLUMNS, deletedBetween(timestamp, sharedEnd), null, null);
-          cur.moveToFirst();
-          while (!cur.isAfterLast()) {
-            guids.add(cur.getString(0));
-            cur.moveToNext();
-          }
-        } catch (RemoteException | NullCursorException e) {
-          delegate.onGuidsSinceFailed(e);
-          return;
-        } finally {
-          if (cur != null) {
-            cur.close();
-          }
-        }
-
-        String guidsArray[] = guids.toArray(new String[guids.size()]);
-        delegate.onGuidsSinceSucceeded(guidsArray);
-      }
-    };
-    delegateQueue.execute(command);
-  }
-
-  protected static FormHistoryRecord retrieveDuringFetch(final Cursor cursor) {
-    // A simple and efficient way to distinguish two tables.
-    if (cursor.getColumnCount() == BrowserContractHelpers.FormHistoryColumns.length) {
-      return formHistoryRecordFromCursor(cursor);
-    } else {
-      return deletedFormHistoryRecordFromCursor(cursor);
-    }
-  }
-
-  protected static FormHistoryRecord formHistoryRecordFromCursor(final Cursor cursor) {
-    String guid = RepoUtils.getStringFromCursor(cursor, FormHistory.GUID);
-    String collection = "forms";
-    FormHistoryRecord record = new FormHistoryRecord(guid, collection, 0, false);
-
-    record.fieldName = RepoUtils.getStringFromCursor(cursor, FormHistory.FIELD_NAME);
-    record.fieldValue = RepoUtils.getStringFromCursor(cursor, FormHistory.VALUE);
-    record.androidID = RepoUtils.getLongFromCursor(cursor, FormHistory.ID);
-    record.lastModified = RepoUtils.getLongFromCursor(cursor, FormHistory.FIRST_USED) / 1000; // Convert microseconds to milliseconds.
-    record.deleted = false;
-
-    record.log(LOG_TAG);
-    return record;
-  }
-
-  protected static FormHistoryRecord deletedFormHistoryRecordFromCursor(final Cursor cursor) {
-    String guid = RepoUtils.getStringFromCursor(cursor, DeletedFormHistory.GUID);
-    String collection = "forms";
-    FormHistoryRecord record = new FormHistoryRecord(guid, collection, 0, false);
-
-    record.guid = RepoUtils.getStringFromCursor(cursor, DeletedFormHistory.GUID);
-    record.androidID = RepoUtils.getLongFromCursor(cursor, DeletedFormHistory.ID);
-    record.lastModified = RepoUtils.getLongFromCursor(cursor, DeletedFormHistory.TIME_DELETED);
-    record.deleted = true;
-
-    record.log(LOG_TAG);
-    return record;
-  }
-
-  protected static void fetchFromCursor(final Cursor cursor, final RecordFilter filter, final RepositorySessionFetchRecordsDelegate delegate)
-      throws NullCursorException {
-    Logger.debug(LOG_TAG, "Fetch from cursor");
-    if (cursor == null) {
-      throw new NullCursorException(null);
-    }
-    try {
-      if (!cursor.moveToFirst()) {
-        return;
-      }
-      while (!cursor.isAfterLast()) {
-        Record r = retrieveDuringFetch(cursor);
-        if (r != null) {
-          if (filter == null || !filter.excludeRecord(r)) {
-            Logger.trace(LOG_TAG, "Processing record " + r.guid);
-            delegate.onFetchedRecord(r);
-          } else {
-            Logger.debug(LOG_TAG, "Skipping filtered record " + r.guid);
-          }
-        }
-        cursor.moveToNext();
-      }
-    } finally {
-      Logger.trace(LOG_TAG, "Closing cursor after fetch.");
-      cursor.close();
-    }
-  }
-
-  protected void fetchHelper(final RepositorySessionFetchRecordsDelegate delegate, final long end, final List<Callable<Cursor>> cursorCallables) {
-    if (this.storeTracker == null) {
-      throw new IllegalStateException("Store tracker not yet initialized!");
-    }
-
-    final RecordFilter filter = this.storeTracker.getFilter();
-
-    Runnable command = new Runnable() {
-      @Override
-      public void run() {
-        if (!isActive()) {
-          delegate.onFetchFailed(new InactiveSessionException(null), null);
-          return;
-        }
-
-        for (Callable<Cursor> cursorCallable : cursorCallables) {
-          Cursor cursor = null;
-          try {
-            cursor = cursorCallable.call();
-            fetchFromCursor(cursor, filter, delegate); // Closes cursor.
-          } catch (Exception e) {
-            Logger.warn(LOG_TAG, "Exception during fetchHelper", e);
-            delegate.onFetchFailed(e, null);
-            return;
-          }
-        }
-
-        delegate.onFetchCompleted(end);
-      }
-    };
-
-    delegateQueue.execute(command);
-  }
-
-  protected static String regularBetween(long start, long end) {
-    return FormHistory.FIRST_USED + " >= " + Long.toString(1000 * start) + " AND " +
-           FormHistory.FIRST_USED + " <= " + Long.toString(1000 * end); // Microseconds.
-  }
-
-  protected static String deletedBetween(long start, long end) {
-    return DeletedFormHistory.TIME_DELETED + " >= " + Long.toString(start) + " AND " +
-           DeletedFormHistory.TIME_DELETED + " <= " + Long.toString(end); // Milliseconds.
-  }
-
-  @Override
-  public void fetchSince(final long timestamp, final RepositorySessionFetchRecordsDelegate delegate) {
-    Logger.trace(LOG_TAG, "Running fetchSince(" + timestamp + ").");
-
-    /*
-     * We need to be careful about the timestamp we complete the fetch with. If
-     * the first cursor Callable takes a year, then the second could return
-     * records long after the first was kicked off. To protect against this, we
-     * set an end point and bound our search.
-     */
-    final long sharedEnd = now();
-
-    Callable<Cursor> regularCallable = new Callable<Cursor>() {
-      @Override
-      public Cursor call() throws Exception {
-        return regularHelper.safeQuery(formsProvider, ".fetchSince(regular)", null, regularBetween(timestamp, sharedEnd), null, null);
-      }
-    };
-
-    Callable<Cursor> deletedCallable = new Callable<Cursor>() {
-      @Override
-      public Cursor call() throws Exception {
-        return deletedHelper.safeQuery(formsProvider, ".fetchSince(deleted)", null, deletedBetween(timestamp, sharedEnd), null, null);
-      }
-    };
-
-    @SuppressWarnings("unchecked")
-    List<Callable<Cursor>> callableCursors = Arrays.asList(regularCallable, deletedCallable);
-
-    fetchHelper(delegate, sharedEnd, callableCursors);
-  }
-
-  @Override
-  public void fetchAll(RepositorySessionFetchRecordsDelegate delegate) {
-    Logger.trace(LOG_TAG, "Running fetchAll.");
-    fetchSince(0, delegate);
-  }
-
-  @Override
-  public void fetch(final String[] guids, final RepositorySessionFetchRecordsDelegate delegate) {
-    Logger.trace(LOG_TAG, "Running fetch.");
-
-    final long sharedEnd = now();
-    final String where = RepoUtils.computeSQLInClause(guids.length, FormHistory.GUID);
-
-    Callable<Cursor> regularCallable = new Callable<Cursor>() {
-      @Override
-      public Cursor call() throws Exception {
-        String regularWhere = where + " AND " + FormHistory.FIRST_USED + " <= " + Long.toString(1000 * sharedEnd); // Microseconds.
-        return regularHelper.safeQuery(formsProvider, ".fetch(regular)", null, regularWhere, guids, null);
-      }
-    };
-
-    Callable<Cursor> deletedCallable = new Callable<Cursor>() {
-      @Override
-      public Cursor call() throws Exception {
-        String deletedWhere = where + " AND " + DeletedFormHistory.TIME_DELETED + " <= " + Long.toString(sharedEnd); // Milliseconds.
-        return deletedHelper.safeQuery(formsProvider, ".fetch(deleted)", null, deletedWhere, guids, null);
-      }
-    };
-
-    @SuppressWarnings("unchecked")
-    List<Callable<Cursor>> callableCursors = Arrays.asList(regularCallable, deletedCallable);
-
-    fetchHelper(delegate, sharedEnd, callableCursors);
-  }
-
-  protected static final String GUID_IS = FormHistory.GUID + " = ?";
-
-  protected Record findExistingRecordByGuid(String guid)
-      throws RemoteException, NullCursorException {
-    Cursor cursor = null;
-    try {
-      cursor = regularHelper.safeQuery(formsProvider, ".findExistingRecordByGuid(regular)",
-          null, GUID_IS, new String[] { guid }, null);
-      if (cursor.moveToFirst()) {
-        return formHistoryRecordFromCursor(cursor);
-      }
-    } finally {
-      if (cursor != null) {
-        cursor.close();
-      }
-    }
-
-    try {
-      cursor = deletedHelper.safeQuery(formsProvider, ".findExistingRecordByGuid(deleted)",
-          null, GUID_IS, new String[] { guid }, null);
-      if (cursor.moveToFirst()) {
-        return deletedFormHistoryRecordFromCursor(cursor);
-      }
-    } finally {
-      if (cursor != null) {
-        cursor.close();
-      }
-    }
-
-    return null;
-  }
-
-  protected Record findExistingRecordByPayload(Record rawRecord)
-      throws RemoteException, NullCursorException {
-    if (!rawRecord.deleted) {
-      FormHistoryRecord record = (FormHistoryRecord) rawRecord;
-      Cursor cursor = null;
-      try {
-        String where = FormHistory.FIELD_NAME + " = ? AND " + FormHistory.VALUE + " = ?";
-        cursor = regularHelper.safeQuery(formsProvider, ".findExistingRecordByPayload",
-            null, where, new String[] { record.fieldName, record.fieldValue }, null);
-        if (cursor.moveToFirst()) {
-          return formHistoryRecordFromCursor(cursor);
-        }
-      } finally {
-        if (cursor != null) {
-          cursor.close();
-        }
-      }
-    }
-
-    return null;
-  }
-
-  /**
-   * Called when a record with locally known GUID has been reported deleted by
-   * the server.
-   * <p>
-   * We purge the record's GUID from the regular and deleted tables.
-   *
-   * @param existingRecord
-   *          The local <code>Record</code> to replace.
-   * @throws RemoteException
-   */
-  protected void deleteExistingRecord(Record existingRecord) throws RemoteException {
-    if (existingRecord.deleted) {
-      formsProvider.delete(DELETED_FORM_HISTORY_CONTENT_URI, GUID_IS, new String[] { existingRecord.guid });
-      return;
-    }
-    formsProvider.delete(FORM_HISTORY_CONTENT_URI, GUID_IS, new String[] { existingRecord.guid });
-  }
-
-  protected static ContentValues contentValuesForRegularRecord(Record rawRecord) {
-    if (rawRecord.deleted) {
-      throw new IllegalArgumentException("Deleted record passed to insertNewRegularRecord.");
-    }
-
-    FormHistoryRecord record = (FormHistoryRecord) rawRecord;
-    ContentValues cv = new ContentValues();
-    cv.put(FormHistory.GUID, record.guid);
-    cv.put(FormHistory.FIELD_NAME, record.fieldName);
-    cv.put(FormHistory.VALUE, record.fieldValue);
-    cv.put(FormHistory.FIRST_USED, 1000 * record.lastModified); // Microseconds.
-    return cv;
-  }
-
-  protected final Object recordsBufferMonitor = new Object();
-  protected ArrayList<ContentValues> recordsBuffer = new ArrayList<ContentValues>();
-
-  protected void enqueueRegularRecord(Record record) {
-    synchronized (recordsBufferMonitor) {
-      if (recordsBuffer.size() >= INSERT_ITEM_THRESHOLD) {
-        // Insert the existing contents, then enqueue.
-        try {
-          flushInsertQueue();
-        } catch (Exception e) {
-          delegate.onRecordStoreFailed(e, record.guid);
-          return;
-        }
-      }
-      // Store the ContentValues, rather than the record.
-      recordsBuffer.add(contentValuesForRegularRecord(record));
-    }
-  }
-
-  // Should always be called from storeWorkQueue.
-  protected void flushInsertQueue() throws RemoteException {
-    synchronized (recordsBufferMonitor) {
-      if (recordsBuffer.size() > 0) {
-        final ContentValues[] outgoing = recordsBuffer.toArray(new ContentValues[recordsBuffer.size()]);
-        recordsBuffer = new ArrayList<ContentValues>();
-
-        if (outgoing == null || outgoing.length == 0) {
-          Logger.debug(LOG_TAG, "No form history items to insert; returning immediately.");
-          return;
-        }
-
-        long before = System.currentTimeMillis();
-        formsProvider.bulkInsert(FORM_HISTORY_CONTENT_URI, outgoing);
-        long after = System.currentTimeMillis();
-        Logger.debug(LOG_TAG, "Inserted " + outgoing.length + " form history items in (" + (after - before) + " milliseconds).");
-      }
-    }
-  }
-
-  @Override
-  public void storeDone() {
-    Runnable command = new Runnable() {
-      @Override
-      public void run() {
-        Logger.debug(LOG_TAG, "Checking for residual form history items to insert.");
-        try {
-          synchronized (recordsBufferMonitor) {
-            flushInsertQueue();
-          }
-          storeDone(now());
-        } catch (Exception e) {
-          // XXX TODO
-          delegate.onRecordStoreFailed(e, null);
-        }
-      }
-    };
-    storeWorkQueue.execute(command);
-  }
-
-  /**
-   * Called when a regular record with locally unknown GUID has been fetched
-   * from the server.
-   * <p>
-   * Since the record is regular, we insert it into the regular table.
-   *
-   * @param record The regular <code>Record</code> from the server.
-   * @throws RemoteException
-   */
-  protected void insertNewRegularRecord(Record record)
-      throws RemoteException {
-    enqueueRegularRecord(record);
-  }
-
-  /**
-   * Called when a regular record with has been fetched from the server and
-   * should replace an existing record.
-   * <p>
-   * We delete the existing record entirely, and then insert the new record into
-   * the regular table.
-   *
-   * @param toStore
-   *          The regular <code>Record</code> from the server.
-   * @param existingRecord
-   *          The local <code>Record</code> to replace.
-   * @throws RemoteException
-   */
-  protected void replaceExistingRecordWithRegularRecord(Record toStore, Record existingRecord)
-      throws RemoteException {
-    if (existingRecord.deleted) {
-      // Need two database operations -- purge from deleted table, insert into regular table.
-      deleteExistingRecord(existingRecord);
-      insertNewRegularRecord(toStore);
-      return;
-    }
-
-    final ContentValues cv = contentValuesForRegularRecord(toStore);
-    int updated = formsProvider.update(FORM_HISTORY_CONTENT_URI, cv, GUID_IS, new String[] { existingRecord.guid });
-    if (updated != 1) {
-      Logger.warn(LOG_TAG, "Expected to update 1 record with guid " + existingRecord.guid + " but updated " + updated + " records.");
-    }
-  }
-
-  @Override
-  public void store(Record rawRecord) throws NoStoreDelegateException {
-    if (delegate == null) {
-      Logger.warn(LOG_TAG, "No store delegate.");
-      throw new NoStoreDelegateException();
-    }
-    if (rawRecord == null) {
-      Logger.error(LOG_TAG, "Record sent to store was null");
-      throw new IllegalArgumentException("Null record passed to FormHistoryRepositorySession.store().");
-    }
-    if (!(rawRecord instanceof FormHistoryRecord)) {
-      Logger.error(LOG_TAG, "Can't store anything but a FormHistoryRecord");
-      throw new IllegalArgumentException("Non-FormHistoryRecord passed to FormHistoryRepositorySession.store().");
-    }
-    final FormHistoryRecord record = (FormHistoryRecord) rawRecord;
-
-    Runnable command = new Runnable() {
-      @Override
-      public void run() {
-        if (!isActive()) {
-          Logger.warn(LOG_TAG, "FormHistoryRepositorySession is inactive. Store failing.");
-          delegate.onRecordStoreFailed(new InactiveSessionException(null), record.guid);
-          return;
-        }
-
-        // TODO: lift these into the session.
-        // Temporary: this matches prior syncing semantics, in which only
-        // the relationship between the local and remote record is considered.
-        // In the future we'll track these two timestamps and use them to
-        // determine which records have changed, and thus process incoming
-        // records more efficiently.
-        long lastLocalRetrieval  = 0;      // lastSyncTimestamp?
-        long lastRemoteRetrieval = 0;      // TODO: adjust for clock skew.
-        boolean remotelyModified = record.lastModified > lastRemoteRetrieval;
-
-        Record existingRecord;
-        try {
-          // GUID matching only: deleted records don't have a payload with which to search.
-          existingRecord = findExistingRecordByGuid(record.guid);
-          if (record.deleted) {
-            if (existingRecord == null) {
-              // We're done. Don't bother with a callback. That can change later
-              // if we want it to.
-              Logger.trace(LOG_TAG, "Incoming record " + record.guid + " is deleted, and no local version. Bye!");
-              return;
-            }
-
-            if (existingRecord.deleted) {
-              Logger.trace(LOG_TAG, "Local record already deleted. Purging local.");
-              deleteExistingRecord(existingRecord);
-              return;
-            }
-
-            // Which one wins?
-            if (!remotelyModified) {
-              Logger.trace(LOG_TAG, "Ignoring deleted record from the past.");
-              return;
-            }
-
-            boolean locallyModified = existingRecord.lastModified > lastLocalRetrieval;
-            if (!locallyModified) {
-              Logger.trace(LOG_TAG, "Remote modified, local not. Deleting.");
-              deleteExistingRecord(existingRecord);
-              trackRecord(record);
-              delegate.onRecordStoreSucceeded(record.guid);
-              return;
-            }
-
-            Logger.trace(LOG_TAG, "Both local and remote records have been modified.");
-            if (record.lastModified > existingRecord.lastModified) {
-              Logger.trace(LOG_TAG, "Remote is newer, and deleted. Purging local.");
-              deleteExistingRecord(existingRecord);
-              trackRecord(record);
-              delegate.onRecordStoreSucceeded(record.guid);
-              return;
-            }
-
-            Logger.trace(LOG_TAG, "Remote is older, local is not deleted. Ignoring.");
-            if (!locallyModified) {
-              Logger.warn(LOG_TAG, "Inconsistency: old remote record is deleted, but local record not modified!");
-              // Ensure that this is tracked for upload.
-            }
-            return;
-          }
-          // End deletion logic.
-
-          // Now we're processing a non-deleted incoming record.
-          if (existingRecord == null) {
-            Logger.trace(LOG_TAG, "Looking up match for record " + record.guid);
-            existingRecord = findExistingRecordByPayload(record);
-          }
-
-          if (existingRecord == null) {
-            // The record is new.
-            Logger.trace(LOG_TAG, "No match. Inserting.");
-            insertNewRegularRecord(record);
-            trackRecord(record);
-            delegate.onRecordStoreSucceeded(record.guid);
-            return;
-          }
-
-          // We found a local duplicate.
-          Logger.trace(LOG_TAG, "Incoming record " + record.guid + " dupes to local record " + existingRecord.guid);
-
-          if (!RepoUtils.stringsEqual(record.guid, existingRecord.guid)) {
-            // We found a local record that does NOT have the same GUID -- keep the server's version.
-            Logger.trace(LOG_TAG, "Remote guid different from local guid. Storing to keep remote guid.");
-            replaceExistingRecordWithRegularRecord(record, existingRecord);
-            trackRecord(record);
-            delegate.onRecordStoreSucceeded(record.guid);
-            return;
-          }
-
-          // We found a local record that does have the same GUID -- check modification times.
-          boolean locallyModified = existingRecord.lastModified > lastLocalRetrieval;
-          if (!locallyModified) {
-            Logger.trace(LOG_TAG, "Remote modified, local not. Storing.");
-            replaceExistingRecordWithRegularRecord(record, existingRecord);
-            trackRecord(record);
-            delegate.onRecordStoreSucceeded(record.guid);
-            return;
-          }
-
-          Logger.trace(LOG_TAG, "Both local and remote records have been modified.");
-          if (record.lastModified > existingRecord.lastModified) {
-            Logger.trace(LOG_TAG, "Remote is newer, and not deleted. Storing.");
-            replaceExistingRecordWithRegularRecord(record, existingRecord);
-            trackRecord(record);
-            delegate.onRecordStoreSucceeded(record.guid);
-            return;
-          }
-
-          Logger.trace(LOG_TAG, "Remote is older, local is not deleted. Ignoring.");
-          if (!locallyModified) {
-            Logger.warn(LOG_TAG, "Inconsistency: old remote record is not deleted, but local record not modified!");
-          }
-          return;
-        } catch (Exception e) {
-          Logger.error(LOG_TAG, "Store failed for " + record.guid, e);
-          delegate.onRecordStoreFailed(e, record.guid);
-          return;
-        }
-      }
-    };
-
-    storeWorkQueue.execute(command);
-  }
-
-  /**
-   * Purge all data from the underlying databases.
-   */
-  public static void purgeDatabases(ContentProviderClient formsProvider)
-      throws RemoteException {
-    formsProvider.delete(FORM_HISTORY_CONTENT_URI, null, null);
-    formsProvider.delete(DELETED_FORM_HISTORY_CONTENT_URI, null, null);
-  }
-
-  @Override
-  public void wipe(final RepositorySessionWipeDelegate delegate) {
-    Runnable command = new Runnable() {
-      @Override
-      public void run() {
-        if (!isActive()) {
-          delegate.onWipeFailed(new InactiveSessionException(null));
-          return;
-        }
-
-        try {
-          Logger.debug(LOG_TAG, "Wiping form history and deleted form history...");
-          purgeDatabases(formsProvider);
-          Logger.debug(LOG_TAG, "Wiping form history and deleted form history... DONE");
-        } catch (Exception e) {
-          delegate.onWipeFailed(e);
-          return;
-        }
-
-        delegate.onWipeSucceeded();
-      }
-    };
-    storeWorkQueue.execute(command);
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/PasswordsRepositorySession.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/PasswordsRepositorySession.java
deleted file mode 100644
index f7b7416..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/PasswordsRepositorySession.java
+++ /dev/null
@@ -1,725 +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.sync.repositories.android;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.db.BrowserContract;
-import org.mozilla.gecko.db.BrowserContract.DeletedColumns;
-import org.mozilla.gecko.db.BrowserContract.DeletedPasswords;
-import org.mozilla.gecko.db.BrowserContract.Passwords;
-import org.mozilla.gecko.sync.repositories.InactiveSessionException;
-import org.mozilla.gecko.sync.repositories.NoStoreDelegateException;
-import org.mozilla.gecko.sync.repositories.NullCursorException;
-import org.mozilla.gecko.sync.repositories.RecordFilter;
-import org.mozilla.gecko.sync.repositories.Repository;
-import org.mozilla.gecko.sync.repositories.StoreTrackingRepositorySession;
-import org.mozilla.gecko.sync.repositories.android.RepoUtils.QueryHelper;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionCreationDelegate;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionFetchRecordsDelegate;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionFinishDelegate;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionGuidsSinceDelegate;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionWipeDelegate;
-import org.mozilla.gecko.sync.repositories.domain.PasswordRecord;
-import org.mozilla.gecko.sync.repositories.domain.Record;
-
-import android.content.ContentProviderClient;
-import android.content.ContentUris;
-import android.content.ContentValues;
-import android.content.Context;
-import android.database.Cursor;
-import android.net.Uri;
-import android.os.RemoteException;
-
-public class PasswordsRepositorySession extends
-    StoreTrackingRepositorySession {
-
-  public static class PasswordsRepository extends Repository {
-    @Override
-    public void createSession(RepositorySessionCreationDelegate delegate,
-        Context context) {
-      PasswordsRepositorySession session = new PasswordsRepositorySession(PasswordsRepository.this, context);
-      final RepositorySessionCreationDelegate deferredCreationDelegate = delegate.deferredCreationDelegate();
-      deferredCreationDelegate.onSessionCreated(session);
-    }
-  }
-
-  private static final String LOG_TAG = "PasswordsRepoSession";
-  private static final String COLLECTION = "passwords";
-
-  private final RepoUtils.QueryHelper passwordsHelper;
-  private final RepoUtils.QueryHelper deletedPasswordsHelper;
-  private final ContentProviderClient passwordsProvider;
-
-  private final Context context;
-
-  public PasswordsRepositorySession(Repository repository, Context context) {
-    super(repository);
-    this.context = context;
-    this.passwordsHelper        = new QueryHelper(context, BrowserContractHelpers.PASSWORDS_CONTENT_URI, LOG_TAG);
-    this.deletedPasswordsHelper = new QueryHelper(context, BrowserContractHelpers.DELETED_PASSWORDS_CONTENT_URI, LOG_TAG);
-    this.passwordsProvider      = context.getContentResolver().acquireContentProviderClient(BrowserContract.PASSWORDS_AUTHORITY_URI);
-  }
-
-  private static final String[] GUID_COLS = new String[] { Passwords.GUID };
-  private static final String[] DELETED_GUID_COLS = new String[] { DeletedColumns.GUID };
-
-  private static final String WHERE_GUID_IS = Passwords.GUID + " = ?";
-  private static final String WHERE_DELETED_GUID_IS = DeletedPasswords.GUID + " = ?";
-
-  @Override
-  public void guidsSince(final long timestamp, final RepositorySessionGuidsSinceDelegate delegate) {
-    final Runnable guidsSinceRunnable = new Runnable() {
-      @Override
-      public void run() {
-
-        if (!isActive()) {
-          delegate.onGuidsSinceFailed(new InactiveSessionException(null));
-          return;
-        }
-
-        // Checks succeeded, now get GUIDs.
-        final List<String> guids = new ArrayList<String>();
-        try {
-          Logger.debug(LOG_TAG, "Fetching guidsSince from data table.");
-          final Cursor data = passwordsHelper.safeQuery(passwordsProvider, ".getGUIDsSince", GUID_COLS, dateModifiedWhere(timestamp), null, null);
-          try {
-            if (data.moveToFirst()) {
-              while (!data.isAfterLast()) {
-                guids.add(RepoUtils.getStringFromCursor(data, Passwords.GUID));
-                data.moveToNext();
-              }
-            }
-          } finally {
-            data.close();
-          }
-
-          // Fetch guids from deleted table.
-          Logger.debug(LOG_TAG, "Fetching guidsSince from deleted table.");
-          final Cursor deleted = deletedPasswordsHelper.safeQuery(passwordsProvider, ".getGUIDsSince", DELETED_GUID_COLS, dateModifiedWhereDeleted(timestamp), null, null);
-          try {
-            if (deleted.moveToFirst()) {
-              while (!deleted.isAfterLast()) {
-                guids.add(RepoUtils.getStringFromCursor(deleted, DeletedColumns.GUID));
-                deleted.moveToNext();
-              }
-            }
-          } finally {
-            deleted.close();
-          }
-        } catch (Exception e) {
-          Logger.error(LOG_TAG, "Exception in fetch.");
-          delegate.onGuidsSinceFailed(e);
-          return;
-        }
-        String[] guidStrings = new String[guids.size()];
-        delegate.onGuidsSinceSucceeded(guids.toArray(guidStrings));
-      }
-    };
-
-    delegateQueue.execute(guidsSinceRunnable);
-  }
-
-  @Override
-  public void fetchSince(final long timestamp, final RepositorySessionFetchRecordsDelegate delegate) {
-    final RecordFilter filter = this.storeTracker.getFilter();
-    final Runnable fetchSinceRunnable = new Runnable() {
-      @Override
-      public void run() {
-        if (!isActive()) {
-          delegate.onFetchFailed(new InactiveSessionException(null), null);
-          return;
-        }
-
-        final long end = now();
-        try {
-          // Fetch from data table.
-          Cursor data = passwordsHelper.safeQuery(passwordsProvider, ".fetchSince",
-                                                  getAllColumns(),
-                                                  dateModifiedWhere(timestamp),
-                                                  null, null);
-          if (!fetchAndCloseCursorDeleted(data, false, filter, delegate)) {
-            return;
-          }
-
-          // Fetch from deleted table.
-          Cursor deleted = deletedPasswordsHelper.safeQuery(passwordsProvider, ".fetchSince",
-                                                            getAllDeletedColumns(),
-                                                            dateModifiedWhereDeleted(timestamp),
-                                                            null, null);
-          if (!fetchAndCloseCursorDeleted(deleted, true, filter, delegate)) {
-            return;
-          }
-
-          // Success!
-          try {
-            delegate.onFetchCompleted(end);
-          } catch (Exception e) {
-            Logger.error(LOG_TAG, "Delegate fetch completed callback failed.", e);
-            // Don't call failure callback.
-            return;
-          }
-        } catch (Exception e) {
-          Logger.error(LOG_TAG, "Exception in fetch.");
-          delegate.onFetchFailed(e, null);
-        }
-      }
-    };
-
-    delegateQueue.execute(fetchSinceRunnable);
-  }
-
-  @Override
-  public void fetch(final String[] guids, final RepositorySessionFetchRecordsDelegate delegate) {
-    if (guids == null || guids.length < 1) {
-      Logger.error(LOG_TAG, "No guids to be fetched.");
-      final long end = now();
-      delegateQueue.execute(new Runnable() {
-        @Override
-        public void run() {
-          delegate.onFetchCompleted(end);
-        }
-      });
-      return;
-    }
-
-    // Checks succeeded, now fetch.
-    final RecordFilter filter = this.storeTracker.getFilter();
-    final Runnable fetchRunnable = new Runnable() {
-      @Override
-      public void run() {
-        if (!isActive()) {
-          delegate.onFetchFailed(new InactiveSessionException(null), null);
-          return;
-        }
-
-        final long end = now();
-        final String where = RepoUtils.computeSQLInClause(guids.length, "guid");
-        Logger.trace(LOG_TAG, "Fetch guids where: " + where);
-
-        try {
-          // Fetch records from data table.
-          Cursor data = passwordsHelper.safeQuery(passwordsProvider, ".fetch",
-                                                  getAllColumns(),
-                                                  where, guids, null);
-          if (!fetchAndCloseCursorDeleted(data, false, filter, delegate)) {
-            return;
-          }
-
-          // Fetch records from deleted table.
-          Cursor deleted = deletedPasswordsHelper.safeQuery(passwordsProvider, ".fetch",
-                                                            getAllDeletedColumns(),
-                                                            where, guids, null);
-          if (!fetchAndCloseCursorDeleted(deleted, true, filter, delegate)) {
-            return;
-          }
-
-          delegate.onFetchCompleted(end);
-
-        } catch (Exception e) {
-          Logger.error(LOG_TAG, "Exception in fetch.");
-          delegate.onFetchFailed(e, null);
-        }
-      }
-    };
-
-    delegateQueue.execute(fetchRunnable);
-  }
-
-  @Override
-  public void fetchAll(RepositorySessionFetchRecordsDelegate delegate) {
-    fetchSince(0, delegate);
-  }
-
-  @Override
-  public void store(final Record record) throws NoStoreDelegateException {
-    if (delegate == null) {
-      Logger.error(LOG_TAG, "No store delegate.");
-      throw new NoStoreDelegateException();
-    }
-    if (record == null) {
-      Logger.error(LOG_TAG, "Record sent to store was null.");
-      throw new IllegalArgumentException("Null record passed to PasswordsRepositorySession.store().");
-    }
-    if (!(record instanceof PasswordRecord)) {
-      Logger.error(LOG_TAG, "Can't store anything but a PasswordRecord.");
-      throw new IllegalArgumentException("Non-PasswordRecord passed to PasswordsRepositorySession.store().");
-    }
-
-    final PasswordRecord remoteRecord = (PasswordRecord) record;
-
-    final Runnable storeRunnable = new Runnable() {
-      @Override
-      public void run() {
-        if (!isActive()) {
-          Logger.warn(LOG_TAG, "RepositorySession is inactive. Store failing.");
-          delegate.onRecordStoreFailed(new InactiveSessionException(null), record.guid);
-          return;
-        }
-
-        final String guid = remoteRecord.guid;
-        if (guid == null) {
-          delegate.onRecordStoreFailed(new RuntimeException("Can't store record with null GUID."), record.guid);
-          return;
-        }
-
-        PasswordRecord existingRecord;
-        try {
-          existingRecord = retrieveByGUID(guid);
-        } catch (NullCursorException | RemoteException e) {
-          // Indicates a serious problem.
-          delegate.onRecordStoreFailed(e, record.guid);
-          return;
-        }
-
-        long lastLocalRetrieval  = 0;      // lastSyncTimestamp?
-        long lastRemoteRetrieval = 0;      // TODO: adjust for clock skew.
-        boolean remotelyModified = remoteRecord.lastModified > lastRemoteRetrieval;
-
-        // Check deleted state first.
-        if (remoteRecord.deleted) {
-          if (existingRecord == null) {
-            // Do nothing, record does not exist anyways.
-            Logger.info(LOG_TAG, "Incoming record " + remoteRecord.guid + " is deleted, and no local version.");
-            return;
-          }
-
-          if (existingRecord.deleted) {
-            // Record is already tracked as deleted. Delete from local.
-            storeRecordDeletion(existingRecord); // different from ABRepoSess.
-            Logger.info(LOG_TAG, "Incoming record " + remoteRecord.guid + " and local are both deleted.");
-            return;
-          }
-
-          // Which one wins?
-          if (!remotelyModified) {
-            trace("Ignoring deleted record from the past.");
-            return;
-          }
-
-          boolean locallyModified = existingRecord.lastModified > lastLocalRetrieval;
-          if (!locallyModified) {
-            trace("Remote modified, local not. Deleting.");
-            storeRecordDeletion(remoteRecord);
-            return;
-          }
-
-          trace("Both local and remote records have been modified.");
-          if (remoteRecord.lastModified > existingRecord.lastModified) {
-            trace("Remote is newer, and deleted. Deleting local.");
-            storeRecordDeletion(remoteRecord);
-            return;
-          }
-
-          trace("Remote is older, local is not deleted. Ignoring.");
-          if (!locallyModified) {
-            Logger.warn(LOG_TAG, "Inconsistency: old remote record is deleted, but local record not modified!");
-            // Ensure that this is tracked for upload.
-          }
-          return;
-        }
-        // End deletion logic.
-
-        // Validate the incoming record.
-        if (!remoteRecord.isValid()) {
-            Logger.warn(LOG_TAG, "Incoming record is invalid. Reporting store failed.");
-            delegate.onRecordStoreFailed(new RuntimeException("Can't store invalid password record."), record.guid);
-            return;
-        }
-
-        // Now we're processing a non-deleted incoming record.
-        if (existingRecord == null) {
-          trace("Looking up match for record " + remoteRecord.guid);
-          try {
-            existingRecord = findExistingRecord(remoteRecord);
-          } catch (RemoteException e) {
-            Logger.error(LOG_TAG, "Remote exception in findExistingRecord.");
-            delegate.onRecordStoreFailed(e, record.guid);
-          } catch (NullCursorException e) {
-            Logger.error(LOG_TAG, "Null cursor in findExistingRecord.");
-            delegate.onRecordStoreFailed(e, record.guid);
-          }
-        }
-
-        if (existingRecord == null) {
-          // The record is new.
-          trace("No match. Inserting.");
-          Logger.debug(LOG_TAG, "Didn't find matching record. Inserting.");
-          Record inserted = null;
-          try {
-            inserted = insert(remoteRecord);
-          } catch (RemoteException e) {
-            Logger.debug(LOG_TAG, "Record insert caused a RemoteException.");
-            delegate.onRecordStoreFailed(e, record.guid);
-            return;
-          }
-          trackRecord(inserted);
-          delegate.onRecordStoreSucceeded(inserted.guid);
-          return;
-        }
-
-        // We found a local dupe.
-        trace("Incoming record " + remoteRecord.guid + " dupes to local record " + existingRecord.guid);
-        Logger.debug(LOG_TAG, "remote " + remoteRecord.guid + " dupes to " + existingRecord.guid);
-
-        if (existingRecord.deleted && existingRecord.lastModified > remoteRecord.lastModified) {
-          Logger.debug(LOG_TAG, "Local deletion is newer, not storing remote record.");
-          return;
-        }
-
-        Record toStore = reconcileRecords(remoteRecord, existingRecord, lastRemoteRetrieval, lastLocalRetrieval);
-        if (toStore == null) {
-          Logger.debug(LOG_TAG, "Reconciling returned null. Not inserting a record.");
-          return;
-        }
-
-        // TODO: pass in timestamps?
-        Logger.debug(LOG_TAG, "Replacing " + existingRecord.guid + " with record " + toStore.guid);
-        Record replaced = null;
-        try {
-          replaced = replace(existingRecord, toStore);
-        } catch (RemoteException e) {
-          Logger.debug(LOG_TAG, "Record replace caused a RemoteException.");
-          delegate.onRecordStoreFailed(e, record.guid);
-          return;
-        }
-
-        // Note that we don't track records here; deciding that is the job
-        // of reconcileRecords.
-        Logger.debug(LOG_TAG, "Calling delegate callback with guid " + replaced.guid +
-                              "(" + replaced.androidID + ")");
-        delegate.onRecordStoreSucceeded(record.guid);
-        return;
-      }
-    };
-    storeWorkQueue.execute(storeRunnable);
-  }
-
-  @Override
-  public void wipe(final RepositorySessionWipeDelegate delegate) {
-    Logger.info(LOG_TAG, "Wiping " + BrowserContractHelpers.PASSWORDS_CONTENT_URI + ", " + BrowserContractHelpers.DELETED_PASSWORDS_CONTENT_URI);
-
-    Runnable wipeRunnable = new Runnable() {
-      @Override
-      public void run() {
-        if (!isActive()) {
-          delegate.onWipeFailed(new InactiveSessionException(null));
-          return;
-        }
-
-        // Wipe both data and deleted.
-        try {
-          context.getContentResolver().delete(BrowserContractHelpers.PASSWORDS_CONTENT_URI, null, null);
-          context.getContentResolver().delete(BrowserContractHelpers.DELETED_PASSWORDS_CONTENT_URI, null, null);
-        } catch (Exception e) {
-          delegate.onWipeFailed(e);
-          return;
-        }
-        delegate.onWipeSucceeded();
-      }
-    };
-    storeWorkQueue.execute(wipeRunnable);
-  }
-
-  @Override
-  public void abort() {
-    passwordsProvider.release();
-    super.abort();
-  }
-
-  @Override
-  public void finish(final RepositorySessionFinishDelegate delegate) throws InactiveSessionException {
-    passwordsProvider.release();
-    super.finish(delegate);
-  }
-
-  public void deleteGUID(String guid) throws RemoteException {
-    final String[] args = new String[] { guid };
-
-    int deleted = passwordsProvider.delete(BrowserContractHelpers.PASSWORDS_CONTENT_URI, WHERE_GUID_IS, args) +
-                  passwordsProvider.delete(BrowserContractHelpers.DELETED_PASSWORDS_CONTENT_URI, WHERE_DELETED_GUID_IS, args);
-    if (deleted == 1) {
-      return;
-    }
-    Logger.warn(LOG_TAG, "Unexpectedly deleted " + deleted + " rows for guid " + guid);
-  }
-
-  /**
-   * Insert record and return the record with its updated androidId set.
-   *
-   * @param record the record to insert.
-   * @return updated record.
-   * @throws RemoteException
-   */
-  public PasswordRecord insert(PasswordRecord record) throws RemoteException {
-    record.timePasswordChanged = now();
-    // TODO: are these necessary for Fennec autocomplete?
-    // record.timesUsed = 1;
-    // record.timeLastUsed = now();
-    ContentValues cv = getContentValues(record);
-    Uri insertedUri = passwordsProvider.insert(BrowserContractHelpers.PASSWORDS_CONTENT_URI, cv);
-    if (insertedUri == null) {
-      throw new RemoteException(); // Not much to be done here, save throw.
-    }
-    record.androidID = ContentUris.parseId(insertedUri);
-    return record;
-  }
-
-  public Record replace(Record origRecord, Record newRecord) throws RemoteException {
-    PasswordRecord newPasswordRecord = (PasswordRecord) newRecord;
-    PasswordRecord origPasswordRecord = (PasswordRecord) origRecord;
-    propagateTimes(newPasswordRecord, origPasswordRecord);
-    ContentValues cv = getContentValues(newPasswordRecord);
-
-    final String[] args = new String[] { origRecord.guid };
-
-    if (origRecord.deleted) {
-      // Purge from deleted table.
-      deleteGUID(origRecord.guid);
-      insert(newPasswordRecord);
-    } else {
-      int updated = context.getContentResolver().update(BrowserContractHelpers.PASSWORDS_CONTENT_URI, cv, WHERE_GUID_IS, args);
-      if (updated != 1) {
-        Logger.warn(LOG_TAG, "Unexpectedly updated " + updated + " rows for guid " + origPasswordRecord.guid);
-      }
-    }
-
-    return newRecord;
-  }
-
-  // When replacing a record, propagate the times.
-  private static void propagateTimes(PasswordRecord toRecord, PasswordRecord fromRecord) {
-    toRecord.timePasswordChanged = now();
-    toRecord.timeCreated  = fromRecord.timeCreated;
-    toRecord.timeLastUsed = fromRecord.timeLastUsed;
-    toRecord.timesUsed    = fromRecord.timesUsed;
-  }
-
-  private static String[] getAllColumns() {
-    return BrowserContractHelpers.PasswordColumns;
-  }
-
-  private static String[] getAllDeletedColumns() {
-    return BrowserContractHelpers.DeletedColumns;
-  }
-
-  /**
-   * Constructs the DB query string for entry age for deleted records.
-   *
-   * @param timestamp
-   * @return String DB query string for dates to fetch.
-   */
-  private static String dateModifiedWhereDeleted(long timestamp) {
-    return DeletedColumns.TIME_DELETED + " >= " + Long.toString(timestamp);
-  }
-
-  /**
-   * Constructs the DB query string for entry age for (undeleted) records.
-   *
-   * @param timestamp
-   * @return String DB query string for dates to fetch.
-   */
-  private static String dateModifiedWhere(long timestamp) {
-    return Passwords.TIME_PASSWORD_CHANGED + " >= " + Long.toString(timestamp);
-  }
-
-
-  /**
-   * Fetch from the cursor with the given parameters, invoking
-   * delegate callbacks and closing the cursor.
-   * Returns true on success, false if failure was signaled.
-   *
-   * @param cursor
-            fetch* cursor.
-   * @param deleted
-   *        true if using deleted table, false when using data table.
-   * @param delegate
-   *        FetchRecordsDelegate to process records.
-   */
-  private static boolean fetchAndCloseCursorDeleted(final Cursor cursor,
-                                                    final boolean deleted,
-                                                    final RecordFilter filter,
-                                                    final RepositorySessionFetchRecordsDelegate delegate) {
-    if (cursor == null) {
-      return true;
-    }
-
-    try {
-      while (cursor.moveToNext()) {
-        Record r = deleted ? deletedPasswordRecordFromCursor(cursor) : passwordRecordFromCursor(cursor);
-        if (r != null) {
-          if (filter == null || !filter.excludeRecord(r)) {
-            Logger.debug(LOG_TAG, "Processing record " + r.guid);
-            delegate.onFetchedRecord(r);
-          } else {
-            Logger.debug(LOG_TAG, "Skipping filtered record " + r.guid);
-          }
-        }
-      }
-    } catch (Exception e) {
-      Logger.error(LOG_TAG, "Exception in fetch.");
-      delegate.onFetchFailed(e, null);
-      return false;
-    } finally {
-      cursor.close();
-    }
-
-    return true;
-  }
-
-  private PasswordRecord retrieveByGUID(String guid) throws NullCursorException, RemoteException {
-    final String[] guidArg = new String[] { guid };
-
-    // Check data table.
-    final Cursor data = passwordsHelper.safeQuery(passwordsProvider, ".store", BrowserContractHelpers.PasswordColumns, WHERE_GUID_IS, guidArg, null);
-    try {
-      if (data.moveToFirst()) {
-        return passwordRecordFromCursor(data);
-      }
-    } finally {
-      data.close();
-    }
-
-    // Check deleted table.
-    final Cursor deleted = deletedPasswordsHelper.safeQuery(passwordsProvider, ".retrieveByGuid", BrowserContractHelpers.DeletedColumns, WHERE_DELETED_GUID_IS, guidArg, null);
-    try {
-      if (deleted.moveToFirst()) {
-        return deletedPasswordRecordFromCursor(deleted);
-      }
-    } finally {
-      deleted.close();
-    }
-
-    return null;
-  }
-
-  private static final String WHERE_RECORD_DATA =
-    Passwords.HOSTNAME        + " = ? AND " +
-    Passwords.HTTP_REALM      + " = ? AND " +
-    Passwords.FORM_SUBMIT_URL + " = ? AND " +
-    Passwords.USERNAME_FIELD  + " = ? AND " +
-    Passwords.PASSWORD_FIELD  + " = ?";
-
-  private PasswordRecord findExistingRecord(PasswordRecord record) throws NullCursorException, RemoteException {
-    PasswordRecord foundRecord = null;
-    Cursor cursor = null;
-    // Only check the data table.
-    // We can't encrypt username directly for query, so run a more general query and then filter.
-    final String[] whereArgs = new String[] {
-      record.hostname,
-      record.httpRealm,
-      record.formSubmitURL,
-      record.usernameField,
-      record.passwordField
-    };
-
-    try {
-      cursor = passwordsHelper.safeQuery(passwordsProvider, ".findRecord", getAllColumns(), WHERE_RECORD_DATA, whereArgs, null);
-      while (cursor.moveToNext()) {
-        foundRecord = passwordRecordFromCursor(cursor);
-
-        // We don't directly query for username because the
-        // username/password values are encrypted in the db.
-        // We don't have the keys for encrypting our query,
-        // so we run a more general query and then filter
-        // the returned records for a matching username.
-        Logger.pii(LOG_TAG, "Checking incoming [" + record.encryptedUsername + "] to [" + foundRecord.encryptedUsername + "]");
-        if (record.encryptedUsername.equals(foundRecord.encryptedUsername)) {
-          Logger.trace(LOG_TAG, "Found matching record: " + foundRecord.guid);
-          return foundRecord;
-        }
-      }
-    } finally {
-      if (cursor != null) {
-        cursor.close();
-      }
-    }
-    Logger.debug(LOG_TAG, "No matching records, returning null.");
-    return null;
-  }
-
-  private void storeRecordDeletion(Record record) {
-    try {
-      deleteGUID(record.guid);
-    } catch (RemoteException e) {
-      Logger.error(LOG_TAG, "RemoteException in password delete.");
-      delegate.onRecordStoreFailed(e, record.guid);
-      return;
-    }
-    delegate.onRecordStoreSucceeded(record.guid);
-  }
-
-  /**
-   * Make a PasswordRecord from a Cursor.
-   * @param cur
-   *        Cursor from query.
-   * @param deleted
-   *        true if creating a deleted Record, false if otherwise.
-   * @return
-   *        PasswordRecord populated from Cursor.
-   */
-  private static PasswordRecord passwordRecordFromCursor(Cursor cur) {
-    if (cur.isAfterLast()) {
-      return null;
-    }
-    String guid = RepoUtils.getStringFromCursor(cur, BrowserContract.Passwords.GUID);
-    long lastModified = RepoUtils.getLongFromCursor(cur, BrowserContract.Passwords.TIME_PASSWORD_CHANGED);
-
-    PasswordRecord rec = new PasswordRecord(guid, COLLECTION, lastModified, false);
-    rec.id = RepoUtils.getStringFromCursor(cur, BrowserContract.Passwords.ID);
-    rec.hostname = RepoUtils.getStringFromCursor(cur, BrowserContract.Passwords.HOSTNAME);
-    rec.httpRealm = RepoUtils.getStringFromCursor(cur, BrowserContract.Passwords.HTTP_REALM);
-    rec.formSubmitURL = RepoUtils.getStringFromCursor(cur, BrowserContract.Passwords.FORM_SUBMIT_URL);
-    rec.usernameField = RepoUtils.getStringFromCursor(cur, BrowserContract.Passwords.USERNAME_FIELD);
-    rec.passwordField = RepoUtils.getStringFromCursor(cur, BrowserContract.Passwords.PASSWORD_FIELD);
-    rec.encType = RepoUtils.getStringFromCursor(cur, BrowserContract.Passwords.ENC_TYPE);
-
-    // TODO decryption of username/password here (Bug 711636)
-    rec.encryptedUsername = RepoUtils.getStringFromCursor(cur, BrowserContract.Passwords.ENCRYPTED_USERNAME);
-    rec.encryptedPassword = RepoUtils.getStringFromCursor(cur, BrowserContract.Passwords.ENCRYPTED_PASSWORD);
-
-    rec.timeCreated = RepoUtils.getLongFromCursor(cur, BrowserContract.Passwords.TIME_CREATED);
-    rec.timeLastUsed = RepoUtils.getLongFromCursor(cur, BrowserContract.Passwords.TIME_LAST_USED);
-    rec.timePasswordChanged = RepoUtils.getLongFromCursor(cur, BrowserContract.Passwords.TIME_PASSWORD_CHANGED);
-    rec.timesUsed = RepoUtils.getLongFromCursor(cur, BrowserContract.Passwords.TIMES_USED);
-    return rec;
-  }
-
-  private static PasswordRecord deletedPasswordRecordFromCursor(Cursor cur) {
-    if (cur.isAfterLast()) {
-      return null;
-    }
-    String guid = RepoUtils.getStringFromCursor(cur, DeletedColumns.GUID);
-    long lastModified = RepoUtils.getLongFromCursor(cur, DeletedColumns.TIME_DELETED);
-    PasswordRecord rec = new PasswordRecord(guid, COLLECTION, lastModified, true);
-    rec.androidID = RepoUtils.getLongFromCursor(cur, DeletedColumns.ID);
-    return rec;
-  }
-
-  private static ContentValues getContentValues(Record record) {
-    PasswordRecord rec = (PasswordRecord) record;
-
-    ContentValues cv = new ContentValues();
-    cv.put(BrowserContract.Passwords.GUID,            rec.guid);
-    cv.put(BrowserContract.Passwords.HOSTNAME,        rec.hostname);
-    cv.put(BrowserContract.Passwords.HTTP_REALM,      rec.httpRealm);
-    cv.put(BrowserContract.Passwords.FORM_SUBMIT_URL, rec.formSubmitURL);
-    cv.put(BrowserContract.Passwords.USERNAME_FIELD,  rec.usernameField);
-    cv.put(BrowserContract.Passwords.PASSWORD_FIELD,  rec.passwordField);
-
-    // TODO Do encryption of username/password here. Bug 711636
-    cv.put(BrowserContract.Passwords.ENC_TYPE,           rec.encType);
-    cv.put(BrowserContract.Passwords.ENCRYPTED_USERNAME, rec.encryptedUsername);
-    cv.put(BrowserContract.Passwords.ENCRYPTED_PASSWORD, rec.encryptedPassword);
-
-    cv.put(BrowserContract.Passwords.TIME_CREATED,          rec.timeCreated);
-    cv.put(BrowserContract.Passwords.TIME_LAST_USED,        rec.timeLastUsed);
-    cv.put(BrowserContract.Passwords.TIME_PASSWORD_CHANGED, rec.timePasswordChanged);
-    cv.put(BrowserContract.Passwords.TIMES_USED,            rec.timesUsed);
-    return cv;
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/RepoUtils.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/RepoUtils.java
deleted file mode 100644
index 9c29953..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/RepoUtils.java
+++ /dev/null
@@ -1,290 +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.sync.repositories.android;
-
-import android.content.ContentProviderClient;
-import android.content.Context;
-import android.database.Cursor;
-import android.database.sqlite.SQLiteDatabase;
-import android.net.Uri;
-import android.os.RemoteException;
-
-import org.json.simple.JSONArray;
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.db.BrowserContract;
-import org.mozilla.gecko.sync.ExtendedJSONObject;
-import org.mozilla.gecko.sync.NonArrayJSONException;
-import org.mozilla.gecko.sync.repositories.NullCursorException;
-import org.mozilla.gecko.sync.repositories.domain.ClientRecord;
-import org.mozilla.gecko.sync.repositories.domain.HistoryRecord;
-
-import java.io.IOException;
-
-public class RepoUtils {
-
-  private static final String LOG_TAG = "RepoUtils";
-
-  /**
-   * A helper class for monotonous SQL querying. Does timing and logging,
-   * offers a utility to throw on a null cursor.
-   *
-   * @author rnewman
-   *
-   */
-  public static class QueryHelper {
-    private final Context context;
-    private final Uri     uri;
-    private final String  tag;
-
-    public QueryHelper(Context context, Uri uri, String tag) {
-      this.context = context;
-      this.uri     = uri;
-      this.tag     = tag;
-    }
-
-    // For ContentProvider queries.
-    public Cursor safeQuery(String label, String[] projection,
-                            String selection, String[] selectionArgs, String sortOrder) throws NullCursorException {
-      long queryStart = android.os.SystemClock.uptimeMillis();
-      Cursor c = context.getContentResolver().query(uri, projection, selection, selectionArgs, sortOrder);
-      return checkAndLogCursor(label, queryStart, c);
-    }
-
-    public Cursor safeQuery(String[] projection, String selection, String[] selectionArgs, String sortOrder) throws NullCursorException {
-      return this.safeQuery(null, projection, selection, selectionArgs, sortOrder);
-    }
-
-    // For ContentProviderClient queries.
-    public Cursor safeQuery(ContentProviderClient client, String label, String[] projection,
-                            String selection, String[] selectionArgs, String sortOrder) throws NullCursorException, RemoteException {
-      long queryStart = android.os.SystemClock.uptimeMillis();
-      Cursor c = client.query(uri, projection, selection, selectionArgs, sortOrder);
-      return checkAndLogCursor(label, queryStart, c);
-    }
-
-    // For SQLiteOpenHelper queries.
-    public Cursor safeQuery(SQLiteDatabase db, String label, String table, String[] columns,
-                            String selection, String[] selectionArgs,
-                            String groupBy, String having, String orderBy, String limit) throws NullCursorException {
-      long queryStart = android.os.SystemClock.uptimeMillis();
-      Cursor c = db.query(table, columns, selection, selectionArgs, groupBy, having, orderBy, limit);
-      return checkAndLogCursor(label, queryStart, c);
-    }
-
-    public Cursor safeQuery(SQLiteDatabase db, String label, String table, String[] columns,
-                            String selection, String[] selectionArgs) throws NullCursorException {
-      return safeQuery(db, label, table, columns, selection, selectionArgs, null, null, null, null);
-    }
-
-    private Cursor checkAndLogCursor(String label, long queryStart, Cursor c) throws NullCursorException {
-      long queryEnd = android.os.SystemClock.uptimeMillis();
-      String logLabel = (label == null) ? tag : (tag + label);
-      RepoUtils.queryTimeLogger(logLabel, queryStart, queryEnd);
-      return checkNullCursor(logLabel, c);
-    }
-
-    public Cursor checkNullCursor(String logLabel, Cursor cursor) throws NullCursorException {
-      if (cursor == null) {
-        Logger.error(tag, "Got null cursor exception in " + logLabel);
-        throw new NullCursorException(null);
-      }
-      return cursor;
-    }
-  }
-
-  /**
-   * This method exists because the behavior of <code>cur.getString()</code> is undefined
-   * when the value in the database is <code>NULL</code>.
-   * This method will return <code>null</code> in that case.
-   */
-  public static String optStringFromCursor(final Cursor cur, final String colId) {
-    final int col = cur.getColumnIndex(colId);
-    if (cur.isNull(col)) {
-      return null;
-    }
-    return cur.getString(col);
-  }
-
-  /**
-   * The behavior of this method when the value in the database is <code>NULL</code> is
-   * determined by the implementation of the {@link Cursor}.
-   */
-  public static String getStringFromCursor(final Cursor cur, final String colId) {
-    // TODO: getColumnIndexOrThrow?
-    // TODO: don't look up columns by name!
-    return cur.getString(cur.getColumnIndex(colId));
-  }
-
-  public static long getLongFromCursor(Cursor cur, String colId) {
-    return cur.getLong(cur.getColumnIndex(colId));
-  }
-
-  public static int getIntFromCursor(Cursor cur, String colId) {
-    return cur.getInt(cur.getColumnIndex(colId));
-  }
-
-  public static JSONArray getJSONArrayFromCursor(Cursor cur, String colId) {
-    String jsonArrayAsString = getStringFromCursor(cur, colId);
-    if (jsonArrayAsString == null) {
-      return new JSONArray();
-    }
-    try {
-      return ExtendedJSONObject.parseJSONArray(getStringFromCursor(cur, colId));
-    } catch (NonArrayJSONException e) {
-      Logger.error(LOG_TAG, "JSON parsing error for " + colId, e);
-      return null;
-    } catch (IOException e) {
-      Logger.error(LOG_TAG, "JSON parsing error for " + colId, e);
-      return null;
-    }
-  }
-
-  /**
-   * Return true if the provided URI is non-empty and acceptable to Fennec
-   * (i.e., not an undesirable scheme).
-   *
-   * This code is pilfered from Fennec, which pilfered from Places.
-   */
-  public static boolean isValidHistoryURI(String uri) {
-    if (uri == null || uri.length() == 0) {
-      return false;
-    }
-
-    // First, check the most common cases (HTTP, HTTPS) to avoid most of the work.
-    if (uri.startsWith("http:") || uri.startsWith("https:")) {
-      return true;
-    }
-
-    String scheme = Uri.parse(uri).getScheme();
-    if (scheme == null) {
-      return false;
-    }
-
-    // Now check for all bad things.
-    if (scheme.equals("about") ||
-        scheme.equals("imap") ||
-        scheme.equals("news") ||
-        scheme.equals("mailbox") ||
-        scheme.equals("moz-anno") ||
-        scheme.equals("view-source") ||
-        scheme.equals("chrome") ||
-        scheme.equals("resource") ||
-        scheme.equals("data") ||
-        scheme.equals("wyciwyg") ||
-        scheme.equals("javascript")) {
-      return false;
-    }
-
-    return true;
-  }
-
-  /**
-   * Create a HistoryRecord object from a cursor row.
-   *
-   * @return a HistoryRecord, or null if this row would produce
-   *         an invalid record (e.g., with a null URI or no visits).
-   */
-  public static HistoryRecord historyFromMirrorCursor(Cursor cur) {
-    final String guid = getStringFromCursor(cur, BrowserContract.SyncColumns.GUID);
-    if (guid == null) {
-      Logger.debug(LOG_TAG, "Skipping history record with null GUID.");
-      return null;
-    }
-
-    final String historyURI = getStringFromCursor(cur, BrowserContract.History.URL);
-    if (!isValidHistoryURI(historyURI)) {
-      Logger.debug(LOG_TAG, "Skipping history record " + guid + " with unwanted/invalid URI " + historyURI);
-      return null;
-    }
-
-    final long visitCount = getLongFromCursor(cur, BrowserContract.History.VISITS);
-    if (visitCount <= 0) {
-      Logger.debug(LOG_TAG, "Skipping history record " + guid + " with <= 0 visit count.");
-      return null;
-    }
-
-    final String collection = "history";
-    final long lastModified = getLongFromCursor(cur, BrowserContract.SyncColumns.DATE_MODIFIED);
-    final boolean deleted = getLongFromCursor(cur, BrowserContract.SyncColumns.IS_DELETED) == 1;
-
-    final HistoryRecord rec = new HistoryRecord(guid, collection, lastModified, deleted);
-
-    rec.androidID         = getLongFromCursor(cur, BrowserContract.History._ID);
-    rec.fennecDateVisited = getLongFromCursor(cur, BrowserContract.History.DATE_LAST_VISITED);
-    rec.fennecVisitCount  = visitCount;
-    rec.histURI           = historyURI;
-    rec.title             = getStringFromCursor(cur, BrowserContract.History.TITLE);
-
-    return logHistory(rec);
-  }
-
-  private static HistoryRecord logHistory(HistoryRecord rec) {
-    try {
-      Logger.debug(LOG_TAG, "Returning history record " + rec.guid + " (" + rec.androidID + ")");
-      Logger.debug(LOG_TAG, "> Visited:          " + rec.fennecDateVisited);
-      Logger.debug(LOG_TAG, "> Visits:           " + rec.fennecVisitCount);
-      if (Logger.LOG_PERSONAL_INFORMATION) {
-        Logger.pii(LOG_TAG, "> Title:            " + rec.title);
-        Logger.pii(LOG_TAG, "> URI:              " + rec.histURI);
-      }
-    } catch (Exception e) {
-      Logger.debug(LOG_TAG, "Exception logging history record " + rec, e);
-    }
-    return rec;
-  }
-
-  public static void logClient(ClientRecord rec) {
-    if (Logger.shouldLogVerbose(LOG_TAG)) {
-      Logger.trace(LOG_TAG, "Returning client record " + rec.guid + " (" + rec.androidID + ")");
-      Logger.trace(LOG_TAG, "Client Name:   " + rec.name);
-      Logger.trace(LOG_TAG, "Client Type:   " + rec.type);
-      Logger.trace(LOG_TAG, "Last Modified: " + rec.lastModified);
-      Logger.trace(LOG_TAG, "Deleted:       " + rec.deleted);
-    }
-  }
-
-  public static void queryTimeLogger(String methodCallingQuery, long queryStart, long queryEnd) {
-    long elapsedTime = queryEnd - queryStart;
-    Logger.debug(LOG_TAG, "Query timer: " + methodCallingQuery + " took " + elapsedTime + "ms.");
-  }
-
-  public static boolean stringsEqual(String a, String b) {
-    // Check for nulls
-    if (a == b) return true;
-    if (a == null && b != null) return false;
-    if (a != null && b == null) return false;
-
-    return a.equals(b);
-  }
-
-  public static String computeSQLLongInClause(long[] items, String field) {
-    final StringBuilder builder = new StringBuilder(field);
-    builder.append(" IN (");
-    int i = 0;
-    for (; i < items.length - 1; ++i) {
-      builder.append(items[i]);
-      builder.append(", ");
-    }
-    if (i < items.length) {
-      builder.append(items[i]);
-    }
-    builder.append(")");
-    return builder.toString();
-  }
-
-  public static String computeSQLInClause(int items, String field) {
-    final StringBuilder builder = new StringBuilder(field);
-    builder.append(" IN (");
-    int i = 0;
-    for (; i < items - 1; ++i) {
-      builder.append("?, ");
-    }
-    if (i < items) {
-      builder.append("?");
-    }
-    builder.append(")");
-    return builder.toString();
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/VisitsHelper.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/VisitsHelper.java
deleted file mode 100644
index 9ba7847..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/VisitsHelper.java
+++ /dev/null
@@ -1,130 +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.sync.repositories.android;
-
-import android.content.ContentProviderClient;
-import android.content.ContentValues;
-import android.database.Cursor;
-import android.net.Uri;
-import android.os.RemoteException;
-import android.support.annotation.NonNull;
-
-import org.json.simple.JSONArray;
-import org.json.simple.JSONObject;
-import org.mozilla.gecko.db.BrowserContract.Visits;
-
-/**
- * This class is used by History Sync code (see <code>AndroidBrowserHistoryDataAccessor</code> and <code>AndroidBrowserHistoryRepositorySession</code>,
- * and provides utility functions for working with history visits. Primarily we're either inserting visits
- * into local database based on data received from Sync, or we're preparing local visits for upload into Sync.
- */
-public class VisitsHelper {
-    public static final boolean DEFAULT_IS_LOCAL_VALUE = false;
-    public static final String SYNC_TYPE_KEY = "type";
-    public static final String SYNC_DATE_KEY = "date";
-
-    /**
-     * Returns a list of ContentValues of visits ready for insertion for a provided History GUID.
-     * Visits must have data and type. See <code>getVisitContentValues</code>.
-     *
-     * @param guid History GUID to use when inserting visit records
-     * @param visits <code>JSONArray</code> list of (date, type) tuples for visits
-     * @return visits ready for insertion
-     */
-    public static ContentValues[] getVisitsContentValues(@NonNull String guid, @NonNull JSONArray visits) {
-        final ContentValues[] visitsToStore = new ContentValues[visits.size()];
-        final int visitCount = visits.size();
-
-        if (visitCount == 0) {
-            return visitsToStore;
-        }
-
-        for (int i = 0; i < visitCount; i++) {
-            visitsToStore[i] = getVisitContentValues(
-                    guid, (JSONObject) visits.get(i), DEFAULT_IS_LOCAL_VALUE);
-        }
-        return visitsToStore;
-    }
-
-    /**
-     * Maps up to <code>limit</code> visits for a given history GUID to an array of JSONObjects with "date" and "type" keys
-     *
-     * @param contentClient <code>ContentProviderClient</code> to use for querying Visits table
-     * @param guid History GUID for which to return visits
-     * @param limit Will return at most this number of visits
-     * @return <code>JSONArray</code> of all visits found for given History GUID
-     */
-    public static JSONArray getRecentHistoryVisitsForGUID(@NonNull ContentProviderClient contentClient,
-                                                          @NonNull String guid, int limit) throws RemoteException {
-        final JSONArray visits = new JSONArray();
-
-        final Cursor cursor = contentClient.query(
-                visitsUriWithLimit(limit),
-                new String[] {Visits.VISIT_TYPE, Visits.DATE_VISITED},
-                Visits.HISTORY_GUID + " = ?",
-                new String[] {guid}, null);
-        if (cursor == null) {
-            return visits;
-        }
-        try {
-            if (!cursor.moveToFirst()) {
-                return visits;
-            }
-
-            final int dateVisitedCol = cursor.getColumnIndexOrThrow(Visits.DATE_VISITED);
-            final int visitTypeCol = cursor.getColumnIndexOrThrow(Visits.VISIT_TYPE);
-
-            while (!cursor.isAfterLast()) {
-                insertTupleIntoVisitsUnchecked(visits,
-                        cursor.getLong(visitTypeCol),
-                        cursor.getLong(dateVisitedCol)
-                );
-                cursor.moveToNext();
-            }
-        } finally {
-            cursor.close();
-        }
-
-        return visits;
-    }
-
-    /**
-     * Constructs <code>ContentValues</code> object for a visit based on passed in parameters.
-     *
-     * @param visit <code>JSONObject</code> containing visit type and visit date keys for the visit
-     * @param guid History GUID with with to associate this visit
-     * @param isLocal Whether or not to mark this visit as local
-     * @return <code>ContentValues</code> with all visit values necessary for database insertion
-     * @throws IllegalArgumentException if visit object is missing date or type keys
-     */
-    public static ContentValues getVisitContentValues(@NonNull String guid, @NonNull JSONObject visit, boolean isLocal) {
-        if (!visit.containsKey(SYNC_DATE_KEY) || !visit.containsKey(SYNC_TYPE_KEY)) {
-            throw new IllegalArgumentException("Visit missing required keys");
-        }
-
-        final ContentValues cv = new ContentValues();
-        cv.put(Visits.HISTORY_GUID, guid);
-        cv.put(Visits.IS_LOCAL, isLocal ? Visits.VISIT_IS_LOCAL : Visits.VISIT_IS_REMOTE);
-        cv.put(Visits.VISIT_TYPE, (Long) visit.get(SYNC_TYPE_KEY));
-        cv.put(Visits.DATE_VISITED, (Long) visit.get(SYNC_DATE_KEY));
-
-        return cv;
-    }
-
-    @SuppressWarnings("unchecked")
-    private static void insertTupleIntoVisitsUnchecked(@NonNull final JSONArray visits, @NonNull Long type, @NonNull Long date) {
-        final JSONObject visit = new JSONObject();
-        visit.put(SYNC_TYPE_KEY, type);
-        visit.put(SYNC_DATE_KEY, date);
-        visits.add(visit);
-    }
-
-    private static Uri visitsUriWithLimit(int limit) {
-        return BrowserContractHelpers.VISITS_CONTENT_URI
-                .buildUpon()
-                .appendQueryParameter("limit", Integer.toString(limit))
-                .build();
-    }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/delegates/DeferrableRepositorySessionCreationDelegate.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/delegates/DeferrableRepositorySessionCreationDelegate.java
deleted file mode 100644
index f292600..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/delegates/DeferrableRepositorySessionCreationDelegate.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.sync.repositories.delegates;
-
-import org.mozilla.gecko.sync.ThreadPool;
-import org.mozilla.gecko.sync.repositories.RepositorySession;
-
-public abstract class DeferrableRepositorySessionCreationDelegate implements RepositorySessionCreationDelegate {
-  @Override
-  public RepositorySessionCreationDelegate deferredCreationDelegate() {
-    final RepositorySessionCreationDelegate self = this;
-    return new RepositorySessionCreationDelegate() {
-
-      // TODO: rewrite to use ExecutorService.
-      @Override
-      public void onSessionCreated(final RepositorySession session) {
-        ThreadPool.run(new Runnable() {
-          @Override
-          public void run() {
-            self.onSessionCreated(session);
-          }});
-      }
-
-      @Override
-      public void onSessionCreateFailed(final Exception ex) {
-        ThreadPool.run(new Runnable() {
-          @Override
-          public void run() {
-            self.onSessionCreateFailed(ex);
-          }});
-      }
-
-      @Override
-      public RepositorySessionCreationDelegate deferredCreationDelegate() {
-        return this;
-      }
-    };
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/delegates/DeferredRepositorySessionBeginDelegate.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/delegates/DeferredRepositorySessionBeginDelegate.java
deleted file mode 100644
index 1ccdcce..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/delegates/DeferredRepositorySessionBeginDelegate.java
+++ /dev/null
@@ -1,46 +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.sync.repositories.delegates;
-
-import java.util.concurrent.ExecutorService;
-
-import org.mozilla.gecko.sync.repositories.RepositorySession;
-
-public class DeferredRepositorySessionBeginDelegate implements RepositorySessionBeginDelegate {
-  private final RepositorySessionBeginDelegate inner;
-  private final ExecutorService executor;
-  public DeferredRepositorySessionBeginDelegate(final RepositorySessionBeginDelegate inner, final ExecutorService executor) {
-    this.inner = inner;
-    this.executor = executor;
-  }
-
-  @Override
-  public void onBeginSucceeded(final RepositorySession session) {
-    executor.execute(new Runnable() {
-      @Override
-      public void run() {
-        inner.onBeginSucceeded(session);
-      }
-    });
-  }
-
-  @Override
-  public void onBeginFailed(final Exception ex) {
-    executor.execute(new Runnable() {
-      @Override
-      public void run() {
-        inner.onBeginFailed(ex);
-      }
-    });
-  }
-  
-  @Override
-  public RepositorySessionBeginDelegate deferredBeginDelegate(ExecutorService newExecutor) {
-    if (newExecutor == executor) {
-      return this;
-    }
-    throw new IllegalArgumentException("Can't re-defer this delegate.");
-  }
-}
\ No newline at end of file
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/delegates/DeferredRepositorySessionFetchRecordsDelegate.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/delegates/DeferredRepositorySessionFetchRecordsDelegate.java
deleted file mode 100644
index 1178d9b..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/delegates/DeferredRepositorySessionFetchRecordsDelegate.java
+++ /dev/null
@@ -1,56 +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.sync.repositories.delegates;
-
-import java.util.concurrent.ExecutorService;
-
-import org.mozilla.gecko.sync.repositories.domain.Record;
-
-public class DeferredRepositorySessionFetchRecordsDelegate implements RepositorySessionFetchRecordsDelegate {
-  private final RepositorySessionFetchRecordsDelegate inner;
-  private final ExecutorService executor;
-  public DeferredRepositorySessionFetchRecordsDelegate(final RepositorySessionFetchRecordsDelegate inner, final ExecutorService executor) {
-    this.inner = inner;
-    this.executor = executor;
-  }
-
-  @Override
-  public void onFetchedRecord(final Record record) {
-    executor.execute(new Runnable() {
-      @Override
-      public void run() {
-         inner.onFetchedRecord(record);
-      }
-    });
-  }
-
-  @Override
-  public void onFetchFailed(final Exception ex, final Record record) {
-    executor.execute(new Runnable() {
-      @Override
-      public void run() {
-        inner.onFetchFailed(ex, record);
-      }
-    });
-  }
-
-  @Override
-  public void onFetchCompleted(final long fetchEnd) {
-    executor.execute(new Runnable() {
-      @Override
-      public void run() {
-        inner.onFetchCompleted(fetchEnd);
-      }
-    });
-  }
-
-  @Override
-  public RepositorySessionFetchRecordsDelegate deferredFetchDelegate(ExecutorService newExecutor) {
-    if (newExecutor == executor) {
-      return this;
-    }
-    throw new IllegalArgumentException("Can't re-defer this delegate.");
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/delegates/DeferredRepositorySessionFinishDelegate.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/delegates/DeferredRepositorySessionFinishDelegate.java
deleted file mode 100644
index dbe7e43..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/delegates/DeferredRepositorySessionFinishDelegate.java
+++ /dev/null
@@ -1,51 +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.sync.repositories.delegates;
-
-import java.util.concurrent.ExecutorService;
-
-import org.mozilla.gecko.sync.repositories.RepositorySession;
-import org.mozilla.gecko.sync.repositories.RepositorySessionBundle;
-
-public class DeferredRepositorySessionFinishDelegate implements
-    RepositorySessionFinishDelegate {
-  protected final ExecutorService executor;
-  protected final RepositorySessionFinishDelegate inner;
-
-  public DeferredRepositorySessionFinishDelegate(RepositorySessionFinishDelegate inner,
-                                                 ExecutorService executor) {
-    this.executor = executor;
-    this.inner = inner;
-  }
-
-  @Override
-  public void onFinishSucceeded(final RepositorySession session,
-                                final RepositorySessionBundle bundle) {
-    executor.execute(new Runnable() {
-      @Override
-      public void run() {
-        inner.onFinishSucceeded(session, bundle);
-      }
-    });
-  }
-
-  @Override
-  public void onFinishFailed(final Exception ex) {
-    executor.execute(new Runnable() {
-      @Override
-      public void run() {
-        inner.onFinishFailed(ex);
-      }
-    });
-  }
-
-  @Override
-  public RepositorySessionFinishDelegate deferredFinishDelegate(ExecutorService newExecutor) {
-    if (newExecutor == executor) {
-      return this;
-    }
-    throw new IllegalArgumentException("Can't re-defer this delegate.");
-  }
-}
\ No newline at end of file
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/delegates/DeferredRepositorySessionStoreDelegate.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/delegates/DeferredRepositorySessionStoreDelegate.java
deleted file mode 100644
index 2f659c7..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/delegates/DeferredRepositorySessionStoreDelegate.java
+++ /dev/null
@@ -1,57 +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.sync.repositories.delegates;
-
-import java.util.concurrent.ExecutorService;
-
-public class DeferredRepositorySessionStoreDelegate implements
-    RepositorySessionStoreDelegate {
-  protected final RepositorySessionStoreDelegate inner;
-  protected final ExecutorService                executor;
-
-  public DeferredRepositorySessionStoreDelegate(
-      RepositorySessionStoreDelegate inner, ExecutorService executor) {
-    this.inner = inner;
-    this.executor = executor;
-  }
-
-  @Override
-  public void onRecordStoreSucceeded(final String guid) {
-    executor.execute(new Runnable() {
-      @Override
-      public void run() {
-        inner.onRecordStoreSucceeded(guid);
-      }
-    });
-  }
-
-  @Override
-  public void onRecordStoreFailed(final Exception ex, final String guid) {
-    executor.execute(new Runnable() {
-      @Override
-      public void run() {
-        inner.onRecordStoreFailed(ex, guid);
-      }
-    });
-  }
-
-  @Override
-  public RepositorySessionStoreDelegate deferredStoreDelegate(ExecutorService newExecutor) {
-    if (newExecutor == executor) {
-      return this;
-    }
-    throw new IllegalArgumentException("Can't re-defer this delegate.");
-  }
-
-  @Override
-  public void onStoreCompleted(final long storeEnd) {
-    executor.execute(new Runnable() {
-      @Override
-      public void run() {
-        inner.onStoreCompleted(storeEnd);
-      }
-    });
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/delegates/RepositorySessionBeginDelegate.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/delegates/RepositorySessionBeginDelegate.java
deleted file mode 100644
index f585364..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/delegates/RepositorySessionBeginDelegate.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.sync.repositories.delegates;
-
-import java.util.concurrent.ExecutorService;
-
-import org.mozilla.gecko.sync.repositories.RepositorySession;
-
-/**
- * One of these two methods is guaranteed to be called after session.begin() is
- * invoked (possibly during the invocation). The callback will be invoked prior
- * to any other RepositorySession callbacks.
- *
- * @author rnewman
- *
- */
-public interface RepositorySessionBeginDelegate {
-  public void onBeginFailed(Exception ex);
-  public void onBeginSucceeded(RepositorySession session);
-  public RepositorySessionBeginDelegate deferredBeginDelegate(ExecutorService executor);
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/delegates/RepositorySessionCleanDelegate.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/delegates/RepositorySessionCleanDelegate.java
deleted file mode 100644
index 139c561..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/delegates/RepositorySessionCleanDelegate.java
+++ /dev/null
@@ -1,12 +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.sync.repositories.delegates;
-
-import org.mozilla.gecko.sync.repositories.Repository;
-
-public interface RepositorySessionCleanDelegate {
-  public void onCleaned(Repository repo);
-  public void onCleanFailed(Repository repo, Exception ex);
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/delegates/RepositorySessionCreationDelegate.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/delegates/RepositorySessionCreationDelegate.java
deleted file mode 100644
index 6ad4991..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/delegates/RepositorySessionCreationDelegate.java
+++ /dev/null
@@ -1,15 +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.sync.repositories.delegates;
-
-import org.mozilla.gecko.sync.repositories.RepositorySession;
-
-// Used to provide the sessionCallback and storeCallback
-// mechanism to repository instances.
-public interface RepositorySessionCreationDelegate {
-  public void onSessionCreateFailed(Exception ex);
-  public void onSessionCreated(RepositorySession session);
-  public RepositorySessionCreationDelegate deferredCreationDelegate();
-}
\ No newline at end of file
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/delegates/RepositorySessionFetchRecordsDelegate.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/delegates/RepositorySessionFetchRecordsDelegate.java
deleted file mode 100644
index 589a093..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/delegates/RepositorySessionFetchRecordsDelegate.java
+++ /dev/null
@@ -1,27 +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.sync.repositories.delegates;
-
-import java.util.concurrent.ExecutorService;
-
-import org.mozilla.gecko.sync.repositories.domain.Record;
-
-public interface RepositorySessionFetchRecordsDelegate {
-  public void onFetchFailed(Exception ex, Record record);
-  public void onFetchedRecord(Record record);
-
-  /**
-   * Called when all records in this fetch have been returned.
-   *
-   * @param fetchEnd
-   *        A millisecond-resolution timestamp indicating the *remote* timestamp
-   *        at the end of the range of records. Usually this is the timestamp at
-   *        which the request was received.
-   *        E.g., the (normalized) value of the X-Weave-Timestamp header.
-   */
-  public void onFetchCompleted(final long fetchEnd);
-
-  public RepositorySessionFetchRecordsDelegate deferredFetchDelegate(ExecutorService executor);
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/delegates/RepositorySessionFinishDelegate.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/delegates/RepositorySessionFinishDelegate.java
deleted file mode 100644
index 40296dd..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/delegates/RepositorySessionFinishDelegate.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.sync.repositories.delegates;
-
-import java.util.concurrent.ExecutorService;
-
-import org.mozilla.gecko.sync.repositories.RepositorySession;
-import org.mozilla.gecko.sync.repositories.RepositorySessionBundle;
-
-public interface RepositorySessionFinishDelegate {
-  public void onFinishFailed(Exception ex);
-  public void onFinishSucceeded(RepositorySession session, RepositorySessionBundle bundle);
-  public RepositorySessionFinishDelegate deferredFinishDelegate(ExecutorService executor);
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/delegates/RepositorySessionGuidsSinceDelegate.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/delegates/RepositorySessionGuidsSinceDelegate.java
deleted file mode 100644
index 4f82768..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/delegates/RepositorySessionGuidsSinceDelegate.java
+++ /dev/null
@@ -1,10 +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.sync.repositories.delegates;
-
-public interface RepositorySessionGuidsSinceDelegate {
-  public void onGuidsSinceFailed(Exception ex);
-  public void onGuidsSinceSucceeded(String[] guids);
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/delegates/RepositorySessionStoreDelegate.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/delegates/RepositorySessionStoreDelegate.java
deleted file mode 100644
index 01e44c3..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/delegates/RepositorySessionStoreDelegate.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.sync.repositories.delegates;
-
-import java.util.concurrent.ExecutorService;
-
-/**
- * These methods *must* be invoked asynchronously. Use deferredStoreDelegate if you
- * need help doing this.
- *
- * @author rnewman
- *
- */
-public interface RepositorySessionStoreDelegate {
-  public void onRecordStoreFailed(Exception ex, String recordGuid);
-
-  // Called with a GUID when store has succeeded.
-  public void onRecordStoreSucceeded(String guid);
-  public void onStoreCompleted(long storeEnd);
-  public RepositorySessionStoreDelegate deferredStoreDelegate(ExecutorService executor);
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/delegates/RepositorySessionWipeDelegate.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/delegates/RepositorySessionWipeDelegate.java
deleted file mode 100644
index cc88307..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/delegates/RepositorySessionWipeDelegate.java
+++ /dev/null
@@ -1,13 +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.sync.repositories.delegates;
-
-import java.util.concurrent.ExecutorService;
-
-public interface RepositorySessionWipeDelegate {
-  public void onWipeFailed(Exception ex);
-  public void onWipeSucceeded();
-  public RepositorySessionWipeDelegate deferredWipeDelegate(ExecutorService executor);
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/domain/BookmarkRecord.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/domain/BookmarkRecord.java
deleted file mode 100644
index 27b8e71..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/domain/BookmarkRecord.java
+++ /dev/null
@@ -1,488 +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.sync.repositories.domain;
-
-import java.io.UnsupportedEncodingException;
-import java.net.URLEncoder;
-import java.util.Map;
-
-import org.json.simple.JSONArray;
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.sync.ExtendedJSONObject;
-import org.mozilla.gecko.sync.NonArrayJSONException;
-import org.mozilla.gecko.sync.Utils;
-import org.mozilla.gecko.sync.repositories.android.RepoUtils;
-
-/**
- * Covers the fields used by all bookmark objects.
- * @author rnewman
- *
- */
-public class BookmarkRecord extends Record {
-  public static final String PLACES_URI_PREFIX = "places:";
-
-  private static final String LOG_TAG = "BookmarkRecord";
-
-  public static final String COLLECTION_NAME = "bookmarks";
-  public static final long BOOKMARKS_TTL = -1; // Never ttl bookmarks.
-
-  public BookmarkRecord(String guid, String collection, long lastModified, boolean deleted) {
-    super(guid, collection, lastModified, deleted);
-    this.ttl = BOOKMARKS_TTL;
-  }
-  public BookmarkRecord(String guid, String collection, long lastModified) {
-    this(guid, collection, lastModified, false);
-  }
-  public BookmarkRecord(String guid, String collection) {
-    this(guid, collection, 0, false);
-  }
-  public BookmarkRecord(String guid) {
-    this(guid, COLLECTION_NAME, 0, false);
-  }
-  public BookmarkRecord() {
-    this(Utils.generateGuid(), COLLECTION_NAME, 0, false);
-  }
-
-  // Note: redundant accessors are evil. We're all grownups; let's just use
-  // public fields.
-  public String  title;
-  public String  bookmarkURI;
-  public String  description;
-  public String  keyword;
-  public String  parentID;
-  public String  parentName;
-  public long    androidParentID;
-  public String  type;
-  public long    androidPosition;
-
-  public JSONArray children;
-  public JSONArray tags;
-
-  @Override
-  public String toString() {
-    return "#<Bookmark " + guid + " (" + androidID + "), parent " +
-           parentID + "/" + androidParentID + "/" + parentName + ">";
-  }
-
-  // Oh God, this is terribly thread-unsafe. These record objects should be immutable.
-  @SuppressWarnings("unchecked")
-  protected JSONArray copyChildren() {
-    if (this.children == null) {
-      return null;
-    }
-    JSONArray children = new JSONArray();
-    children.addAll(this.children);
-    return children;
-  }
-
-  @SuppressWarnings("unchecked")
-  protected JSONArray copyTags() {
-    if (this.tags == null) {
-      return null;
-    }
-    JSONArray tags = new JSONArray();
-    tags.addAll(this.tags);
-    return tags;
-  }
-
-  @Override
-  public Record copyWithIDs(String guid, long androidID) {
-    BookmarkRecord out = new BookmarkRecord(guid, this.collection, this.lastModified, this.deleted);
-    out.androidID = androidID;
-    out.sortIndex = this.sortIndex;
-    out.ttl       = this.ttl;
-
-    // Copy BookmarkRecord fields.
-    out.title           = this.title;
-    out.bookmarkURI     = this.bookmarkURI;
-    out.description     = this.description;
-    out.keyword         = this.keyword;
-    out.parentID        = this.parentID;
-    out.parentName      = this.parentName;
-    out.androidParentID = this.androidParentID;
-    out.type            = this.type;
-    out.androidPosition = this.androidPosition;
-
-    out.children        = this.copyChildren();
-    out.tags            = this.copyTags();
-
-    return out;
-  }
-
-  public boolean isBookmark() {
-    if (type == null) {
-      return false;
-    }
-    return type.equals("bookmark");
-  }
-
-  public boolean isFolder() {
-    if (type == null) {
-      return false;
-    }
-    return type.equals("folder");
-  }
-
-  public boolean isLivemark() {
-    if (type == null) {
-      return false;
-    }
-    return type.equals("livemark");
-  }
-
-  public boolean isSeparator() {
-    if (type == null) {
-      return false;
-    }
-    return type.equals("separator");
-  }
-
-  public boolean isMicrosummary() {
-    if (type == null) {
-      return false;
-    }
-    return type.equals("microsummary");
-  }
-
-  public boolean isQuery() {
-    if (type == null) {
-      return false;
-    }
-    return type.equals("query");
-  }
-
-  /**
-   * Return true if this record should have the Sync fields
-   * of a bookmark, microsummary, or query.
-   */
-  private boolean isBookmarkIsh() {
-    if (type == null) {
-      return false;
-    }
-    return type.equals("bookmark") ||
-           type.equals("microsummary") ||
-           type.equals("query");
-  }
-
-  @Override
-  protected void initFromPayload(ExtendedJSONObject payload) {
-    this.type        = payload.getString("type");
-    this.title       = payload.getString("title");
-    this.description = payload.getString("description");
-    this.parentID    = payload.getString("parentid");
-    this.parentName  = payload.getString("parentName");
-
-    if (isFolder()) {
-      try {
-        this.children = payload.getArray("children");
-      } catch (NonArrayJSONException e) {
-        Logger.error(LOG_TAG, "Got non-array children in bookmark record " + this.guid, e);
-        // Let's see if we can recover later by using the parentid pointers.
-        this.children = new JSONArray();
-      }
-      return;
-    }
-
-    final String bmkUri = payload.getString("bmkUri");
-
-    // bookmark, microsummary, query.
-    if (isBookmarkIsh()) {
-      this.keyword = payload.getString("keyword");
-      try {
-        this.tags = payload.getArray("tags");
-      } catch (NonArrayJSONException e) {
-        Logger.warn(LOG_TAG, "Got non-array tags in bookmark record " + this.guid, e);
-        this.tags = new JSONArray();
-      }
-    }
-
-    if (isBookmark()) {
-      this.bookmarkURI = bmkUri;
-      return;
-    }
-
-    if (isLivemark()) {
-      String siteUri = payload.getString("siteUri");
-      String feedUri = payload.getString("feedUri");
-      this.bookmarkURI = encodeUnsupportedTypeURI(bmkUri,
-                                                  "siteUri", siteUri,
-                                                  "feedUri", feedUri);
-      return;
-    }
-    if (isQuery()) {
-      String queryId = payload.getString("queryId");
-      String folderName = payload.getString("folderName");
-      this.bookmarkURI = encodeUnsupportedTypeURI(bmkUri,
-                                                  "queryId", queryId,
-                                                  "folderName", folderName);
-      return;
-    }
-    if (isMicrosummary()) {
-      String generatorUri = payload.getString("generatorUri");
-      String staticTitle = payload.getString("staticTitle");
-      this.bookmarkURI = encodeUnsupportedTypeURI(bmkUri,
-                                                  "generatorUri", generatorUri,
-                                                  "staticTitle", staticTitle);
-      return;
-    }
-    if (isSeparator()) {
-      Object p = payload.get("pos");
-      if (p instanceof Long) {
-        this.androidPosition = (Long) p;
-      } else if (p instanceof String) {
-        try {
-          this.androidPosition = Long.parseLong((String) p, 10);
-        } catch (NumberFormatException e) {
-          return;
-        }
-      } else {
-        Logger.warn(LOG_TAG, "Unsupported position value " + p);
-        return;
-      }
-      String pos = String.valueOf(this.androidPosition);
-      this.bookmarkURI = encodeUnsupportedTypeURI(null, "pos", pos, null, null);
-      return;
-    }
-  }
-
-  @Override
-  protected void populatePayload(ExtendedJSONObject payload) {
-    putPayload(payload, "type", this.type);
-    putPayload(payload, "title", this.title);
-    putPayload(payload, "description", this.description);
-    putPayload(payload, "parentid", this.parentID);
-    putPayload(payload, "parentName", this.parentName);
-    putPayload(payload, "keyword", this.keyword);
-
-    if (isFolder()) {
-      payload.put("children", this.children);
-      return;
-    }
-
-    // bookmark, microsummary, query.
-    if (isBookmarkIsh()) {
-      if (isBookmark()) {
-        payload.put("bmkUri", bookmarkURI);
-      }
-
-      if (isQuery()) {
-        Map<String, String> parts = Utils.extractURIComponents(PLACES_URI_PREFIX, this.bookmarkURI);
-        putPayload(payload, "queryId", parts.get("queryId"), true);
-        putPayload(payload, "folderName", parts.get("folderName"), true);
-        putPayload(payload, "bmkUri", parts.get("uri"));
-        return;
-      }
-
-      if (this.tags != null) {
-        payload.put("tags", this.tags);
-      }
-
-      putPayload(payload, "keyword", this.keyword);
-      return;
-    }
-
-    if (isLivemark()) {
-      Map<String, String> parts = Utils.extractURIComponents(PLACES_URI_PREFIX, this.bookmarkURI);
-      putPayload(payload, "siteUri", parts.get("siteUri"));
-      putPayload(payload, "feedUri", parts.get("feedUri"));
-      return;
-    }
-    if (isMicrosummary()) {
-      Map<String, String> parts = Utils.extractURIComponents(PLACES_URI_PREFIX, this.bookmarkURI);
-      putPayload(payload, "generatorUri", parts.get("generatorUri"));
-      putPayload(payload, "staticTitle", parts.get("staticTitle"));
-      return;
-    }
-    if (isSeparator()) {
-      Map<String, String> parts = Utils.extractURIComponents(PLACES_URI_PREFIX, this.bookmarkURI);
-      String pos = parts.get("pos");
-      if (pos == null) {
-        return;
-      }
-      try {
-        payload.put("pos", Long.parseLong(pos, 10));
-      } catch (NumberFormatException e) {
-        return;
-      }
-      return;
-    }
-  }
-
-  private void trace(String s) {
-    Logger.trace(LOG_TAG, s);
-  }
-
-  @Override
-  public boolean equalPayloads(Object o) {
-    trace("Calling BookmarkRecord.equalPayloads.");
-    if (!(o instanceof BookmarkRecord)) {
-      return false;
-    }
-
-    BookmarkRecord other = (BookmarkRecord) o;
-    if (!super.equalPayloads(other)) {
-      return false;
-    }
-
-    if (!RepoUtils.stringsEqual(this.type, other.type)) {
-      return false;
-    }
-
-    // Check children.
-    if (isFolder() && (this.children != other.children)) {
-      trace("BookmarkRecord.equals: this folder: " + this.title + ", " + this.guid);
-      trace("BookmarkRecord.equals: other: " + other.title + ", " + other.guid);
-      if (this.children  == null &&
-          other.children != null) {
-        trace("Records differ: one children array is null.");
-        return false;
-      }
-      if (this.children  != null &&
-          other.children == null) {
-        trace("Records differ: one children array is null.");
-        return false;
-      }
-      if (this.children.size() != other.children.size()) {
-        trace("Records differ: children arrays differ in size (" +
-              this.children.size() + " vs. " + other.children.size() + ").");
-        return false;
-      }
-
-      for (int i = 0; i < this.children.size(); i++) {
-        String child = (String) this.children.get(i);
-        if (!other.children.contains(child)) {
-          trace("Records differ: child " + child + " not found.");
-          return false;
-        }
-      }
-    }
-
-    trace("Checking strings.");
-    return RepoUtils.stringsEqual(this.title, other.title)
-        && RepoUtils.stringsEqual(this.bookmarkURI, other.bookmarkURI)
-        && RepoUtils.stringsEqual(this.parentID, other.parentID)
-        && RepoUtils.stringsEqual(this.parentName, other.parentName)
-        && RepoUtils.stringsEqual(this.description, other.description)
-        && RepoUtils.stringsEqual(this.keyword, other.keyword)
-        && jsonArrayStringsEqual(this.tags, other.tags);
-  }
-
-  // TODO: two records can be congruent if their child lists are different.
-  @Override
-  public boolean congruentWith(Object o) {
-    return this.equalPayloads(o) &&
-           super.congruentWith(o);
-  }
-
-  // Converts two JSONArrays to strings and checks if they are the same.
-  // This is only useful for stuff like tags where we aren't actually
-  // touching the data there (and therefore ordering won't change)
-  private boolean jsonArrayStringsEqual(JSONArray a, JSONArray b) {
-    // Check for nulls
-    if (a == b) return true;
-    if (a == null && b != null) return false;
-    if (a != null && b == null) return false;
-    return RepoUtils.stringsEqual(a.toJSONString(), b.toJSONString());
-  }
-
-  /**
-   * URL-encode the provided string. If the input is null,
-   * the empty string is returned.
-   *
-   * @param in the string to encode.
-   * @return a URL-encoded version of the input.
-   */
-  protected static String encode(String in) {
-    if (in == null) {
-      return "";
-    }
-    try {
-      return URLEncoder.encode(in, "UTF-8");
-    } catch (UnsupportedEncodingException e) {
-      // Will never occur.
-      return null;
-    }
-  }
-
-  /**
-   * Take the provided URI and two parameters, constructing a URI like
-   *
-   *   places:uri=$uri&p1=$p1&p2=$p2
-   *
-   * null values in either parameter or value result in the parameter being omitted.
-   */
-  protected static String encodeUnsupportedTypeURI(String originalURI, String p1, String v1, String p2, String v2) {
-    StringBuilder b = new StringBuilder(PLACES_URI_PREFIX);
-    boolean previous = false;
-    if (originalURI != null) {
-      b.append("uri=");
-      b.append(encode(originalURI));
-      previous = true;
-    }
-    if (p1 != null && v1 != null) {
-      if (previous) {
-        b.append("&");
-      }
-      b.append(p1);
-      b.append("=");
-      b.append(encode(v1));
-      previous = true;
-    }
-    if (p2 != null && v2 != null) {
-      if (previous) {
-        b.append("&");
-      }
-      b.append(p2);
-      b.append("=");
-      b.append(encode(v2));
-      previous = true;
-    }
-    return b.toString();
-  }
-}
-
-
-/*
-// Bookmark:
-{cleartext:
-  {id:            "l7p2xqOTMMXw",
-   type:          "bookmark",
-   title:         "Your Flight Status",
-   parentName:    "mobile",
-   bmkUri:        "http: //www.flightstats.com/go/Mobile/flightStatusByFlightProcess.do;jsessionid=13A6C8DCC9592AF141A43349040262CE.web3: 8009?utm_medium=cpc&utm_campaign=co-op&utm_source=airlineInformationAndStatus&id=212492593",
-   tags:          [],
-   keyword:       null,
-   description:   null,
-   loadInSidebar: false,
-   parentid:      "mobile"},
- data: {payload: {ciphertext: null},
- id:         "l7p2xqOTMMXw",
- sortindex:  107},
- collection: "bookmarks"}
-
-// Folder:
-{cleartext:
-  {id:          "mobile",
-   type:        "folder",
-   parentName:  "",
-   title:       "mobile",
-   description: null,
-   children:    ["1ROdlTuIoddD", "3Z_bMIHPSZQ8", "4mSDUuOo2iVB", "8aEdE9IIrJVr",
-                 "9DzPTmkkZRDb", "Qwwb99HtVKsD", "s8tM36aGPKbq", "JMTi61hOO3JV",
-                 "JQUDk0wSvYip", "LmVH-J1r3HLz", "NhgQlC5ykYGW", "OVanevUUaqO2",
-                 "OtQVX0PMiWQj", "_GP5cF595iie", "fkRssjXSZDL3", "k7K_NwIA1Ya0",
-                 "raox_QGzvqh1", "vXYL-xHjK06k", "QKHKUN6Dm-xv", "pmN2dYWT2MJ_",
-                 "EVeO_J1SQiwL", "7N-qkepS7bec", "NIGa3ha-HVOE", "2Phv1I25wbuH",
-                 "TTSIAH1fV0VE", "WOmZ8PfH39Da", "gDTXNg4m1AJZ", "ayI30OZslHbO",
-                 "zSEs4O3n6CzQ", "oWTDR0gO2aWf", "wWHUoFaInXi9", "F7QTuVJDpsTM",
-                 "FIboggegplk-", "G4HWrT5nfRYS", "MHA7y9bupDdv", "T_Ldzmj0Ttte",
-                 "U9eYu3SxsE_U", "bk463Kl9IO_m", "brUfrqJjFNSR", "ccpawfWsD-bY",
-                 "l7p2xqOTMMXw", "o-nSDKtXYln7"],
-   parentid: "places"},
- data:        {payload: {ciphertext: null},
- id:          "mobile",
- sortindex:   1000000},
- collection: "bookmarks"}
-*/
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/domain/BookmarkRecordFactory.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/domain/BookmarkRecordFactory.java
deleted file mode 100644
index edf7b28..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/domain/BookmarkRecordFactory.java
+++ /dev/null
@@ -1,25 +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.sync.repositories.domain;
-
-import org.mozilla.gecko.sync.CryptoRecord;
-import org.mozilla.gecko.sync.repositories.RecordFactory;
-
-/**
- * Turns CryptoRecords into BookmarkRecords.
- *
- * @author rnewman
- *
- */
-public class BookmarkRecordFactory extends RecordFactory {
-
-  @Override
-  public Record createRecord(Record record) {
-    BookmarkRecord r = new BookmarkRecord();
-    r.initFromEnvelope((CryptoRecord) record);
-    return r;
-  }
-
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/domain/ClientRecord.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/domain/ClientRecord.java
deleted file mode 100644
index 0c513a4..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/domain/ClientRecord.java
+++ /dev/null
@@ -1,231 +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.sync.repositories.domain;
-
-import org.json.simple.JSONArray;
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.sync.ExtendedJSONObject;
-import org.mozilla.gecko.sync.NonArrayJSONException;
-import org.mozilla.gecko.sync.Utils;
-import org.mozilla.gecko.sync.repositories.android.RepoUtils;
-
-public class ClientRecord extends Record {
-  private static final String LOG_TAG = "ClientRecord";
-
-  public static final String CLIENT_TYPE         = "mobile";
-  public static final String COLLECTION_NAME     = "clients";
-  public static final long CLIENTS_TTL = 21 * 24 * 60 * 60; // 21 days in seconds.
-  public static final String DEFAULT_CLIENT_NAME = "Default Name";
-
-  public static final String PROTOCOL_LEGACY_SYNC = "1.1";
-  public static final String PROTOCOL_FXA_SYNC = "1.5";
-
-  /**
-   * Each of these fields is 'owned' by the client it represents. For example,
-   * the "version" field is the Firefox version of that client; some time after
-   * that client upgrades, it'll upload a new record with its new version.
-   *
-   * The only exception is for commands. When a command is sent to a client, the
-   * sender will download its current record, append the command to the
-   * "commands" array, and reupload the record. After processing, the recipient
-   * will reupload its record with an empty commands array.
-   *
-   * Note that the version, then, will remain the version of the recipient, as
-   * with the other descriptive fields.
-   */
-  public String name = ClientRecord.DEFAULT_CLIENT_NAME;
-  public String type = ClientRecord.CLIENT_TYPE;
-  public String version = null;                      // Free-form string, optional.
-  public JSONArray commands;
-  public JSONArray protocols;
-
-  // Optional fields.
-  // See <https://github.com/mozilla-services/docs/blob/master/source/sync/objectformats.rst#user-content-clients>
-  // for full formats.
-  // If a value isn't known, the field is omitted.
-  public String formfactor;          // "phone", "largetablet", "smalltablet", "desktop", "laptop", "tv".
-  public String os;                  // One of "Android", "Darwin", "WINNT", "Linux", "iOS", "Firefox OS".
-  public String application;         // Display name, E.g., "Firefox Beta"
-  public String appPackage;          // E.g., "org.mozilla.firefox_beta"
-  public String device;              // E.g., "HTC One"
-  public String fxaDeviceId;         // E.g., "525b624eaaf1e40d21ec8997c3116ad8"
-
-  public ClientRecord(String guid, String collection, long lastModified, boolean deleted) {
-    super(guid, collection, lastModified, deleted);
-    this.ttl = CLIENTS_TTL;
-  }
-
-  public ClientRecord(String guid, String collection, long lastModified) {
-    this(guid, collection, lastModified, false);
-  }
-
-  public ClientRecord(String guid, String collection) {
-    this(guid, collection, 0, false);
-  }
-
-  public ClientRecord(String guid) {
-    this(guid, COLLECTION_NAME, 0, false);
-  }
-
-  public ClientRecord() {
-    this(Utils.generateGuid(), COLLECTION_NAME, 0, false);
-  }
-
-  @Override
-  protected void initFromPayload(ExtendedJSONObject payload) {
-    this.name = (String) payload.get("name");
-    this.type = (String) payload.get("type");
-    try {
-      this.version = (String) payload.get("version");
-    } catch (Exception e) {
-      // Oh well.
-    }
-
-    try {
-      commands = payload.getArray("commands");
-    } catch (NonArrayJSONException e) {
-      Logger.debug(LOG_TAG, "Got non-array commands in client record " + guid, e);
-      commands = null;
-    }
-
-    try {
-      protocols = payload.getArray("protocols");
-    } catch (NonArrayJSONException e) {
-      Logger.debug(LOG_TAG, "Got non-array protocols in client record " + guid, e);
-      protocols = null;
-    }
-
-    if (payload.containsKey("formfactor")) {
-      this.formfactor = payload.getString("formfactor");
-    }
-
-    if (payload.containsKey("os")) {
-      this.os = payload.getString("os");
-    }
-
-    if (payload.containsKey("application")) {
-      this.application = payload.getString("application");
-    }
-
-    if (payload.containsKey("appPackage")) {
-      this.appPackage = payload.getString("appPackage");
-    }
-
-    if (payload.containsKey("device")) {
-      this.device = payload.getString("device");
-    }
-
-    if (payload.containsKey("fxaDeviceId")) {
-      this.fxaDeviceId = payload.getString("fxaDeviceId");
-    }
-  }
-
-  @Override
-  protected void populatePayload(ExtendedJSONObject payload) {
-    putPayload(payload, "id",   this.guid);
-    putPayload(payload, "name", this.name);
-    putPayload(payload, "type", this.type);
-    putPayload(payload, "version", this.version);
-
-    if (this.commands != null) {
-      payload.put("commands",  this.commands);
-    }
-
-    if (this.protocols != null) {
-      payload.put("protocols",  this.protocols);
-    }
-
-    if (this.formfactor != null) {
-      payload.put("formfactor", this.formfactor);
-    }
-
-    if (this.os != null) {
-      payload.put("os", this.os);
-    }
-
-    if (this.application != null) {
-      payload.put("application", this.application);
-    }
-
-    if (this.appPackage != null) {
-      payload.put("appPackage", this.appPackage);
-    }
-
-    if (this.device != null) {
-      payload.put("device", this.device);
-    }
-
-    if (this.fxaDeviceId != null) {
-      payload.put("fxaDeviceId", this.fxaDeviceId);
-    }
-  }
-
-  @Override
-  public boolean equals(Object o) {
-    if (!(o instanceof ClientRecord) || !super.equals(o)) {
-      return false;
-    }
-
-    return this.equalPayloads(o);
-  }
-
-  @Override
-  public int hashCode() {
-    return super.hashCode();
-  }
-
-  @Override
-  public boolean equalPayloads(Object o) {
-    if (!(o instanceof ClientRecord) || !super.equalPayloads(o)) {
-      return false;
-    }
-
-    // Don't compare versions, protocols, or other optional fields, no matter how much we might want to.
-    // They're not required by the spec.
-    ClientRecord other = (ClientRecord) o;
-    if (!RepoUtils.stringsEqual(other.name, this.name) ||
-        !RepoUtils.stringsEqual(other.type, this.type)) {
-      return false;
-    }
-    return true;
-  }
-
-  @Override
-  public Record copyWithIDs(String guid, long androidID) {
-    ClientRecord out = new ClientRecord(guid, this.collection, this.lastModified, this.deleted);
-    out.androidID = androidID;
-    out.sortIndex = this.sortIndex;
-    out.ttl       = this.ttl;
-
-    out.name = this.name;
-    out.type = this.type;
-    out.version = this.version;
-    out.protocols = this.protocols;
-
-    out.formfactor = this.formfactor;
-    out.os = this.os;
-    out.application = this.application;
-    out.appPackage = this.appPackage;
-    out.device = this.device;
-    out.fxaDeviceId = this.fxaDeviceId;
-
-    return out;
-  }
-
-/*
-Example record:
-
-{id:"relf31w7B4F1",
- name:"marina_mac",
- type:"mobile"
- commands:[{"args":["bookmarks"],"command":"wipeEngine"},
-           {"args":["forms"],"command":"wipeEngine"},
-           {"args":["history"],"command":"wipeEngine"},
-           {"args":["passwords"],"command":"wipeEngine"},
-           {"args":["prefs"],"command":"wipeEngine"},
-           {"args":["tabs"],"command":"wipeEngine"},
-           {"args":["addons"],"command":"wipeEngine"}]}
-*/
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/domain/ClientRecordFactory.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/domain/ClientRecordFactory.java
deleted file mode 100644
index 897d285..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/domain/ClientRecordFactory.java
+++ /dev/null
@@ -1,17 +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.sync.repositories.domain;
-
-import org.mozilla.gecko.sync.CryptoRecord;
-import org.mozilla.gecko.sync.repositories.RecordFactory;
-
-public class ClientRecordFactory extends RecordFactory {
-  @Override
-  public Record createRecord(Record record) {
-    ClientRecord r = new ClientRecord();
-    r.initFromEnvelope((CryptoRecord) record);
-    return r;
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/domain/FormHistoryRecord.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/domain/FormHistoryRecord.java
deleted file mode 100644
index e7ca70c..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/domain/FormHistoryRecord.java
+++ /dev/null
@@ -1,139 +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.sync.repositories.domain;
-
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.sync.ExtendedJSONObject;
-import org.mozilla.gecko.sync.Utils;
-import org.mozilla.gecko.sync.repositories.android.RepoUtils;
-
-/**
- * A FormHistoryRecord represents a saved form element.
- *
- * I map a <code>fieldName</code> string to a <code>value</code> string.
- *
- * @see "<a href='http://dxr.mozilla.org/services-central/source/services-central/services/sync/modules/engines/forms.js'>http://dxr.mozilla.org/services-central/source/services-central/services/sync/modules/engines/forms.js</a>."
- */
-public class FormHistoryRecord extends Record {
-  private static final String LOG_TAG = "FormHistoryRecord";
-
-  public static final String  COLLECTION_NAME = "forms";
-  private static final String PAYLOAD_NAME    = "name";
-  private static final String PAYLOAD_VALUE   = "value";
-  public static final long FORMS_TTL = 3 * 365 * 24 * 60 * 60;   // Three years in seconds.
-
-  /**
-   * The name of the saved form field.
-   */
-  public String fieldName;
-
-  /**
-   * The value of the saved form field.
-   */
-  public String fieldValue;
-
-  public FormHistoryRecord(String guid, String collection, long lastModified, boolean deleted) {
-    super(guid, collection, lastModified, deleted);
-    this.ttl = FORMS_TTL;
-  }
-
-  public FormHistoryRecord(String guid, String collection, long lastModified) {
-    this(guid, collection, lastModified, false);
-  }
-
-  public FormHistoryRecord(String guid, String collection) {
-    this(guid, collection, 0, false);
-  }
-
-  public FormHistoryRecord(String guid) {
-    this(guid, COLLECTION_NAME, 0, false);
-  }
-
-  public FormHistoryRecord() {
-    this(Utils.generateGuid(), COLLECTION_NAME, 0, false);
-  }
-
-  @Override
-  public Record copyWithIDs(String guid, long androidID) {
-    FormHistoryRecord out = new FormHistoryRecord(guid, this.collection, this.lastModified, this.deleted);
-    out.androidID = androidID;
-    out.sortIndex = this.sortIndex;
-
-    // Copy FormHistoryRecord fields.
-    out.fieldName = this.fieldName;
-    out.fieldValue = this.fieldValue;
-
-    return out;
-  }
-
-  @Override
-  public void populatePayload(ExtendedJSONObject payload) {
-    putPayload(payload, PAYLOAD_NAME,  this.fieldName);
-    putPayload(payload, PAYLOAD_VALUE, this.fieldValue);
-  }
-
-  @Override
-  public void initFromPayload(ExtendedJSONObject payload) {
-    this.fieldName  = payload.getString(PAYLOAD_NAME);
-    this.fieldValue = payload.getString(PAYLOAD_VALUE);
-  }
-
-  /**
-   * We consider two form history records to be congruent if they represent the
-   * same form element regardless of times used.
-   */
-  @Override
-  public boolean congruentWith(Object o) {
-    if (!(o instanceof FormHistoryRecord)) {
-      return false;
-    }
-    FormHistoryRecord other = (FormHistoryRecord) o;
-    if (!super.congruentWith(other)) {
-      return false;
-    }
-    return RepoUtils.stringsEqual(this.fieldName, other.fieldName) &&
-           RepoUtils.stringsEqual(this.fieldValue, other.fieldValue);
-  }
-
-  @Override
-  public boolean equalPayloads(Object o) {
-    if (!(o instanceof FormHistoryRecord)) {
-      Logger.debug(LOG_TAG, "Not a FormHistoryRecord: " + o.getClass());
-      return false;
-    }
-    FormHistoryRecord other = (FormHistoryRecord) o;
-    if (!super.equalPayloads(other)) {
-      Logger.debug(LOG_TAG, "super.equalPayloads returned false.");
-      return false;
-    }
-
-    if (this.deleted) {
-      // FormHistoryRecords are equal if they are both deleted (which
-      // they are, since super.equalPayloads is true) and have the
-      // same GUID.
-      if (other.deleted) {
-        return RepoUtils.stringsEqual(this.guid, other.guid);
-      }
-      return false;
-    }
-
-    return RepoUtils.stringsEqual(this.fieldName,  other.fieldName) &&
-           RepoUtils.stringsEqual(this.fieldValue, other.fieldValue);
-  }
-
-  public FormHistoryRecord log(String logTag) {
-    try {
-      Logger.debug(logTag, "Returning form history record " + guid + " (" + androidID + ")");
-      Logger.debug(logTag, "> Last modified: " + lastModified);
-      if (Logger.LOG_PERSONAL_INFORMATION) {
-        Logger.pii(logTag, "> Field name:    " + fieldName);
-        Logger.pii(logTag, "> Field value:   " + fieldValue);
-      }
-    } catch (Exception e) {
-      Logger.debug(logTag, "Exception logging form history record " + this, e);
-    }
-    return this;
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/domain/HistoryRecord.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/domain/HistoryRecord.java
deleted file mode 100644
index 94eae13..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/domain/HistoryRecord.java
+++ /dev/null
@@ -1,217 +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.sync.repositories.domain;
-
-import java.util.HashMap;
-
-import org.json.simple.JSONArray;
-import org.json.simple.JSONObject;
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.sync.ExtendedJSONObject;
-import org.mozilla.gecko.sync.NonArrayJSONException;
-import org.mozilla.gecko.sync.Utils;
-import org.mozilla.gecko.sync.repositories.android.RepoUtils;
-
-/**
- * Visits are in microsecond precision.
- *
- * @author rnewman
- *
- */
-public class HistoryRecord extends Record {
-  private static final String LOG_TAG = "HistoryRecord";
-
-  public static final String COLLECTION_NAME = "history";
-  public static final long HISTORY_TTL = 60 * 24 * 60 * 60; // 60 days in seconds.
-
-  public HistoryRecord(String guid, String collection, long lastModified, boolean deleted) {
-    super(guid, collection, lastModified, deleted);
-    this.ttl = HISTORY_TTL;
-  }
-  public HistoryRecord(String guid, String collection, long lastModified) {
-    this(guid, collection, lastModified, false);
-  }
-  public HistoryRecord(String guid, String collection) {
-    this(guid, collection, 0, false);
-  }
-  public HistoryRecord(String guid) {
-    this(guid, COLLECTION_NAME, 0, false);
-  }
-  public HistoryRecord() {
-    this(Utils.generateGuid(), COLLECTION_NAME, 0, false);
-  }
-
-  public String    title;
-  public String    histURI;
-  public JSONArray visits;
-  public long      fennecDateVisited;
-  public long      fennecVisitCount;
-
-  @SuppressWarnings("unchecked")
-  private JSONArray copyVisits() {
-    if (this.visits == null) {
-      return null;
-    }
-    JSONArray out = new JSONArray();
-    out.addAll(this.visits);
-    return out;
-  }
-
-  @Override
-  public Record copyWithIDs(String guid, long androidID) {
-    HistoryRecord out = new HistoryRecord(guid, this.collection, this.lastModified, this.deleted);
-    out.androidID = androidID;
-    out.sortIndex = this.sortIndex;
-    out.ttl       = this.ttl;
-
-    // Copy HistoryRecord fields.
-    out.title             = this.title;
-    out.histURI           = this.histURI;
-    out.fennecDateVisited = this.fennecDateVisited;
-    out.fennecVisitCount  = this.fennecVisitCount;
-    out.visits            = this.copyVisits();
-
-    return out;
-  }
-
-  @Override
-  protected void populatePayload(ExtendedJSONObject payload) {
-    putPayload(payload, "id",      this.guid);
-    putPayload(payload, "title",   this.title);
-    putPayload(payload, "histUri", this.histURI);             // TODO: encoding?
-    payload.put("visits",  this.visits);
-  }
-
-  @Override
-  protected void initFromPayload(ExtendedJSONObject payload) {
-    this.histURI = (String) payload.get("histUri");
-    this.title   = (String) payload.get("title");
-    try {
-      this.visits = payload.getArray("visits");
-    } catch (NonArrayJSONException e) {
-      Logger.error(LOG_TAG, "Got non-array visits in history record " + this.guid, e);
-      this.visits = new JSONArray();
-    }
-  }
-
-  /**
-   * We consider two history records to be congruent if they represent the
-   * same history record regardless of visits. Titles are allowed to differ,
-   * but the URI must be the same.
-   */
-  @Override
-  public boolean congruentWith(Object o) {
-    if (!(o instanceof HistoryRecord)) {
-      return false;
-    }
-    HistoryRecord other = (HistoryRecord) o;
-    if (!super.congruentWith(other)) {
-      return false;
-    }
-    return RepoUtils.stringsEqual(this.histURI, other.histURI);
-  }
-
-  @Override
-  public boolean equalPayloads(Object o) {
-    if (!(o instanceof HistoryRecord)) {
-      Logger.debug(LOG_TAG, "Not a HistoryRecord: " + o.getClass());
-      return false;
-    }
-    HistoryRecord other = (HistoryRecord) o;
-    if (!super.equalPayloads(other)) {
-      Logger.debug(LOG_TAG, "super.equalPayloads returned false.");
-      return false;
-    }
-    return RepoUtils.stringsEqual(this.title, other.title) &&
-           RepoUtils.stringsEqual(this.histURI, other.histURI) &&
-           checkVisitsEquals(other);
-  }
-
-  @Override
-  public boolean equalAndroidIDs(Record other) {
-    return super.equalAndroidIDs(other) &&
-           this.equalFennecVisits(other);
-  }
-
-  private boolean equalFennecVisits(Record other) {
-    if (!(other instanceof HistoryRecord)) {
-      return false;
-    }
-    HistoryRecord h = (HistoryRecord) other;
-    return this.fennecDateVisited == h.fennecDateVisited &&
-           this.fennecVisitCount  == h.fennecVisitCount;
-  }
-
-  private boolean checkVisitsEquals(HistoryRecord other) {
-    Logger.debug(LOG_TAG, "Checking visits.");
-    if (Logger.LOG_PERSONAL_INFORMATION) {
-      // Don't JSON-encode unless we're logging.
-      Logger.pii(LOG_TAG, ">> Mine:   " + ((this.visits == null) ? "null" : this.visits.toJSONString()));
-      Logger.pii(LOG_TAG, ">> Theirs: " + ((other.visits == null) ? "null" : other.visits.toJSONString()));
-    }
-
-    // Handle nulls.
-    if (this.visits == other.visits) {
-      return true;
-    }
-
-    // Now they can't both be null.
-    int aSize = this.visits == null ? 0 : this.visits.size();
-    int bSize = other.visits == null ? 0 : other.visits.size();
-    
-    if (aSize != bSize) {
-      return false;
-    }
-
-    // Now neither of them can be null.
-
-    // TODO: do this by maintaining visits as a sorted array.
-    HashMap<Long, Long> otherVisits = new HashMap<Long, Long>();
-    for (int i = 0; i < bSize; i++) {
-      JSONObject visit = (JSONObject) other.visits.get(i);
-      otherVisits.put((Long) visit.get("date"), (Long) visit.get("type"));
-    }
-    
-    for (int i = 0; i < aSize; i++) {
-      JSONObject visit = (JSONObject) this.visits.get(i);
-      if (!otherVisits.containsKey(visit.get("date"))) {
-        return false;
-      }
-      Long otherDate = (Long) visit.get("date");
-      Long otherType = otherVisits.get(otherDate);
-      if (otherType == null) {
-        return false;
-      }
-      if (!otherType.equals((Long) visit.get("type"))) {
-        return false;
-      }
-    }
-    
-    return true;
-  }
-  
-//  
-//  Example record (note microsecond resolution):
-//
-//  {id:"--DUvUomABNq",
-//   histUri:"https://bugzilla.mozilla.org/show_bug.cgi?id=697634",
-//   title:"697634 \u2013 xpcshell test failures on 10.7",
-//   visits:[{date:1320087601465600, type:2},
-//           {date:1320084970724990, type:1},
-//           {date:1320084847035717, type:1},
-//           {date:1319764134412287, type:1},
-//           {date:1319757917982518, type:1},
-//           {date:1319751664627351, type:1},
-//           {date:1319681421072326, type:1},
-//           {date:1319681306455594, type:1},
-//           {date:1319678117125234, type:1},
-//           {date:1319677508862901, type:1}]
-//  }
-//   
-//"type" is a transition type:
-//
-//https://developer.mozilla.org/en/XPCOM_Interface_Reference/nsINavHistoryService#Transition_type_constants
-
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/domain/HistoryRecordFactory.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/domain/HistoryRecordFactory.java
deleted file mode 100644
index ac2c6a1..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/domain/HistoryRecordFactory.java
+++ /dev/null
@@ -1,25 +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.sync.repositories.domain;
-
-import org.mozilla.gecko.sync.CryptoRecord;
-import org.mozilla.gecko.sync.repositories.RecordFactory;
-
-/**
- * Turns CryptoRecords into HistoryRecords.
- *
- * @author rnewman
- *
- */
-public class HistoryRecordFactory extends RecordFactory {
-
-  @Override
-  public Record createRecord(Record record) {
-    HistoryRecord r = new HistoryRecord();
-    r.initFromEnvelope((CryptoRecord) record);
-    return r;
-  }
-
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/domain/PasswordRecord.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/domain/PasswordRecord.java
deleted file mode 100644
index b2de60f..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/domain/PasswordRecord.java
+++ /dev/null
@@ -1,205 +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.sync.repositories.domain;
-
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.sync.ExtendedJSONObject;
-import org.mozilla.gecko.sync.Utils;
-import org.mozilla.gecko.sync.repositories.android.RepoUtils;
-
-public class PasswordRecord extends Record {
-  private static final String LOG_TAG = "PasswordRecord";
-
-  public static final String COLLECTION_NAME = "passwords";
-  public static long PASSWORDS_TTL = -1; // Never expire passwords.
-
-  // Payload strings.
-  public static final String PAYLOAD_HOSTNAME = "hostname";
-  public static final String PAYLOAD_FORM_SUBMIT_URL = "formSubmitURL";
-  public static final String PAYLOAD_HTTP_REALM = "httpRealm";
-  public static final String PAYLOAD_USERNAME = "username";
-  public static final String PAYLOAD_PASSWORD = "password";
-  public static final String PAYLOAD_USERNAME_FIELD = "usernameField";
-  public static final String PAYLOAD_PASSWORD_FIELD = "passwordField";
-
-  public PasswordRecord(String guid, String collection, long lastModified, boolean deleted) {
-    super(guid, collection, lastModified, deleted);
-    this.ttl = PASSWORDS_TTL;
-  }
-  public PasswordRecord(String guid, String collection, long lastModified) {
-    this(guid, collection, lastModified, false);
-  }
-  public PasswordRecord(String guid, String collection) {
-    this(guid, collection, 0, false);
-  }
-  public PasswordRecord(String guid) {
-    this(guid, COLLECTION_NAME, 0, false);
-  }
-  public PasswordRecord() {
-    this(Utils.generateGuid(), COLLECTION_NAME, 0, false);
-  }
-
-  public String id;
-  public String hostname;
-  public String formSubmitURL;
-  public String httpRealm;
-  // TODO these are encrypted in the passwords content provider,
-  // need to figure out what we need to do here.
-  public String usernameField;
-  public String passwordField;
-  public String encryptedUsername;
-  public String encryptedPassword;
-  public String encType;
-
-  public long   timeCreated;
-  public long   timeLastUsed;
-  public long   timePasswordChanged;
-  public long   timesUsed;
-
-
-  @Override
-  public Record copyWithIDs(String guid, long androidID) {
-    PasswordRecord out = new PasswordRecord(guid, this.collection, this.lastModified, this.deleted);
-    out.androidID = androidID;
-    out.sortIndex = this.sortIndex;
-    out.ttl       = this.ttl;
-
-    // Copy PasswordRecord fields.
-    out.id            = this.id;
-    out.hostname      = this.hostname;
-    out.formSubmitURL = this.formSubmitURL;
-    out.httpRealm     = this.httpRealm;
-
-    out.usernameField       = this.usernameField;
-    out.passwordField       = this.passwordField;
-    out.encryptedUsername   = this.encryptedUsername;
-    out.encryptedPassword   = this.encryptedPassword;
-    out.encType             = this.encType;
-
-    out.timeCreated         = this.timeCreated;
-    out.timeLastUsed        = this.timeLastUsed;
-    out.timePasswordChanged = this.timePasswordChanged;
-    out.timesUsed           = this.timesUsed;
-
-    return out;
-  }
-
-  @Override
-  public void initFromPayload(ExtendedJSONObject payload) {
-    this.hostname = payload.getString(PAYLOAD_HOSTNAME);
-    this.formSubmitURL = payload.getString(PAYLOAD_FORM_SUBMIT_URL);
-    this.httpRealm = payload.getString(PAYLOAD_HTTP_REALM);
-    this.encryptedUsername = payload.getString(PAYLOAD_USERNAME);
-    this.encryptedPassword = payload.getString(PAYLOAD_PASSWORD);
-    this.usernameField = payload.getString(PAYLOAD_USERNAME_FIELD);
-    this.passwordField = payload.getString(PAYLOAD_PASSWORD_FIELD);
-  }
-
-  @Override
-  public void populatePayload(ExtendedJSONObject payload) {
-    putPayload(payload, PAYLOAD_HOSTNAME, this.hostname);
-    putPayload(payload, PAYLOAD_FORM_SUBMIT_URL, this.formSubmitURL);
-    putPayload(payload, PAYLOAD_HTTP_REALM, this.httpRealm);
-    putPayload(payload, PAYLOAD_USERNAME, this.encryptedUsername);
-    putPayload(payload, PAYLOAD_PASSWORD, this.encryptedPassword);
-    putPayload(payload, PAYLOAD_USERNAME_FIELD, this.usernameField);
-    putPayload(payload, PAYLOAD_PASSWORD_FIELD, this.passwordField);
-  }
-
-  @Override
-  public boolean congruentWith(Object o) {
-    if (!(o instanceof PasswordRecord)) {
-      return false;
-    }
-    PasswordRecord other = (PasswordRecord) o;
-    if (!super.congruentWith(other)) {
-      return false;
-    }
-    return RepoUtils.stringsEqual(this.hostname, other.hostname)
-        && RepoUtils.stringsEqual(this.formSubmitURL, other.formSubmitURL)
-        // Bug 738347 - SQLiteBridge does not check for nulls in ContentValues.
-        // && RepoUtils.stringsEqual(this.httpRealm, other.httpRealm)
-        // && RepoUtils.stringsEqual(this.encType, other.encType)
-        && RepoUtils.stringsEqual(this.usernameField, other.usernameField)
-        && RepoUtils.stringsEqual(this.passwordField, other.passwordField)
-        && RepoUtils.stringsEqual(this.encryptedUsername, other.encryptedUsername)
-        && RepoUtils.stringsEqual(this.encryptedPassword, other.encryptedPassword);
-  }
-
-  @Override
-  public boolean equalPayloads(Object o) {
-    if (!(o instanceof PasswordRecord)) {
-      return false;
-    }
-
-    PasswordRecord other = (PasswordRecord) o;
-    Logger.debug("PasswordRecord", "thisRecord:" + this.toString());
-    Logger.debug("PasswordRecord", "otherRecord:" + o.toString());
-
-    if (this.deleted) {
-      if (other.deleted) {
-        // Deleted records are equal if their guids match.
-        return RepoUtils.stringsEqual(this.guid, other.guid);
-      }
-      // One record is deleted, the other is not. Not equal.
-      return false;
-    }
-
-    if (!super.equalPayloads(other)) {
-      Logger.debug(LOG_TAG, "super.equalPayloads returned false.");
-      return false;
-    }
-
-    return RepoUtils.stringsEqual(this.hostname, other.hostname)
-        && RepoUtils.stringsEqual(this.formSubmitURL, other.formSubmitURL)
-        // Bug 738347 - SQLiteBridge does not check for nulls in ContentValues.
-        // && RepoUtils.stringsEqual(this.httpRealm, other.httpRealm)
-        // && RepoUtils.stringsEqual(this.encType, other.encType)
-        && RepoUtils.stringsEqual(this.usernameField, other.usernameField)
-        && RepoUtils.stringsEqual(this.passwordField, other.passwordField)
-        && RepoUtils.stringsEqual(this.encryptedUsername, other.encryptedUsername)
-        && RepoUtils.stringsEqual(this.encryptedPassword, other.encryptedPassword);
-        // Desktop sync never sets timeCreated so this isn't relevant for sync records.
-  }
-
-  @Override
-  public String toString() {
-    return "PasswordRecord {"
-        + "lastModified: " + this.lastModified + ", "
-        + "hostname null?: " + (this.hostname == null) + ", "
-        + "formSubmitURL null?: " + (this.formSubmitURL == null) + ", "
-        + "httpRealm null?: " + (this.httpRealm == null) + ", "
-        + "usernameField null?: " + (this.usernameField == null) + ", "
-        + "passwordField null?: " + (this.passwordField == null) + ", "
-        + "encryptedUsername null?: " + (this.encryptedUsername == null) + ", "
-        + "encryptedPassword null?: " + (this.encryptedPassword == null) + ", "
-        + "encType: " + this.encType + ", "
-        + "timeCreated: " + this.timeCreated + ", "
-        + "timeLastUsed: " + this.timeLastUsed + ", "
-        + "timePasswordChanged: " + this.timePasswordChanged + ", "
-        + "timesUsed: " + this.timesUsed;
-  }
-
-  /**
-   * A PasswordRecord is considered valid if it abides by the database
-   * constraints of the PasswordsProvider (moz_logins).
-   *
-   * See toolkit/components/passwordmgr/storage-mozStorage.js for the
-   * definitions:
-   *
-   * http://hg.mozilla.org/mozilla-central/file/00955d61cc94/toolkit/components/passwordmgr/storage-mozStorage.js#l98
-   */
-    public boolean isValid() {
-        if (this.deleted) {
-            return true;
-        }
-
-        return this.hostname != null &&
-               this.encryptedUsername != null &&
-               this.encryptedPassword != null &&
-               this.usernameField != null &&
-               this.passwordField != null;
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/domain/PasswordRecordFactory.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/domain/PasswordRecordFactory.java
deleted file mode 100644
index fc7ef91..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/domain/PasswordRecordFactory.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.sync.repositories.domain;
-
-import org.mozilla.gecko.sync.CryptoRecord;
-import org.mozilla.gecko.sync.repositories.RecordFactory;
-import org.mozilla.gecko.sync.repositories.domain.PasswordRecord;
-import org.mozilla.gecko.sync.repositories.domain.Record;
-
-public class PasswordRecordFactory extends RecordFactory {
-  @Override
-  public Record createRecord(Record record) {
-    PasswordRecord r = new PasswordRecord();
-    r.initFromEnvelope((CryptoRecord) record);
-    return r;
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/domain/Record.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/domain/Record.java
deleted file mode 100644
index 145704c..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/domain/Record.java
+++ /dev/null
@@ -1,308 +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.sync.repositories.domain;
-
-import java.io.UnsupportedEncodingException;
-
-import org.mozilla.gecko.sync.CryptoRecord;
-import org.mozilla.gecko.sync.ExtendedJSONObject;
-
-/**
- * Record is the abstract base class for all entries that Sync processes:
- * bookmarks, passwords, history, and such.
- *
- * A Record can be initialized from or serialized to a CryptoRecord for
- * submission to an encrypted store.
- *
- * Records should be considered to be conventionally immutable: modifications
- * should be completed before the new record object escapes its constructing
- * scope. Note that this is a critically important part of equality. As Rich
- * Hickey notes:
- *
- *   … the only things you can really compare for equality are immutable things,
- *   because if you compare two things for equality that are mutable, and ever
- *   say true, and they're ever not the same thing, you are wrong. Or you will
- *   become wrong at some point in the future.
- *
- * Records have a layered definition of equality. Two records can be said to be
- * "equal" if:
- *
- * * They have the same GUID and collection. Two crypto/keys records are in some
- *   way "the same".
- *   This is `equalIdentifiers`.
- *
- * * Their most significant fields are the same. That is to say, they share a
- *   GUID, a collection, deletion, and domain-specific fields. Two copies of
- *   crypto/keys, neither deleted, with the same encrypted data but different
- *   modified times and sortIndex are in a stronger way "the same".
- *   This is `equalPayloads`.
- *
- * * Their most significant fields are the same, and their local fields (e.g.,
- *   the androidID to which we have decided that this record maps) are congruent.
- *   A record with the same androidID, or one whose androidID has not been set,
- *   can be considered "the same".
- *   This concept can be extended by Record subclasses. The key point is that
- *   reconciling should be applied to the contents of these records. For example,
- *   two history records with the same URI and GUID, but different visit arrays,
- *   can be said to be congruent.
- *   This is `congruentWith`.
- *
- * * They are strictly identical. Every field that is persisted, including
- *   lastModified and androidID, is equal.
- *   This is `equals`.
- *
- * Different parts of the codebase have use for different layers of this
- * comparison hierarchy. For instance, lastModified times change every time a
- * record is stored; a store followed by a retrieval will return a Record that
- * shares its most significant fields with the input, but has a later
- * lastModified time and might not yet have values set for others. Reconciling
- * will thus ignore the modification time of a record.
- *
- * @author rnewman
- *
- */
-public abstract class Record {
-
-  public String guid;
-  public String collection;
-  public long lastModified;
-  public boolean deleted;
-  public long androidID;
-  /**
-   * An integer indicating the relative importance of this item in the collection.
-   * <p>
-   * Default is 0.
-   */
-  public long sortIndex;
-  /**
-   * The number of seconds to keep this record. After that time this item will
-   * no longer be returned in response to any request, and it may be pruned from
-   * the database.
-   * <p>
-   * Negative values mean never forget this record.
-   * <p>
-   * Default is 1 year.
-   */
-  public long ttl;
-
-  public Record(String guid, String collection, long lastModified, boolean deleted) {
-    this.guid         = guid;
-    this.collection   = collection;
-    this.lastModified = lastModified;
-    this.deleted      = deleted;
-    this.sortIndex    = 0;
-    this.ttl          = 365 * 24 * 60 * 60; // Seconds.
-    this.androidID    = -1;
-  }
-
-  /**
-   * Return true iff the input is a Record and has the same
-   * collection and guid as this object.
-   */
-  public boolean equalIdentifiers(Object o) {
-    if (!(o instanceof Record)) {
-      return false;
-    }
-
-    Record other = (Record) o;
-    if (this.guid == null) {
-      if (other.guid != null) {
-        return false;
-      }
-    } else {
-      if (!this.guid.equals(other.guid)) {
-        return false;
-      }
-    }
-    if (this.collection == null) {
-      if (other.collection != null) {
-        return false;
-      }
-    } else {
-      if (!this.collection.equals(other.collection)) {
-        return false;
-      }
-    }
-    return true;
-  }
-
-  /**
-   * @param o
-   *        The object to which this object should be compared.
-   * @return
-   *        true iff the input is a Record which is substantially the
-   *        same as this object.
-   */
-  public boolean equalPayloads(Object o) {
-    if (!this.equalIdentifiers(o)) {
-      return false;
-    }
-    Record other = (Record) o;
-    return this.deleted == other.deleted;
-  }
-
-  /**
-   *
-   *
-   * @param o
-   *        The object to which this object should be compared.
-   * @return
-   *        true iff the input is a Record which is substantially the
-   *        same as this object, considering the ability and desire to
-   *        reconcile the two objects if possible.
-   */
-  public boolean congruentWith(Object o) {
-    if (!this.equalIdentifiers(o)) {
-      return false;
-    }
-    Record other = (Record) o;
-    return congruentAndroidIDs(other) &&
-           (this.deleted == other.deleted);
-  }
-
-  public boolean congruentAndroidIDs(Record other) {
-    // We treat -1 as "unset", and treat this as
-    // congruent with any other value.
-    if (this.androidID  != -1 &&
-        other.androidID != -1 &&
-        this.androidID  != other.androidID) {
-      return false;
-    }
-    return true;
-  }
-
-  /**
-   * Return true iff the input is both equal in terms of payload,
-   * and also shares transient values such as timestamps.
-   */
-  @Override
-  public boolean equals(Object o) {
-    if (!(o instanceof Record)) {
-      return false;
-    }
-
-    Record other = (Record) o;
-    return equalTimestamps(other) &&
-           equalSortIndices(other) &&
-           equalAndroidIDs(other) &&
-           equalPayloads(o);
-  }
-
-  public boolean equalAndroidIDs(Record other) {
-    return this.androidID == other.androidID;
-  }
-
-  public boolean equalSortIndices(Record other) {
-    return this.sortIndex == other.sortIndex;
-  }
-
-  public boolean equalTimestamps(Object o) {
-    if (!(o instanceof Record)) {
-      return false;
-    }
-    return ((Record) o).lastModified == this.lastModified;
-  }
-
-  protected abstract void populatePayload(ExtendedJSONObject payload);
-  protected abstract void initFromPayload(ExtendedJSONObject payload);
-
-  public void initFromEnvelope(CryptoRecord envelope) {
-    ExtendedJSONObject p = envelope.payload;
-    this.guid = envelope.guid;
-    checkGUIDs(p);
-
-    this.collection    = envelope.collection;
-    this.lastModified  = envelope.lastModified;
-
-    final Object del = p.get("deleted");
-    if (del instanceof Boolean) {
-      this.deleted = (Boolean) del;
-    } else {
-      this.initFromPayload(p);
-    }
-
-  }
-
-  public CryptoRecord getEnvelope() {
-    CryptoRecord rec = new CryptoRecord(this);
-    ExtendedJSONObject payload = new ExtendedJSONObject();
-    payload.put("id", this.guid);
-
-    if (this.deleted) {
-      payload.put("deleted", true);
-    } else {
-      populatePayload(payload);
-    }
-    rec.payload = payload;
-    return rec;
-  }
-
-  @SuppressWarnings("static-method")
-  public String toJSONString() {
-    throw new RuntimeException("Cannot JSONify non-CryptoRecord Records.");
-  }
-
-  public byte[] toJSONBytes() {
-    try {
-      return this.toJSONString().getBytes("UTF-8");
-    } catch (UnsupportedEncodingException e) {
-      // Can't happen.
-      return null;
-    }
-  }
-
-  /**
-   * Utility for safely populating an output CryptoRecord.
-   *
-   * @param rec
-   * @param key
-   * @param value
-   */
-  @SuppressWarnings("static-method")
-  protected void putPayload(CryptoRecord rec, String key, String value) {
-    if (value == null) {
-      return;
-    }
-    rec.payload.put(key, value);
-  }
-
-  protected void putPayload(ExtendedJSONObject payload, String key, String value) {
-    this.putPayload(payload, key, value, false);
-  }
-
-  @SuppressWarnings("static-method")
-  protected void putPayload(ExtendedJSONObject payload, String key, String value, boolean excludeEmpty) {
-    if (value == null) {
-      return;
-    }
-    if (excludeEmpty && value.equals("")) {
-      return;
-    }
-    payload.put(key, value);
-  }
-
-  protected void checkGUIDs(ExtendedJSONObject payload) {
-    String payloadGUID = (String) payload.get("id");
-    if (this.guid == null ||
-        payloadGUID == null) {
-      String detailMessage = "Inconsistency: either envelope or payload GUID missing.";
-      throw new IllegalStateException(detailMessage);
-    }
-    if (!this.guid.equals(payloadGUID)) {
-      String detailMessage = "Inconsistency: record has envelope ID " + this.guid + ", payload ID " + payloadGUID;
-      throw new IllegalStateException(detailMessage);
-    }
-  }
-
-  /**
-   * Oh for persistent data structures.
-   *
-   * @param guid
-   * @param androidID
-   * @return
-   *        An identical copy of this record with the provided two values.
-   */
-  public abstract Record copyWithIDs(String guid, long androidID);
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/domain/RecordParseException.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/domain/RecordParseException.java
deleted file mode 100644
index 0d8fe90..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/domain/RecordParseException.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.sync.repositories.domain;
-
-
-public class RecordParseException extends Exception {
-  private static final long serialVersionUID = -5145494854722254491L;
-
-  public RecordParseException(String detailMessage) {
-    super(detailMessage);
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/domain/TabsRecord.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/domain/TabsRecord.java
deleted file mode 100644
index eb3a4f6..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/domain/TabsRecord.java
+++ /dev/null
@@ -1,153 +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.sync.repositories.domain;
-
-import java.util.ArrayList;
-
-import org.json.simple.JSONArray;
-import org.json.simple.JSONObject;
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.background.db.Tab;
-import org.mozilla.gecko.db.BrowserContract;
-import org.mozilla.gecko.sync.ExtendedJSONObject;
-import org.mozilla.gecko.sync.NonArrayJSONException;
-import org.mozilla.gecko.sync.Utils;
-
-import android.content.ContentValues;
-
-/**
- * Represents a client's collection of tabs.
- *
- * @author rnewman
- *
- */
-public class TabsRecord extends Record {
-  public static final String LOG_TAG = "TabsRecord";
-
-  public static final String COLLECTION_NAME = "tabs";
-  public static final long TABS_TTL = 7 * 24 * 60 * 60; // 7 days in seconds.
-
-  public TabsRecord(String guid, String collection, long lastModified, boolean deleted) {
-    super(guid, collection, lastModified, deleted);
-    this.ttl = TABS_TTL;
-  }
-  public TabsRecord(String guid, String collection, long lastModified) {
-    this(guid, collection, lastModified, false);
-  }
-  public TabsRecord(String guid, String collection) {
-    this(guid, collection, 0, false);
-  }
-  public TabsRecord(String guid) {
-    this(guid, COLLECTION_NAME, 0, false);
-  }
-  public TabsRecord() {
-    this(Utils.generateGuid(), COLLECTION_NAME, 0, false);
-  }
-
-  public String clientName;
-  public ArrayList<Tab> tabs;
-
-  @Override
-  public void initFromPayload(ExtendedJSONObject payload) {
-    clientName = (String) payload.get("clientName");
-    try {
-      tabs = tabsFrom(payload.getArray("tabs"));
-    } catch (NonArrayJSONException e) {
-      // Oh well.
-      tabs = new ArrayList<Tab>();
-    }
-  }
-
-  @SuppressWarnings("unchecked")
-  protected static JSONArray tabsToJSON(ArrayList<Tab> tabs) {
-    JSONArray out = new JSONArray();
-    for (Tab tab : tabs) {
-      out.add(tabToJSONObject(tab));
-    }
-    return out;
-  }
-
-  protected static ArrayList<Tab> tabsFrom(JSONArray in) {
-    ArrayList<Tab> tabs = new ArrayList<Tab>(in.size());
-    for (Object o : in) {
-      if (o instanceof JSONObject) {
-        try {
-          tabs.add(TabsRecord.tabFromJSONObject((JSONObject) o));
-        } catch (NonArrayJSONException e) {
-          Logger.warn(LOG_TAG, "urlHistory is not an array for this tab.", e);
-        }
-      }
-    }
-    return tabs;
-  }
-
-  @Override
-  public void populatePayload(ExtendedJSONObject payload) {
-    putPayload(payload, "id", this.guid);
-    putPayload(payload, "clientName", this.clientName);
-    payload.put("tabs", tabsToJSON(this.tabs));
-  }
-
-  @Override
-  public Record copyWithIDs(String guid, long androidID) {
-    TabsRecord out = new TabsRecord(guid, this.collection, this.lastModified, this.deleted);
-    out.androidID = androidID;
-    out.sortIndex = this.sortIndex;
-    out.ttl       = this.ttl;
-
-    out.clientName = this.clientName;
-    out.tabs = new ArrayList<Tab>(this.tabs);
-
-    return out;
-  }
-
-  public ContentValues getClientsContentValues() {
-    ContentValues cv = new ContentValues();
-    cv.put(BrowserContract.Clients.GUID, this.guid);
-    cv.put(BrowserContract.Clients.NAME, this.clientName);
-    cv.put(BrowserContract.Clients.LAST_MODIFIED, this.lastModified);
-    return cv;
-  }
-
-  public ContentValues[] getTabsContentValues() {
-    int c = tabs.size();
-    ContentValues[] out = new ContentValues[c];
-    for (int i = 0; i < c; i++) {
-      out[i] = tabs.get(i).toContentValues(this.guid, i);
-    }
-    return out;
-  }
-
-  public static Tab tabFromJSONObject(JSONObject o) throws NonArrayJSONException {
-    ExtendedJSONObject obj = new ExtendedJSONObject(o);
-    String title      = obj.getString("title");
-    String icon       = obj.getString("icon");
-    JSONArray history = obj.getArray("urlHistory");
-
-    // Last used is inexplicably a string in seconds. Most of the time.
-    long lastUsed = 0;
-    Object lU = obj.get("lastUsed");
-    if (lU instanceof Number) {
-      lastUsed = ((Long) lU) * 1000L;
-    } else if (lU instanceof String) {
-      try {
-        lastUsed = Long.parseLong((String) lU, 10) * 1000L;
-      } catch (NumberFormatException e) {
-        Logger.debug(TabsRecord.LOG_TAG, "Invalid number format in lastUsed: " + lU);
-      }
-    }
-    return new Tab(title, icon, history, lastUsed);
-  }
-
-  @SuppressWarnings("unchecked")
-  public static JSONObject tabToJSONObject(Tab tab) {
-    JSONObject o = new JSONObject();
-    o.put("title", tab.title);
-    o.put("icon", tab.icon);
-    o.put("urlHistory", tab.history);
-    o.put("lastUsed", tab.lastUsed / 1000);
-    return o;
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/domain/TabsRecordFactory.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/domain/TabsRecordFactory.java
deleted file mode 100644
index 9504434..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/domain/TabsRecordFactory.java
+++ /dev/null
@@ -1,17 +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.sync.repositories.domain;
-
-import org.mozilla.gecko.sync.CryptoRecord;
-import org.mozilla.gecko.sync.repositories.RecordFactory;
-
-public class TabsRecordFactory extends RecordFactory {
-  @Override
-  public Record createRecord(Record record) {
-    TabsRecord r = new TabsRecord();
-    r.initFromEnvelope((CryptoRecord) record);
-    return r;
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/domain/VersionConstants.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/domain/VersionConstants.java
deleted file mode 100644
index 2d3d4fd..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/domain/VersionConstants.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.sync.repositories.domain;
-
-public class VersionConstants {
-  public static final int BOOKMARKS_ENGINE_VERSION = 2;
-  public static final int CLIENTS_ENGINE_VERSION = 1;
-  public static final int FORMS_ENGINE_VERSION = 1;
-  public static final int HISTORY_ENGINE_VERSION = 1;
-  public static final int PASSWORDS_ENGINE_VERSION = 1;
-  public static final int TABS_ENGINE_VERSION = 1;
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/downloaders/BatchingDownloader.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/downloaders/BatchingDownloader.java
deleted file mode 100644
index 5c3037e..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/downloaders/BatchingDownloader.java
+++ /dev/null
@@ -1,310 +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.sync.repositories.downloaders;
-
-import android.support.annotation.Nullable;
-import android.support.annotation.VisibleForTesting;
-
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.sync.CryptoRecord;
-import org.mozilla.gecko.sync.DelayedWorkTracker;
-import org.mozilla.gecko.sync.net.SyncResponse;
-import org.mozilla.gecko.sync.net.SyncStorageCollectionRequest;
-import org.mozilla.gecko.sync.net.SyncStorageResponse;
-import org.mozilla.gecko.sync.repositories.Server11Repository;
-import org.mozilla.gecko.sync.repositories.Server11RepositorySession;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionFetchRecordsDelegate;
-
-import java.io.UnsupportedEncodingException;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.net.URLEncoder;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.Set;
-
-/**
- * Batching Downloader, which implements batching protocol as supported by Sync 1.5.
- *
- * Downloader's batching behaviour is configured via two parameters, obtained from the repository:
- * - Per-batch limit, which specified how many records may be fetched in an individual GET request.
- * - Total limit, which controls number of batch GET requests we will make.
- *
- *
- * Batching is implemented via specifying a 'limit' GET parameter, and looking for an 'offset' token
- * in the response. If offset token is present, this indicates that there are more records than what
- * we've received so far, and we perform an additional fetch. Batching stops when either we hit a total
- * limit, or offset token is no longer present (indicating that we're done).
- *
- * For unlimited repositories (such as passwords), both of these value will be -1. Downloader will not
- * specify a limit parameter in this case, and the response will contain every record available and no
- * offset token, thus fully completing in one go.
- *
- * In between batches, we maintain a Last-Modified timestamp, based off the value return in the header
- * of the first response. Every response will have a Last-Modified header, indicating when the collection
- * was modified last. We pass along this header in our subsequent requests in a X-If-Unmodified-Since
- * header. Server will ensure that our collection did not change while we are batching, if it did it will
- * fail our fetch with a 412 (Consequent Modification) error. Additionally, we perform the same checks
- * locally.
- */
-public class BatchingDownloader {
-    public static final String LOG_TAG = "BatchingDownloader";
-
-    protected final Server11Repository repository;
-    private final Server11RepositorySession repositorySession;
-    private final DelayedWorkTracker workTracker = new DelayedWorkTracker();
-    // Used to track outstanding requests, so that we can abort them as needed.
-    @VisibleForTesting
-    protected final Set<SyncStorageCollectionRequest> pending = Collections.synchronizedSet(new HashSet<SyncStorageCollectionRequest>());
-    /* @GuardedBy("this") */ private String lastModified;
-    /* @GuardedBy("this") */ private long numRecords = 0;
-
-    public BatchingDownloader(final Server11Repository repository, final Server11RepositorySession repositorySession) {
-        this.repository = repository;
-        this.repositorySession = repositorySession;
-    }
-
-    @VisibleForTesting
-    protected static String flattenIDs(String[] guids) {
-        // Consider using Utils.toDelimitedString if and when the signature changes
-        // to Collection<String> guids.
-        if (guids.length == 0) {
-            return "";
-        }
-        if (guids.length == 1) {
-            return guids[0];
-        }
-        // Assuming 12-char GUIDs. There should be a -1 in there, but we accumulate one comma too many.
-        StringBuilder b = new StringBuilder(guids.length * 12 + guids.length);
-        for (String guid : guids) {
-            b.append(guid);
-            b.append(",");
-        }
-        return b.substring(0, b.length() - 1);
-    }
-
-    @VisibleForTesting
-    protected void fetchWithParameters(long newer,
-                                    long batchLimit,
-                                    boolean full,
-                                    String sort,
-                                    String ids,
-                                    SyncStorageCollectionRequest request,
-                                    RepositorySessionFetchRecordsDelegate fetchRecordsDelegate)
-            throws URISyntaxException, UnsupportedEncodingException {
-        if (batchLimit > repository.getDefaultTotalLimit()) {
-            throw new IllegalArgumentException("Batch limit should not be greater than total limit");
-        }
-
-        request.delegate = new BatchingDownloaderDelegate(this, fetchRecordsDelegate, request,
-                newer, batchLimit, full, sort, ids);
-        this.pending.add(request);
-        request.get();
-    }
-
-    @VisibleForTesting
-    @Nullable
-    protected String encodeParam(String param) throws UnsupportedEncodingException {
-        if (param != null) {
-            return URLEncoder.encode(param, "UTF-8");
-        }
-        return null;
-    }
-
-    @VisibleForTesting
-    protected SyncStorageCollectionRequest makeSyncStorageCollectionRequest(long newer,
-                                                  long batchLimit,
-                                                  boolean full,
-                                                  String sort,
-                                                  String ids,
-                                                  String offset)
-            throws URISyntaxException, UnsupportedEncodingException {
-        URI collectionURI = repository.collectionURI(full, newer, batchLimit, sort, ids, encodeParam(offset));
-        Logger.debug(LOG_TAG, collectionURI.toString());
-
-        return new SyncStorageCollectionRequest(collectionURI);
-    }
-
-    public void fetchSince(long timestamp, RepositorySessionFetchRecordsDelegate fetchRecordsDelegate) {
-        this.fetchSince(timestamp, null, fetchRecordsDelegate);
-    }
-
-    private void fetchSince(long timestamp, String offset,
-                           RepositorySessionFetchRecordsDelegate fetchRecordsDelegate) {
-        long batchLimit = repository.getDefaultBatchLimit();
-        String sort = repository.getDefaultSort();
-
-        try {
-            SyncStorageCollectionRequest request = makeSyncStorageCollectionRequest(timestamp,
-                    batchLimit, true, sort, null, offset);
-            this.fetchWithParameters(timestamp, batchLimit, true, sort, null, request, fetchRecordsDelegate);
-        } catch (URISyntaxException | UnsupportedEncodingException e) {
-            fetchRecordsDelegate.onFetchFailed(e, null);
-        }
-    }
-
-    public void fetch(String[] guids, RepositorySessionFetchRecordsDelegate fetchRecordsDelegate) {
-        String ids = flattenIDs(guids);
-        String index = "index";
-
-        try {
-            SyncStorageCollectionRequest request = makeSyncStorageCollectionRequest(
-                    -1, -1, true, index, ids, null);
-            this.fetchWithParameters(-1, -1, true, index, ids, request, fetchRecordsDelegate);
-        } catch (URISyntaxException | UnsupportedEncodingException e) {
-            fetchRecordsDelegate.onFetchFailed(e, null);
-        }
-    }
-
-    public Server11Repository getServerRepository() {
-        return this.repository;
-    }
-
-    public void onFetchCompleted(SyncStorageResponse response,
-                                 final RepositorySessionFetchRecordsDelegate fetchRecordsDelegate,
-                                 final SyncStorageCollectionRequest request, long newer,
-                                 long limit, boolean full, String sort, String ids) {
-        removeRequestFromPending(request);
-
-        // When we process our first request, we get back a X-Last-Modified header indicating when collection was modified last.
-        // We pass it to the server with every subsequent request (if we need to make more) as the X-If-Unmodified-Since header,
-        // and server is supposed to ensure that this pre-condition is met, and fail our request with a 412 error code otherwise.
-        // So, if all of this happens, these checks should never fail.
-        // However, we also track this header in client side, and can defensively validate against it here as well.
-        final String currentLastModifiedTimestamp = response.lastModified();
-        Logger.debug(LOG_TAG, "Last modified timestamp " + currentLastModifiedTimestamp);
-
-        // Sanity check. We also did a null check in delegate before passing it into here.
-        if (currentLastModifiedTimestamp == null) {
-            this.abort(fetchRecordsDelegate, "Last modified timestamp is missing");
-            return;
-        }
-
-        final boolean lastModifiedChanged;
-        synchronized (this) {
-            if (this.lastModified == null) {
-                // First time seeing last modified timestamp.
-                this.lastModified = currentLastModifiedTimestamp;
-            }
-            lastModifiedChanged = !this.lastModified.equals(currentLastModifiedTimestamp);
-        }
-
-        if (lastModifiedChanged) {
-            this.abort(fetchRecordsDelegate, "Last modified timestamp has changed unexpectedly");
-            return;
-        }
-
-        final boolean hasNotReachedLimit;
-        synchronized (this) {
-            this.numRecords += response.weaveRecords();
-            hasNotReachedLimit = this.numRecords < repository.getDefaultTotalLimit();
-        }
-
-        final String offset = response.weaveOffset();
-        final SyncStorageCollectionRequest newRequest;
-        try {
-            newRequest = makeSyncStorageCollectionRequest(newer,
-                    limit, full, sort, ids, offset);
-        } catch (final URISyntaxException | UnsupportedEncodingException e) {
-            this.workTracker.delayWorkItem(new Runnable() {
-                @Override
-                public void run() {
-                    Logger.debug(LOG_TAG, "Delayed onFetchCompleted running.");
-                    fetchRecordsDelegate.onFetchFailed(e, null);
-                }
-            });
-            return;
-        }
-
-        if (offset != null && hasNotReachedLimit) {
-            try {
-                this.fetchWithParameters(newer, limit, full, sort, ids, newRequest, fetchRecordsDelegate);
-            } catch (final URISyntaxException | UnsupportedEncodingException e) {
-                this.workTracker.delayWorkItem(new Runnable() {
-                    @Override
-                    public void run() {
-                        Logger.debug(LOG_TAG, "Delayed onFetchCompleted running.");
-                        fetchRecordsDelegate.onFetchFailed(e, null);
-                    }
-                });
-            }
-            return;
-        }
-
-        final long normalizedTimestamp = response.normalizedTimestampForHeader(SyncResponse.X_LAST_MODIFIED);
-        Logger.debug(LOG_TAG, "Fetch completed. Timestamp is " + normalizedTimestamp);
-
-        this.workTracker.delayWorkItem(new Runnable() {
-            @Override
-            public void run() {
-                Logger.debug(LOG_TAG, "Delayed onFetchCompleted running.");
-                fetchRecordsDelegate.onFetchCompleted(normalizedTimestamp);
-            }
-        });
-    }
-
-    public void onFetchFailed(final Exception ex,
-                              final RepositorySessionFetchRecordsDelegate fetchRecordsDelegate,
-                              final SyncStorageCollectionRequest request) {
-        removeRequestFromPending(request);
-        this.workTracker.delayWorkItem(new Runnable() {
-            @Override
-            public void run() {
-                Logger.debug(LOG_TAG, "Running onFetchFailed.");
-                fetchRecordsDelegate.onFetchFailed(ex, null);
-            }
-        });
-    }
-
-    public void onFetchedRecord(CryptoRecord record,
-                                RepositorySessionFetchRecordsDelegate fetchRecordsDelegate) {
-        this.workTracker.incrementOutstanding();
-        try {
-            fetchRecordsDelegate.onFetchedRecord(record);
-        } catch (Exception ex) {
-            Logger.warn(LOG_TAG, "Got exception calling onFetchedRecord with WBO.", ex);
-            throw new RuntimeException(ex);
-        } finally {
-            this.workTracker.decrementOutstanding();
-        }
-    }
-
-    private void removeRequestFromPending(SyncStorageCollectionRequest request) {
-        if (request == null) {
-            return;
-        }
-        this.pending.remove(request);
-    }
-
-    @VisibleForTesting
-    protected void abortRequests() {
-        this.repositorySession.abort();
-        synchronized (this.pending) {
-            for (SyncStorageCollectionRequest request : this.pending) {
-                request.abort();
-            }
-            this.pending.clear();
-        }
-    }
-
-    @Nullable
-    protected synchronized String getLastModified() {
-        return this.lastModified;
-    }
-
-    private void abort(final RepositorySessionFetchRecordsDelegate delegate, final String msg) {
-        Logger.error(LOG_TAG, msg);
-        this.abortRequests();
-        this.workTracker.delayWorkItem(new Runnable() {
-            @Override
-            public void run() {
-                Logger.debug(LOG_TAG, "Delayed onFetchCompleted running.");
-                delegate.onFetchFailed(
-                        new IllegalStateException(msg),
-                        null);
-            }
-        });
-    }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/downloaders/BatchingDownloaderDelegate.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/downloaders/BatchingDownloaderDelegate.java
deleted file mode 100644
index eb9f76d..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/downloaders/BatchingDownloaderDelegate.java
+++ /dev/null
@@ -1,91 +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.sync.repositories.downloaders;
-
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.sync.CryptoRecord;
-import org.mozilla.gecko.sync.HTTPFailureException;
-import org.mozilla.gecko.sync.crypto.KeyBundle;
-import org.mozilla.gecko.sync.net.AuthHeaderProvider;
-import org.mozilla.gecko.sync.net.SyncStorageCollectionRequest;
-import org.mozilla.gecko.sync.net.SyncStorageResponse;
-import org.mozilla.gecko.sync.net.WBOCollectionRequestDelegate;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionFetchRecordsDelegate;
-
-/**
- * Delegate that gets passed into fetch methods to handle server response from fetch.
- */
-public class BatchingDownloaderDelegate extends WBOCollectionRequestDelegate {
-    public static final String LOG_TAG = "BatchingDownloaderDelegate";
-
-    private BatchingDownloader downloader;
-    private RepositorySessionFetchRecordsDelegate fetchRecordsDelegate;
-    public SyncStorageCollectionRequest request;
-    // Used to pass back to BatchDownloader to start another fetch with these parameters if needed.
-    private long newer;
-    private long batchLimit;
-    private boolean full;
-    private String sort;
-    private String ids;
-
-    public BatchingDownloaderDelegate(final BatchingDownloader downloader,
-                                      final RepositorySessionFetchRecordsDelegate fetchRecordsDelegate,
-                                      final SyncStorageCollectionRequest request, long newer,
-                                      long batchLimit, boolean full, String sort, String ids) {
-        this.downloader = downloader;
-        this.fetchRecordsDelegate = fetchRecordsDelegate;
-        this.request = request;
-        this.newer = newer;
-        this.batchLimit = batchLimit;
-        this.full = full;
-        this.sort = sort;
-        this.ids = ids;
-    }
-
-    @Override
-    public AuthHeaderProvider getAuthHeaderProvider() {
-        return this.downloader.getServerRepository().getAuthHeaderProvider();
-    }
-
-    @Override
-    public String ifUnmodifiedSince() {
-        return this.downloader.getLastModified();
-    }
-
-    @Override
-    public void handleRequestSuccess(SyncStorageResponse response) {
-        Logger.debug(LOG_TAG, "Fetch done.");
-        if (response.lastModified() != null) {
-            this.downloader.onFetchCompleted(response, this.fetchRecordsDelegate, this.request,
-                    this.newer, this.batchLimit, this.full, this.sort, this.ids);
-            return;
-        }
-        this.downloader.onFetchFailed(
-                new IllegalStateException("Missing last modified header from response"),
-                this.fetchRecordsDelegate,
-                this.request);
-    }
-
-    @Override
-    public void handleRequestFailure(SyncStorageResponse response) {
-        this.handleRequestError(new HTTPFailureException(response));
-    }
-
-    @Override
-    public void handleRequestError(final Exception ex) {
-        Logger.warn(LOG_TAG, "Got request error.", ex);
-        this.downloader.onFetchFailed(ex, this.fetchRecordsDelegate, this.request);
-    }
-
-    @Override
-    public void handleWBO(CryptoRecord record) {
-        this.downloader.onFetchedRecord(record, this.fetchRecordsDelegate);
-    }
-
-    @Override
-    public KeyBundle keyBundle() {
-        return null;
-    }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/uploaders/BatchMeta.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/uploaders/BatchMeta.java
deleted file mode 100644
index 9515885..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/uploaders/BatchMeta.java
+++ /dev/null
@@ -1,165 +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.sync.repositories.uploaders;
-
-import android.support.annotation.CheckResult;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-
-import org.mozilla.gecko.background.common.log.Logger;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import org.mozilla.gecko.sync.repositories.uploaders.BatchingUploader.TokenModifiedException;
-import org.mozilla.gecko.sync.repositories.uploaders.BatchingUploader.LastModifiedChangedUnexpectedly;
-import org.mozilla.gecko.sync.repositories.uploaders.BatchingUploader.LastModifiedDidNotChange;
-
-/**
- * Keeps track of token, Last-Modified value and GUIDs of succeeded records.
- */
-/* @ThreadSafe */
-public class BatchMeta extends BufferSizeTracker {
-    private static final String LOG_TAG = "BatchMeta";
-
-    // Will be set once first payload upload succeeds. We don't expect this to change until we
-    // commit the batch, and which point it must change.
-    /* @GuardedBy("this") */ private Long lastModified;
-
-    // Will be set once first payload upload succeeds. We don't expect this to ever change until
-    // a commit succeeds, at which point this gets set to null.
-    /* @GuardedBy("this") */ private String token;
-
-    /* @GuardedBy("accessLock") */ private boolean isUnlimited = false;
-
-    // Accessed by synchronously running threads.
-    /* @GuardedBy("accessLock") */ private final List<String> successRecordGuids = new ArrayList<>();
-
-    /* @GuardedBy("accessLock") */ private boolean needsCommit = false;
-
-    protected final Long collectionLastModified;
-
-    public BatchMeta(@NonNull Object payloadLock, long maxBytes, long maxRecords, @Nullable Long collectionLastModified) {
-        super(payloadLock, maxBytes, maxRecords);
-        this.collectionLastModified = collectionLastModified;
-    }
-
-    protected void setIsUnlimited(boolean isUnlimited) {
-        synchronized (accessLock) {
-            this.isUnlimited = isUnlimited;
-        }
-    }
-
-    @Override
-    protected boolean canFit(long recordDeltaByteCount) {
-        synchronized (accessLock) {
-            return isUnlimited || super.canFit(recordDeltaByteCount);
-        }
-    }
-
-    @Override
-    @CheckResult
-    protected boolean addAndEstimateIfFull(long recordDeltaByteCount) {
-        synchronized (accessLock) {
-            needsCommit = true;
-            boolean isFull = super.addAndEstimateIfFull(recordDeltaByteCount);
-            return !isUnlimited && isFull;
-        }
-    }
-
-    protected boolean needToCommit() {
-        synchronized (accessLock) {
-            return needsCommit;
-        }
-    }
-
-    protected synchronized String getToken() {
-        return token;
-    }
-
-    protected synchronized void setToken(final String newToken, boolean isCommit) throws TokenModifiedException {
-        // Set token once in a batching mode.
-        // In a non-batching mode, this.token and newToken will be null, and this is a no-op.
-        if (token == null) {
-            token = newToken;
-            return;
-        }
-
-        // Sanity checks.
-        if (isCommit) {
-            // We expect token to be null when commit payload succeeds.
-            if (newToken != null) {
-                throw new TokenModifiedException();
-            } else {
-                token = null;
-            }
-            return;
-        }
-
-        // We expect new token to always equal current token for non-commit payloads.
-        if (!token.equals(newToken)) {
-            throw new TokenModifiedException();
-        }
-    }
-
-    protected synchronized Long getLastModified() {
-        if (lastModified == null) {
-            return collectionLastModified;
-        }
-        return lastModified;
-    }
-
-    protected synchronized void setLastModified(final Long newLastModified, final boolean expectedToChange) throws LastModifiedChangedUnexpectedly, LastModifiedDidNotChange {
-        if (lastModified == null) {
-            lastModified = newLastModified;
-            return;
-        }
-
-        if (!expectedToChange && !lastModified.equals(newLastModified)) {
-            Logger.debug(LOG_TAG, "Last-Modified timestamp changed when we didn't expect it");
-            throw new LastModifiedChangedUnexpectedly();
-
-        } else if (expectedToChange && lastModified.equals(newLastModified)) {
-            Logger.debug(LOG_TAG, "Last-Modified timestamp did not change when we expected it to");
-            throw new LastModifiedDidNotChange();
-
-        } else {
-            lastModified = newLastModified;
-        }
-    }
-
-    protected ArrayList<String> getSuccessRecordGuids() {
-        synchronized (accessLock) {
-            return new ArrayList<>(this.successRecordGuids);
-        }
-    }
-
-    protected void recordSucceeded(final String recordGuid) {
-        // Sanity check.
-        if (recordGuid == null) {
-            throw new IllegalStateException();
-        }
-
-        synchronized (accessLock) {
-            successRecordGuids.add(recordGuid);
-        }
-    }
-
-    @Override
-    protected boolean canFitRecordByteDelta(long byteDelta, long recordCount, long byteCount) {
-        return isUnlimited || super.canFitRecordByteDelta(byteDelta, recordCount, byteCount);
-    }
-
-    @Override
-    protected void reset() {
-        synchronized (accessLock) {
-            super.reset();
-            token = null;
-            lastModified = null;
-            successRecordGuids.clear();
-            needsCommit = false;
-        }
-    }
-}
\ No newline at end of file
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/uploaders/BatchingUploader.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/uploaders/BatchingUploader.java
deleted file mode 100644
index 26efbd1..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/uploaders/BatchingUploader.java
+++ /dev/null
@@ -1,344 +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.sync.repositories.uploaders;
-
-import android.net.Uri;
-import android.support.annotation.VisibleForTesting;
-
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.sync.InfoConfiguration;
-import org.mozilla.gecko.sync.Server11RecordPostFailedException;
-import org.mozilla.gecko.sync.net.SyncResponse;
-import org.mozilla.gecko.sync.net.SyncStorageResponse;
-import org.mozilla.gecko.sync.repositories.Server11RepositorySession;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionStoreDelegate;
-import org.mozilla.gecko.sync.repositories.domain.Record;
-
-import java.util.ArrayList;
-import java.util.concurrent.Executor;
-import java.util.concurrent.atomic.AtomicLong;
-
-/**
- * Uploader which implements batching introduced in Sync 1.5.
- *
- * Batch vs payload terminology:
- * - batch is comprised of a series of payloads, which are all committed at the same time.
- * -- identified via a "batch token", which is returned after first payload for the batch has been uploaded.
- * - payload is a collection of records which are uploaded together. Associated with a batch.
- * -- last payload, identified via commit=true, commits the batch.
- *
- * Limits for how many records can fit into a payload and into a batch are defined in the passed-in
- * InfoConfiguration object.
- *
- * If we can't fit everything we'd like to upload into one batch (according to max-total-* limits),
- * then we commit that batch, and start a new one. There are no explicit limits on total number of
- * batches we might use, although at some point we'll start to run into storage limit errors from the API.
- *
- * Once we go past using one batch this uploader is no longer "atomic". Partial state is exposed
- * to other clients after our first batch is committed and before our last batch is committed.
- * However, our per-batch limits are high, X-I-U-S mechanics help protect downloading clients
- * (as long as they implement X-I-U-S) with 412 error codes in case of interleaving upload and download,
- * and most mobile clients will not be uploading large-enough amounts of data (especially structured
- * data, such as bookmarks).
- *
- * Last-Modified header returned with the first batch payload POST success is maintained for a batch,
- * to guard against concurrent-modification errors (different uploader commits before we're done).
- *
- * Non-batching mode notes:
- * We also support Sync servers which don't enable batching for uploads. In this case, we respect
- * payload limits for individual uploads, and every upload is considered a commit. Batching limits
- * do not apply, and batch token is irrelevant.
- * We do keep track of Last-Modified and send along X-I-U-S with our uploads, to protect against
- * concurrent modifications by other clients.
- */
-public class BatchingUploader {
-    private static final String LOG_TAG = "BatchingUploader";
-
-    private final Uri collectionUri;
-
-    private volatile boolean recordUploadFailed = false;
-
-    private final BatchMeta batchMeta;
-    private final Payload payload;
-
-    // Accessed by synchronously running threads, OK to not synchronize and just make it volatile.
-    private volatile Boolean inBatchingMode;
-
-    // Used to ensure we have thread-safe access to the following:
-    // - byte and record counts in both Payload and BatchMeta objects
-    // - buffers in the Payload object
-    private final Object payloadLock = new Object();
-
-    protected Executor workQueue;
-    protected final RepositorySessionStoreDelegate sessionStoreDelegate;
-    protected final Server11RepositorySession repositorySession;
-
-    protected AtomicLong uploadTimestamp = new AtomicLong(0);
-
-    protected static final int PER_RECORD_OVERHEAD_BYTE_COUNT = RecordUploadRunnable.RECORD_SEPARATOR.length;
-    protected static final int PER_PAYLOAD_OVERHEAD_BYTE_COUNT = RecordUploadRunnable.RECORDS_END.length;
-
-    // Sanity check. RECORD_SEPARATOR and RECORD_START are assumed to be of the same length.
-    static {
-        if (RecordUploadRunnable.RECORD_SEPARATOR.length != RecordUploadRunnable.RECORDS_START.length) {
-            throw new IllegalStateException("Separator and start tokens must be of the same length");
-        }
-    }
-
-    public BatchingUploader(final Server11RepositorySession repositorySession, final Executor workQueue, final RepositorySessionStoreDelegate sessionStoreDelegate) {
-        this.repositorySession = repositorySession;
-        this.workQueue = workQueue;
-        this.sessionStoreDelegate = sessionStoreDelegate;
-        this.collectionUri = Uri.parse(repositorySession.getServerRepository().collectionURI().toString());
-
-        InfoConfiguration config = repositorySession.getServerRepository().getInfoConfiguration();
-        this.batchMeta = new BatchMeta(
-                payloadLock, config.maxTotalBytes, config.maxTotalRecords,
-                repositorySession.getServerRepository().getCollectionLastModified()
-        );
-        this.payload = new Payload(payloadLock, config.maxPostBytes, config.maxPostRecords);
-    }
-
-    public void process(final Record record) {
-        final String guid = record.guid;
-        final byte[] recordBytes = record.toJSONBytes();
-        final long recordDeltaByteCount = recordBytes.length + PER_RECORD_OVERHEAD_BYTE_COUNT;
-
-        Logger.debug(LOG_TAG, "Processing a record with guid: " + guid);
-
-        // We can't upload individual records which exceed our payload byte limit.
-        if ((recordDeltaByteCount + PER_PAYLOAD_OVERHEAD_BYTE_COUNT) > payload.maxBytes) {
-            sessionStoreDelegate.onRecordStoreFailed(new RecordTooLargeToUpload(), guid);
-            return;
-        }
-
-        synchronized (payloadLock) {
-            final boolean canFitRecordIntoBatch = batchMeta.canFit(recordDeltaByteCount);
-            final boolean canFitRecordIntoPayload = payload.canFit(recordDeltaByteCount);
-
-            // Record fits!
-            if (canFitRecordIntoBatch && canFitRecordIntoPayload) {
-                Logger.debug(LOG_TAG, "Record fits into the current batch and payload");
-                addAndFlushIfNecessary(recordDeltaByteCount, recordBytes, guid);
-
-            // Payload won't fit the record.
-            } else if (canFitRecordIntoBatch) {
-                Logger.debug(LOG_TAG, "Current payload won't fit incoming record, uploading payload.");
-                flush(false, false);
-
-                Logger.debug(LOG_TAG, "Recording the incoming record into a new payload");
-
-                // Keep track of the overflow record.
-                addAndFlushIfNecessary(recordDeltaByteCount, recordBytes, guid);
-
-            // Batch won't fit the record.
-            } else {
-                Logger.debug(LOG_TAG, "Current batch won't fit incoming record, committing batch.");
-                flush(true, false);
-
-                Logger.debug(LOG_TAG, "Recording the incoming record into a new batch");
-                batchMeta.reset();
-
-                // Keep track of the overflow record.
-                addAndFlushIfNecessary(recordDeltaByteCount, recordBytes, guid);
-            }
-        }
-    }
-
-    // Convenience function used from the process method; caller must hold a payloadLock.
-    private void addAndFlushIfNecessary(long byteCount, byte[] recordBytes, String guid) {
-        boolean isPayloadFull = payload.addAndEstimateIfFull(byteCount, recordBytes, guid);
-        boolean isBatchFull = batchMeta.addAndEstimateIfFull(byteCount);
-
-        // Preemptive commit batch or upload a payload if they're estimated to be full.
-        if (isBatchFull) {
-            flush(true, false);
-            batchMeta.reset();
-        } else if (isPayloadFull) {
-            flush(false, false);
-        }
-    }
-
-    public void noMoreRecordsToUpload() {
-        Logger.debug(LOG_TAG, "Received 'no more records to upload' signal.");
-
-        // Run this after the last payload succeeds, so that we know for sure if we're in a batching
-        // mode and need to commit with a potentially empty payload.
-        workQueue.execute(new Runnable() {
-            @Override
-            public void run() {
-                commitIfNecessaryAfterLastPayload();
-            }
-        });
-    }
-
-    @VisibleForTesting
-    protected void commitIfNecessaryAfterLastPayload() {
-        // Must be called after last payload upload finishes.
-        synchronized (payload) {
-            // If we have any pending records in the Payload, flush them!
-            if (!payload.isEmpty()) {
-                flush(true, true);
-
-            // If we have an empty payload but need to commit the batch in the batching mode, flush!
-            } else if (batchMeta.needToCommit() && Boolean.TRUE.equals(inBatchingMode)) {
-                flush(true, true);
-
-            // Otherwise, we're done.
-            } else {
-                finished(uploadTimestamp);
-            }
-        }
-    }
-
-    /**
-     * We've been told by our upload delegate that a payload succeeded.
-     * Depending on the type of payload and batch mode status, inform our delegate of progress.
-     *
-     * @param response success response to our commit post
-     * @param isCommit was this a commit upload?
-     * @param isLastPayload was this a very last payload we'll upload?
-     */
-    public void payloadSucceeded(final SyncStorageResponse response, final boolean isCommit, final boolean isLastPayload) {
-        // Sanity check.
-        if (inBatchingMode == null) {
-            throw new IllegalStateException("Can't process payload success until we know if we're in a batching mode");
-        }
-
-        // We consider records to have been committed if we're not in a batching mode or this was a commit.
-        // If records have been committed, notify our store delegate.
-        if (!inBatchingMode || isCommit) {
-            for (String guid : batchMeta.getSuccessRecordGuids()) {
-                sessionStoreDelegate.onRecordStoreSucceeded(guid);
-            }
-        }
-
-        // If this was our very last commit, we're done storing records.
-        // Get Last-Modified timestamp from the response, and pass it upstream.
-        if (isLastPayload) {
-            finished(response.normalizedTimestampForHeader(SyncResponse.X_LAST_MODIFIED));
-        }
-    }
-
-    public void lastPayloadFailed() {
-        finished(uploadTimestamp);
-    }
-
-    private void finished(long lastModifiedTimestamp) {
-        bumpTimestampTo(uploadTimestamp, lastModifiedTimestamp);
-        finished(uploadTimestamp);
-    }
-
-    private void finished(AtomicLong lastModifiedTimestamp) {
-        repositorySession.storeDone(lastModifiedTimestamp.get());
-    }
-
-    public BatchMeta getCurrentBatch() {
-        return batchMeta;
-    }
-
-    public void setInBatchingMode(boolean inBatchingMode) {
-        this.inBatchingMode = inBatchingMode;
-
-        // If we know for sure that we're not in a batching mode,
-        // consider our batch to be of unlimited size.
-        this.batchMeta.setIsUnlimited(!inBatchingMode);
-    }
-
-    public Boolean getInBatchingMode() {
-        return inBatchingMode;
-    }
-
-    public void setLastModified(final Long lastModified, final boolean isCommit) throws BatchingUploaderException {
-        // Sanity check.
-        if (inBatchingMode == null) {
-            throw new IllegalStateException("Can't process Last-Modified before we know we're in a batching mode.");
-        }
-
-        // In non-batching mode, every time we receive a Last-Modified timestamp, we expect it to change
-        // since records are "committed" (become visible to other clients) on every payload.
-        // In batching mode, we only expect Last-Modified to change when we commit a batch.
-        batchMeta.setLastModified(lastModified, isCommit || !inBatchingMode);
-    }
-
-    public void recordSucceeded(final String recordGuid) {
-        Logger.debug(LOG_TAG, "Record store succeeded: " + recordGuid);
-        batchMeta.recordSucceeded(recordGuid);
-    }
-
-    public void recordFailed(final String recordGuid) {
-        recordFailed(new Server11RecordPostFailedException(), recordGuid);
-    }
-
-    public void recordFailed(final Exception e, final String recordGuid) {
-        Logger.debug(LOG_TAG, "Record store failed for guid " + recordGuid + " with exception: " + e.toString());
-        recordUploadFailed = true;
-        sessionStoreDelegate.onRecordStoreFailed(e, recordGuid);
-    }
-
-    public Server11RepositorySession getRepositorySession() {
-        return repositorySession;
-    }
-
-    private static void bumpTimestampTo(final AtomicLong current, long newValue) {
-        while (true) {
-            long existing = current.get();
-            if (existing > newValue) {
-                return;
-            }
-            if (current.compareAndSet(existing, newValue)) {
-                return;
-            }
-        }
-    }
-
-    private void flush(final boolean isCommit, final boolean isLastPayload) {
-        final ArrayList<byte[]> outgoing;
-        final ArrayList<String> outgoingGuids;
-        final long byteCount;
-
-        // Even though payload object itself is thread-safe, we want to ensure we get these altogether
-        // as a "unit". Another approach would be to create a wrapper object for these values, but this works.
-        synchronized (payloadLock) {
-            outgoing = payload.getRecordsBuffer();
-            outgoingGuids = payload.getRecordGuidsBuffer();
-            byteCount = payload.getByteCount();
-        }
-
-        workQueue.execute(new RecordUploadRunnable(
-                new BatchingAtomicUploaderMayUploadProvider(),
-                collectionUri,
-                batchMeta,
-                new PayloadUploadDelegate(this, outgoingGuids, isCommit, isLastPayload),
-                outgoing,
-                byteCount,
-                isCommit
-        ));
-
-        payload.reset();
-    }
-
-    private class BatchingAtomicUploaderMayUploadProvider implements MayUploadProvider {
-        public boolean mayUpload() {
-            return !recordUploadFailed;
-        }
-    }
-
-    public static class BatchingUploaderException extends Exception {
-        private static final long serialVersionUID = 1L;
-    }
-    public static class RecordTooLargeToUpload extends BatchingUploaderException {
-        private static final long serialVersionUID = 1L;
-    }
-    public static class LastModifiedDidNotChange extends BatchingUploaderException {
-        private static final long serialVersionUID = 1L;
-    }
-    public static class LastModifiedChangedUnexpectedly extends BatchingUploaderException {
-        private static final long serialVersionUID = 1L;
-    }
-    public static class TokenModifiedException extends BatchingUploaderException {
-        private static final long serialVersionUID = 1L;
-    };
-}
\ No newline at end of file
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/uploaders/BufferSizeTracker.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/uploaders/BufferSizeTracker.java
deleted file mode 100644
index 7f4c305..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/uploaders/BufferSizeTracker.java
+++ /dev/null
@@ -1,103 +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.sync.repositories.uploaders;
-
-import android.support.annotation.CallSuper;
-import android.support.annotation.CheckResult;
-
-/**
- * Implements functionality shared by BatchMeta and Payload objects, namely:
- * - keeping track of byte and record counts
- * - incrementing those counts when records are added
- * - checking if a record can fit
- */
-/* @ThreadSafe */
-public abstract class BufferSizeTracker {
-    protected final Object accessLock;
-
-    /* @GuardedBy("accessLock") */ private long byteCount = BatchingUploader.PER_PAYLOAD_OVERHEAD_BYTE_COUNT;
-    /* @GuardedBy("accessLock") */ private long recordCount = 0;
-    /* @GuardedBy("accessLock") */ protected Long smallestRecordByteCount;
-
-    protected final long maxBytes;
-    protected final long maxRecords;
-
-    public BufferSizeTracker(Object accessLock, long maxBytes, long maxRecords) {
-        this.accessLock = accessLock;
-        this.maxBytes = maxBytes;
-        this.maxRecords = maxRecords;
-    }
-
-    @CallSuper
-    protected boolean canFit(long recordDeltaByteCount) {
-        synchronized (accessLock) {
-            return canFitRecordByteDelta(recordDeltaByteCount, recordCount, byteCount);
-        }
-    }
-
-    protected boolean isEmpty() {
-        synchronized (accessLock) {
-            return recordCount == 0;
-        }
-    }
-
-    /**
-     * Adds a record and returns a boolean indicating whether batch is estimated to be full afterwards.
-     */
-    @CheckResult
-    protected boolean addAndEstimateIfFull(long recordDeltaByteCount) {
-        synchronized (accessLock) {
-            // Sanity check. Calling this method when buffer won't fit the record is an error.
-            if (!canFitRecordByteDelta(recordDeltaByteCount, recordCount, byteCount)) {
-                throw new IllegalStateException("Buffer size exceeded");
-            }
-
-            byteCount += recordDeltaByteCount;
-            recordCount += 1;
-
-            if (smallestRecordByteCount == null || smallestRecordByteCount > recordDeltaByteCount) {
-                smallestRecordByteCount = recordDeltaByteCount;
-            }
-
-            // See if we're full or nearly full after adding a record.
-            // We're halving smallestRecordByteCount because we're erring
-            // on the side of "can hopefully fit". We're trying to upload as soon as we know we
-            // should, but we also need to be mindful of minimizing total number of uploads we make.
-            return !canFitRecordByteDelta(smallestRecordByteCount / 2, recordCount, byteCount);
-        }
-    }
-
-    protected long getByteCount() {
-        synchronized (accessLock) {
-            // Ensure we account for payload overhead twice when the batch is empty.
-            // Payload overhead is either RECORDS_START ("[") or RECORDS_END ("]"),
-            // and for an empty payload we need account for both ("[]").
-            if (recordCount == 0) {
-                return byteCount + BatchingUploader.PER_PAYLOAD_OVERHEAD_BYTE_COUNT;
-            }
-            return byteCount;
-        }
-    }
-
-    protected long getRecordCount() {
-        synchronized (accessLock) {
-            return recordCount;
-        }
-    }
-
-    @CallSuper
-    protected void reset() {
-        synchronized (accessLock) {
-            byteCount = BatchingUploader.PER_PAYLOAD_OVERHEAD_BYTE_COUNT;
-            recordCount = 0;
-        }
-    }
-
-    @CallSuper
-    protected boolean canFitRecordByteDelta(long byteDelta, long recordCount, long byteCount) {
-        return recordCount < maxRecords
-                && (byteCount + byteDelta) <= maxBytes;
-    }
-}
\ No newline at end of file
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/uploaders/MayUploadProvider.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/uploaders/MayUploadProvider.java
deleted file mode 100644
index a1994cf..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/uploaders/MayUploadProvider.java
+++ /dev/null
@@ -1,9 +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.sync.repositories.uploaders;
-
-public interface MayUploadProvider {
-    boolean mayUpload();
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/uploaders/Payload.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/uploaders/Payload.java
deleted file mode 100644
index 1ed9b57..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/uploaders/Payload.java
+++ /dev/null
@@ -1,66 +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.sync.repositories.uploaders;
-
-import android.support.annotation.CheckResult;
-
-import java.util.ArrayList;
-
-/**
- * Owns per-payload record byte and recordGuid buffers.
- */
-/* @ThreadSafe */
-public class Payload extends BufferSizeTracker {
-    // Data of outbound records.
-    /* @GuardedBy("accessLock") */ private final ArrayList<byte[]> recordsBuffer = new ArrayList<>();
-
-    // GUIDs of outbound records. Used to fail entire payloads.
-    /* @GuardedBy("accessLock") */ private final ArrayList<String> recordGuidsBuffer = new ArrayList<>();
-
-    public Payload(Object payloadLock, long maxBytes, long maxRecords) {
-        super(payloadLock, maxBytes, maxRecords);
-    }
-
-    @Override
-    protected boolean addAndEstimateIfFull(long recordDelta) {
-        throw new UnsupportedOperationException();
-    }
-
-    @CheckResult
-    protected boolean addAndEstimateIfFull(long recordDelta, byte[] recordBytes, String guid) {
-        synchronized (accessLock) {
-            recordsBuffer.add(recordBytes);
-            recordGuidsBuffer.add(guid);
-            return super.addAndEstimateIfFull(recordDelta);
-        }
-    }
-
-    @Override
-    protected void reset() {
-        synchronized (accessLock) {
-            super.reset();
-            recordsBuffer.clear();
-            recordGuidsBuffer.clear();
-        }
-    }
-
-    protected ArrayList<byte[]> getRecordsBuffer() {
-        synchronized (accessLock) {
-            return new ArrayList<>(recordsBuffer);
-        }
-    }
-
-    protected ArrayList<String> getRecordGuidsBuffer() {
-        synchronized (accessLock) {
-            return new ArrayList<>(recordGuidsBuffer);
-        }
-    }
-
-    protected boolean isEmpty() {
-        synchronized (accessLock) {
-            return recordsBuffer.isEmpty();
-        }
-    }
-}
\ No newline at end of file
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/uploaders/PayloadUploadDelegate.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/uploaders/PayloadUploadDelegate.java
deleted file mode 100644
index e8bbb7d..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/uploaders/PayloadUploadDelegate.java
+++ /dev/null
@@ -1,185 +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.sync.repositories.uploaders;
-
-import org.json.simple.JSONArray;
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.sync.ExtendedJSONObject;
-import org.mozilla.gecko.sync.HTTPFailureException;
-import org.mozilla.gecko.sync.NonArrayJSONException;
-import org.mozilla.gecko.sync.NonObjectJSONException;
-import org.mozilla.gecko.sync.Utils;
-import org.mozilla.gecko.sync.net.AuthHeaderProvider;
-import org.mozilla.gecko.sync.net.SyncResponse;
-import org.mozilla.gecko.sync.net.SyncStorageRequestDelegate;
-import org.mozilla.gecko.sync.net.SyncStorageResponse;
-
-import java.util.ArrayList;
-
-public class PayloadUploadDelegate implements SyncStorageRequestDelegate {
-    private static final String LOG_TAG = "PayloadUploadDelegate";
-
-    private static final String KEY_BATCH = "batch";
-
-    private final BatchingUploader uploader;
-    private ArrayList<String> postedRecordGuids;
-    private final boolean isCommit;
-    private final boolean isLastPayload;
-
-    public PayloadUploadDelegate(BatchingUploader uploader, ArrayList<String> postedRecordGuids, boolean isCommit, boolean isLastPayload) {
-        this.uploader = uploader;
-        this.postedRecordGuids = postedRecordGuids;
-        this.isCommit = isCommit;
-        this.isLastPayload = isLastPayload;
-    }
-
-    @Override
-    public AuthHeaderProvider getAuthHeaderProvider() {
-        return uploader.getRepositorySession().getServerRepository().getAuthHeaderProvider();
-    }
-
-    @Override
-    public String ifUnmodifiedSince() {
-        final Long lastModified = uploader.getCurrentBatch().getLastModified();
-        if (lastModified == null) {
-            return null;
-        }
-        return Utils.millisecondsToDecimalSecondsString(lastModified);
-    }
-
-    @Override
-    public void handleRequestSuccess(final SyncStorageResponse response) {
-        // First, do some sanity checking.
-        if (response.getStatusCode() != 200 && response.getStatusCode() != 202) {
-            handleRequestError(
-                new IllegalStateException("handleRequestSuccess received a non-200/202 response: " + response.getStatusCode())
-            );
-            return;
-        }
-
-        // We always expect to see a Last-Modified header. It's returned with every success response.
-        if (!response.httpResponse().containsHeader(SyncResponse.X_LAST_MODIFIED)) {
-            handleRequestError(
-                    new IllegalStateException("Response did not have a Last-Modified header")
-            );
-            return;
-        }
-
-        // We expect to be able to parse the response as a JSON object.
-        final ExtendedJSONObject body;
-        try {
-            body = response.jsonObjectBody(); // jsonObjectBody() throws or returns non-null.
-        } catch (Exception e) {
-            Logger.error(LOG_TAG, "Got exception parsing POST success body.", e);
-            this.handleRequestError(e);
-            return;
-        }
-
-        // If we got a 200, it could be either a non-batching result, or a batch commit.
-        // - if we're in a batching mode, we expect this to be a commit.
-        // If we got a 202, we expect there to be a token present in the response
-        if (response.getStatusCode() == 200 && uploader.getCurrentBatch().getToken() != null) {
-            if (uploader.getInBatchingMode() && !isCommit) {
-                handleRequestError(
-                        new IllegalStateException("Got 200 OK in batching mode, but this was not a commit payload")
-                );
-                return;
-            }
-        } else if (response.getStatusCode() == 202) {
-            if (!body.containsKey(KEY_BATCH)) {
-                handleRequestError(
-                        new IllegalStateException("Batch response did not have a batch ID")
-                );
-                return;
-            }
-        }
-
-        // With sanity checks out of the way, can now safely say if we're in a batching mode or not.
-        // We only do this once per session.
-        if (uploader.getInBatchingMode() == null) {
-            uploader.setInBatchingMode(body.containsKey(KEY_BATCH));
-        }
-
-        // Tell current batch about the token we've received.
-        // Throws if token changed after being set once, or if we got a non-null token after a commit.
-        try {
-            uploader.getCurrentBatch().setToken(body.getString(KEY_BATCH), isCommit);
-        } catch (BatchingUploader.BatchingUploaderException e) {
-            handleRequestError(e);
-            return;
-        }
-
-        // Will throw if Last-Modified changed when it shouldn't have.
-        try {
-            uploader.setLastModified(
-                    response.normalizedTimestampForHeader(SyncResponse.X_LAST_MODIFIED),
-                    isCommit);
-        } catch (BatchingUploader.BatchingUploaderException e) {
-            handleRequestError(e);
-            return;
-        }
-
-        // All looks good up to this point, let's process success and failed arrays.
-        JSONArray success;
-        try {
-            success = body.getArray("success");
-        } catch (NonArrayJSONException e) {
-            handleRequestError(e);
-            return;
-        }
-
-        if (success != null && !success.isEmpty()) {
-            Logger.trace(LOG_TAG, "Successful records: " + success.toString());
-            for (Object o : success) {
-                try {
-                    uploader.recordSucceeded((String) o);
-                } catch (ClassCastException e) {
-                    Logger.error(LOG_TAG, "Got exception parsing POST success guid.", e);
-                    // Not much to be done.
-                }
-            }
-        }
-        // GC
-        success = null;
-
-        ExtendedJSONObject failed;
-        try {
-            failed = body.getObject("failed");
-        } catch (NonObjectJSONException e) {
-            handleRequestError(e);
-            return;
-        }
-
-        if (failed != null && !failed.object.isEmpty()) {
-            Logger.debug(LOG_TAG, "Failed records: " + failed.object.toString());
-            for (String guid : failed.keySet()) {
-                uploader.recordFailed(guid);
-            }
-        }
-        // GC
-        failed = null;
-
-        // And we're done! Let uploader finish up.
-        uploader.payloadSucceeded(response, isCommit, isLastPayload);
-    }
-
-    @Override
-    public void handleRequestFailure(final SyncStorageResponse response) {
-        this.handleRequestError(new HTTPFailureException(response));
-    }
-
-    @Override
-    public void handleRequestError(Exception e) {
-        for (String guid : postedRecordGuids) {
-            uploader.recordFailed(e, guid);
-        }
-        // GC
-        postedRecordGuids = null;
-
-        if (isLastPayload) {
-            uploader.lastPayloadFailed();
-        }
-    }
-}
\ No newline at end of file
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/uploaders/RecordUploadRunnable.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/uploaders/RecordUploadRunnable.java
deleted file mode 100644
index ce29551..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/uploaders/RecordUploadRunnable.java
+++ /dev/null
@@ -1,176 +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.sync.repositories.uploaders;
-
-import android.net.Uri;
-import android.support.annotation.VisibleForTesting;
-
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.sync.Server11PreviousPostFailedException;
-import org.mozilla.gecko.sync.net.SyncStorageRequest;
-import org.mozilla.gecko.sync.net.SyncStorageRequestDelegate;
-
-import java.io.IOException;
-import java.io.OutputStream;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.util.ArrayList;
-
-import ch.boye.httpclientandroidlib.entity.ContentProducer;
-import ch.boye.httpclientandroidlib.entity.EntityTemplate;
-
-/**
- * Responsible for creating and posting a <code>SyncStorageRequest</code> request object.
- */
-public class RecordUploadRunnable implements Runnable {
-    public final String LOG_TAG = "RecordUploadRunnable";
-
-    public final static byte[] RECORDS_START = { 91 };      // [ in UTF-8
-    public final static byte[] RECORD_SEPARATOR = { 44 };   // , in UTF-8
-    public final static byte[] RECORDS_END = { 93 };        // ] in UTF-8
-
-    private static final String QUERY_PARAM_BATCH = "batch";
-    private static final String QUERY_PARAM_TRUE = "true";
-    private static final String QUERY_PARAM_BATCH_COMMIT = "commit";
-
-    private final MayUploadProvider mayUploadProvider;
-    private final SyncStorageRequestDelegate uploadDelegate;
-
-    private final ArrayList<byte[]> outgoing;
-    private final long byteCount;
-
-    // Used to construct POST URI during run().
-    @VisibleForTesting
-    public final boolean isCommit;
-    private final Uri collectionUri;
-    private final BatchMeta batchMeta;
-
-    public RecordUploadRunnable(MayUploadProvider mayUploadProvider,
-                                Uri collectionUri,
-                                BatchMeta batchMeta,
-                                SyncStorageRequestDelegate uploadDelegate,
-                                ArrayList<byte[]> outgoing,
-                                long byteCount,
-                                boolean isCommit) {
-        this.mayUploadProvider = mayUploadProvider;
-        this.uploadDelegate = uploadDelegate;
-        this.outgoing = outgoing;
-        this.byteCount = byteCount;
-        this.batchMeta = batchMeta;
-        this.collectionUri = collectionUri;
-        this.isCommit = isCommit;
-    }
-
-    public static class ByteArraysContentProducer implements ContentProducer {
-        ArrayList<byte[]> outgoing;
-        public ByteArraysContentProducer(ArrayList<byte[]> arrays) {
-            outgoing = arrays;
-        }
-
-        @Override
-        public void writeTo(OutputStream outstream) throws IOException {
-            int count = outgoing.size();
-            outstream.write(RECORDS_START);
-            if (count > 0) {
-                outstream.write(outgoing.get(0));
-                for (int i = 1; i < count; ++i) {
-                    outstream.write(RECORD_SEPARATOR);
-                    outstream.write(outgoing.get(i));
-                }
-            }
-            outstream.write(RECORDS_END);
-        }
-
-        public static long outgoingBytesCount(ArrayList<byte[]> outgoing) {
-            final long numberOfRecords = outgoing.size();
-
-            // Account for start and end tokens.
-            long count = RECORDS_START.length + RECORDS_END.length;
-
-            // Account for all the records.
-            for (int i = 0; i < numberOfRecords; i++) {
-                count += outgoing.get(i).length;
-            }
-
-            // Account for a separator between the records.
-            // There's one less separator than there are records.
-            if (numberOfRecords > 1) {
-                count += RECORD_SEPARATOR.length * (numberOfRecords - 1);
-            }
-
-            return count;
-        }
-    }
-
-    public static class ByteArraysEntity extends EntityTemplate {
-        private final long count;
-        public ByteArraysEntity(ArrayList<byte[]> arrays, long totalBytes) {
-            super(new ByteArraysContentProducer(arrays));
-            this.count = totalBytes;
-            this.setContentType("application/json");
-            // charset is set in BaseResource.
-
-            // Sanity check our byte counts.
-            long realByteCount = ByteArraysContentProducer.outgoingBytesCount(arrays);
-            if (realByteCount != totalBytes) {
-                throw new IllegalStateException("Mismatched byte counts. Received " + totalBytes + " while real byte count is " + realByteCount);
-            }
-        }
-
-        @Override
-        public long getContentLength() {
-            return count;
-        }
-
-        @Override
-        public boolean isRepeatable() {
-            return true;
-        }
-    }
-
-    @Override
-    public void run() {
-        if (!mayUploadProvider.mayUpload()) {
-            Logger.info(LOG_TAG, "Told not to proceed by the uploader. Cancelling upload, failing records.");
-            uploadDelegate.handleRequestError(new Server11PreviousPostFailedException());
-            return;
-        }
-
-        Logger.trace(LOG_TAG, "Running upload task. Outgoing records: " + outgoing.size());
-
-        // We don't want the task queue to proceed until this request completes.
-        // Fortunately, BaseResource is currently synchronous.
-        // If that ever changes, you'll need to block here.
-
-        final URI postURI = buildPostURI(isCommit, batchMeta, collectionUri);
-        final SyncStorageRequest request = new SyncStorageRequest(postURI);
-        request.delegate = uploadDelegate;
-
-        ByteArraysEntity body = new ByteArraysEntity(outgoing, byteCount);
-        request.post(body);
-    }
-
-    @VisibleForTesting
-    public static URI buildPostURI(boolean isCommit, BatchMeta batchMeta, Uri collectionUri) {
-        final Uri.Builder uriBuilder = collectionUri.buildUpon();
-        final String batchToken = batchMeta.getToken();
-
-        if (batchToken != null) {
-            uriBuilder.appendQueryParameter(QUERY_PARAM_BATCH, batchToken);
-        } else {
-            uriBuilder.appendQueryParameter(QUERY_PARAM_BATCH, QUERY_PARAM_TRUE);
-        }
-
-        if (isCommit) {
-            uriBuilder.appendQueryParameter(QUERY_PARAM_BATCH_COMMIT, QUERY_PARAM_TRUE);
-        }
-
-        try {
-            return new URI(uriBuilder.build().toString());
-        } catch (URISyntaxException e) {
-            throw new IllegalStateException("Failed to construct a collection URI", e);
-        }
-    }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/setup/Constants.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/setup/Constants.java
deleted file mode 100644
index 66e6768..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/setup/Constants.java
+++ /dev/null
@@ -1,29 +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.sync.setup;
-
-public class Constants {
-  public static final String DEFAULT_PROFILE = "default";
-
-  /**
-   * Key in sync extras bundle specifying stages to sync this sync session.
-   * <p>
-   * Corresponding value should be a String JSON-encoding an object, the keys of
-   * which are the stage names to sync. For example:
-   * <code>"{ \"stageToSync\": 0 }"</code>.
-   */
-  public static final String EXTRAS_KEY_STAGES_TO_SYNC = "sync";
-
-  /**
-   * Key in sync extras bundle specifying stages to skip this sync session.
-   * <p>
-   * Corresponding value should be a String JSON-encoding an object, the keys of
-   * which are the stage names to skip. For example:
-   * <code>"{ \"stageToSkip\": 0 }"</code>.
-   */
-  public static final String EXTRAS_KEY_STAGES_TO_SKIP = "skip";
-
-  public static final String JSON_KEY_ACCOUNT    = "account";
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/setup/InvalidSyncKeyException.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/setup/InvalidSyncKeyException.java
deleted file mode 100644
index ac0fd58..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/setup/InvalidSyncKeyException.java
+++ /dev/null
@@ -1,9 +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.sync.setup;
-
-public class InvalidSyncKeyException extends Exception {
-  private static final long serialVersionUID = -6504925951580479894L;
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/setup/activities/ActivityUtils.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/setup/activities/ActivityUtils.java
deleted file mode 100644
index 6542e1b..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/setup/activities/ActivityUtils.java
+++ /dev/null
@@ -1,34 +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.sync.setup.activities;
-
-import android.content.Context;
-import android.content.Intent;
-import android.net.Uri;
-
-import org.mozilla.gecko.background.common.GlobalConstants;
-import org.mozilla.gecko.db.BrowserContract;
-
-public class ActivityUtils {
-  /**
-   * Open a URL in Fennec, if one is provided; or just open Fennec.
-   *
-   * @param context Android context.
-   * @param url to visit, or null to just open Fennec.
-   */
-  public static void openURLInFennec(final Context context, final String url) {
-    Intent intent;
-    if (url != null) {
-      intent = new Intent(Intent.ACTION_VIEW);
-      intent.setData(Uri.parse(url));
-    } else {
-      intent = new Intent(Intent.ACTION_MAIN);
-    }
-    intent.setClassName(GlobalConstants.BROWSER_INTENT_PACKAGE, GlobalConstants.BROWSER_INTENT_CLASS);
-    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-    intent.putExtra(BrowserContract.SKIP_TAB_QUEUE_FLAG, true);
-    context.startActivity(intent);
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/setup/activities/WebURLFinder.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/setup/activities/WebURLFinder.java
deleted file mode 100644
index 8411d2a..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/setup/activities/WebURLFinder.java
+++ /dev/null
@@ -1,161 +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.sync.setup.activities;
-
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-public class WebURLFinder {
-  /**
-   * These regular expressions are taken from Android's Patterns.java.
-   * We brought them in to standardize URL matching across Android versions, instead of relying
-   * on Android version-dependent built-ins that can vary across Android versions.
-   * The original code can be found here:
-   * http://androidxref.com/6.0.1_r10/xref/frameworks/base/core/java/android/util/Patterns.java
-   *
-   */
-  public static final String GOOD_IRI_CHAR = "a-zA-Z0-9\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF";
-  public static final String GOOD_GTLD_CHAR = "a-zA-Z\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF";
-  public static final String IRI = "[" + GOOD_IRI_CHAR + "]([" + GOOD_IRI_CHAR + "\\-]{0,61}[" + GOOD_IRI_CHAR + "]){0,1}";
-  public static final String GTLD = "[" + GOOD_GTLD_CHAR + "]{2,63}";
-  public static final String HOST_NAME = "(" + IRI + "\\.)+" + GTLD;
-  public static final Pattern IP_ADDRESS = Pattern.compile("((25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9])\\.(25[0-5]|2[0-4]"
-          + "[0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9]|0)\\.(25[0-5]|2[0-4][0-9]|[0-1]"
-          + "[0-9]{2}|[1-9][0-9]|[1-9]|0)\\.(25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}"
-          + "|[1-9][0-9]|[0-9]))");
-  public static final Pattern DOMAIN_NAME = Pattern.compile("(" + HOST_NAME + "|" + IP_ADDRESS + ")");
-  public static final Pattern WEB_URL = Pattern.compile("((?:(http|https|Http|Https|rtsp|Rtsp):\\/\\/(?:(?:[a-zA-Z0-9\\$\\-\\_\\.\\+\\!\\*\\'\\(\\)"
-          + "\\,\\;\\?\\&\\=]|(?:\\%[a-fA-F0-9]{2})){1,64}(?:\\:(?:[a-zA-Z0-9\\$\\-\\_"
-          + "\\.\\+\\!\\*\\'\\(\\)\\,\\;\\?\\&\\=]|(?:\\%[a-fA-F0-9]{2})){1,25})?\\@)?)?"
-          + "(?:" + DOMAIN_NAME + ")"
-          + "(?:\\:\\d{1,5})?)"
-          + "(\\/(?:(?:[" + GOOD_IRI_CHAR + "\\;\\/\\?\\:\\@\\&\\=\\#\\~"
-          + "\\-\\.\\+\\!\\*\\'\\(\\)\\,\\_])|(?:\\%[a-fA-F0-9]{2}))*)?"
-          + "(?:\\b|$)");
-
-  public final List<String> candidates;
-
-  public WebURLFinder(String string) {
-    if (string == null) {
-      throw new IllegalArgumentException("string must not be null");
-    }
-
-    this.candidates = candidateWebURLs(string);
-  }
-
-  public WebURLFinder(List<String> strings) {
-    if (strings == null) {
-      throw new IllegalArgumentException("strings must not be null");
-    }
-
-    this.candidates = candidateWebURLs(strings);
-  }
-
-  /**
-   * Check if string is a Web URL.
-   * <p>
-   * A Web URL is a URI that is not a <code>file:</code> or
-   * <code>javascript:</code> scheme.
-   *
-   * @param string
-   *          to check.
-   * @return <code>true</code> if <code>string</code> is a Web URL.
-   */
-  public static boolean isWebURL(String string) {
-    try {
-      new URI(string);
-    } catch (Exception e) {
-      return false;
-    }
-
-    if (android.webkit.URLUtil.isFileUrl(string) ||
-        android.webkit.URLUtil.isJavaScriptUrl(string)) {
-      return false;
-    }
-
-    return true;
-  }
-
-  /**
-   * Return best Web URL.
-   * <p>
-   * "Best" means a Web URL with a scheme, and failing that, a Web URL without a
-   * scheme.
-   *
-   * @return a Web URL or <code>null</code>.
-   */
-  public String bestWebURL() {
-    String firstWebURLWithScheme = firstWebURLWithScheme();
-    if (firstWebURLWithScheme != null) {
-      return firstWebURLWithScheme;
-    }
-
-    return firstWebURLWithoutScheme();
-  }
-
-  protected static List<String> candidateWebURLs(Collection<String> strings) {
-    List<String> candidates = new ArrayList<String>();
-
-    for (String string : strings) {
-      if (string == null) {
-        continue;
-      }
-
-      candidates.addAll(candidateWebURLs(string));
-    }
-
-    return candidates;
-  }
-
-  protected static List<String> candidateWebURLs(String string) {
-    Matcher matcher = WEB_URL.matcher(string);
-    List<String> matches = new LinkedList<String>();
-
-    while (matcher.find()) {
-      // Remove URLs with bad schemes.
-      if (!isWebURL(matcher.group())) {
-        continue;
-      }
-
-      // Remove parts of email addresses.
-      if (matcher.start() > 0 && (string.charAt(matcher.start() - 1) == '@')) {
-        continue;
-      }
-
-      matches.add(matcher.group());
-    }
-
-    return matches;
-  }
-
-  protected String firstWebURLWithScheme() {
-    for (String match : candidates) {
-      try {
-        if (new URI(match).getScheme() != null) {
-          return match;
-        }
-      } catch (URISyntaxException e) {
-        // Ignore: on to the next.
-        continue;
-      }
-    }
-
-    return null;
-  }
-
-  protected String firstWebURLWithoutScheme() {
-    if (!candidates.isEmpty()) {
-      return candidates.get(0);
-    }
-
-    return null;
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/AbstractNonRepositorySyncStage.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/AbstractNonRepositorySyncStage.java
deleted file mode 100644
index c910216..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/AbstractNonRepositorySyncStage.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.sync.stage;
-
-
-/**
- * This is simply a stage that is not responsible for synchronizing repositories.
- */
-public abstract class AbstractNonRepositorySyncStage extends AbstractSessionManagingSyncStage {
-  @Override
-  protected void resetLocal() {
-    // Do nothing.
-  }
-
-  @Override
-  protected void wipeLocal() {
-    // Do nothing.
-  }
-
-  @Override
-  public Integer getStorageVersion() {
-    return null; // Never include these engines in any meta/global records.
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/AbstractSessionManagingSyncStage.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/AbstractSessionManagingSyncStage.java
deleted file mode 100644
index 6592c3b..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/AbstractSessionManagingSyncStage.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.stage;
-
-import org.mozilla.gecko.sync.GlobalSession;
-
-/**
- * A global sync stage that manages a <code>GlobalSession</code> instance. This
- * class is intended to be temporary: it should disappear as work to make
- * data-driven syncs progresses.
- * <p>
- * This class is inherently <b>thread-unsafe</b>: if <code>session</code> is
- * mutated after being set, all sorts of bad things could occur. At the time of
- * writing, every <code>GlobalSyncStage</code> created is executed (wiped,
- * reset) with the same <code>GlobalSession</code> argument.
- */
-public abstract class AbstractSessionManagingSyncStage implements GlobalSyncStage {
-  protected GlobalSession session;
-
-  protected abstract void execute() throws NoSuchStageException;
-  protected abstract void resetLocal();
-  protected abstract void wipeLocal() throws Exception;
-
-  @Override
-  public void resetLocal(GlobalSession session) {
-    this.session = session;
-    resetLocal();
-  }
-
-  @Override
-  public void wipeLocal(GlobalSession session) throws Exception {
-    this.session = session;
-    wipeLocal();
-  }
-
-  @Override
-  public void execute(GlobalSession session) throws NoSuchStageException {
-    this.session = session;
-    execute();
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/AndroidBrowserBookmarksServerSyncStage.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/AndroidBrowserBookmarksServerSyncStage.java
deleted file mode 100644
index 10e2092..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/AndroidBrowserBookmarksServerSyncStage.java
+++ /dev/null
@@ -1,80 +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.sync.stage;
-
-import java.net.URISyntaxException;
-
-import org.mozilla.gecko.sync.JSONRecordFetcher;
-import org.mozilla.gecko.sync.MetaGlobalException;
-import org.mozilla.gecko.sync.net.AuthHeaderProvider;
-import org.mozilla.gecko.sync.repositories.RecordFactory;
-import org.mozilla.gecko.sync.repositories.Repository;
-import org.mozilla.gecko.sync.repositories.android.AndroidBrowserBookmarksRepository;
-import org.mozilla.gecko.sync.repositories.domain.BookmarkRecordFactory;
-import org.mozilla.gecko.sync.repositories.domain.VersionConstants;
-
-public class AndroidBrowserBookmarksServerSyncStage extends ServerSyncStage {
-  protected static final String LOG_TAG = "BookmarksStage";
-
-  // Eventually this kind of sync stage will be data-driven,
-  // and all this hard-coding can go away.
-  private static final String BOOKMARKS_SORT          = "index";
-  // Sanity limit. Batch and total limit are the same for now, and will be adjusted
-  // once buffer and high water mark are in place. See Bug 730142.
-  private static final long BOOKMARKS_BATCH_LIMIT = 5000;
-  private static final long BOOKMARKS_TOTAL_LIMIT = 5000;
-
-  @Override
-  protected String getCollection() {
-    return "bookmarks";
-  }
-
-  @Override
-  protected String getEngineName() {
-    return "bookmarks";
-  }
-
-  @Override
-  public Integer getStorageVersion() {
-    return VersionConstants.BOOKMARKS_ENGINE_VERSION;
-  }
-
-  @Override
-  protected Repository getRemoteRepository() throws URISyntaxException {
-    // If this is a first sync, we need to check server counts to make sure that we aren't
-    // going to screw up. SafeConstrainedServer11Repository does this. See Bug 814331.
-    AuthHeaderProvider authHeaderProvider = session.getAuthHeaderProvider();
-    final JSONRecordFetcher countsFetcher = new JSONRecordFetcher(session.config.infoCollectionCountsURL(), authHeaderProvider);
-    String collection = getCollection();
-    return new SafeConstrainedServer11Repository(
-        collection,
-        session.config.storageURL(),
-        session.getAuthHeaderProvider(),
-        session.config.infoCollections,
-        session.config.infoConfiguration,
-        BOOKMARKS_BATCH_LIMIT,
-        BOOKMARKS_TOTAL_LIMIT,
-        BOOKMARKS_SORT,
-        countsFetcher);
-  }
-
-  @Override
-  protected Repository getLocalRepository() {
-    return new AndroidBrowserBookmarksRepository();
-  }
-
-  @Override
-  protected RecordFactory getRecordFactory() {
-    return new BookmarkRecordFactory();
-  }
-
-  @Override
-  protected boolean isEnabled() throws MetaGlobalException {
-    if (session == null || session.getContext() == null) {
-      return false;
-    }
-    return super.isEnabled();
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/AndroidBrowserHistoryServerSyncStage.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/AndroidBrowserHistoryServerSyncStage.java
deleted file mode 100644
index 947a108..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/AndroidBrowserHistoryServerSyncStage.java
+++ /dev/null
@@ -1,74 +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.sync.stage;
-
-import java.net.URISyntaxException;
-
-import org.mozilla.gecko.sync.MetaGlobalException;
-import org.mozilla.gecko.sync.repositories.ConstrainedServer11Repository;
-import org.mozilla.gecko.sync.repositories.RecordFactory;
-import org.mozilla.gecko.sync.repositories.Repository;
-import org.mozilla.gecko.sync.repositories.android.AndroidBrowserHistoryRepository;
-import org.mozilla.gecko.sync.repositories.domain.HistoryRecordFactory;
-import org.mozilla.gecko.sync.repositories.domain.VersionConstants;
-
-public class AndroidBrowserHistoryServerSyncStage extends ServerSyncStage {
-  protected static final String LOG_TAG = "HistoryStage";
-
-  // Eventually this kind of sync stage will be data-driven,
-  // and all this hard-coding can go away.
-  private static final String HISTORY_SORT          = "index";
-  // Sanity limit. Batch and total limit are the same for now, and will be adjusted
-  // once buffer and high water mark are in place. See Bug 730142.
-  private static final long HISTORY_BATCH_LIMIT = 250;
-  private static final long HISTORY_TOTAL_LIMIT = 250;
-
-  @Override
-  protected String getCollection() {
-    return "history";
-  }
-
-  @Override
-  protected String getEngineName() {
-    return "history";
-  }
-
-  @Override
-  public Integer getStorageVersion() {
-    return VersionConstants.HISTORY_ENGINE_VERSION;
-  }
-
-  @Override
-  protected Repository getLocalRepository() {
-    return new AndroidBrowserHistoryRepository();
-  }
-
-  @Override
-  protected Repository getRemoteRepository() throws URISyntaxException {
-    String collection = getCollection();
-    return new ConstrainedServer11Repository(
-                                             collection,
-                                             session.config.storageURL(),
-                                             session.getAuthHeaderProvider(),
-                                             session.config.infoCollections,
-                                             session.config.infoConfiguration,
-                                             HISTORY_BATCH_LIMIT,
-                                             HISTORY_TOTAL_LIMIT,
-                                             HISTORY_SORT);
-  }
-
-  @Override
-  protected RecordFactory getRecordFactory() {
-    return new HistoryRecordFactory();
-  }
-
-  @Override
-  protected boolean isEnabled() throws MetaGlobalException {
-    if (session == null || session.getContext() == null) {
-      return false;
-    }
-    return super.isEnabled();
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/CheckPreconditionsStage.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/CheckPreconditionsStage.java
deleted file mode 100644
index b33f83a..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/CheckPreconditionsStage.java
+++ /dev/null
@@ -1,13 +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.sync.stage;
-
-
-public class CheckPreconditionsStage extends AbstractNonRepositorySyncStage {
-  @Override
-  public void execute() throws NoSuchStageException {
-    session.advance();
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/CompletedStage.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/CompletedStage.java
deleted file mode 100644
index 7ec7763..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/CompletedStage.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.sync.stage;
-
-
-
-public class CompletedStage extends AbstractNonRepositorySyncStage {
-  @Override
-  public void execute() throws NoSuchStageException {
-    // TODO: Update tracking timestamps, close connections, etc.
-    // TODO: call clean() on each Repository in the sync constellation.
-    session.completeSync();
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/EnsureCrypto5KeysStage.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/EnsureCrypto5KeysStage.java
deleted file mode 100644
index 5031cf7..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/EnsureCrypto5KeysStage.java
+++ /dev/null
@@ -1,192 +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.sync.stage;
-
-import java.net.URISyntaxException;
-import java.util.HashSet;
-import java.util.Set;
-
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.sync.CollectionKeys;
-import org.mozilla.gecko.sync.CryptoRecord;
-import org.mozilla.gecko.sync.ExtendedJSONObject;
-import org.mozilla.gecko.sync.InfoCollections;
-import org.mozilla.gecko.sync.NoCollectionKeysSetException;
-import org.mozilla.gecko.sync.crypto.KeyBundle;
-import org.mozilla.gecko.sync.crypto.PersistedCrypto5Keys;
-import org.mozilla.gecko.sync.net.AuthHeaderProvider;
-import org.mozilla.gecko.sync.net.SyncStorageRecordRequest;
-import org.mozilla.gecko.sync.net.SyncStorageRequestDelegate;
-import org.mozilla.gecko.sync.net.SyncStorageResponse;
-
-public class EnsureCrypto5KeysStage
-extends AbstractNonRepositorySyncStage
-implements SyncStorageRequestDelegate {
-
-  private static final String LOG_TAG = "EnsureC5KeysStage";
-  private static final String CRYPTO_COLLECTION = "crypto";
-  protected boolean retrying = false;
-
-  @Override
-  public void execute() throws NoSuchStageException {
-    InfoCollections infoCollections = session.config.infoCollections;
-    if (infoCollections == null) {
-      session.abort(null, "No info/collections set in EnsureCrypto5KeysStage.");
-      return;
-    }
-
-    PersistedCrypto5Keys pck = session.config.persistedCryptoKeys();
-    long lastModified = pck.lastModified();
-    if (retrying || !infoCollections.updateNeeded(CRYPTO_COLLECTION, lastModified)) {
-      // Try to use our local collection keys for this session.
-      Logger.debug(LOG_TAG, "Trying to use persisted collection keys for this session.");
-      CollectionKeys keys = pck.keys();
-      if (keys != null) {
-        Logger.trace(LOG_TAG, "Using persisted collection keys for this session.");
-        session.config.setCollectionKeys(keys);
-        session.advance();
-        return;
-      }
-      Logger.trace(LOG_TAG, "Failed to use persisted collection keys for this session.");
-    }
-
-    // We need an update: fetch fresh keys.
-    Logger.debug(LOG_TAG, "Fetching fresh collection keys for this session.");
-    try {
-      SyncStorageRecordRequest request = new SyncStorageRecordRequest(session.wboURI(CRYPTO_COLLECTION, "keys"));
-      request.delegate = this;
-      request.get();
-    } catch (URISyntaxException e) {
-      session.abort(e, "Invalid URI.");
-    }
-  }
-
-  @Override
-  public AuthHeaderProvider getAuthHeaderProvider() {
-    return session.getAuthHeaderProvider();
-  }
-
-  @Override
-  public String ifUnmodifiedSince() {
-    // TODO: last key time!
-    return null;
-  }
-
-  protected void setAndPersist(PersistedCrypto5Keys pck, CollectionKeys keys, long timestamp) {
-    session.config.setCollectionKeys(keys);
-    pck.persistKeys(keys);
-    pck.persistLastModified(timestamp);
-  }
-
-  /**
-   * Return collections where either the individual key has changed, or if the
-   * new default key is not the same as the old default key, where the
-   * collection is using the default key.
-   */
-  protected Set<String> collectionsToUpdate(CollectionKeys oldKeys, CollectionKeys newKeys) {
-    // These keys have explicitly changed; they definitely need updating.
-    Set<String> changedKeys = new HashSet<String>(CollectionKeys.differences(oldKeys, newKeys));
-
-    boolean defaultKeyChanged = true; // Most pessimistic is to assume default key has changed.
-    KeyBundle newDefaultKeyBundle = null;
-    try {
-      KeyBundle oldDefaultKeyBundle = oldKeys.defaultKeyBundle();
-      newDefaultKeyBundle = newKeys.defaultKeyBundle();
-      defaultKeyChanged = !oldDefaultKeyBundle.equals(newDefaultKeyBundle);
-    } catch (NoCollectionKeysSetException e) {
-      Logger.warn(LOG_TAG, "NoCollectionKeysSetException in EnsureCrypto5KeysStage.", e);
-    }
-
-    if (newDefaultKeyBundle == null) {
-      Logger.trace(LOG_TAG, "New default key not provided; returning changed individual keys.");
-      return changedKeys;
-    }
-
-    if (!defaultKeyChanged) {
-      Logger.trace(LOG_TAG, "New default key is the same as old default key; returning changed individual keys.");
-      return changedKeys;
-    }
-
-    // New keys have a different default/sync key; check known collections against the default key.
-    Logger.debug(LOG_TAG, "New default key is not the same as old default key.");
-    for (Stage stage : Stage.getNamedStages()) {
-      String name = stage.getRepositoryName();
-      if (!newKeys.keyBundleForCollectionIsNotDefault(name)) {
-        // Default key has changed, so this collection has changed.
-        changedKeys.add(name);
-      }
-    }
-
-    return changedKeys;
-  }
-
-  @Override
-  public void handleRequestSuccess(SyncStorageResponse response) {
-    // Take the timestamp from the response since it is later than the timestamp from info/collections.
-    long responseTimestamp = response.normalizedWeaveTimestamp();
-    CollectionKeys keys = new CollectionKeys();
-    try {
-      ExtendedJSONObject body = response.jsonObjectBody();
-      if (Logger.LOG_PERSONAL_INFORMATION) {
-        Logger.pii(LOG_TAG, "Fetched keys: " + body.toJSONString());
-      }
-      keys.setKeyPairsFromWBO(CryptoRecord.fromJSONRecord(body), session.config.syncKeyBundle);
-    } catch (Exception e) {
-      session.abort(e, "Invalid keys WBO.");
-      return;
-    }
-
-    PersistedCrypto5Keys pck = session.config.persistedCryptoKeys();
-    if (!pck.persistedKeysExist()) {
-      // New keys, and no old keys! Persist keys and server timestamp.
-      Logger.trace(LOG_TAG, "Setting fetched keys for this session; persisting fetched keys and last modified.");
-      setAndPersist(pck, keys, responseTimestamp);
-      session.advance();
-      return;
-    }
-
-    // New keys, but we had old keys.  Check for differences.
-    CollectionKeys oldKeys = pck.keys();
-    Set<String> changedCollections = collectionsToUpdate(oldKeys, keys);
-    if (!changedCollections.isEmpty()) {
-      // New keys, different from old keys.
-      Logger.trace(LOG_TAG, "Fetched keys are not the same as persisted keys; " +
-          "setting fetched keys for this session before resetting changed engines.");
-      setAndPersist(pck, keys, responseTimestamp);
-      session.resetStagesByName(changedCollections);
-      session.abort(null, "crypto/keys changed on server.");
-      return;
-    }
-
-    // New keys don't differ from old keys; persist timestamp and move on.
-    Logger.trace(LOG_TAG, "Fetched keys are the same as persisted keys; persisting only last modified.");
-    session.config.setCollectionKeys(oldKeys);
-    pck.persistLastModified(response.normalizedWeaveTimestamp());
-    session.advance();
-  }
-
-  @Override
-  public void handleRequestFailure(SyncStorageResponse response) {
-    if (retrying) {
-      // Should happen very rarely -- this means we uploaded our crypto/keys
-      // successfully, but failed to re-download.
-      session.handleHTTPError(response, "Failure while re-downloading already uploaded keys.");
-      return;
-    }
-
-    int statusCode = response.getStatusCode();
-    if (statusCode == 404) {
-      Logger.info(LOG_TAG, "Got 404 fetching keys.  Fresh starting since keys are missing on server.");
-      session.freshStart();
-      return;
-    }
-    session.handleHTTPError(response, "Failure fetching keys: got response status code " + statusCode);
-  }
-
-  @Override
-  public void handleRequestError(Exception ex) {
-    session.abort(ex, "Failure fetching keys.");
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/FennecTabsServerSyncStage.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/FennecTabsServerSyncStage.java
deleted file mode 100644
index 40a474e..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/FennecTabsServerSyncStage.java
+++ /dev/null
@@ -1,40 +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.sync.stage;
-
-import org.mozilla.gecko.sync.repositories.RecordFactory;
-import org.mozilla.gecko.sync.repositories.Repository;
-import org.mozilla.gecko.sync.repositories.android.FennecTabsRepository;
-import org.mozilla.gecko.sync.repositories.domain.TabsRecordFactory;
-import org.mozilla.gecko.sync.repositories.domain.VersionConstants;
-
-public class FennecTabsServerSyncStage extends ServerSyncStage {
-  private static final String COLLECTION = "tabs";
-
-  @Override
-  protected String getCollection() {
-    return COLLECTION;
-  }
-
-  @Override
-  protected String getEngineName() {
-    return COLLECTION;
-  }
-
-  @Override
-  public Integer getStorageVersion() {
-    return VersionConstants.TABS_ENGINE_VERSION;
-  }
-
-  @Override
-  protected Repository getLocalRepository() {
-    return new FennecTabsRepository(session.getClientsDelegate());
-  }
-
-  @Override
-  protected RecordFactory getRecordFactory() {
-    return new TabsRecordFactory();
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/FetchInfoCollectionsStage.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/FetchInfoCollectionsStage.java
deleted file mode 100644
index 088321d..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/FetchInfoCollectionsStage.java
+++ /dev/null
@@ -1,44 +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.sync.stage;
-
-import java.net.URISyntaxException;
-
-import org.mozilla.gecko.sync.ExtendedJSONObject;
-import org.mozilla.gecko.sync.InfoCollections;
-import org.mozilla.gecko.sync.delegates.JSONRecordFetchDelegate;
-import org.mozilla.gecko.sync.net.SyncStorageResponse;
-
-public class FetchInfoCollectionsStage extends AbstractNonRepositorySyncStage {
-  public class StageInfoCollectionsDelegate implements JSONRecordFetchDelegate {
-
-    @Override
-    public void handleSuccess(ExtendedJSONObject global) {
-      session.config.infoCollections = new InfoCollections(global);
-      session.advance();
-    }
-
-    @Override
-    public void handleFailure(SyncStorageResponse response) {
-      session.handleHTTPError(response, "Failure fetching info/collections.");
-    }
-
-    @Override
-    public void handleError(Exception e) {
-      session.abort(e, "Failure fetching info/collections.");
-    }
-
-  }
-
-  @Override
-  public void execute() throws NoSuchStageException {
-    try {
-      session.fetchInfoCollections(new StageInfoCollectionsDelegate());
-    } catch (URISyntaxException e) {
-      session.abort(e, "Invalid URI.");
-    }
-  }
-
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/FetchInfoConfigurationStage.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/FetchInfoConfigurationStage.java
deleted file mode 100644
index 7f53c27..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/FetchInfoConfigurationStage.java
+++ /dev/null
@@ -1,59 +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.sync.stage;
-
-import org.mozilla.gecko.sync.ExtendedJSONObject;
-import org.mozilla.gecko.sync.InfoConfiguration;
-import org.mozilla.gecko.sync.JSONRecordFetcher;
-import org.mozilla.gecko.sync.delegates.JSONRecordFetchDelegate;
-import org.mozilla.gecko.sync.net.AuthHeaderProvider;
-import org.mozilla.gecko.sync.net.SyncStorageResponse;
-
-/**
- * Fetches configuration data from info/configurations endpoint.
- */
-public class FetchInfoConfigurationStage extends AbstractNonRepositorySyncStage {
-    private final String configurationURL;
-    private final AuthHeaderProvider authHeaderProvider;
-
-    public FetchInfoConfigurationStage(final String configurationURL, final AuthHeaderProvider authHeaderProvider) {
-        super();
-        this.configurationURL = configurationURL;
-        this.authHeaderProvider = authHeaderProvider;
-    }
-
-    public class StageInfoConfigurationDelegate implements JSONRecordFetchDelegate {
-        @Override
-        public void handleSuccess(final ExtendedJSONObject result) {
-            session.config.infoConfiguration = new InfoConfiguration(result);
-            session.advance();
-        }
-
-        @Override
-        public void handleFailure(final SyncStorageResponse response) {
-            // Handle all non-404 failures upstream.
-            if (response.getStatusCode() != 404) {
-                session.handleHTTPError(response, "Failure fetching info/configuration");
-                return;
-            }
-
-            // End-point might not be available (404) if server is running an older version.
-            // We will use default config values in this case.
-            session.config.infoConfiguration = new InfoConfiguration();
-            session.advance();
-        }
-
-        @Override
-        public void handleError(final Exception e) {
-            session.abort(e, "Failure fetching info/configuration");
-        }
-    }
-    @Override
-    public void execute() {
-        final StageInfoConfigurationDelegate delegate = new StageInfoConfigurationDelegate();
-        final JSONRecordFetcher fetcher = new JSONRecordFetcher(configurationURL, authHeaderProvider);
-        fetcher.fetch(delegate);
-    }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/FetchMetaGlobalStage.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/FetchMetaGlobalStage.java
deleted file mode 100644
index b4407b2..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/FetchMetaGlobalStage.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.sync.stage;
-
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.sync.GlobalSession;
-import org.mozilla.gecko.sync.InfoCollections;
-import org.mozilla.gecko.sync.MetaGlobal;
-import org.mozilla.gecko.sync.PersistedMetaGlobal;
-import org.mozilla.gecko.sync.delegates.MetaGlobalDelegate;
-import org.mozilla.gecko.sync.net.SyncStorageResponse;
-
-public class FetchMetaGlobalStage extends AbstractNonRepositorySyncStage {
-  private static final String LOG_TAG = "FetchMetaGlobalStage";
-  private static final String META_COLLECTION = "meta";
-
-  public class StageMetaGlobalDelegate implements MetaGlobalDelegate {
-
-    private final GlobalSession session;
-    public StageMetaGlobalDelegate(GlobalSession session) {
-      this.session = session;
-    }
-
-    @Override
-    public void handleSuccess(MetaGlobal global, SyncStorageResponse response) {
-      Logger.trace(LOG_TAG, "Persisting fetched meta/global and last modified.");
-      PersistedMetaGlobal pmg = session.config.persistedMetaGlobal();
-      pmg.persistMetaGlobal(global);
-      // Take the timestamp from the response since it is later than the timestamp from info/collections.
-      pmg.persistLastModified(response.normalizedWeaveTimestamp());
-
-      session.processMetaGlobal(global);
-    }
-
-    @Override
-    public void handleFailure(SyncStorageResponse response) {
-      session.handleHTTPError(response, "Failure fetching meta/global.");
-    }
-
-    @Override
-    public void handleError(Exception e) {
-      session.abort(e, "Failure fetching meta/global.");
-    }
-
-    @Override
-    public void handleMissing(MetaGlobal global, SyncStorageResponse response) {
-      session.processMissingMetaGlobal(global);
-    }
-  }
-
-  @Override
-  public void execute() throws NoSuchStageException {
-    InfoCollections infoCollections = session.config.infoCollections;
-    if (infoCollections == null) {
-      session.abort(null, "No info/collections set in FetchMetaGlobalStage.");
-      return;
-    }
-
-    long lastModified = session.config.persistedMetaGlobal().lastModified();
-    if (!infoCollections.updateNeeded(META_COLLECTION, lastModified)) {
-      // Try to use our local collection keys for this session.
-      Logger.info(LOG_TAG, "Trying to use persisted meta/global for this session.");
-      MetaGlobal global = session.config.persistedMetaGlobal().metaGlobal(session.config.metaURL(), session.getAuthHeaderProvider());
-      if (global != null) {
-        Logger.info(LOG_TAG, "Using persisted meta/global for this session.");
-        session.processMetaGlobal(global); // Calls session.advance().
-        return;
-      }
-      Logger.info(LOG_TAG, "Failed to use persisted meta/global for this session.");
-    }
-
-    // We need an update: fetch or upload meta/global as necessary.
-    Logger.info(LOG_TAG, "Fetching fresh meta/global for this session.");
-    MetaGlobal global = new MetaGlobal(session.config.metaURL(), session.getAuthHeaderProvider());
-    global.fetch(new StageMetaGlobalDelegate(session));
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/FormHistoryServerSyncStage.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/FormHistoryServerSyncStage.java
deleted file mode 100644
index 0a5d974..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/FormHistoryServerSyncStage.java
+++ /dev/null
@@ -1,76 +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.sync.stage;
-
-import java.net.URISyntaxException;
-
-import org.mozilla.gecko.sync.CryptoRecord;
-import org.mozilla.gecko.sync.repositories.ConstrainedServer11Repository;
-import org.mozilla.gecko.sync.repositories.RecordFactory;
-import org.mozilla.gecko.sync.repositories.Repository;
-import org.mozilla.gecko.sync.repositories.android.FormHistoryRepositorySession;
-import org.mozilla.gecko.sync.repositories.domain.FormHistoryRecord;
-import org.mozilla.gecko.sync.repositories.domain.Record;
-import org.mozilla.gecko.sync.repositories.domain.VersionConstants;
-
-public class FormHistoryServerSyncStage extends ServerSyncStage {
-
-  // Eventually this kind of sync stage will be data-driven,
-  // and all this hard-coding can go away.
-  private static final String FORM_HISTORY_SORT          = "index";
-  // Sanity limit. Batch and total limit are the same for now, and will be adjusted
-  // once buffer and high water mark are in place. See Bug 730142.
-  private static final long FORM_HISTORY_BATCH_LIMIT = 5000;
-  private static final long FORM_HISTORY_TOTAL_LIMIT = 5000;
-
-  @Override
-  protected String getCollection() {
-    return "forms";
-  }
-
-  @Override
-  protected String getEngineName() {
-    return "forms";
-  }
-
-  @Override
-  public Integer getStorageVersion() {
-    return VersionConstants.FORMS_ENGINE_VERSION;
-  }
-
-  @Override
-  protected Repository getRemoteRepository() throws URISyntaxException {
-    String collection = getCollection();
-    return new ConstrainedServer11Repository(
-        collection,
-        session.config.storageURL(),
-        session.getAuthHeaderProvider(),
-        session.config.infoCollections,
-        session.config.infoConfiguration,
-        FORM_HISTORY_BATCH_LIMIT,
-        FORM_HISTORY_TOTAL_LIMIT,
-        FORM_HISTORY_SORT);
-  }
-
-  @Override
-  protected Repository getLocalRepository() {
-    return new FormHistoryRepositorySession.FormHistoryRepository();
-  }
-
-  public class FormHistoryRecordFactory extends RecordFactory {
-
-    @Override
-    public Record createRecord(Record record) {
-      FormHistoryRecord r = new FormHistoryRecord();
-      r.initFromEnvelope((CryptoRecord) record);
-      return r;
-    }
-  }
-
-  @Override
-  protected RecordFactory getRecordFactory() {
-    return new FormHistoryRecordFactory();
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/GlobalSyncStage.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/GlobalSyncStage.java
deleted file mode 100644
index 6dee71f..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/GlobalSyncStage.java
+++ /dev/null
@@ -1,93 +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.sync.stage;
-
-import java.util.Collection;
-import java.util.Collections;
-import java.util.EnumSet;
-import java.util.HashMap;
-import java.util.Map;
-
-import org.mozilla.gecko.sync.GlobalSession;
-
-
-public interface GlobalSyncStage {
-  public static enum Stage {
-    idle,                       // Start state.
-    checkPreconditions,         // Preparation of the basics. TODO: clear status
-    fetchInfoCollections,       // Take a look at timestamps.
-    fetchInfoConfiguration,     // Fetch server upload limits
-    fetchMetaGlobal,
-    ensureKeysStage,
-    /*
-    ensureSpecialRecords,
-    updateEngineTimestamps,
-    */
-    syncClientsEngine(SyncClientsEngineStage.STAGE_NAME),
-    /*
-    processFirstSyncPref,
-    processClientCommands,
-    updateEnabledEngines,
-    */
-    syncTabs("tabs"),
-    syncPasswords("passwords"),
-    syncBookmarks("bookmarks"),
-    syncHistory("history"),
-    syncFormHistory("forms"),
-
-    uploadMetaGlobal,
-    completed;
-
-    // Maintain a mapping from names ("bookmarks") to Stage enumerations (syncBookmarks).
-    private static final Map<String, Stage> named = new HashMap<String, Stage>();
-    static {
-      for (Stage s : EnumSet.allOf(Stage.class)) {
-        if (s.getRepositoryName() != null) {
-          named.put(s.getRepositoryName(), s);
-        }
-      }
-    }
-
-    public static Stage byName(final String name) {
-      if (name == null) {
-        return null;
-      }
-      return named.get(name);
-    }
-
-    /**
-     * @return an immutable collection of Stages.
-     */
-    public static Collection<Stage> getNamedStages() {
-      return Collections.unmodifiableCollection(named.values());
-    }
-
-    // Each Stage tracks its repositoryName.
-    private final String repositoryName;
-    public String getRepositoryName() {
-      return repositoryName;
-    }
-
-    private Stage() {
-      this.repositoryName = null;
-    }
-
-    private Stage(final String name) {
-      this.repositoryName = name;
-    }
-  }
-
-  public void execute(GlobalSession session) throws NoSuchStageException;
-  public void resetLocal(GlobalSession session);
-  public void wipeLocal(GlobalSession session) throws Exception;
-
-  /**
-   * What storage version number this engine supports.
-   * <p>
-   * Used to generate a fresh meta/global record for upload.
-   * @return a version number or <code>null</code> to never include this engine in a fresh meta/global record.
-   */
-  public Integer getStorageVersion();
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/NoSuchStageException.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/NoSuchStageException.java
deleted file mode 100644
index 14c9bb4..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/NoSuchStageException.java
+++ /dev/null
@@ -1,13 +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.sync.stage;
-
-public class NoSuchStageException extends Exception {
-  private static final long serialVersionUID = 8338484472880746971L;
-  GlobalSyncStage.Stage stage;
-  public NoSuchStageException(GlobalSyncStage.Stage stage) {
-    this.stage = stage;
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/PasswordsServerSyncStage.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/PasswordsServerSyncStage.java
deleted file mode 100644
index c781ce2..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/PasswordsServerSyncStage.java
+++ /dev/null
@@ -1,38 +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.sync.stage;
-
-import org.mozilla.gecko.sync.repositories.RecordFactory;
-import org.mozilla.gecko.sync.repositories.Repository;
-import org.mozilla.gecko.sync.repositories.android.PasswordsRepositorySession;
-import org.mozilla.gecko.sync.repositories.domain.PasswordRecordFactory;
-import org.mozilla.gecko.sync.repositories.domain.VersionConstants;
-
-public class PasswordsServerSyncStage extends ServerSyncStage {
-  @Override
-  protected String getCollection() {
-    return "passwords";
-  }
-
-  @Override
-  protected String getEngineName() {
-    return "passwords";
-  }
-
-  @Override
-  public Integer getStorageVersion() {
-    return VersionConstants.PASSWORDS_ENGINE_VERSION;
-  }
-
-  @Override
-  protected Repository getLocalRepository() {
-    return new PasswordsRepositorySession.PasswordsRepository();
-  }
-
-  @Override
-  protected RecordFactory getRecordFactory() {
-    return new PasswordRecordFactory();
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/SafeConstrainedServer11Repository.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/SafeConstrainedServer11Repository.java
deleted file mode 100644
index 733c887..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/SafeConstrainedServer11Repository.java
+++ /dev/null
@@ -1,110 +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.sync.stage;
-
-import java.net.URISyntaxException;
-
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.sync.InfoCollections;
-import org.mozilla.gecko.sync.InfoConfiguration;
-import org.mozilla.gecko.sync.InfoCounts;
-import org.mozilla.gecko.sync.JSONRecordFetcher;
-import org.mozilla.gecko.sync.net.AuthHeaderProvider;
-import org.mozilla.gecko.sync.repositories.ConstrainedServer11Repository;
-import org.mozilla.gecko.sync.repositories.Repository;
-import org.mozilla.gecko.sync.repositories.Server11RepositorySession;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionCreationDelegate;
-
-import android.content.Context;
-
-/**
- * This is a constrained repository -- one which fetches a limited number
- * of records -- that additionally refuses to sync if the limit will
- * be exceeded on a first sync by the number of records on the server.
- *
- * You must pass an {@link InfoCounts} instance, which will be interrogated
- * in the event of a first sync.
- *
- * "First sync" means that our sync timestamp is not greater than zero.
- */
-public class SafeConstrainedServer11Repository extends ConstrainedServer11Repository {
-
-  // This can be lazily evaluated if we need it.
-  private final JSONRecordFetcher countFetcher;
-
-  public SafeConstrainedServer11Repository(String collection,
-                                           String storageURL,
-                                           AuthHeaderProvider authHeaderProvider,
-                                           InfoCollections infoCollections,
-                                           InfoConfiguration infoConfiguration,
-                                           long batchLimit,
-                                           long totalLimit,
-                                           String sort,
-                                           JSONRecordFetcher countFetcher)
-    throws URISyntaxException {
-    super(collection, storageURL, authHeaderProvider, infoCollections, infoConfiguration,
-            batchLimit, totalLimit, sort);
-    if (countFetcher == null) {
-      throw new IllegalArgumentException("countFetcher must not be null");
-    }
-    this.countFetcher = countFetcher;
-  }
-
-  @Override
-  public void createSession(RepositorySessionCreationDelegate delegate,
-                            Context context) {
-    delegate.onSessionCreated(new CountCheckingServer11RepositorySession(this, this.getDefaultBatchLimit()));
-  }
-
-  public class CountCheckingServer11RepositorySession extends Server11RepositorySession {
-    private static final String LOG_TAG = "CountCheckingServer11RepositorySession";
-
-    /**
-     * The session will report no data available if this is a first sync
-     * and the server has more data available than this limit.
-     */
-    private final long fetchLimit;
-
-    public CountCheckingServer11RepositorySession(Repository repository, long fetchLimit) {
-      super(repository);
-      this.fetchLimit = fetchLimit;
-    }
-
-    @Override
-    public boolean shouldSkip() {
-      // If this is a first sync, verify that we aren't going to blow through our limit.
-      final long lastSyncTimestamp = getLastSyncTimestamp();
-      if (lastSyncTimestamp > 0) {
-        Logger.info(LOG_TAG, "Collection " + collection + " has already had a first sync: " +
-            "timestamp is " + lastSyncTimestamp  + "; " +
-            "ignoring any updated counts and syncing as usual.");
-      } else {
-        Logger.info(LOG_TAG, "Collection " + collection + " is starting a first sync; checking counts.");
-
-        final InfoCounts counts;
-        try {
-          // This'll probably be the same object, but best to obey the API.
-          counts = new InfoCounts(countFetcher.fetchBlocking());
-        } catch (Exception e) {
-          Logger.warn(LOG_TAG, "Skipping " + collection + " until we can fetch counts.", e);
-          return true;
-        }
-
-        Integer c = counts.getCount(collection);
-        if (c == null) {
-          Logger.info(LOG_TAG, "Fetched counts does not include collection " + collection + "; syncing as usual.");
-          return false;
-        }
-
-        Logger.info(LOG_TAG, "First sync for " + collection + ": " + c + " items.");
-        if (c > fetchLimit) {
-          Logger.warn(LOG_TAG, "Too many items to sync safely. Skipping.");
-          return true;
-        }
-      }
-      return super.shouldSkip();
-    }
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/ServerSyncStage.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/ServerSyncStage.java
deleted file mode 100644
index 733e69d..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/ServerSyncStage.java
+++ /dev/null
@@ -1,627 +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.sync.stage;
-
-import android.content.Context;
-
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.sync.EngineSettings;
-import org.mozilla.gecko.sync.GlobalSession;
-import org.mozilla.gecko.sync.HTTPFailureException;
-import org.mozilla.gecko.sync.MetaGlobalException;
-import org.mozilla.gecko.sync.NoCollectionKeysSetException;
-import org.mozilla.gecko.sync.NonObjectJSONException;
-import org.mozilla.gecko.sync.SynchronizerConfiguration;
-import org.mozilla.gecko.sync.Utils;
-import org.mozilla.gecko.sync.crypto.KeyBundle;
-import org.mozilla.gecko.sync.delegates.WipeServerDelegate;
-import org.mozilla.gecko.sync.middleware.Crypto5MiddlewareRepository;
-import org.mozilla.gecko.sync.net.AuthHeaderProvider;
-import org.mozilla.gecko.sync.net.BaseResource;
-import org.mozilla.gecko.sync.net.SyncStorageRequest;
-import org.mozilla.gecko.sync.net.SyncStorageRequestDelegate;
-import org.mozilla.gecko.sync.net.SyncStorageResponse;
-import org.mozilla.gecko.sync.repositories.InactiveSessionException;
-import org.mozilla.gecko.sync.repositories.InvalidSessionTransitionException;
-import org.mozilla.gecko.sync.repositories.RecordFactory;
-import org.mozilla.gecko.sync.repositories.Repository;
-import org.mozilla.gecko.sync.repositories.RepositorySession;
-import org.mozilla.gecko.sync.repositories.RepositorySessionBundle;
-import org.mozilla.gecko.sync.repositories.Server11Repository;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionBeginDelegate;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionCreationDelegate;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionFinishDelegate;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionWipeDelegate;
-import org.mozilla.gecko.sync.synchronizer.ServerLocalSynchronizer;
-import org.mozilla.gecko.sync.synchronizer.Synchronizer;
-import org.mozilla.gecko.sync.synchronizer.SynchronizerDelegate;
-import org.mozilla.gecko.sync.synchronizer.SynchronizerSession;
-
-import java.io.IOException;
-import java.net.URISyntaxException;
-import java.util.Map;
-import java.util.concurrent.ExecutorService;
-
-/**
- * Fetch from a server collection into a local repository, encrypting
- * and decrypting along the way.
- *
- * @author rnewman
- *
- */
-public abstract class ServerSyncStage extends AbstractSessionManagingSyncStage implements SynchronizerDelegate {
-
-  protected static final String LOG_TAG = "ServerSyncStage";
-
-  protected long stageStartTimestamp = -1;
-  protected long stageCompleteTimestamp = -1;
-
-  /**
-   * Override these in your subclasses.
-   *
-   * @return true if this stage should be executed.
-   * @throws MetaGlobalException
-   */
-  protected boolean isEnabled() throws MetaGlobalException {
-    EngineSettings engineSettings = null;
-    try {
-       engineSettings = getEngineSettings();
-    } catch (Exception e) {
-      Logger.warn(LOG_TAG, "Unable to get engine settings for " + this + ": fetching config failed.", e);
-      // Fall through; null engineSettings will pass below.
-    }
-
-    // We can be disabled by the server's meta/global record, or malformed in the server's meta/global record,
-    // or by the user manually in Sync Settings.
-    // We catch the subclasses of MetaGlobalException to trigger various resets and wipes in execute().
-    boolean enabledInMetaGlobal = session.isEngineRemotelyEnabled(this.getEngineName(), engineSettings);
-
-    // Check for manual changes to engines by the user.
-    checkAndUpdateUserSelectedEngines(enabledInMetaGlobal);
-
-    // Check for changes on the server.
-    if (!enabledInMetaGlobal) {
-      Logger.debug(LOG_TAG, "Stage " + this.getEngineName() + " disabled by server meta/global.");
-      return false;
-    }
-
-    // We can also be disabled just for this sync.
-    boolean enabledThisSync = session.isEngineLocallyEnabled(this.getEngineName()); // For ServerSyncStage, stage name == engine name.
-    if (!enabledThisSync) {
-      Logger.debug(LOG_TAG, "Stage " + this.getEngineName() + " disabled just for this sync.");
-    }
-    return enabledThisSync;
-  }
-
-  /**
-   * Compares meta/global engine state to user selected engines from Sync
-   * Settings and throws an exception if they don't match and meta/global needs
-   * to be updated.
-   *
-   * @param enabledInMetaGlobal
-   *          boolean of engine sync state in meta/global
-   * @throws MetaGlobalException
-   *           if engine sync state has been changed in Sync Settings, with new
-   *           engine sync state.
-   */
-  protected void checkAndUpdateUserSelectedEngines(boolean enabledInMetaGlobal) throws MetaGlobalException {
-    Map<String, Boolean> selectedEngines = session.config.userSelectedEngines;
-    String thisEngine = this.getEngineName();
-
-    if (selectedEngines != null && selectedEngines.containsKey(thisEngine)) {
-      boolean enabledInSelection = selectedEngines.get(thisEngine);
-      if (enabledInMetaGlobal != enabledInSelection) {
-        // Engine enable state has been changed by the user.
-        Logger.debug(LOG_TAG, "Engine state has been changed by user. Throwing exception.");
-        throw new MetaGlobalException.MetaGlobalEngineStateChangedException(enabledInSelection);
-      }
-    }
-  }
-
-  protected EngineSettings getEngineSettings() throws NonObjectJSONException, IOException {
-    Integer version = getStorageVersion();
-    if (version == null) {
-      Logger.warn(LOG_TAG, "null storage version for " + this + "; using version 0.");
-      version = 0;
-    }
-
-    SynchronizerConfiguration config = this.getConfig();
-    if (config == null) {
-      return new EngineSettings(null, version);
-    }
-    return new EngineSettings(config.syncID, version);
-  }
-
-  protected abstract String getCollection();
-  protected abstract String getEngineName();
-  protected abstract Repository getLocalRepository();
-  protected abstract RecordFactory getRecordFactory();
-
-  // Override this in subclasses.
-  protected Repository getRemoteRepository() throws URISyntaxException {
-    String collection = getCollection();
-    return new Server11Repository(collection,
-                                  session.config.storageURL(),
-                                  session.getAuthHeaderProvider(),
-                                  session.config.infoCollections,
-                                  session.config.infoConfiguration);
-  }
-
-  /**
-   * Return a Crypto5Middleware-wrapped Server11Repository.
-   *
-   * @throws NoCollectionKeysSetException
-   * @throws URISyntaxException
-   */
-  protected Repository wrappedServerRepo() throws NoCollectionKeysSetException, URISyntaxException {
-    String collection = this.getCollection();
-    KeyBundle collectionKey = session.keyBundleForCollection(collection);
-    Crypto5MiddlewareRepository cryptoRepo = new Crypto5MiddlewareRepository(getRemoteRepository(), collectionKey);
-    cryptoRepo.recordFactory = getRecordFactory();
-    return cryptoRepo;
-  }
-
-  protected String bundlePrefix() {
-    return this.getCollection() + ".";
-  }
-
-  protected SynchronizerConfiguration getConfig() throws NonObjectJSONException, IOException {
-    return new SynchronizerConfiguration(session.config.getBranch(bundlePrefix()));
-  }
-
-  protected void persistConfig(SynchronizerConfiguration synchronizerConfiguration) {
-    synchronizerConfiguration.persist(session.config.getBranch(bundlePrefix()));
-  }
-
-  public Synchronizer getConfiguredSynchronizer(GlobalSession session) throws NoCollectionKeysSetException, URISyntaxException, NonObjectJSONException, IOException {
-    Repository remote = wrappedServerRepo();
-
-    Synchronizer synchronizer = new ServerLocalSynchronizer();
-    synchronizer.repositoryA = remote;
-    synchronizer.repositoryB = this.getLocalRepository();
-    synchronizer.load(getConfig());
-
-    return synchronizer;
-  }
-
-  /**
-   * Reset timestamps.
-   */
-  @Override
-  protected void resetLocal() {
-    resetLocalWithSyncID(null);
-  }
-
-  /**
-   * Reset timestamps and possibly set syncID.
-   * @param syncID if non-null, new syncID to persist.
-   */
-  protected void resetLocalWithSyncID(String syncID) {
-    // Clear both timestamps.
-    SynchronizerConfiguration config;
-    try {
-      config = this.getConfig();
-    } catch (Exception e) {
-      Logger.warn(LOG_TAG, "Unable to reset " + this + ": fetching config failed.", e);
-      return;
-    }
-
-    if (syncID != null) {
-      config.syncID = syncID;
-      Logger.info(LOG_TAG, "Setting syncID for " + this + " to '" + syncID + "'.");
-    }
-    config.localBundle.setTimestamp(0L);
-    config.remoteBundle.setTimestamp(0L);
-    persistConfig(config);
-    Logger.info(LOG_TAG, "Reset timestamps for " + this);
-  }
-
-  // Not thread-safe. Use with caution.
-  private class WipeWaiter {
-    public boolean sessionSucceeded = true;
-    public boolean wipeSucceeded = true;
-    public Exception error;
-
-    public void notify(Exception e, boolean sessionSucceeded) {
-      this.sessionSucceeded = sessionSucceeded;
-      this.wipeSucceeded = false;
-      this.error = e;
-      this.notify();
-    }
-  }
-
-  /**
-   * Synchronously wipe this stage by instantiating a local repository session
-   * and wiping that.
-   * <p>
-   * Logs and re-throws an exception on failure.
-   */
-  @Override
-  protected void wipeLocal() throws Exception {
-    // Reset, then clear data.
-    this.resetLocal();
-
-    final WipeWaiter monitor = new WipeWaiter();
-    final Context context = session.getContext();
-    final Repository r = this.getLocalRepository();
-
-    final Runnable doWipe = new Runnable() {
-      @Override
-      public void run() {
-        r.createSession(new RepositorySessionCreationDelegate() {
-
-          @Override
-          public void onSessionCreated(final RepositorySession session) {
-            try {
-              session.begin(new RepositorySessionBeginDelegate() {
-
-                @Override
-                public void onBeginSucceeded(final RepositorySession session) {
-                  session.wipe(new RepositorySessionWipeDelegate() {
-                    @Override
-                    public void onWipeSucceeded() {
-                      try {
-                        session.finish(new RepositorySessionFinishDelegate() {
-
-                          @Override
-                          public void onFinishSucceeded(RepositorySession session,
-                                                        RepositorySessionBundle bundle) {
-                            // Hurrah.
-                            synchronized (monitor) {
-                              monitor.notify();
-                            }
-                          }
-
-                          @Override
-                          public void onFinishFailed(Exception ex) {
-                            // Assume that no finish => no wipe.
-                            synchronized (monitor) {
-                              monitor.notify(ex, true);
-                            }
-                          }
-
-                          @Override
-                          public RepositorySessionFinishDelegate deferredFinishDelegate(ExecutorService executor) {
-                            return this;
-                          }
-                        });
-                      } catch (InactiveSessionException e) {
-                        // Cannot happen. Call for safety.
-                        synchronized (monitor) {
-                          monitor.notify(e, true);
-                        }
-                      }
-                    }
-
-                    @Override
-                    public void onWipeFailed(Exception ex) {
-                      session.abort();
-                      synchronized (monitor) {
-                        monitor.notify(ex, true);
-                      }
-                    }
-
-                    @Override
-                    public RepositorySessionWipeDelegate deferredWipeDelegate(ExecutorService executor) {
-                      return this;
-                    }
-                  });
-                }
-
-                @Override
-                public void onBeginFailed(Exception ex) {
-                  session.abort();
-                  synchronized (monitor) {
-                    monitor.notify(ex, true);
-                  }
-                }
-
-                @Override
-                public RepositorySessionBeginDelegate deferredBeginDelegate(ExecutorService executor) {
-                  return this;
-                }
-              });
-            } catch (InvalidSessionTransitionException e) {
-              session.abort();
-              synchronized (monitor) {
-                monitor.notify(e, true);
-              }
-            }
-          }
-
-          @Override
-          public void onSessionCreateFailed(Exception ex) {
-            synchronized (monitor) {
-              monitor.notify(ex, false);
-            }
-          }
-
-          @Override
-          public RepositorySessionCreationDelegate deferredCreationDelegate() {
-            return this;
-          }
-        }, context);
-      }
-    };
-
-    final Thread wiping = new Thread(doWipe);
-    synchronized (monitor) {
-      wiping.start();
-      try {
-        monitor.wait();
-      } catch (InterruptedException e) {
-        Logger.error(LOG_TAG, "Wipe interrupted.");
-      }
-    }
-
-    if (!monitor.sessionSucceeded) {
-      Logger.error(LOG_TAG, "Failed to create session for wipe.");
-      throw monitor.error;
-    }
-
-    if (!monitor.wipeSucceeded) {
-      Logger.error(LOG_TAG, "Failed to wipe session.");
-      throw monitor.error;
-    }
-
-    Logger.info(LOG_TAG, "Wiping stage complete.");
-  }
-
-  /**
-   * Asynchronously wipe collection on server.
-   */
-  protected void wipeServer(final AuthHeaderProvider authHeaderProvider, final WipeServerDelegate wipeDelegate) {
-    SyncStorageRequest request;
-
-    try {
-      request = new SyncStorageRequest(session.config.collectionURI(getCollection()));
-    } catch (URISyntaxException ex) {
-      Logger.warn(LOG_TAG, "Invalid URI in wipeServer.");
-      wipeDelegate.onWipeFailed(ex);
-      return;
-    }
-
-    request.delegate = new SyncStorageRequestDelegate() {
-
-      @Override
-      public String ifUnmodifiedSince() {
-        return null;
-      }
-
-      @Override
-      public void handleRequestSuccess(SyncStorageResponse response) {
-        BaseResource.consumeEntity(response);
-        resetLocal();
-        wipeDelegate.onWiped(response.normalizedWeaveTimestamp());
-      }
-
-      @Override
-      public void handleRequestFailure(SyncStorageResponse response) {
-        Logger.warn(LOG_TAG, "Got request failure " + response.getStatusCode() + " in wipeServer.");
-        // Process HTTP failures here to pick up backoffs, etc.
-        session.interpretHTTPFailure(response.httpResponse());
-        BaseResource.consumeEntity(response); // The exception thrown should not need the body of the response.
-        wipeDelegate.onWipeFailed(new HTTPFailureException(response));
-      }
-
-      @Override
-      public void handleRequestError(Exception ex) {
-        Logger.warn(LOG_TAG, "Got exception in wipeServer.", ex);
-        wipeDelegate.onWipeFailed(ex);
-      }
-
-      @Override
-      public AuthHeaderProvider getAuthHeaderProvider() {
-        return authHeaderProvider;
-      }
-    };
-
-    request.delete();
-  }
-
-  /**
-   * Synchronously wipe the server.
-   * <p>
-   * Logs and re-throws an exception on failure.
-   */
-  public void wipeServer(final GlobalSession session) throws Exception {
-    this.session = session;
-
-    final WipeWaiter monitor = new WipeWaiter();
-
-    final Runnable doWipe = new Runnable() {
-      @Override
-      public void run() {
-        wipeServer(session.getAuthHeaderProvider(), new WipeServerDelegate() {
-          @Override
-          public void onWiped(long timestamp) {
-            synchronized (monitor) {
-              monitor.notify();
-            }
-          }
-
-          @Override
-          public void onWipeFailed(Exception e) {
-            synchronized (monitor) {
-              monitor.notify(e, false);
-            }
-          }
-        });
-      }
-    };
-
-    final Thread wiping = new Thread(doWipe);
-    synchronized (monitor) {
-      wiping.start();
-      try {
-        monitor.wait();
-      } catch (InterruptedException e) {
-        Logger.error(LOG_TAG, "Server wipe interrupted.");
-      }
-    }
-
-    if (!monitor.wipeSucceeded) {
-      Logger.error(LOG_TAG, "Failed to wipe server.");
-      throw monitor.error;
-    }
-
-    Logger.info(LOG_TAG, "Wiping server complete.");
-  }
-
-  @Override
-  public void execute() throws NoSuchStageException {
-    final String name = getEngineName();
-    Logger.debug(LOG_TAG, "Starting execute for " + name);
-
-    stageStartTimestamp = System.currentTimeMillis();
-
-    try {
-      if (!this.isEnabled()) {
-        Logger.info(LOG_TAG, "Skipping stage " + name + ".");
-        session.advance();
-        return;
-      }
-    } catch (MetaGlobalException.MetaGlobalMalformedSyncIDException e) {
-      // Bad engine syncID. This should never happen. Wipe the server.
-      try {
-        session.recordForMetaGlobalUpdate(name, new EngineSettings(Utils.generateGuid(), this.getStorageVersion()));
-        Logger.info(LOG_TAG, "Wiping server because malformed engine sync ID was found in meta/global.");
-        wipeServer(session);
-        Logger.info(LOG_TAG, "Wiped server after malformed engine sync ID found in meta/global.");
-      } catch (Exception ex) {
-        session.abort(ex, "Failed to wipe server after malformed engine sync ID found in meta/global.");
-      }
-    } catch (MetaGlobalException.MetaGlobalMalformedVersionException e) {
-      // Bad engine version. This should never happen. Wipe the server.
-      try {
-        session.recordForMetaGlobalUpdate(name, new EngineSettings(Utils.generateGuid(), this.getStorageVersion()));
-        Logger.info(LOG_TAG, "Wiping server because malformed engine version was found in meta/global.");
-        wipeServer(session);
-        Logger.info(LOG_TAG, "Wiped server after malformed engine version found in meta/global.");
-      } catch (Exception ex) {
-        session.abort(ex, "Failed to wipe server after malformed engine version found in meta/global.");
-      }
-    } catch (MetaGlobalException.MetaGlobalStaleClientSyncIDException e) {
-      // Our syncID is wrong. Reset client and take the server syncID.
-      Logger.warn(LOG_TAG, "Remote engine syncID different from local engine syncID:" +
-                           " resetting local engine and assuming remote engine syncID.");
-      this.resetLocalWithSyncID(e.serverSyncID);
-    } catch (MetaGlobalException.MetaGlobalEngineStateChangedException e) {
-      boolean isEnabled = e.isEnabled;
-      if (!isEnabled) {
-        // Engine has been disabled; update meta/global with engine removal for upload.
-        session.removeEngineFromMetaGlobal(name);
-        session.config.declinedEngineNames.add(name);
-      } else {
-        session.config.declinedEngineNames.remove(name);
-        // Add engine with new syncID to meta/global for upload.
-        String newSyncID = Utils.generateGuid();
-        session.recordForMetaGlobalUpdate(name, new EngineSettings(newSyncID, this.getStorageVersion()));
-        // Update SynchronizerConfiguration w/ new engine syncID.
-        this.resetLocalWithSyncID(newSyncID);
-      }
-      try {
-        // Engine sync status has changed. Wipe server.
-        Logger.warn(LOG_TAG, "Wiping server because engine sync state changed.");
-        wipeServer(session);
-        Logger.warn(LOG_TAG, "Wiped server because engine sync state changed.");
-      } catch (Exception ex) {
-        session.abort(ex, "Failed to wipe server after engine sync state changed");
-      }
-      if (!isEnabled) {
-        Logger.warn(LOG_TAG, "Stage has been disabled. Advancing to next stage.");
-        session.advance();
-        return;
-      }
-    } catch (MetaGlobalException e) {
-      session.abort(e, "Inappropriate meta/global; refusing to execute " + name + " stage.");
-      return;
-    }
-
-    Synchronizer synchronizer;
-    try {
-      synchronizer = this.getConfiguredSynchronizer(session);
-    } catch (NoCollectionKeysSetException e) {
-      session.abort(e, "No CollectionKeys.");
-      return;
-    } catch (URISyntaxException e) {
-      session.abort(e, "Invalid URI syntax for server repository.");
-      return;
-    } catch (NonObjectJSONException | IOException e) {
-      session.abort(e, "Invalid persisted JSON for config.");
-      return;
-    }
-
-    Logger.debug(LOG_TAG, "Invoking synchronizer.");
-    synchronizer.synchronize(session.getContext(), this);
-    Logger.debug(LOG_TAG, "Reached end of execute.");
-  }
-
-  /**
-   * Express the duration taken by this stage as a String, like "0.56 seconds".
-   *
-   * @return formatted string.
-   */
-  protected String getStageDurationString() {
-    return Utils.formatDuration(stageStartTimestamp, stageCompleteTimestamp);
-  }
-
-  /**
-   * We synced this engine!  Persist timestamps and advance the session.
-   *
-   * @param synchronizer the <code>Synchronizer</code> that succeeded.
-   */
-  @Override
-  public void onSynchronized(Synchronizer synchronizer) {
-    stageCompleteTimestamp = System.currentTimeMillis();
-    Logger.debug(LOG_TAG, "onSynchronized.");
-
-    SynchronizerConfiguration newConfig = synchronizer.save();
-    if (newConfig != null) {
-      persistConfig(newConfig);
-    } else {
-      Logger.warn(LOG_TAG, "Didn't get configuration from synchronizer after success.");
-    }
-
-    final SynchronizerSession synchronizerSession = synchronizer.getSynchronizerSession();
-    int inboundCount = synchronizerSession.getInboundCount();
-    int outboundCount = synchronizerSession.getOutboundCount();
-    Logger.info(LOG_TAG, "Stage " + getEngineName() +
-        " received " + inboundCount + " and sent " + outboundCount +
-        " records in " + getStageDurationString() + ".");
-    Logger.info(LOG_TAG, "Advancing session.");
-    session.advance();
-  }
-
-  /**
-   * We failed to sync this engine! Do not persist timestamps (which means that
-   * the next sync will include this sync's data), but do advance the session
-   * (if we didn't get a Retry-After header).
-   *
-   * @param synchronizer the <code>Synchronizer</code> that failed.
-   */
-  @Override
-  public void onSynchronizeFailed(Synchronizer synchronizer,
-                                  Exception lastException, String reason) {
-    stageCompleteTimestamp = System.currentTimeMillis();
-    Logger.warn(LOG_TAG, "Synchronize failed: " + reason, lastException);
-
-    // This failure could be due to a 503 or a 401 and it could have headers.
-    // Interrogate the headers but only abort the global session if Retry-After header is set.
-    if (lastException instanceof HTTPFailureException) {
-      SyncStorageResponse response = ((HTTPFailureException)lastException).response;
-      if (response.retryAfterInSeconds() > 0) {
-        session.handleHTTPError(response, reason); // Calls session.abort().
-        return;
-      } else {
-        session.interpretHTTPFailure(response.httpResponse()); // Does not call session.abort().
-      }
-    }
-
-    Logger.info(LOG_TAG, "Advancing session even though stage failed (took " + getStageDurationString() +
-        "). Timestamps not persisted.");
-    session.advance();
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/SyncClientsEngineStage.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/SyncClientsEngineStage.java
deleted file mode 100644
index 04d3e7c..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/SyncClientsEngineStage.java
+++ /dev/null
@@ -1,691 +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.sync.stage;
-
-import android.accounts.Account;
-import android.content.Context;
-import android.support.annotation.NonNull;
-import android.text.TextUtils;
-import android.util.Log;
-
-import java.io.UnsupportedEncodingException;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicInteger;
-
-import org.json.simple.JSONArray;
-import org.json.simple.JSONObject;
-import org.mozilla.gecko.AppConstants;
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.background.fxa.FxAccountClient;
-import org.mozilla.gecko.background.fxa.FxAccountClient20;
-import org.mozilla.gecko.background.fxa.FxAccountClientException;
-import org.mozilla.gecko.fxa.FirefoxAccounts;
-import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
-import org.mozilla.gecko.sync.CommandProcessor;
-import org.mozilla.gecko.sync.CommandProcessor.Command;
-import org.mozilla.gecko.sync.CryptoRecord;
-import org.mozilla.gecko.sync.ExtendedJSONObject;
-import org.mozilla.gecko.sync.HTTPFailureException;
-import org.mozilla.gecko.sync.NoCollectionKeysSetException;
-import org.mozilla.gecko.sync.Utils;
-import org.mozilla.gecko.sync.crypto.CryptoException;
-import org.mozilla.gecko.sync.crypto.KeyBundle;
-import org.mozilla.gecko.sync.delegates.ClientsDataDelegate;
-import org.mozilla.gecko.sync.net.AuthHeaderProvider;
-import org.mozilla.gecko.sync.net.BaseResource;
-import org.mozilla.gecko.sync.net.SyncStorageCollectionRequest;
-import org.mozilla.gecko.sync.net.SyncStorageRecordRequest;
-import org.mozilla.gecko.sync.net.SyncStorageResponse;
-import org.mozilla.gecko.sync.net.WBOCollectionRequestDelegate;
-import org.mozilla.gecko.sync.net.WBORequestDelegate;
-import org.mozilla.gecko.sync.repositories.NullCursorException;
-import org.mozilla.gecko.sync.repositories.android.ClientsDatabaseAccessor;
-import org.mozilla.gecko.sync.repositories.android.RepoUtils;
-import org.mozilla.gecko.sync.repositories.domain.ClientRecord;
-import org.mozilla.gecko.sync.repositories.domain.ClientRecordFactory;
-import org.mozilla.gecko.sync.repositories.domain.VersionConstants;
-
-import ch.boye.httpclientandroidlib.HttpStatus;
-
-public class SyncClientsEngineStage extends AbstractSessionManagingSyncStage {
-  private static final String LOG_TAG = "SyncClientsEngineStage";
-
-  public static final String COLLECTION_NAME       = "clients";
-  public static final String STAGE_NAME            = COLLECTION_NAME;
-  public static final int CLIENTS_TTL_REFRESH      = 604800000;   // 7 days in milliseconds.
-  public static final int MAX_UPLOAD_FAILURE_COUNT = 5;
-  public static final long NOTIFY_TAB_SENT_TTL_SECS = TimeUnit.SECONDS.convert(1L, TimeUnit.HOURS); // 1 hour
-
-  protected final ClientRecordFactory factory = new ClientRecordFactory();
-  protected ClientUploadDelegate clientUploadDelegate;
-  protected ClientDownloadDelegate clientDownloadDelegate;
-
-  // Be sure to use this safely via getClientsDatabaseAccessor/closeDataAccessor.
-  protected ClientsDatabaseAccessor db;
-
-  protected volatile boolean shouldWipe;
-  protected volatile boolean shouldUploadLocalRecord;     // Set if, e.g., we received commands or need to refresh our version.
-  protected final AtomicInteger uploadAttemptsCount = new AtomicInteger();
-  protected final List<ClientRecord> modifiedClientsToUpload = new ArrayList<ClientRecord>();
-
-  protected int getClientsCount() {
-    return getClientsDatabaseAccessor().clientsCount();
-  }
-
-  protected synchronized ClientsDatabaseAccessor getClientsDatabaseAccessor() {
-    if (db == null) {
-      db = new ClientsDatabaseAccessor(session.getContext());
-    }
-    return db;
-  }
-
-  protected synchronized void closeDataAccessor() {
-    if (db == null) {
-      return;
-    }
-    db.close();
-    db = null;
-  }
-
-  /**
-   * The following two delegates, ClientDownloadDelegate and ClientUploadDelegate
-   * are both triggered in a chain, starting when execute() calls
-   * downloadClientRecords().
-   *
-   * Client records are downloaded using a get() request. Upon success of the
-   * get() request, the local client record is uploaded.
-   *
-   * @author Marina Samuel
-   *
-   */
-  public class ClientDownloadDelegate extends WBOCollectionRequestDelegate {
-
-    // We use this on each WBO, so lift it out.
-    final ClientsDataDelegate clientsDelegate = session.getClientsDelegate();
-    boolean localAccountGUIDDownloaded = false;
-
-    @Override
-    public AuthHeaderProvider getAuthHeaderProvider() {
-      return session.getAuthHeaderProvider();
-    }
-
-    @Override
-    public String ifUnmodifiedSince() {
-      // TODO last client download time?
-      return null;
-    }
-
-    @Override
-    public void handleRequestSuccess(SyncStorageResponse response) {
-
-      // Hang onto the server's last modified timestamp to use
-      // in X-If-Unmodified-Since for upload.
-      session.config.persistServerClientsTimestamp(response.normalizedWeaveTimestamp());
-      BaseResource.consumeEntity(response);
-
-      // Wipe the clients table if it still hasn't been wiped but needs to be.
-      wipeAndStore(null);
-
-      // If we successfully downloaded all records but ours was not one of them
-      // then reset the timestamp.
-      if (!localAccountGUIDDownloaded) {
-        Logger.info(LOG_TAG, "Local client GUID does not exist on the server. Upload timestamp will be reset.");
-        session.config.persistServerClientRecordTimestamp(0);
-      }
-      localAccountGUIDDownloaded = false;
-
-      final int clientsCount;
-      try {
-        clientsCount = getClientsCount();
-      } finally {
-        // Close the database to clear cached readableDatabase/writableDatabase
-        // after we've completed our last transaction (db.store()).
-        closeDataAccessor();
-      }
-
-      Logger.debug(LOG_TAG, "Database contains " + clientsCount + " clients.");
-      Logger.debug(LOG_TAG, "Server response asserts " + response.weaveRecords() + " records.");
-
-      // TODO: persist the response timestamp to know whether to download next time (Bug 726055).
-      clientUploadDelegate = new ClientUploadDelegate();
-      clientsDelegate.setClientsCount(clientsCount);
-
-      // If we upload remote records, checkAndUpload() will be called upon
-      // upload success in the delegate. Otherwise call checkAndUpload() now.
-      if (modifiedClientsToUpload.size() > 0) {
-        // modifiedClientsToUpload is cleared in uploadRemoteRecords, save what we need here
-        final List<String> devicesToNotify = new ArrayList<>();
-        for (ClientRecord record : modifiedClientsToUpload) {
-          if (!TextUtils.isEmpty(record.fxaDeviceId)) {
-            devicesToNotify.add(record.fxaDeviceId);
-          }
-        }
-
-        // This method is synchronous, there's no risk of notifying the clients
-        // before we actually uploaded the records
-        uploadRemoteRecords();
-
-        // Notify the clients who got their record written
-        notifyClients(devicesToNotify);
-
-        return;
-      }
-      checkAndUpload();
-    }
-
-    private void notifyClients(final List<String> devicesToNotify) {
-      final ExecutorService executor = Executors.newSingleThreadExecutor();
-      final Context context = session.getContext();
-      final Account account = FirefoxAccounts.getFirefoxAccount(context);
-      if (account == null) {
-        Log.e(LOG_TAG, "Can't notify other clients: no account");
-        return;
-      }
-      final AndroidFxAccount fxAccount = new AndroidFxAccount(context, account);
-      final ExtendedJSONObject payload = createNotifyDevicesPayload();
-
-      final byte[] sessionToken;
-      try {
-        sessionToken = fxAccount.getSessionToken();
-      } catch (AndroidFxAccount.InvalidFxAState invalidFxAState) {
-        Log.e(LOG_TAG, "Could not get session token", invalidFxAState);
-        return;
-      }
-
-      // API doc : https://github.com/mozilla/fxa-auth-server/blob/master/docs/api.md#post-v1accountdevicesnotify
-      final FxAccountClient fxAccountClient = new FxAccountClient20(fxAccount.getAccountServerURI(), executor);
-      fxAccountClient.notifyDevices(sessionToken, devicesToNotify, payload, NOTIFY_TAB_SENT_TTL_SECS, new FxAccountClient20.RequestDelegate<ExtendedJSONObject>() {
-        @Override
-        public void handleError(Exception e) {
-          Log.e(LOG_TAG, "Error while notifying devices", e);
-        }
-
-        @Override
-        public void handleFailure(FxAccountClientException.FxAccountClientRemoteException e) {
-          Log.e(LOG_TAG, "Error while notifying devices", e);
-        }
-
-        @Override
-        public void handleSuccess(ExtendedJSONObject result) {
-          Log.i(LOG_TAG, devicesToNotify.size() + " devices notified");
-        }
-      });
-    }
-
-    @NonNull
-    @SuppressWarnings("unchecked")
-    private ExtendedJSONObject createNotifyDevicesPayload() {
-      final ExtendedJSONObject payload = new ExtendedJSONObject();
-      payload.put("version", 1);
-      payload.put("command", "sync:collection_changed");
-      final ExtendedJSONObject data = new ExtendedJSONObject();
-      final JSONArray collections = new JSONArray();
-      collections.add("clients");
-      data.put("collections", collections);
-      payload.put("data", data);
-      return payload;
-    }
-
-    @Override
-    public void handleRequestFailure(SyncStorageResponse response) {
-      BaseResource.consumeEntity(response); // We don't need the response at all, and any exception handling shouldn't need the response body.
-      localAccountGUIDDownloaded = false;
-
-      try {
-        Logger.info(LOG_TAG, "Client upload failed. Aborting sync.");
-        session.abort(new HTTPFailureException(response), "Client download failed.");
-      } finally {
-        // Close the database upon failure.
-        closeDataAccessor();
-      }
-    }
-
-    @Override
-    public void handleRequestError(Exception ex) {
-      localAccountGUIDDownloaded = false;
-      try {
-        Logger.info(LOG_TAG, "Client upload error. Aborting sync.");
-        session.abort(ex, "Failure fetching client record.");
-      } finally {
-        // Close the database upon error.
-        closeDataAccessor();
-      }
-    }
-
-    @Override
-    public void handleWBO(CryptoRecord record) {
-      ClientRecord r;
-      try {
-        r = (ClientRecord) factory.createRecord(record.decrypt());
-        if (clientsDelegate.isLocalGUID(r.guid)) {
-          Logger.info(LOG_TAG, "Local client GUID exists on server and was downloaded.");
-          localAccountGUIDDownloaded = true;
-          handleDownloadedLocalRecord(r);
-        } else {
-          // Only need to store record if it isn't our local one.
-          wipeAndStore(r);
-          addCommands(r);
-        }
-        RepoUtils.logClient(r);
-      } catch (Exception e) {
-        session.abort(e, "Exception handling client WBO.");
-        return;
-      }
-    }
-
-    @Override
-    public KeyBundle keyBundle() {
-      try {
-        return session.keyBundleForCollection(COLLECTION_NAME);
-      } catch (NoCollectionKeysSetException e) {
-        return null;
-      }
-    }
-  }
-
-  public class ClientUploadDelegate extends WBORequestDelegate {
-    protected static final String LOG_TAG = "ClientUploadDelegate";
-    public Long currentlyUploadingRecordTimestamp;
-    public boolean currentlyUploadingLocalRecord;
-
-    @Override
-    public AuthHeaderProvider getAuthHeaderProvider() {
-      return session.getAuthHeaderProvider();
-    }
-
-    private void setUploadDetails(boolean isLocalRecord) {
-      // Use the timestamp for the whole collection per Sync storage 1.1 spec.
-      currentlyUploadingRecordTimestamp = session.config.getPersistedServerClientsTimestamp();
-      currentlyUploadingLocalRecord = isLocalRecord;
-    }
-
-    @Override
-    public String ifUnmodifiedSince() {
-      Long timestampInMilliseconds = currentlyUploadingRecordTimestamp;
-
-      // It's the first upload so we don't care about X-If-Unmodified-Since.
-      if (timestampInMilliseconds <= 0) {
-        return null;
-      }
-
-      return Utils.millisecondsToDecimalSecondsString(timestampInMilliseconds);
-    }
-
-    @Override
-    public void handleRequestSuccess(SyncStorageResponse response) {
-      Logger.debug(LOG_TAG, "Upload succeeded.");
-      uploadAttemptsCount.set(0);
-
-      // X-Weave-Timestamp is the modified time of uploaded records.
-      // Always persist this.
-      final long responseTimestamp = response.normalizedWeaveTimestamp();
-      Logger.trace(LOG_TAG, "Timestamp from header is: " + responseTimestamp);
-
-      if (responseTimestamp == -1) {
-        final String message = "Response did not contain a valid timestamp.";
-        session.abort(new RuntimeException(message), message);
-        return;
-      }
-
-      BaseResource.consumeEntity(response);
-      session.config.persistServerClientsTimestamp(responseTimestamp);
-
-      // If we're not uploading our record, we're done here; just
-      // clean up and finish.
-      if (!currentlyUploadingLocalRecord) {
-        // TODO: check failed uploads in body.
-        clearRecordsToUpload();
-        checkAndUpload();
-        return;
-      }
-
-      // If we're processing our record, we have a little more cleanup
-      // to do.
-      shouldUploadLocalRecord = false;
-      session.config.persistServerClientRecordTimestamp(responseTimestamp);
-      session.advance();
-    }
-
-    @Override
-    public void handleRequestFailure(SyncStorageResponse response) {
-      int statusCode = response.getStatusCode();
-
-      // If upload failed because of `ifUnmodifiedSince` then there are new
-      // commands uploaded to our record. We must download and process them first.
-      if (!shouldUploadLocalRecord ||
-          statusCode == HttpStatus.SC_PRECONDITION_FAILED ||
-          uploadAttemptsCount.incrementAndGet() > MAX_UPLOAD_FAILURE_COUNT) {
-
-        Logger.debug(LOG_TAG, "Client upload failed. Aborting sync.");
-        if (!currentlyUploadingLocalRecord) {
-          modifiedClientsToUpload.clear(); // These will be redownloaded.
-        }
-        BaseResource.consumeEntity(response); // The exception thrown should need the response body.
-        session.abort(new HTTPFailureException(response), "Client upload failed.");
-        return;
-      }
-      Logger.trace(LOG_TAG, "Retrying upload…");
-      // Preconditions:
-      // shouldUploadLocalRecord == true &&
-      // statusCode != 412 &&
-      // uploadAttemptCount < MAX_UPLOAD_FAILURE_COUNT
-      checkAndUpload();
-    }
-
-    @Override
-    public void handleRequestError(Exception ex) {
-      Logger.info(LOG_TAG, "Client upload error. Aborting sync.");
-      session.abort(ex, "Client upload failed.");
-    }
-
-    @Override
-    public KeyBundle keyBundle() {
-      try {
-        return session.keyBundleForCollection(COLLECTION_NAME);
-      } catch (NoCollectionKeysSetException e) {
-        return null;
-      }
-    }
-  }
-
-  @Override
-  public void execute() throws NoSuchStageException {
-    // We can be disabled just for this sync.
-    boolean enabledThisSync = session.isEngineLocallyEnabled(STAGE_NAME);
-    if (!enabledThisSync) {
-      // These log messages look best when they match the messages in ServerSyncStage.
-      Logger.debug(LOG_TAG, "Stage " + STAGE_NAME + " disabled just for this sync.");
-      Logger.info(LOG_TAG, "Skipping stage " + STAGE_NAME + ".");
-      session.advance();
-      return;
-    }
-
-    if (shouldDownload()) {
-      downloadClientRecords();   // Will kick off upload, too…
-    } else {
-      // Upload if necessary.
-    }
-  }
-
-  @Override
-  protected void resetLocal() {
-    // Clear timestamps and local data.
-    session.config.persistServerClientRecordTimestamp(0L);   // TODO: roll these into one.
-    session.config.persistServerClientsTimestamp(0L);
-
-    session.getClientsDelegate().setClientsCount(0);
-    try {
-      getClientsDatabaseAccessor().wipeDB();
-    } finally {
-      closeDataAccessor();
-    }
-  }
-
-  @Override
-  protected void wipeLocal() throws Exception {
-    // Nothing more to do.
-    this.resetLocal();
-  }
-
-  @Override
-  public Integer getStorageVersion() {
-    return VersionConstants.CLIENTS_ENGINE_VERSION;
-  }
-
-  protected String getLocalClientVersion() {
-    return AppConstants.MOZ_APP_VERSION;
-  }
-
-  @SuppressWarnings("unchecked")
-  protected JSONArray getLocalClientProtocols() {
-    final JSONArray protocols = new JSONArray();
-    protocols.add(ClientRecord.PROTOCOL_LEGACY_SYNC);
-    protocols.add(ClientRecord.PROTOCOL_FXA_SYNC);
-    return protocols;
-  }
-
-  protected ClientRecord newLocalClientRecord(ClientsDataDelegate delegate) {
-    final String ourGUID = delegate.getAccountGUID();
-    final String ourName = delegate.getClientName();
-
-    ClientRecord r = new ClientRecord(ourGUID);
-    r.name = ourName;
-    r.version = getLocalClientVersion();
-    r.protocols = getLocalClientProtocols();
-
-    r.os = "Android";
-    r.application = AppConstants.MOZ_APP_DISPLAYNAME;
-    r.appPackage = AppConstants.ANDROID_PACKAGE_NAME;
-    r.device = android.os.Build.MODEL;
-    r.formfactor = delegate.getFormFactor();
-
-    Context context = session.getContext();
-    final Account account = FirefoxAccounts.getFirefoxAccount(context);
-    if (account != null) {
-      final AndroidFxAccount fxAccount = new AndroidFxAccount(context, account);
-      final String deviceId = fxAccount.getDeviceId();
-      if (!TextUtils.isEmpty(deviceId)) {
-        r.fxaDeviceId = deviceId;
-      }
-    }
-
-    return r;
-  }
-
-  // TODO: Bug 726055 - More considered handling of when to sync.
-  protected boolean shouldDownload() {
-    // Ask info/collections whether a download is needed.
-    return true;
-  }
-
-  protected boolean shouldUpload() {
-    if (shouldUploadLocalRecord) {
-      return true;
-    }
-
-    long lastUpload = session.config.getPersistedServerClientRecordTimestamp();   // Defaults to 0.
-    if (lastUpload == 0) {
-      return true;
-    }
-
-    if (session.getClientsDelegate().getLastModifiedTimestamp() > lastUpload) {
-      // Something's changed locally since we last uploaded.
-      return true;
-    }
-
-    // Note the opportunity for clock drift problems here.
-    // TODO: if we track download times, we can use the timestamp of most
-    // recent download response instead of the current time.
-    long now = System.currentTimeMillis();
-    long age = now - lastUpload;
-    return age >= CLIENTS_TTL_REFRESH;
-  }
-
-  protected void handleDownloadedLocalRecord(ClientRecord r) {
-    session.config.persistServerClientRecordTimestamp(r.lastModified);
-
-    if (!getLocalClientVersion().equals(r.version) ||
-        !getLocalClientProtocols().equals(r.protocols)) {
-      shouldUploadLocalRecord = true;
-    }
-    processCommands(r.commands);
-  }
-
-  protected void processCommands(JSONArray commands) {
-    if (commands == null ||
-        commands.size() == 0) {
-      return;
-    }
-
-    shouldUploadLocalRecord = true;
-    CommandProcessor processor = CommandProcessor.getProcessor();
-
-    for (Object o : commands) {
-      processor.processCommand(session, new ExtendedJSONObject((JSONObject) o));
-    }
-  }
-
-  @SuppressWarnings("unchecked")
-  protected void addCommands(ClientRecord record) throws NullCursorException {
-    Logger.trace(LOG_TAG, "Adding commands to " + record.guid);
-    List<Command> commands = db.fetchCommandsForClient(record.guid);
-
-    if (commands == null || commands.size() == 0) {
-      Logger.trace(LOG_TAG, "No commands to add.");
-      return;
-    }
-
-    for (Command command : commands) {
-      JSONObject jsonCommand = command.asJSONObject();
-      if (record.commands == null) {
-        record.commands = new JSONArray();
-      }
-      record.commands.add(jsonCommand);
-    }
-    modifiedClientsToUpload.add(record);
-  }
-
-  @SuppressWarnings("unchecked")
-  protected void uploadRemoteRecords() {
-    Logger.trace(LOG_TAG, "In uploadRemoteRecords. Uploading " + modifiedClientsToUpload.size() + " records" );
-
-    for (ClientRecord r : modifiedClientsToUpload) {
-      Logger.trace(LOG_TAG, ">> Uploading record " + r.guid + ": " + r.name);
-    }
-
-    if (modifiedClientsToUpload.size() == 1) {
-      ClientRecord record = modifiedClientsToUpload.get(0);
-      Logger.debug(LOG_TAG, "Only 1 remote record to upload.");
-      Logger.debug(LOG_TAG, "Record last modified: " + record.lastModified);
-      CryptoRecord cryptoRecord = encryptClientRecord(record);
-      if (cryptoRecord != null) {
-        clientUploadDelegate.setUploadDetails(false);
-        this.uploadClientRecord(cryptoRecord);
-      }
-      return;
-    }
-
-    JSONArray cryptoRecords = new JSONArray();
-    for (ClientRecord record : modifiedClientsToUpload) {
-      Logger.trace(LOG_TAG, "Record " + record.guid + " is being uploaded" );
-
-      CryptoRecord cryptoRecord = encryptClientRecord(record);
-      cryptoRecords.add(cryptoRecord.toJSONObject());
-    }
-    Logger.debug(LOG_TAG, "Uploading records: " + cryptoRecords.size());
-    clientUploadDelegate.setUploadDetails(false);
-    this.uploadClientRecords(cryptoRecords);
-  }
-
-  protected void checkAndUpload() {
-    if (!shouldUpload()) {
-      Logger.debug(LOG_TAG, "Not uploading client record.");
-      session.advance();
-      return;
-    }
-
-    final ClientRecord localClient = newLocalClientRecord(session.getClientsDelegate());
-    clientUploadDelegate.setUploadDetails(true);
-    CryptoRecord cryptoRecord = encryptClientRecord(localClient);
-    if (cryptoRecord != null) {
-      this.uploadClientRecord(cryptoRecord);
-    }
-  }
-
-  protected CryptoRecord encryptClientRecord(ClientRecord recordToUpload) {
-    // Generate CryptoRecord from ClientRecord to upload.
-    final String encryptionFailure = "Couldn't encrypt new client record.";
-
-    try {
-      CryptoRecord cryptoRecord = recordToUpload.getEnvelope();
-      cryptoRecord.keyBundle = clientUploadDelegate.keyBundle();
-      if (cryptoRecord.keyBundle == null) {
-        session.abort(new NoCollectionKeysSetException(), "No collection keys set.");
-        return null;
-      }
-      return cryptoRecord.encrypt();
-    } catch (UnsupportedEncodingException e) {
-      session.abort(e, encryptionFailure + " Unsupported encoding.");
-    } catch (CryptoException e) {
-      session.abort(e, encryptionFailure);
-    }
-    return null;
-  }
-
-  public void clearRecordsToUpload() {
-    try {
-      getClientsDatabaseAccessor().wipeCommandsTable();
-      modifiedClientsToUpload.clear();
-    } finally {
-      closeDataAccessor();
-    }
-  }
-
-  protected void downloadClientRecords() {
-    shouldWipe = true;
-    clientDownloadDelegate = makeClientDownloadDelegate();
-
-    try {
-      final URI getURI = session.config.collectionURI(COLLECTION_NAME, true);
-      final SyncStorageCollectionRequest request = new SyncStorageCollectionRequest(getURI);
-      request.delegate = clientDownloadDelegate;
-
-      Logger.trace(LOG_TAG, "Downloading client records.");
-      request.get();
-    } catch (URISyntaxException e) {
-      session.abort(e, "Invalid URI.");
-    }
-  }
-
-  protected void uploadClientRecords(JSONArray records) {
-    Logger.trace(LOG_TAG, "Uploading " + records.size() + " client records.");
-    try {
-      final URI postURI = session.config.collectionURI(COLLECTION_NAME, false);
-      final SyncStorageRecordRequest request = new SyncStorageRecordRequest(postURI);
-      request.delegate = clientUploadDelegate;
-      request.post(records);
-    } catch (URISyntaxException e) {
-      session.abort(e, "Invalid URI.");
-    } catch (Exception e) {
-      session.abort(e, "Unable to parse body.");
-    }
-  }
-
-  /**
-   * Upload a client record via HTTP POST to the parent collection.
-   */
-  protected void uploadClientRecord(CryptoRecord record) {
-    Logger.debug(LOG_TAG, "Uploading client record " + record.guid);
-    try {
-      final URI postURI = session.config.collectionURI(COLLECTION_NAME);
-      final SyncStorageRecordRequest request = new SyncStorageRecordRequest(postURI);
-      request.delegate = clientUploadDelegate;
-      request.post(record);
-    } catch (URISyntaxException e) {
-      session.abort(e, "Invalid URI.");
-    }
-  }
-
-  protected ClientDownloadDelegate makeClientDownloadDelegate() {
-    return new ClientDownloadDelegate();
-  }
-
-  protected void wipeAndStore(ClientRecord record) {
-    final ClientsDatabaseAccessor db = getClientsDatabaseAccessor();
-    if (shouldWipe) {
-      db.wipeClientsTable();
-      shouldWipe = false;
-    }
-    if (record != null) {
-      db.store(record);
-    }
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/UploadMetaGlobalStage.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/UploadMetaGlobalStage.java
deleted file mode 100644
index 77846c2..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/UploadMetaGlobalStage.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.sync.stage;
-
-
-public class UploadMetaGlobalStage extends AbstractNonRepositorySyncStage {
-  public static final String LOG_TAG = "UploadMGStage";
-
-  @Override
-  public void execute() throws NoSuchStageException {
-    if (session.hasUpdatedMetaGlobal()) {
-      session.uploadUpdatedMetaGlobal();
-    }
-    session.advance();
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/synchronizer/ConcurrentRecordConsumer.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/synchronizer/ConcurrentRecordConsumer.java
deleted file mode 100644
index 9b1ef3e..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/synchronizer/ConcurrentRecordConsumer.java
+++ /dev/null
@@ -1,122 +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.sync.synchronizer;
-
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.sync.repositories.domain.Record;
-
-/**
- * Consume records from a queue inside a RecordsChannel, as fast as we can.
- * TODO: rewrite this in terms of an ExecutorService and a CompletionService.
- * See Bug 713483.
- *
- * @author rnewman
- *
- */
-class ConcurrentRecordConsumer extends RecordConsumer {
-  private static final String LOG_TAG = "CRecordConsumer";
-
-  /**
-   * When this is true and all records have been processed, the consumer
-   * will notify its delegate.
-   */
-  protected boolean allRecordsQueued = false;
-  private long counter = 0;
-
-  public ConcurrentRecordConsumer(RecordsConsumerDelegate delegate) {
-    this.delegate = delegate;
-  }
-
-  private final Object monitor = new Object();
-  @Override
-  public void doNotify() {
-    synchronized (monitor) {
-      monitor.notify();
-    }
-  }
-
-  @Override
-  public void queueFilled() {
-    Logger.debug(LOG_TAG, "Queue filled.");
-    synchronized (monitor) {
-      this.allRecordsQueued = true;
-      monitor.notify();
-    }
-  }
-
-  @Override
-  public void halt() {
-    synchronized (monitor) {
-      this.stopImmediately = true;
-      monitor.notify();
-    }
-  }
-
-  private final Object countMonitor = new Object();
-  @Override
-  public void stored() {
-    Logger.trace(LOG_TAG, "Record stored. Notifying.");
-    synchronized (countMonitor) {
-      counter++;
-    }
-  }
-
-  private void consumerIsDone() {
-    Logger.debug(LOG_TAG, "Consumer is done. Processed " + counter + ((counter == 1) ? " record." : " records."));
-    delegate.consumerIsDone(!allRecordsQueued);
-  }
-
-  @Override
-  public void run() {
-    Record record;
-
-    while (true) {
-      // The queue is concurrent-safe.
-      while ((record = delegate.getQueue().poll()) != null) {
-        synchronized (monitor) {
-          Logger.trace(LOG_TAG, "run() took monitor.");
-          if (stopImmediately) {
-            Logger.debug(LOG_TAG, "Stopping immediately. Clearing queue.");
-            delegate.getQueue().clear();
-            Logger.debug(LOG_TAG, "Notifying consumer.");
-            consumerIsDone();
-            return;
-          }
-          Logger.debug(LOG_TAG, "run() dropped monitor.");
-        }
-
-        Logger.trace(LOG_TAG, "Storing record with guid " + record.guid + ".");
-        try {
-          delegate.store(record);
-        } catch (Exception e) {
-          // TODO: Bug 709371: track records that failed to apply.
-          Logger.error(LOG_TAG, "Caught error in store.", e);
-        }
-        Logger.trace(LOG_TAG, "Done with record.");
-      }
-      synchronized (monitor) {
-        Logger.trace(LOG_TAG, "run() took monitor.");
-
-        if (allRecordsQueued) {
-          Logger.debug(LOG_TAG, "Done with records and no more to come. Notifying consumerIsDone.");
-          consumerIsDone();
-          return;
-        }
-        if (stopImmediately) {
-          Logger.debug(LOG_TAG, "Done with records and told to stop immediately. Notifying consumerIsDone.");
-          consumerIsDone();
-          return;
-        }
-        try {
-          Logger.debug(LOG_TAG, "Not told to stop but no records. Waiting.");
-          monitor.wait(10000);
-        } catch (InterruptedException e) {
-          // TODO
-        }
-        Logger.trace(LOG_TAG, "run() dropped monitor.");
-      }
-    }
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/synchronizer/RecordConsumer.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/synchronizer/RecordConsumer.java
deleted file mode 100644
index 35e57d9..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/synchronizer/RecordConsumer.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.sync.synchronizer;
-
-public abstract class RecordConsumer implements Runnable {
-
-  public abstract void stored();
-
-  /**
-   * There are no more store items to arrive at the delegate.
-   * When you're done, take care of finishing up.
-   */
-  public abstract void queueFilled();
-  public abstract void halt();
-
-  public abstract void doNotify();
-
-  protected boolean stopImmediately = false;
-  protected RecordsConsumerDelegate delegate;
-
-  public RecordConsumer() {
-    super();
-  }
-}
\ No newline at end of file
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/synchronizer/RecordsChannel.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/synchronizer/RecordsChannel.java
deleted file mode 100644
index f929cdc..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/synchronizer/RecordsChannel.java
+++ /dev/null
@@ -1,292 +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.sync.synchronizer;
-
-import java.util.concurrent.ConcurrentLinkedQueue;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.atomic.AtomicInteger;
-
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.sync.ThreadPool;
-import org.mozilla.gecko.sync.repositories.InvalidSessionTransitionException;
-import org.mozilla.gecko.sync.repositories.NoStoreDelegateException;
-import org.mozilla.gecko.sync.repositories.RepositorySession;
-import org.mozilla.gecko.sync.repositories.delegates.DeferredRepositorySessionBeginDelegate;
-import org.mozilla.gecko.sync.repositories.delegates.DeferredRepositorySessionStoreDelegate;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionBeginDelegate;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionFetchRecordsDelegate;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionStoreDelegate;
-import org.mozilla.gecko.sync.repositories.domain.Record;
-
-/**
- * Pulls records from `source`, applying them to `sink`.
- * Notifies its delegate of errors and completion.
- *
- * All stores (initiated by a fetch) must have been completed before storeDone
- * is invoked on the sink. This is to avoid the existing stored items being
- * considered as the total set, with onStoreCompleted being called when they're
- * done:
- *
- *   store(A) store(B)
- *   store(C) storeDone()
- *   store(A) finishes. Store job begins.
- *   store(C) finishes. Store job begins.
- *   storeDone() finishes.
- *   Storing of A complete.
- *   Storing of C complete.
- *   We're done! Call onStoreCompleted.
- *   store(B) finishes... uh oh.
- *
- * In other words, storeDone must be gated on the synchronous invocation of every store.
- *
- * Similarly, we require that every store callback have returned before onStoreCompleted is invoked.
- *
- * This whole set of guarantees should be achievable thusly:
- *
- * * The fetch process must run in a single thread, and invoke store()
- *   synchronously. After processing every incoming record, storeDone is called,
- *   setting a flag.
- *   If the fetch cannot be implicitly queued, it must be explicitly queued.
- *   In this implementation, we assume that fetch callbacks are strictly ordered in this way.
- *
- * * The store process must be (implicitly or explicitly) queued. When the
- *   queue empties, the consumer checks the storeDone flag. If it's set, and the
- *   queue is exhausted, invoke onStoreCompleted.
- *
- * RecordsChannel exists to enforce this ordering of operations.
- *
- * @author rnewman
- *
- */
-public class RecordsChannel implements
-  RepositorySessionFetchRecordsDelegate,
-  RepositorySessionStoreDelegate,
-  RecordsConsumerDelegate,
-  RepositorySessionBeginDelegate {
-
-  private static final String LOG_TAG = "RecordsChannel";
-  public RepositorySession source;
-  public RepositorySession sink;
-  private final RecordsChannelDelegate delegate;
-  private long fetchEnd = -1;
-
-  protected final AtomicInteger numFetched = new AtomicInteger();
-  protected final AtomicInteger numFetchFailed = new AtomicInteger();
-  protected final AtomicInteger numStored = new AtomicInteger();
-  protected final AtomicInteger numStoreFailed = new AtomicInteger();
-
-  public RecordsChannel(RepositorySession source, RepositorySession sink, RecordsChannelDelegate delegate) {
-    this.source    = source;
-    this.sink      = sink;
-    this.delegate  = delegate;
-  }
-
-  /*
-   * We push fetched records into a queue.
-   * A separate thread is waiting for us to notify it of work to do.
-   * When we tell it to stop, it'll stop. We do that when the fetch
-   * is completed.
-   * When it stops, we tell the sink that there are no more records,
-   * and wait for the sink to tell us that storing is done.
-   * Then we notify our delegate of completion.
-   */
-  private RecordConsumer consumer;
-  private boolean waitingForQueueDone = false;
-  private final ConcurrentLinkedQueue<Record> toProcess = new ConcurrentLinkedQueue<Record>();
-
-  @Override
-  public ConcurrentLinkedQueue<Record> getQueue() {
-    return toProcess;
-  }
-
-  protected boolean isReady() {
-    return source.isActive() && sink.isActive();
-  }
-
-  /**
-   * Get the number of records fetched so far.
-   *
-   * @return number of fetches.
-   */
-  public int getFetchCount() {
-    return numFetched.get();
-  }
-
-  /**
-   * Get the number of fetch failures recorded so far.
-   *
-   * @return number of fetch failures.
-   */
-  public int getFetchFailureCount() {
-    return numFetchFailed.get();
-  }
-
-  /**
-   * Get the number of store attempts (successful or not) so far.
-   *
-   * @return number of stores attempted.
-   */
-  public int getStoreCount() {
-    return numStored.get();
-  }
-
-  /**
-   * Get the number of store failures recorded so far.
-   *
-   * @return number of store failures.
-   */
-  public int getStoreFailureCount() {
-    return numStoreFailed.get();
-  }
-
-  /**
-   * Start records flowing through the channel.
-   */
-  public void flow() {
-    if (!isReady()) {
-      RepositorySession failed = source;
-      if (source.isActive()) {
-        failed = sink;
-      }
-      this.delegate.onFlowBeginFailed(this, new SessionNotBegunException(failed));
-      return;
-    }
-
-    if (!source.dataAvailable()) {
-      Logger.info(LOG_TAG, "No data available: short-circuiting flow from source " + source);
-      long now = System.currentTimeMillis();
-      this.delegate.onFlowCompleted(this, now, now);
-      return;
-    }
-
-    sink.setStoreDelegate(this);
-    numFetched.set(0);
-    numFetchFailed.set(0);
-    numStored.set(0);
-    numStoreFailed.set(0);
-    // Start a consumer thread.
-    this.consumer = new ConcurrentRecordConsumer(this);
-    ThreadPool.run(this.consumer);
-    waitingForQueueDone = true;
-    source.fetchSince(source.getLastSyncTimestamp(), this);
-  }
-
-  /**
-   * Begin both sessions, invoking flow() when done.
-   * @throws InvalidSessionTransitionException
-   */
-  public void beginAndFlow() throws InvalidSessionTransitionException {
-    Logger.trace(LOG_TAG, "Beginning source.");
-    source.begin(this);
-  }
-
-  @Override
-  public void store(Record record) {
-    numStored.incrementAndGet();
-    try {
-      sink.store(record);
-    } catch (NoStoreDelegateException e) {
-      Logger.error(LOG_TAG, "Got NoStoreDelegateException in RecordsChannel.store(). This should not occur. Aborting.", e);
-      delegate.onFlowStoreFailed(this, e, record.guid);
-    }
-  }
-
-  @Override
-  public void onFetchFailed(Exception ex, Record record) {
-    Logger.warn(LOG_TAG, "onFetchFailed. Calling for immediate stop.", ex);
-    numFetchFailed.incrementAndGet();
-    this.consumer.halt();
-    delegate.onFlowFetchFailed(this, ex);
-  }
-
-  @Override
-  public void onFetchedRecord(Record record) {
-    numFetched.incrementAndGet();
-    this.toProcess.add(record);
-    this.consumer.doNotify();
-  }
-
-  @Override
-  public void onFetchCompleted(final long fetchEnd) {
-    Logger.trace(LOG_TAG, "onFetchCompleted. Stopping consumer once stores are done.");
-    Logger.trace(LOG_TAG, "Fetch timestamp is " + fetchEnd);
-    this.fetchEnd = fetchEnd;
-    this.consumer.queueFilled();
-  }
-
-  @Override
-  public void onRecordStoreFailed(Exception ex, String recordGuid) {
-    Logger.trace(LOG_TAG, "Failed to store record with guid " + recordGuid);
-    numStoreFailed.incrementAndGet();
-    this.consumer.stored();
-    delegate.onFlowStoreFailed(this, ex, recordGuid);
-    // TODO: abort?
-  }
-
-  @Override
-  public void onRecordStoreSucceeded(String guid) {
-    Logger.trace(LOG_TAG, "Stored record with guid " + guid);
-    this.consumer.stored();
-  }
-
-
-  @Override
-  public void consumerIsDone(boolean allRecordsQueued) {
-    Logger.trace(LOG_TAG, "Consumer is done. Are we waiting for it? " + waitingForQueueDone);
-    if (waitingForQueueDone) {
-      waitingForQueueDone = false;
-      this.sink.storeDone();                 // Now we'll be waiting for onStoreCompleted.
-    }
-  }
-
-  @Override
-  public void onStoreCompleted(long storeEnd) {
-    Logger.trace(LOG_TAG, "onStoreCompleted. Notifying delegate of onFlowCompleted. " +
-                          "Fetch end is " + fetchEnd + ", store end is " + storeEnd);
-    // TODO: synchronize on consumer callback?
-    delegate.onFlowCompleted(this, fetchEnd, storeEnd);
-  }
-
-  @Override
-  public void onBeginFailed(Exception ex) {
-    delegate.onFlowBeginFailed(this, ex);
-  }
-
-  @Override
-  public void onBeginSucceeded(RepositorySession session) {
-    if (session == source) {
-      Logger.trace(LOG_TAG, "Source session began. Beginning sink session.");
-      try {
-        sink.begin(this);
-      } catch (InvalidSessionTransitionException e) {
-        onBeginFailed(e);
-        return;
-      }
-    }
-    if (session == sink) {
-      Logger.trace(LOG_TAG, "Sink session began. Beginning flow.");
-      this.flow();
-      return;
-    }
-
-    // TODO: error!
-  }
-
-  @Override
-  public RepositorySessionStoreDelegate deferredStoreDelegate(final ExecutorService executor) {
-    return new DeferredRepositorySessionStoreDelegate(this, executor);
-  }
-
-  @Override
-  public RepositorySessionBeginDelegate deferredBeginDelegate(final ExecutorService executor) {
-    return new DeferredRepositorySessionBeginDelegate(this, executor);
-  }
-
-  @Override
-  public RepositorySessionFetchRecordsDelegate deferredFetchDelegate(ExecutorService executor) {
-    // Lie outright. We know that all of our fetch methods are safe.
-    return this;
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/synchronizer/RecordsChannelDelegate.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/synchronizer/RecordsChannelDelegate.java
deleted file mode 100644
index 8daeb7a..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/synchronizer/RecordsChannelDelegate.java
+++ /dev/null
@@ -1,13 +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.sync.synchronizer;
-
-public interface RecordsChannelDelegate {
-  public void onFlowCompleted(RecordsChannel recordsChannel, long fetchEnd, long storeEnd);
-  public void onFlowBeginFailed(RecordsChannel recordsChannel, Exception ex);
-  public void onFlowFetchFailed(RecordsChannel recordsChannel, Exception ex);
-  public void onFlowStoreFailed(RecordsChannel recordsChannel, Exception ex, String recordGuid);
-  public void onFlowFinishFailed(RecordsChannel recordsChannel, Exception ex);
-}
\ No newline at end of file
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/synchronizer/RecordsConsumerDelegate.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/synchronizer/RecordsConsumerDelegate.java
deleted file mode 100644
index a00abf8..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/synchronizer/RecordsConsumerDelegate.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.sync.synchronizer;
-
-import java.util.concurrent.ConcurrentLinkedQueue;
-
-import org.mozilla.gecko.sync.repositories.domain.Record;
-
-interface RecordsConsumerDelegate {
-  public abstract ConcurrentLinkedQueue<Record> getQueue();
-
-  /**
-   * Called when no more items will be processed.
-   * If forced is true, the consumer is terminating because it was told to halt;
-   * not all items will necessarily have been processed.
-   * If forced is false, the consumer has invoked store and received an onStoreCompleted callback.
-   * @param forced
-   */
-  public abstract void consumerIsDone(boolean forced);
-  public abstract void store(Record record);
-}
\ No newline at end of file
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/synchronizer/SerialRecordConsumer.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/synchronizer/SerialRecordConsumer.java
deleted file mode 100644
index 6ee44ea..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/synchronizer/SerialRecordConsumer.java
+++ /dev/null
@@ -1,131 +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.sync.synchronizer;
-
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.sync.repositories.domain.Record;
-
-/**
- * Consume records from a queue inside a RecordsChannel, storing them serially.
- * @author rnewman
- *
- */
-class SerialRecordConsumer extends RecordConsumer {
-  private static final String LOG_TAG = "SerialRecordConsumer";
-  protected boolean stopEventually = false;
-  private volatile long counter = 0;
-
-  public SerialRecordConsumer(RecordsConsumerDelegate delegate) {
-    this.delegate = delegate;
-  }
-
-  private final Object monitor = new Object();
-  @Override
-  public void doNotify() {
-    synchronized (monitor) {
-      monitor.notify();
-    }
-  }
-
-  @Override
-  public void queueFilled() {
-    Logger.debug(LOG_TAG, "Queue filled.");
-    synchronized (monitor) {
-      this.stopEventually = true;
-      monitor.notify();
-    }
-  }
-
-  @Override
-  public void halt() {
-    Logger.debug(LOG_TAG, "Halting.");
-    synchronized (monitor) {
-      this.stopEventually = true;
-      this.stopImmediately = true;
-      monitor.notify();
-    }
-  }
-
-  private final Object storeSerializer = new Object();
-  @Override
-  public void stored() {
-    Logger.debug(LOG_TAG, "Record stored. Notifying.");
-    synchronized (storeSerializer) {
-      Logger.debug(LOG_TAG, "stored() took storeSerializer.");
-      counter++;
-      storeSerializer.notify();
-      Logger.debug(LOG_TAG, "stored() dropped storeSerializer.");
-    }
-  }
-  private void storeSerially(Record record) {
-    Logger.debug(LOG_TAG, "New record to store.");
-    synchronized (storeSerializer) {
-      Logger.debug(LOG_TAG, "storeSerially() took storeSerializer.");
-      Logger.debug(LOG_TAG, "Storing...");
-      try {
-        this.delegate.store(record);
-      } catch (Exception e) {
-        Logger.warn(LOG_TAG, "Got exception in store. Not waiting.", e);
-        return;      // So we don't block for a stored() that never comes.
-      }
-      try {
-        Logger.debug(LOG_TAG, "Waiting...");
-        storeSerializer.wait();
-      } catch (InterruptedException e) {
-        // TODO
-      }
-      Logger.debug(LOG_TAG, "storeSerially() dropped storeSerializer.");
-    }
-  }
-
-  private void consumerIsDone() {
-    long counterNow = this.counter;
-    Logger.info(LOG_TAG, "Consumer is done. Processed " + counterNow + ((counterNow == 1) ? " record." : " records."));
-    delegate.consumerIsDone(stopImmediately);
-  }
-
-  @Override
-  public void run() {
-    while (true) {
-      synchronized (monitor) {
-        Logger.debug(LOG_TAG, "run() took monitor.");
-        if (stopImmediately) {
-          Logger.debug(LOG_TAG, "Stopping immediately. Clearing queue.");
-          delegate.getQueue().clear();
-          Logger.debug(LOG_TAG, "Notifying consumer.");
-          consumerIsDone();
-          return;
-        }
-        Logger.debug(LOG_TAG, "run() dropped monitor.");
-      }
-      // The queue is concurrent-safe.
-      while (!delegate.getQueue().isEmpty()) {
-        Logger.debug(LOG_TAG, "Grabbing record...");
-        Record record = delegate.getQueue().remove();
-        // Block here, allowing us to process records
-        // serially.
-        Logger.debug(LOG_TAG, "Invoking storeSerially...");
-        this.storeSerially(record);
-        Logger.debug(LOG_TAG, "Done with record.");
-      }
-      synchronized (monitor) {
-        Logger.debug(LOG_TAG, "run() took monitor.");
-
-        if (stopEventually) {
-          Logger.debug(LOG_TAG, "Done with records and told to stop. Notifying consumer.");
-          consumerIsDone();
-          return;
-        }
-        try {
-          Logger.debug(LOG_TAG, "Not told to stop but no records. Waiting.");
-          monitor.wait(10000);
-        } catch (InterruptedException e) {
-          // TODO
-        }
-        Logger.debug(LOG_TAG, "run() dropped monitor.");
-      }
-    }
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/synchronizer/ServerLocalSynchronizer.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/synchronizer/ServerLocalSynchronizer.java
deleted file mode 100644
index ac4f487..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/synchronizer/ServerLocalSynchronizer.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.sync.synchronizer;
-
-/**
- * A <code>SynchronizerSession</code> designed to be used between a remote
- * server and a local repository.
- * <p>
- * See <code>ServerLocalSynchronizerSession</code> for error handling details.
- */
-public class ServerLocalSynchronizer extends Synchronizer {
-  @Override
-  public SynchronizerSession newSynchronizerSession() {
-    return new ServerLocalSynchronizerSession(this, this);
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/synchronizer/ServerLocalSynchronizerSession.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/synchronizer/ServerLocalSynchronizerSession.java
deleted file mode 100644
index dc9eb01..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/synchronizer/ServerLocalSynchronizerSession.java
+++ /dev/null
@@ -1,78 +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.sync.synchronizer;
-
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.sync.repositories.FetchFailedException;
-import org.mozilla.gecko.sync.repositories.StoreFailedException;
-
-/**
- * A <code>SynchronizerSession</code> designed to be used between a remote
- * server and a local repository.
- * <p>
- * Handles failure cases as follows (in the order they will occur during a sync):
- * <ul>
- * <li>Remote fetch failures abort.</li>
- * <li>Local store failures are ignored.</li>
- * <li>Local fetch failures abort.</li>
- * <li>Remote store failures abort.</li>
- * </ul>
- */
-public class ServerLocalSynchronizerSession extends SynchronizerSession {
-  protected static final String LOG_TAG = "ServLocSynchronizerSess";
-
-  public ServerLocalSynchronizerSession(Synchronizer synchronizer, SynchronizerSessionDelegate delegate) {
-    super(synchronizer, delegate);
-  }
-
-  @Override
-  public void onFirstFlowCompleted(RecordsChannel recordsChannel, long fetchEnd, long storeEnd) {
-    // Fetch failures always abort.
-    int numRemoteFetchFailed = recordsChannel.getFetchFailureCount();
-    if (numRemoteFetchFailed > 0) {
-      final String message = "Got " + numRemoteFetchFailed + " failures fetching remote records!";
-      Logger.warn(LOG_TAG, message + " Aborting session.");
-      delegate.onSynchronizeFailed(this, new FetchFailedException(), message);
-      return;
-    }
-    Logger.trace(LOG_TAG, "No failures fetching remote records.");
-
-    // Local store failures are ignored.
-    int numLocalStoreFailed = recordsChannel.getStoreFailureCount();
-    if (numLocalStoreFailed > 0) {
-      final String message = "Got " + numLocalStoreFailed + " failures storing local records!";
-      Logger.warn(LOG_TAG, message + " Ignoring local store failures and continuing synchronizer session.");
-    } else {
-      Logger.trace(LOG_TAG, "No failures storing local records.");
-    }
-
-    super.onFirstFlowCompleted(recordsChannel, fetchEnd, storeEnd);
-  }
-
-  @Override
-  public void onSecondFlowCompleted(RecordsChannel recordsChannel, long fetchEnd, long storeEnd) {
-    // Fetch failures always abort.
-    int numLocalFetchFailed = recordsChannel.getFetchFailureCount();
-    if (numLocalFetchFailed > 0) {
-      final String message = "Got " + numLocalFetchFailed + " failures fetching local records!";
-      Logger.warn(LOG_TAG, message + " Aborting session.");
-      delegate.onSynchronizeFailed(this, new FetchFailedException(), message);
-      return;
-    }
-    Logger.trace(LOG_TAG, "No failures fetching local records.");
-
-    // Remote store failures abort!
-    int numRemoteStoreFailed = recordsChannel.getStoreFailureCount();
-    if (numRemoteStoreFailed > 0) {
-      final String message = "Got " + numRemoteStoreFailed + " failures storing remote records!";
-      Logger.warn(LOG_TAG, message + " Aborting session.");
-      delegate.onSynchronizeFailed(this, new StoreFailedException(), message);
-      return;
-    }
-    Logger.trace(LOG_TAG, "No failures storing remote records.");
-
-    super.onSecondFlowCompleted(recordsChannel, fetchEnd, storeEnd);
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/synchronizer/SessionNotBegunException.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/synchronizer/SessionNotBegunException.java
deleted file mode 100644
index 20c7fcd..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/synchronizer/SessionNotBegunException.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.sync.synchronizer;
-
-import org.mozilla.gecko.sync.SyncException;
-import org.mozilla.gecko.sync.repositories.RepositorySession;
-
-public class SessionNotBegunException extends SyncException {
-  
-  public RepositorySession failed;
-
-  public SessionNotBegunException(RepositorySession failed) {
-    this.failed = failed;
-  }
-
-  private static final long serialVersionUID = -4565241449897072841L;
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/synchronizer/Synchronizer.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/synchronizer/Synchronizer.java
deleted file mode 100644
index cc15b35..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/synchronizer/Synchronizer.java
+++ /dev/null
@@ -1,105 +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.sync.synchronizer;
-
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.sync.SynchronizerConfiguration;
-import org.mozilla.gecko.sync.repositories.Repository;
-import org.mozilla.gecko.sync.repositories.RepositorySessionBundle;
-
-import android.content.Context;
-
-/**
- * I perform a sync.
- *
- * Initialize me by calling `load` with a SynchronizerConfiguration.
- *
- * Start synchronizing by calling `synchronize` with a SynchronizerDelegate. I
- * provide coarse-grained feedback by calling my delegate's callback methods.
- *
- * I always call exactly one of my delegate's `onSynchronized` or
- * `onSynchronizeFailed` callback methods. In addition, I call
- * `onSynchronizeAborted` before `onSynchronizeFailed` when I encounter a fetch,
- * store, or session error while synchronizing.
- *
- * After synchronizing, call `save` to get back a SynchronizerConfiguration with
- * updated bundle information.
- */
-public class Synchronizer implements SynchronizerSessionDelegate {
-  public static final String LOG_TAG = "SyncDelSDelegate";
-
-  protected String configSyncID; // Used to pass syncID from load() back into save().
-
-  protected SynchronizerDelegate synchronizerDelegate;
-
-  protected SynchronizerSession session = null;
-
-  public SynchronizerSession getSynchronizerSession() {
-    return session;
-  }
-
-  @Override
-  public void onInitialized(SynchronizerSession session) {
-    session.synchronize();
-  }
-
-  @Override
-  public void onSynchronized(SynchronizerSession synchronizerSession) {
-    Logger.debug(LOG_TAG, "Got onSynchronized.");
-    Logger.debug(LOG_TAG, "Notifying SynchronizerDelegate.");
-    this.synchronizerDelegate.onSynchronized(synchronizerSession.getSynchronizer());
-  }
-
-  @Override
-  public void onSynchronizeSkipped(SynchronizerSession synchronizerSession) {
-    Logger.debug(LOG_TAG, "Got onSynchronizeSkipped.");
-    Logger.debug(LOG_TAG, "Notifying SynchronizerDelegate as if on success.");
-    this.synchronizerDelegate.onSynchronized(synchronizerSession.getSynchronizer());
-  }
-
-  @Override
-  public void onSynchronizeFailed(SynchronizerSession session,
-      Exception lastException, String reason) {
-    this.synchronizerDelegate.onSynchronizeFailed(session.getSynchronizer(), lastException, reason);
-  }
-
-  public Repository repositoryA;
-  public Repository repositoryB;
-  public RepositorySessionBundle bundleA;
-  public RepositorySessionBundle bundleB;
-
-  /**
-   * Fetch a synchronizer session appropriate for this <code>Synchronizer</code>
-   */
-  protected SynchronizerSession newSynchronizerSession() {
-    return new SynchronizerSession(this, this);
-  }
-
-  /**
-   * Start synchronizing, calling delegate's callback methods.
-   */
-  public void synchronize(Context context, SynchronizerDelegate delegate) {
-    this.synchronizerDelegate = delegate;
-    this.session = newSynchronizerSession();
-    this.session.init(context, bundleA, bundleB);
-  }
-
-  public SynchronizerConfiguration save() {
-    return new SynchronizerConfiguration(configSyncID, bundleA, bundleB);
-  }
-
-  /**
-   * Set my repository session bundles from a SynchronizerConfiguration.
-   *
-   * This method is not thread-safe.
-   *
-   * @param config
-   */
-  public void load(SynchronizerConfiguration config) {
-    bundleA = config.remoteBundle;
-    bundleB = config.localBundle;
-    configSyncID  = config.syncID;
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/synchronizer/SynchronizerDelegate.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/synchronizer/SynchronizerDelegate.java
deleted file mode 100644
index a290188..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/synchronizer/SynchronizerDelegate.java
+++ /dev/null
@@ -1,10 +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.sync.synchronizer;
-
-public interface SynchronizerDelegate {
-  public void onSynchronized(Synchronizer synchronizer);
-  public void onSynchronizeFailed(Synchronizer synchronizer, Exception lastException, String reason);
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/synchronizer/SynchronizerSession.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/synchronizer/SynchronizerSession.java
deleted file mode 100644
index c4d244b..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/synchronizer/SynchronizerSession.java
+++ /dev/null
@@ -1,425 +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.sync.synchronizer;
-
-
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.atomic.AtomicInteger;
-
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.sync.repositories.InactiveSessionException;
-import org.mozilla.gecko.sync.repositories.InvalidSessionTransitionException;
-import org.mozilla.gecko.sync.repositories.RepositorySession;
-import org.mozilla.gecko.sync.repositories.RepositorySessionBundle;
-import org.mozilla.gecko.sync.repositories.delegates.DeferrableRepositorySessionCreationDelegate;
-import org.mozilla.gecko.sync.repositories.delegates.DeferredRepositorySessionFinishDelegate;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionFinishDelegate;
-
-import android.content.Context;
-
-/**
- * I coordinate the moving parts of a sync started by
- * {@link Synchronizer#synchronize}.
- *
- * I flow records twice: first from A to B, and then from B to A. I provide
- * fine-grained feedback by calling my delegate's callback methods.
- *
- * Initialize me by creating me with a Synchronizer and a
- * SynchronizerSessionDelegate. Kick things off by calling `init` with two
- * RepositorySessionBundles, and then call `synchronize` in your `onInitialized`
- * callback.
- *
- * I always call exactly one of my delegate's `onInitialized` or
- * `onSessionError` callback methods from `init`.
- *
- * I call my delegate's `onSynchronizeSkipped` callback method if there is no
- * data to be synchronized in `synchronize`.
- *
- * In addition, I call `onFetchError`, `onStoreError`, and `onSessionError` when
- * I encounter a fetch, store, or session error while synchronizing.
- *
- * Typically my delegate will call `abort` in its error callbacks, which will
- * call my delegate's `onSynchronizeAborted` method and halt the sync.
- *
- * I always call exactly one of my delegate's `onSynchronized` or
- * `onSynchronizeFailed` callback methods if I have not seen an error.
- */
-public class SynchronizerSession
-extends DeferrableRepositorySessionCreationDelegate
-implements RecordsChannelDelegate,
-           RepositorySessionFinishDelegate {
-
-  protected static final String LOG_TAG = "SynchronizerSession";
-  protected Synchronizer synchronizer;
-  protected SynchronizerSessionDelegate delegate;
-  protected Context context;
-
-  /*
-   * Computed during init.
-   */
-  private RepositorySession sessionA;
-  private RepositorySession sessionB;
-  private RepositorySessionBundle bundleA;
-  private RepositorySessionBundle bundleB;
-
-  // Bug 726054: just like desktop, we track our last interaction with the server,
-  // not the last record timestamp that we fetched. This ensures that we don't re-
-  // download the records we just uploaded, at the cost of skipping any records
-  // that a concurrently syncing client has uploaded.
-  private long pendingATimestamp = -1;
-  private long pendingBTimestamp = -1;
-  private long storeEndATimestamp = -1;
-  private long storeEndBTimestamp = -1;
-  private boolean flowAToBCompleted = false;
-  private boolean flowBToACompleted = false;
-
-  protected final AtomicInteger numInboundRecords = new AtomicInteger(-1);
-  protected final AtomicInteger numOutboundRecords = new AtomicInteger(-1);
-
-  /*
-   * Public API: constructor, init, synchronize.
-   */
-  public SynchronizerSession(Synchronizer synchronizer, SynchronizerSessionDelegate delegate) {
-    this.setSynchronizer(synchronizer);
-    this.delegate = delegate;
-  }
-
-  public Synchronizer getSynchronizer() {
-    return synchronizer;
-  }
-
-  public void setSynchronizer(Synchronizer synchronizer) {
-    this.synchronizer = synchronizer;
-  }
-
-  public void init(Context context, RepositorySessionBundle bundleA, RepositorySessionBundle bundleB) {
-    this.context = context;
-    this.bundleA = bundleA;
-    this.bundleB = bundleB;
-    // Begin sessionA and sessionB, call onInitialized in callbacks.
-    this.getSynchronizer().repositoryA.createSession(this, context);
-  }
-
-  /**
-   * Get the number of records fetched from the first repository (usually the
-   * server, hence inbound).
-   * <p>
-   * Valid only after first flow has completed.
-   *
-   * @return number of records, or -1 if not valid.
-   */
-  public int getInboundCount() {
-    return numInboundRecords.get();
-  }
-
-  /**
-   * Get the number of records fetched from the second repository (usually the
-   * local store, hence outbound).
-   * <p>
-   * Valid only after second flow has completed.
-   *
-   * @return number of records, or -1 if not valid.
-   */
-  public int getOutboundCount() {
-    return numOutboundRecords.get();
-  }
-
-  // These are accessed by `abort` and `synchronize`, both of which are synchronized.
-  // Guarded by `this`.
-  protected RecordsChannel channelAToB;
-  protected RecordsChannel channelBToA;
-
-  /**
-   * Please don't call this until you've been notified with onInitialized.
-   */
-  public synchronized void synchronize() {
-    numInboundRecords.set(-1);
-    numOutboundRecords.set(-1);
-
-    // First thing: decide whether we should.
-    if (sessionA.shouldSkip() ||
-        sessionB.shouldSkip()) {
-      Logger.info(LOG_TAG, "Session requested skip. Short-circuiting sync.");
-      sessionA.abort();
-      sessionB.abort();
-      this.delegate.onSynchronizeSkipped(this);
-      return;
-    }
-
-    final SynchronizerSession session = this;
-
-    // TODO: failed record handling.
-
-    // This is the *second* record channel to flow.
-    // I, SynchronizerSession, am the delegate for the *second* flow.
-    channelBToA = new RecordsChannel(this.sessionB, this.sessionA, this);
-
-    // This is the delegate for the *first* flow.
-    RecordsChannelDelegate channelAToBDelegate = new RecordsChannelDelegate() {
-      @Override
-      public void onFlowCompleted(RecordsChannel recordsChannel, long fetchEnd, long storeEnd) {
-        session.onFirstFlowCompleted(recordsChannel, fetchEnd, storeEnd);
-      }
-
-      @Override
-      public void onFlowBeginFailed(RecordsChannel recordsChannel, Exception ex) {
-        Logger.warn(LOG_TAG, "First RecordsChannel onFlowBeginFailed. Logging session error.", ex);
-        session.delegate.onSynchronizeFailed(session, ex, "Failed to begin first flow.");
-      }
-
-      @Override
-      public void onFlowFetchFailed(RecordsChannel recordsChannel, Exception ex) {
-        Logger.warn(LOG_TAG, "First RecordsChannel onFlowFetchFailed. Logging remote fetch error.", ex);
-      }
-
-      @Override
-      public void onFlowStoreFailed(RecordsChannel recordsChannel, Exception ex, String recordGuid) {
-        Logger.warn(LOG_TAG, "First RecordsChannel onFlowStoreFailed. Logging local store error.", ex);
-      }
-
-      @Override
-      public void onFlowFinishFailed(RecordsChannel recordsChannel, Exception ex) {
-        Logger.warn(LOG_TAG, "First RecordsChannel onFlowFinishedFailed. Logging session error.", ex);
-        session.delegate.onSynchronizeFailed(session, ex, "Failed to finish first flow.");
-      }
-    };
-
-    // This is the *first* channel to flow.
-    channelAToB = new RecordsChannel(this.sessionA, this.sessionB, channelAToBDelegate);
-
-    Logger.trace(LOG_TAG, "Starting A to B flow. Channel is " + channelAToB);
-    try {
-      channelAToB.beginAndFlow();
-    } catch (InvalidSessionTransitionException e) {
-      onFlowBeginFailed(channelAToB, e);
-    }
-  }
-
-  /**
-   * Called after the first flow completes.
-   * <p>
-   * By default, any fetch and store failures are ignored.
-   * @param recordsChannel the <code>RecordsChannel</code> (for error testing).
-   * @param fetchEnd timestamp when fetches completed.
-   * @param storeEnd timestamp when stores completed.
-   */
-  public void onFirstFlowCompleted(RecordsChannel recordsChannel, long fetchEnd, long storeEnd) {
-    Logger.trace(LOG_TAG, "First RecordsChannel onFlowCompleted.");
-    Logger.debug(LOG_TAG, "Fetch end is " + fetchEnd + ". Store end is " + storeEnd + ". Starting next.");
-    pendingATimestamp = fetchEnd;
-    storeEndBTimestamp = storeEnd;
-    numInboundRecords.set(recordsChannel.getFetchCount());
-    flowAToBCompleted = true;
-    channelBToA.flow();
-  }
-
-  /**
-   * Called after the second flow completes.
-   * <p>
-   * By default, any fetch and store failures are ignored.
-   * @param recordsChannel the <code>RecordsChannel</code> (for error testing).
-   * @param fetchEnd timestamp when fetches completed.
-   * @param storeEnd timestamp when stores completed.
-   */
-  public void onSecondFlowCompleted(RecordsChannel recordsChannel, long fetchEnd, long storeEnd) {
-    Logger.trace(LOG_TAG, "Second RecordsChannel onFlowCompleted.");
-    Logger.debug(LOG_TAG, "Fetch end is " + fetchEnd + ". Store end is " + storeEnd + ". Finishing.");
-
-    pendingBTimestamp = fetchEnd;
-    storeEndATimestamp = storeEnd;
-    numOutboundRecords.set(recordsChannel.getFetchCount());
-    flowBToACompleted = true;
-
-    // Finish the two sessions.
-    try {
-      this.sessionA.finish(this);
-    } catch (InactiveSessionException e) {
-      this.onFinishFailed(e);
-      return;
-    }
-  }
-
-  @Override
-  public void onFlowCompleted(RecordsChannel recordsChannel, long fetchEnd, long storeEnd) {
-    onSecondFlowCompleted(recordsChannel, fetchEnd, storeEnd);
-  }
-
-  @Override
-  public void onFlowBeginFailed(RecordsChannel recordsChannel, Exception ex) {
-    Logger.warn(LOG_TAG, "Second RecordsChannel onFlowBeginFailed. Logging session error.", ex);
-    this.delegate.onSynchronizeFailed(this, ex, "Failed to begin second flow.");
-  }
-
-  @Override
-  public void onFlowFetchFailed(RecordsChannel recordsChannel, Exception ex) {
-    Logger.warn(LOG_TAG, "Second RecordsChannel onFlowFetchFailed. Logging local fetch error.", ex);
-  }
-
-  @Override
-  public void onFlowStoreFailed(RecordsChannel recordsChannel, Exception ex, String recordGuid) {
-    Logger.warn(LOG_TAG, "Second RecordsChannel onFlowStoreFailed. Logging remote store error.", ex);
-  }
-
-  @Override
-  public void onFlowFinishFailed(RecordsChannel recordsChannel, Exception ex) {
-    Logger.warn(LOG_TAG, "Second RecordsChannel onFlowFinishedFailed. Logging session error.", ex);
-    this.delegate.onSynchronizeFailed(this, ex, "Failed to finish second flow.");
-  }
-
-  /*
-   * RepositorySessionCreationDelegate methods.
-   */
-
-  /**
-   * I could be called twice: once for sessionA and once for sessionB.
-   *
-   * I try to clean up sessionA if it is not null, since the creation of
-   * sessionB must have failed.
-   */
-  @Override
-  public void onSessionCreateFailed(Exception ex) {
-    // Attempt to finish the first session, if the second is the one that failed.
-    if (this.sessionA != null) {
-      try {
-        // We no longer need a reference to our context.
-        this.context = null;
-        this.sessionA.finish(this);
-      } catch (Exception e) {
-        // Never mind; best-effort finish.
-      }
-    }
-    // We no longer need a reference to our context.
-    this.context = null;
-    this.delegate.onSynchronizeFailed(this, ex, "Failed to create session");
-  }
-
-  /**
-   * I should be called twice: first for sessionA and second for sessionB.
-   *
-   * If I am called for sessionB, I call my delegate's `onInitialized` callback
-   * method because my repository sessions are correctly initialized.
-   */
-  // TODO: some of this "finish and clean up" code can be refactored out.
-  @Override
-  public void onSessionCreated(RepositorySession session) {
-    if (session == null ||
-        this.sessionA == session) {
-      // TODO: clean up sessionA.
-      this.delegate.onSynchronizeFailed(this, new UnexpectedSessionException(session), "Failed to create session.");
-      return;
-    }
-    if (this.sessionA == null) {
-      this.sessionA = session;
-
-      // Unbundle.
-      try {
-        this.sessionA.unbundle(this.bundleA);
-      } catch (Exception e) {
-        this.delegate.onSynchronizeFailed(this, new UnbundleError(e, sessionA), "Failed to unbundle first session.");
-        // TODO: abort
-        return;
-      }
-      this.getSynchronizer().repositoryB.createSession(this, this.context);
-      return;
-    }
-    if (this.sessionB == null) {
-      this.sessionB = session;
-      // We no longer need a reference to our context.
-      this.context = null;
-
-      // Unbundle. We unbundled sessionA when that session was created.
-      try {
-        this.sessionB.unbundle(this.bundleB);
-      } catch (Exception e) {
-        this.delegate.onSynchronizeFailed(this, new UnbundleError(e, sessionA), "Failed to unbundle second session.");
-        return;
-      }
-
-      this.delegate.onInitialized(this);
-      return;
-    }
-    // TODO: need a way to make sure we don't call any more delegate methods.
-    this.delegate.onSynchronizeFailed(this, new UnexpectedSessionException(session), "Failed to create session.");
-  }
-
-  /*
-   * RepositorySessionFinishDelegate methods.
-   */
-
-  /**
-   * I could be called twice: once for sessionA and once for sessionB.
-   *
-   * If sessionB couldn't be created, I don't fail again.
-   */
-  @Override
-  public void onFinishFailed(Exception ex) {
-    if (this.sessionB == null) {
-      // Ah, it was a problem cleaning up. Never mind.
-      Logger.warn(LOG_TAG, "Got exception cleaning up first after second session creation failed.", ex);
-      return;
-    }
-    String session = (this.sessionA == null) ? "B" : "A";
-    this.delegate.onSynchronizeFailed(this, ex, "Finish of session " + session + " failed.");
-  }
-
-  /**
-   * I should be called twice: first for sessionA and second for sessionB.
-   *
-   * If I am called for sessionA, I try to finish sessionB.
-   *
-   * If I am called for sessionB, I call my delegate's `onSynchronized` callback
-   * method because my flows should have completed.
-   */
-  @Override
-  public void onFinishSucceeded(RepositorySession session,
-                                RepositorySessionBundle bundle) {
-    Logger.debug(LOG_TAG, "onFinishSucceeded. Flows? " + flowAToBCompleted + ", " + flowBToACompleted);
-
-    if (session == sessionA) {
-      if (flowAToBCompleted) {
-        Logger.debug(LOG_TAG, "onFinishSucceeded: bumping session A's timestamp to " + pendingATimestamp + " or " + storeEndATimestamp);
-        bundle.bumpTimestamp(Math.max(pendingATimestamp, storeEndATimestamp));
-        this.synchronizer.bundleA = bundle;
-      } else {
-        // Should not happen!
-        this.delegate.onSynchronizeFailed(this, new UnexpectedSessionException(sessionA), "Failed to finish first session.");
-        return;
-      }
-      if (this.sessionB != null) {
-        Logger.trace(LOG_TAG, "Finishing session B.");
-        // On to the next.
-        try {
-          this.sessionB.finish(this);
-        } catch (InactiveSessionException e) {
-          this.onFinishFailed(e);
-          return;
-        }
-      }
-    } else if (session == sessionB) {
-      if (flowBToACompleted) {
-        Logger.debug(LOG_TAG, "onFinishSucceeded: bumping session B's timestamp to " + pendingBTimestamp + " or " + storeEndBTimestamp);
-        bundle.bumpTimestamp(Math.max(pendingBTimestamp, storeEndBTimestamp));
-        this.synchronizer.bundleB = bundle;
-        Logger.trace(LOG_TAG, "Notifying delegate.onSynchronized.");
-        this.delegate.onSynchronized(this);
-      } else {
-        // Should not happen!
-        this.delegate.onSynchronizeFailed(this, new UnexpectedSessionException(sessionB), "Failed to finish second session.");
-        return;
-      }
-    } else {
-      // TODO: hurrrrrr...
-    }
-
-    if (this.sessionB == null) {
-      this.sessionA = null; // We're done.
-    }
-  }
-
-  @Override
-  public RepositorySessionFinishDelegate deferredFinishDelegate(final ExecutorService executor) {
-    return new DeferredRepositorySessionFinishDelegate(this, executor);
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/synchronizer/SynchronizerSessionDelegate.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/synchronizer/SynchronizerSessionDelegate.java
deleted file mode 100644
index 1d55274..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/synchronizer/SynchronizerSessionDelegate.java
+++ /dev/null
@@ -1,13 +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.sync.synchronizer;
-
-public interface SynchronizerSessionDelegate {
-  public void onInitialized(SynchronizerSession session);
-
-  public void onSynchronized(SynchronizerSession session);
-  public void onSynchronizeFailed(SynchronizerSession session, Exception lastException, String reason);
-  public void onSynchronizeSkipped(SynchronizerSession synchronizerSession);
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/synchronizer/UnbundleError.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/synchronizer/UnbundleError.java
deleted file mode 100644
index fea7796..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/synchronizer/UnbundleError.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.sync.synchronizer;
-
-import org.mozilla.gecko.sync.SyncException;
-import org.mozilla.gecko.sync.repositories.RepositorySession;
-
-public class UnbundleError extends SyncException {
-  private static final long serialVersionUID = -8709503281041697522L;
-
-  public RepositorySession failedSession;
-
-  public UnbundleError(Exception e, RepositorySession session) {
-    super(e);
-    this.failedSession = session;
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/synchronizer/UnexpectedSessionException.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/synchronizer/UnexpectedSessionException.java
deleted file mode 100644
index 0237b88..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/synchronizer/UnexpectedSessionException.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.sync.synchronizer;
-
-import org.mozilla.gecko.sync.SyncException;
-import org.mozilla.gecko.sync.repositories.RepositorySession;
-
-/**
- * An exception class that indicates that a session was passed
- * to a begin callback and wasn't expected.
- *
- * This shouldn't occur.
- *
- * @author rnewman
- *
- */
-public class UnexpectedSessionException extends SyncException {
-  private static final long serialVersionUID = 949010933527484721L;
-  public RepositorySession session;
-
-  public UnexpectedSessionException(RepositorySession session) {
-    this.session = session;
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/telemetry/TelemetryContract.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/telemetry/TelemetryContract.java
deleted file mode 100644
index e3e134f..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/telemetry/TelemetryContract.java
+++ /dev/null
@@ -1,56 +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.sync.telemetry;
-
-public class TelemetryContract {
-  /**
-   * We are a Sync 1.1 (legacy) client, and we downloaded a migration sentinel.
-   */
-  public static final String SYNC11_MIGRATION_SENTINELS_SEEN = "FENNEC_SYNC11_MIGRATION_SENTINELS_SEEN";
-
-  /**
-   * We are a Sync 1.1 (legacy) client and we have downloaded a migration
-   * sentinel, but there was an error creating a Firefox Account from that
-   * sentinel.
-   * <p>
-   * We have logged the error and are ignoring that sentinel.
-   */
-  public static final String SYNC11_MIGRATIONS_FAILED = "FENNEC_SYNC11_MIGRATIONS_FAILED";
-
-  /**
-   * We are a Sync 1.1 (legacy) client and we have downloaded a migration
-   * sentinel, and there was no reported error creating a Firefox Account from
-   * that sentinel.
-   * <p>
-   * We have created a Firefox Account corresponding to the sentinel and have
-   * queued the existing Old Sync account for removal.
-   */
-  public static final String SYNC11_MIGRATIONS_SUCCEEDED = "FENNEC_SYNC11_MIGRATIONS_SUCCEEDED";
-
-  /**
-   * We are (now) a Sync 1.5 (Firefox Accounts-based) client that migrated from
-   * Sync 1.1. We have presented the user the "complete upgrade" notification.
-   * <p>
-   * We will offer every time a sync is triggered, including when a notification
-   * is already pending.
-   */
-  public static final String SYNC11_MIGRATION_NOTIFICATIONS_OFFERED = "FENNEC_SYNC11_MIGRATION_NOTIFICATIONS_OFFERED";
-
-  /**
-   * We are (now) a Sync 1.5 (Firefox Accounts-based) client that migrated from
-   * Sync 1.1. We have presented the user the "complete upgrade" notification
-   * and they have successfully completed the upgrade process by entering their
-   * Firefox Account credentials.
-   */
-  public static final String SYNC11_MIGRATIONS_COMPLETED = "FENNEC_SYNC11_MIGRATIONS_COMPLETED";
-
-  public static final String SYNC_STARTED = "FENNEC_SYNC_NUMBER_OF_SYNCS_STARTED";
-
-  public static final String SYNC_COMPLETED = "FENNEC_SYNC_NUMBER_OF_SYNCS_COMPLETED";
-
-  public static final String SYNC_FAILED = "FENNEC_SYNC_NUMBER_OF_SYNCS_FAILED";
-
-  public static final String SYNC_FAILED_BACKOFF = "FENNEC_SYNC_NUMBER_OF_SYNCS_FAILED_BACKOFF";
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/tokenserver/TokenServerClient.java b/mobile/android/services/src/main/java/org/mozilla/gecko/tokenserver/TokenServerClient.java
deleted file mode 100644
index 9ee014d..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/tokenserver/TokenServerClient.java
+++ /dev/null
@@ -1,330 +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.tokenserver;
-
-import java.io.IOException;
-import java.net.URI;
-import java.security.GeneralSecurityException;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.Executor;
-
-import org.json.simple.JSONObject;
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.background.fxa.SkewHandler;
-import org.mozilla.gecko.sync.ExtendedJSONObject;
-import org.mozilla.gecko.sync.NonArrayJSONException;
-import org.mozilla.gecko.sync.NonObjectJSONException;
-import org.mozilla.gecko.sync.UnexpectedJSONException.BadRequiredFieldJSONException;
-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.BrowserIDAuthHeaderProvider;
-import org.mozilla.gecko.sync.net.SyncResponse;
-import org.mozilla.gecko.tokenserver.TokenServerException.TokenServerConditionsRequiredException;
-import org.mozilla.gecko.tokenserver.TokenServerException.TokenServerInvalidCredentialsException;
-import org.mozilla.gecko.tokenserver.TokenServerException.TokenServerMalformedRequestException;
-import org.mozilla.gecko.tokenserver.TokenServerException.TokenServerMalformedResponseException;
-import org.mozilla.gecko.tokenserver.TokenServerException.TokenServerUnknownServiceException;
-
-import ch.boye.httpclientandroidlib.Header;
-import ch.boye.httpclientandroidlib.HttpHeaders;
-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;
-import ch.boye.httpclientandroidlib.message.BasicHeader;
-
-/**
- * HTTP client for interacting with the Mozilla Services Token Server API v1.0,
- * as documented at
- * <a href="http://docs.services.mozilla.com/token/apis.html">http://docs.services.mozilla.com/token/apis.html</a>.
- * <p>
- * A token server accepts some authorization credential and returns a different
- * authorization credential. Usually, it used to exchange a public-key
- * authorization token that is expensive to validate for a symmetric-key
- * authorization that is cheap to validate. For example, we might exchange a
- * BrowserID assertion for a HAWK id and key pair.
- */
-public class TokenServerClient {
-  protected static final String LOG_TAG = "TokenServerClient";
-
-  public static final String JSON_KEY_API_ENDPOINT = "api_endpoint";
-  public static final String JSON_KEY_CONDITION_URLS = "condition_urls";
-  public static final String JSON_KEY_DURATION = "duration";
-  public static final String JSON_KEY_ERRORS = "errors";
-  public static final String JSON_KEY_ID = "id";
-  public static final String JSON_KEY_KEY = "key";
-  public static final String JSON_KEY_UID = "uid";
-
-  public static final String HEADER_CONDITIONS_ACCEPTED = "X-Conditions-Accepted";
-  public static final String HEADER_CLIENT_STATE = "X-Client-State";
-
-  protected final Executor executor;
-  protected final URI uri;
-
-  public TokenServerClient(URI uri, Executor executor) {
-    if (uri == null) {
-      throw new IllegalArgumentException("uri must not be null");
-    }
-    if (executor == null) {
-      throw new IllegalArgumentException("executor must not be null");
-    }
-    this.uri = uri;
-    this.executor = executor;
-  }
-
-  protected void invokeHandleSuccess(final TokenServerClientDelegate delegate, final TokenServerToken token) {
-    executor.execute(new Runnable() {
-      @Override
-      public void run() {
-        delegate.handleSuccess(token);
-      }
-    });
-  }
-
-  protected void invokeHandleFailure(final TokenServerClientDelegate delegate, final TokenServerException e) {
-    executor.execute(new Runnable() {
-      @Override
-      public void run() {
-        delegate.handleFailure(e);
-      }
-    });
-  }
-
-  /**
-   * Notify the delegate that some kind of backoff header (X-Backoff,
-   * X-Weave-Backoff, Retry-After) was received and should be acted upon.
-   *
-   * This method is non-terminal, and will be followed by a separate
-   * <code>invoke*</code> call.
-   *
-   * @param delegate
-   *          the delegate to inform.
-   * @param backoffSeconds
-   *          the number of seconds for which the system should wait before
-   *          making another token server request to this server.
-   */
-  protected void notifyBackoff(final TokenServerClientDelegate delegate, final int backoffSeconds) {
-    executor.execute(new Runnable() {
-      @Override
-      public void run() {
-        delegate.handleBackoff(backoffSeconds);
-      }
-    });
-  }
-
-  protected void invokeHandleError(final TokenServerClientDelegate delegate, final Exception e) {
-    executor.execute(new Runnable() {
-      @Override
-      public void run() {
-        delegate.handleError(e);
-      }
-    });
-  }
-
-  public TokenServerToken processResponse(SyncResponse res) throws TokenServerException {
-    int statusCode = res.getStatusCode();
-
-    Logger.debug(LOG_TAG, "Got token response with status code " + statusCode + ".");
-
-    // Responses should *always* be JSON, even in the case of 4xx and 5xx
-    // errors. If we don't see JSON, the server is likely very unhappy.
-    final Header contentType = res.getContentType();
-    if (contentType == null) {
-      throw new TokenServerMalformedResponseException(null, "Non-JSON response Content-Type.");
-    }
-
-    final String type = contentType.getValue();
-    if (!type.equals("application/json") &&
-        !type.startsWith("application/json;")) {
-      Logger.warn(LOG_TAG, "Got non-JSON response with Content-Type " +
-          contentType + ". Misconfigured server?");
-      throw new TokenServerMalformedResponseException(null, "Non-JSON response Content-Type.");
-    }
-
-    // Responses should *always* be a valid JSON object.
-    // It turns out that right now they're not always, but that's a server bug...
-    ExtendedJSONObject result;
-    try {
-      result = res.jsonObjectBody();
-    } catch (Exception e) {
-      Logger.debug(LOG_TAG, "Malformed token response.", e);
-      throw new TokenServerMalformedResponseException(null, e);
-    }
-
-    // The service shouldn't have any 3xx, so we don't need to handle those.
-    if (res.getStatusCode() != 200) {
-      // We should have a (Cornice) error report in the JSON. We log that to
-      // help with debugging.
-      List<ExtendedJSONObject> errorList = new ArrayList<ExtendedJSONObject>();
-
-      if (result.containsKey(JSON_KEY_ERRORS)) {
-        try {
-          for (Object error : result.getArray(JSON_KEY_ERRORS)) {
-            Logger.warn(LOG_TAG, "" + error);
-
-            if (error instanceof JSONObject) {
-              errorList.add(new ExtendedJSONObject((JSONObject) error));
-            }
-          }
-        } catch (NonArrayJSONException e) {
-          Logger.warn(LOG_TAG, "Got non-JSON array '" + JSON_KEY_ERRORS + "'.", e);
-        }
-      }
-
-      if (statusCode == 400) {
-        throw new TokenServerMalformedRequestException(errorList, result.toJSONString());
-      }
-
-      if (statusCode == 401) {
-        throw new TokenServerInvalidCredentialsException(errorList, result.toJSONString());
-      }
-
-      // 403 should represent a "condition acceptance needed" response.
-      //
-      // The extra validation of "urls" is important. We don't want to signal
-      // conditions required unless we are absolutely sure that is what the
-      // server is asking for.
-      if (statusCode == 403) {
-        // Bug 792674 and Bug 783598: make this testing simpler. For now, we
-        // check that errors is an array, and take any condition_urls from the
-        // first element.
-
-        try {
-          if (errorList == null || errorList.isEmpty()) {
-            throw new TokenServerMalformedResponseException(errorList, "403 response without proper fields.");
-          }
-
-          ExtendedJSONObject error = errorList.get(0);
-
-          ExtendedJSONObject condition_urls = error.getObject(JSON_KEY_CONDITION_URLS);
-          if (condition_urls != null) {
-            throw new TokenServerConditionsRequiredException(condition_urls);
-          }
-        } catch (NonObjectJSONException e) {
-          Logger.warn(LOG_TAG, "Got non-JSON error object.");
-        }
-
-        throw new TokenServerMalformedResponseException(errorList, "403 response without proper fields.");
-      }
-
-      if (statusCode == 404) {
-        throw new TokenServerUnknownServiceException(errorList);
-      }
-
-      // We shouldn't ever get here...
-      throw new TokenServerException(errorList);
-    }
-
-    try {
-      result.throwIfFieldsMissingOrMisTyped(new String[] { JSON_KEY_ID, JSON_KEY_KEY, JSON_KEY_API_ENDPOINT }, String.class);
-      result.throwIfFieldsMissingOrMisTyped(new String[] { JSON_KEY_UID }, Long.class);
-    } catch (BadRequiredFieldJSONException e ) {
-      throw new TokenServerMalformedResponseException(null, e);
-    }
-
-    Logger.debug(LOG_TAG, "Successful token response: " + result.getString(JSON_KEY_ID));
-
-    return new TokenServerToken(result.getString(JSON_KEY_ID),
-        result.getString(JSON_KEY_KEY),
-        result.get(JSON_KEY_UID).toString(),
-        result.getString(JSON_KEY_API_ENDPOINT));
-  }
-
-  public static class TokenFetchResourceDelegate extends BaseResourceDelegate {
-    private final TokenServerClient         client;
-    private final TokenServerClientDelegate delegate;
-    private final String                    assertion;
-    private final String                    clientState;
-    private final BaseResource              resource;
-    private final boolean                   conditionsAccepted;
-
-    public TokenFetchResourceDelegate(TokenServerClient client,
-                                      BaseResource resource,
-                                      TokenServerClientDelegate delegate,
-                                      String assertion, String clientState,
-                                      boolean conditionsAccepted) {
-      super(resource);
-      this.client = client;
-      this.delegate = delegate;
-      this.assertion = assertion;
-      this.clientState = clientState;
-      this.resource = resource;
-      this.conditionsAccepted = conditionsAccepted;
-    }
-
-    @Override
-    public String getUserAgent() {
-      return delegate.getUserAgent();
-    }
-
-    @Override
-    public void handleHttpResponse(HttpResponse response) {
-      // Skew.
-      SkewHandler skewHandler = SkewHandler.getSkewHandlerForResource(resource);
-      skewHandler.updateSkew(response, System.currentTimeMillis());
-
-      // Extract backoff regardless of whether this was an error response, and
-      // Retry-After for 503 responses. The error will be handled elsewhere.)
-      SyncResponse res = new SyncResponse(response);
-      final boolean includeRetryAfter = res.getStatusCode() == 503;
-      int backoffInSeconds = res.totalBackoffInSeconds(includeRetryAfter);
-      if (backoffInSeconds > -1) {
-        client.notifyBackoff(delegate, backoffInSeconds);
-      }
-
-      try {
-        TokenServerToken token = client.processResponse(res);
-        client.invokeHandleSuccess(delegate, token);
-      } catch (TokenServerException e) {
-        client.invokeHandleFailure(delegate, e);
-      }
-    }
-
-    @Override
-    public void handleTransportException(GeneralSecurityException e) {
-      client.invokeHandleError(delegate, e);
-    }
-
-    @Override
-    public void handleHttpProtocolException(ClientProtocolException e) {
-      client.invokeHandleError(delegate, e);
-    }
-
-    @Override
-    public void handleHttpIOException(IOException e) {
-      client.invokeHandleError(delegate, e);
-    }
-
-    @Override
-    public AuthHeaderProvider getAuthHeaderProvider() {
-      return new BrowserIDAuthHeaderProvider(assertion);
-    }
-
-    @Override
-    public void addHeaders(HttpRequestBase request, DefaultHttpClient client) {
-      String host = request.getURI().getHost();
-      request.setHeader(new BasicHeader(HttpHeaders.HOST, host));
-      if (clientState != null) {
-        request.setHeader(new BasicHeader(HEADER_CLIENT_STATE, clientState));
-      }
-      if (conditionsAccepted) {
-        request.addHeader(HEADER_CONDITIONS_ACCEPTED, "1");
-      }
-    }
-  }
-
-  public void getTokenFromBrowserIDAssertion(final String assertion,
-                                             final boolean conditionsAccepted,
-                                             final String clientState,
-                                             final TokenServerClientDelegate delegate) {
-    final BaseResource resource = new BaseResource(this.uri);
-    resource.delegate = new TokenFetchResourceDelegate(this, resource, delegate,
-                                                       assertion, clientState,
-                                                       conditionsAccepted);
-    resource.get();
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/tokenserver/TokenServerClientDelegate.java b/mobile/android/services/src/main/java/org/mozilla/gecko/tokenserver/TokenServerClientDelegate.java
deleted file mode 100644
index e1dfe24..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/tokenserver/TokenServerClientDelegate.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.tokenserver;
-
-
-public interface TokenServerClientDelegate {
-  void handleSuccess(TokenServerToken token);
-  void handleFailure(TokenServerException e);
-  void handleError(Exception e);
-
-  /**
-   * Might be called multiple times, in addition to the other terminating handler methods.
-   */
-  void handleBackoff(int backoffSeconds);
-
-  public String getUserAgent();
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/tokenserver/TokenServerException.java b/mobile/android/services/src/main/java/org/mozilla/gecko/tokenserver/TokenServerException.java
deleted file mode 100644
index 099e518..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/tokenserver/TokenServerException.java
+++ /dev/null
@@ -1,89 +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.tokenserver;
-
-import java.util.List;
-
-import org.mozilla.gecko.sync.ExtendedJSONObject;
-
-public class TokenServerException extends Exception {
-  private static final long serialVersionUID = 7185692034925819696L;
-
-  public final List<ExtendedJSONObject> errors;
-
-  public TokenServerException(List<ExtendedJSONObject> errors) {
-    super();
-    this.errors = errors;
-  }
-
-  public TokenServerException(List<ExtendedJSONObject> errors, String string) {
-    super(string);
-    this.errors = errors;
-  }
-
-  public TokenServerException(List<ExtendedJSONObject> errors, Throwable e) {
-    super(e);
-    this.errors = errors;
-  }
-
-  public static class TokenServerConditionsRequiredException extends TokenServerException {
-    private static final long serialVersionUID = 7578072663150608399L;
-
-    public final ExtendedJSONObject conditionUrls;
-
-    public TokenServerConditionsRequiredException(ExtendedJSONObject urls) {
-      super(null);
-      this.conditionUrls = urls;
-    }
-  }
-
-  public static class TokenServerInvalidCredentialsException extends TokenServerException {
-    private static final long serialVersionUID = 7578072663150608398L;
-
-    public TokenServerInvalidCredentialsException(List<ExtendedJSONObject> errors) {
-      super(errors);
-    }
-
-    public TokenServerInvalidCredentialsException(List<ExtendedJSONObject> errors, String message) {
-      super(errors, message);
-    }
-  }
-
-  public static class TokenServerUnknownServiceException extends TokenServerException {
-    private static final long serialVersionUID = 7578072663150608397L;
-
-    public TokenServerUnknownServiceException(List<ExtendedJSONObject> errors) {
-      super(errors);
-    }
-
-    public TokenServerUnknownServiceException(List<ExtendedJSONObject> errors, String message) {
-      super(errors, message);
-    }
-  }
-
-  public static class TokenServerMalformedRequestException extends TokenServerException {
-    private static final long serialVersionUID = 7578072663150608396L;
-
-    public TokenServerMalformedRequestException(List<ExtendedJSONObject> errors) {
-      super(errors);
-    }
-
-    public TokenServerMalformedRequestException(List<ExtendedJSONObject> errors, String message) {
-      super(errors, message);
-    }
-  }
-
-  public static class TokenServerMalformedResponseException extends TokenServerException {
-    private static final long serialVersionUID = 7578072663150608395L;
-
-    public TokenServerMalformedResponseException(List<ExtendedJSONObject> errors, String message) {
-      super(errors, message);
-    }
-
-    public TokenServerMalformedResponseException(List<ExtendedJSONObject> errors, Throwable e) {
-      super(errors, e);
-    }
-  }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/tokenserver/TokenServerToken.java b/mobile/android/services/src/main/java/org/mozilla/gecko/tokenserver/TokenServerToken.java
deleted file mode 100644
index 916586c..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/tokenserver/TokenServerToken.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.tokenserver;
-
-public class TokenServerToken {
-  public final String id;
-  public final String key;
-  public final String uid;
-  public final String endpoint;
-
-  public TokenServerToken(String id, String key, String uid, String endpoint) {
-    this.id = id;
-    this.key = key;
-    this.uid = uid;
-    this.endpoint = endpoint;
-  }
-}
\ No newline at end of file
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/util/PRNGFixes.java b/mobile/android/services/src/main/java/org/mozilla/gecko/util/PRNGFixes.java
deleted file mode 100644
index ebb50f7..0000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/util/PRNGFixes.java
+++ /dev/null
@@ -1,339 +0,0 @@
-/*
- * This software is provided 'as-is', without any express or implied
- * warranty.  In no event will Google be held liable for any damages
- * arising from the use of this software.
- *
- * Permission is granted to anyone to use this software for any purpose,
- * including commercial applications, and to alter it and redistribute it
- * freely, as long as the origin is not misrepresented.
- */
-
-package org.mozilla.gecko.util;
-
-import android.os.Build;
-import android.os.Process;
-import android.util.Log;
-
-import java.io.ByteArrayOutputStream;
-import java.io.DataInputStream;
-import java.io.DataOutputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.io.UnsupportedEncodingException;
-import java.security.NoSuchAlgorithmException;
-import java.security.Provider;
-import java.security.SecureRandom;
-import java.security.SecureRandomSpi;
-import java.security.Security;
-
-/**
- * Fixes for the output of the default PRNG having low entropy.
- *
- * The fixes need to be applied via {@link #apply()} before any use of Java
- * Cryptography Architecture primitives. A good place to invoke them is in the
- * application's {@code onCreate}.
- */
-public final class PRNGFixes {
-    private static final long serialVersionUID = -687331492884005033L;
-
-    private static final int VERSION_CODE_JELLY_BEAN = 16;
-    private static final int VERSION_CODE_JELLY_BEAN_MR2 = 18;
-    private static final byte[] BUILD_FINGERPRINT_AND_DEVICE_SERIAL =
-        getBuildFingerprintAndDeviceSerial();
-
-    /** Hidden constructor to prevent instantiation. */
-    private PRNGFixes() {}
-
-    /**
-     * Applies all fixes.
-     *
-     * @throws SecurityException if a fix is needed but could not be applied.
-     */
-    public static void apply() {
-        applyOpenSSLFix();
-        installLinuxPRNGSecureRandom();
-    }
-
-    /**
-     * Applies the fix for OpenSSL PRNG having low entropy. Does nothing if the
-     * fix is not needed.
-     *
-     * @throws SecurityException if the fix is needed but could not be applied.
-     */
-    private static void applyOpenSSLFix() throws SecurityException {
-        if ((Build.VERSION.SDK_INT < VERSION_CODE_JELLY_BEAN)
-                || (Build.VERSION.SDK_INT > VERSION_CODE_JELLY_BEAN_MR2)) {
-            // No need to apply the fix
-            return;
-        }
-
-        try {
-            // Mix in the device- and invocation-specific seed.
-            Class.forName("org.apache.harmony.xnet.provider.jsse.NativeCrypto")
-                    .getMethod("RAND_seed", byte[].class)
-                    .invoke(null, generateSeed());
-
-            // Mix output of Linux PRNG into OpenSSL's PRNG
-            int bytesRead = (Integer) Class.forName(
-                    "org.apache.harmony.xnet.provider.jsse.NativeCrypto")
-                    .getMethod("RAND_load_file", String.class, long.class)
-                    .invoke(null, "/dev/urandom", 1024);
-            if (bytesRead != 1024) {
-                throw new IOException(
-                        "Unexpected number of bytes read from Linux PRNG: "
-                                + bytesRead);
-            }
-        } catch (Exception e) {
-            throw new SecurityException("Failed to seed OpenSSL PRNG", e);
-        }
-    }
-
-    /**
-     * Installs a Linux PRNG-backed {@code SecureRandom} implementation as the
-     * default. Does nothing if the implementation is already the default or if
-     * there is not need to install the implementation.
-     *
-     * @throws SecurityException if the fix is needed but could not be applied.
-     */
-    private static void installLinuxPRNGSecureRandom()
-            throws SecurityException {
-        if (Build.VERSION.SDK_INT > VERSION_CODE_JELLY_BEAN_MR2) {
-            // No need to apply the fix
-            return;
-        }
-
-        // Install a Linux PRNG-based SecureRandom implementation as the
-        // default, if not yet installed.
-        Provider[] secureRandomProviders =
-                Security.getProviders("SecureRandom.SHA1PRNG");
-        if ((secureRandomProviders == null)
-                || (secureRandomProviders.length < 1)
-                || (!LinuxPRNGSecureRandomProvider.class.equals(
-                        secureRandomProviders[0].getClass()))) {
-            Security.insertProviderAt(new LinuxPRNGSecureRandomProvider(), 1);
-        }
-
-        // Assert that new SecureRandom() and
-        // SecureRandom.getInstance("SHA1PRNG") return a SecureRandom backed
-        // by the Linux PRNG-based SecureRandom implementation.
-        SecureRandom rng1 = new SecureRandom();
-        if (!LinuxPRNGSecureRandomProvider.class.equals(
-                rng1.getProvider().getClass())) {
-            throw new SecurityException(
-                    "new SecureRandom() backed by wrong Provider: "
-                            + rng1.getProvider().getClass());
-        }
-
-        SecureRandom rng2;
-        try {
-            rng2 = SecureRandom.getInstance("SHA1PRNG");
-        } catch (NoSuchAlgorithmException e) {
-            throw new SecurityException("SHA1PRNG not available", e);
-        }
-        if (!LinuxPRNGSecureRandomProvider.class.equals(
-                rng2.getProvider().getClass())) {
-            throw new SecurityException(
-                    "SecureRandom.getInstance(\"SHA1PRNG\") backed by wrong"
-                    + " Provider: " + rng2.getProvider().getClass());
-        }
-    }
-
-    /**
-     * {@code Provider} of {@code SecureRandom} engines which pass through
-     * all requests to the Linux PRNG.
-     */
-    private static class LinuxPRNGSecureRandomProvider extends Provider {
-        private static final long serialVersionUID = -686731492884005033L;
-
-        public LinuxPRNGSecureRandomProvider() {
-            super("LinuxPRNG",
-                    1.0,
-                    "A Linux-specific random number provider that uses"
-                        + " /dev/urandom");
-            // Although /dev/urandom is not a SHA-1 PRNG, some apps
-            // explicitly request a SHA1PRNG SecureRandom and we thus need to
-            // prevent them from getting the default implementation whose output
-            // may have low entropy.
-            put("SecureRandom.SHA1PRNG", LinuxPRNGSecureRandom.class.getName());
-            put("SecureRandom.SHA1PRNG ImplementedIn", "Software");
-        }
-    }
-
-    /**
-     * {@link SecureRandomSpi} which passes all requests to the Linux PRNG
-     * ({@code /dev/urandom}).
-     */
-    public static class LinuxPRNGSecureRandom extends SecureRandomSpi {
-        private static final long serialVersionUID = -696231492884005033L;
-
-        /*
-         * IMPLEMENTATION NOTE: Requests to generate bytes and to mix in a seed
-         * are passed through to the Linux PRNG (/dev/urandom). Instances of
-         * this class seed themselves by mixing in the current time, PID, UID,
-         * build fingerprint, and hardware serial number (where available) into
-         * Linux PRNG.
-         *
-         * Concurrency: Read requests to the underlying Linux PRNG are
-         * serialized (on sLock) to ensure that multiple threads do not get
-         * duplicated PRNG output.
-         */
-
-        private static final File URANDOM_FILE = new File("/dev/urandom");
-
-        private static final Object sLock = new Object();
-
-        /**
-         * Input stream for reading from Linux PRNG or {@code null} if not yet
-         * opened.
-         *
-         * @GuardedBy("sLock")
-         */
-        private static DataInputStream sUrandomIn;
-
-        /**
-         * Output stream for writing to Linux PRNG or {@code null} if not yet
-         * opened.
-         *
-         * @GuardedBy("sLock")
-         */
-        private static OutputStream sUrandomOut;
-
-        /**
-         * Whether this engine instance has been seeded. This is needed because
-         * each instance needs to seed itself if the client does not explicitly
-         * seed it.
-         */
-        private boolean mSeeded;
-
-        @Override
-        protected void engineSetSeed(byte[] bytes) {
-            try {
-                OutputStream out;
-                synchronized (sLock) {
-                    out = getUrandomOutputStream();
-                }
-                out.write(bytes);
-                out.flush();
-            } catch (IOException e) {
-                // On a small fraction of devices /dev/urandom is not writable.
-                // Log and ignore.
-                Log.w(PRNGFixes.class.getSimpleName(),
-                        "Failed to mix seed into " + URANDOM_FILE);
-            } finally {
-                mSeeded = true;
-            }
-        }
-
-        @Override
-        protected void engineNextBytes(byte[] bytes) {
-            if (!mSeeded) {
-                // Mix in the device- and invocation-specific seed.
-                engineSetSeed(generateSeed());
-            }
-
-            try {
-                DataInputStream in;
-                synchronized (sLock) {
-                    in = getUrandomInputStream();
-                }
-                synchronized (in) {
-                    in.readFully(bytes);
-                }
-            } catch (IOException e) {
-                throw new SecurityException(
-                        "Failed to read from " + URANDOM_FILE, e);
-            }
-        }
-
-        @Override
-        protected byte[] engineGenerateSeed(int size) {
-            byte[] seed = new byte[size];
-            engineNextBytes(seed);
-            return seed;
-        }
-
-        private DataInputStream getUrandomInputStream() {
-            synchronized (sLock) {
-                if (sUrandomIn == null) {
-                    // NOTE: Consider inserting a BufferedInputStream between
-                    // DataInputStream and FileInputStream if you need higher
-                    // PRNG output performance and can live with future PRNG
-                    // output being pulled into this process prematurely.
-                    try {
-                        sUrandomIn = new DataInputStream(
-                                new FileInputStream(URANDOM_FILE));
-                    } catch (IOException e) {
-                        throw new SecurityException("Failed to open "
-                                + URANDOM_FILE + " for reading", e);
-                    }
-                }
-                return sUrandomIn;
-            }
-        }
-
-        private OutputStream getUrandomOutputStream() throws IOException {
-            synchronized (sLock) {
-                if (sUrandomOut == null) {
-                    sUrandomOut = new FileOutputStream(URANDOM_FILE);
-                }
-                return sUrandomOut;
-            }
-        }
-    }
-
-    /**
-     * Generates a device- and invocation-specific seed to be mixed into the
-     * Linux PRNG.
-     */
-    private static byte[] generateSeed() {
-        try {
-            ByteArrayOutputStream seedBuffer = new ByteArrayOutputStream();
-            DataOutputStream seedBufferOut =
-                    new DataOutputStream(seedBuffer);
-            seedBufferOut.writeLong(System.currentTimeMillis());
-            seedBufferOut.writeLong(System.nanoTime());
-            seedBufferOut.writeInt(Process.myPid());
-            seedBufferOut.writeInt(Process.myUid());
-            seedBufferOut.write(BUILD_FINGERPRINT_AND_DEVICE_SERIAL);
-            seedBufferOut.close();
-            return seedBuffer.toByteArray();
-        } catch (IOException e) {
-            throw new SecurityException("Failed to generate seed", e);
-        }
-    }
-
-    /**
-     * Gets the hardware serial number of this device.
-     *
-     * @return serial number or {@code null} if not available.
-     */
-    private static String getDeviceSerialNumber() {
-        // We're using the Reflection API because Build.SERIAL is only available
-        // since API Level 9 (Gingerbread, Android 2.3).
-        try {
-            return (String) Build.class.getField("SERIAL").get(null);
-        } catch (Exception ignored) {
-            return null;
-        }
-    }
-
-    private static byte[] getBuildFingerprintAndDeviceSerial() {
-        StringBuilder result = new StringBuilder();
-        String fingerprint = Build.FINGERPRINT;
-        if (fingerprint != null) {
-            result.append(fingerprint);
-        }
-        String serial = getDeviceSerialNumber();
-        if (serial != null) {
-            result.append(serial);
-        }
-        try {
-            return result.toString().getBytes("UTF-8");
-        } catch (UnsupportedEncodingException e) {
-            throw new RuntimeException("UTF-8 encoding not supported");
-        }
-    }
-}
diff --git a/mobile/android/services/src/main/res/drawable-hdpi/fxaccount_sync_error.png b/mobile/android/services/src/main/res/drawable-hdpi/fxaccount_sync_error.png
deleted file mode 100644
index 3a2cbc4..0000000
Binary files a/mobile/android/services/src/main/res/drawable-hdpi/fxaccount_sync_error.png and /dev/null differ
diff --git a/mobile/android/services/src/main/res/drawable-hdpi/sync_avatar_default.png b/mobile/android/services/src/main/res/drawable-hdpi/sync_avatar_default.png
deleted file mode 100644
index caa6ed2..0000000
Binary files a/mobile/android/services/src/main/res/drawable-hdpi/sync_avatar_default.png and /dev/null differ
diff --git a/mobile/android/services/src/main/res/drawable-hdpi/sync_desktop.png b/mobile/android/services/src/main/res/drawable-hdpi/sync_desktop.png
deleted file mode 100644
index abf87f1..0000000
Binary files a/mobile/android/services/src/main/res/drawable-hdpi/sync_desktop.png and /dev/null differ
diff --git a/mobile/android/services/src/main/res/drawable-hdpi/sync_desktop_inactive.png b/mobile/android/services/src/main/res/drawable-hdpi/sync_desktop_inactive.png
deleted file mode 100644
index 869dbf4..0000000
Binary files a/mobile/android/services/src/main/res/drawable-hdpi/sync_desktop_inactive.png and /dev/null differ
diff --git a/mobile/android/services/src/main/res/drawable-hdpi/sync_mobile.png b/mobile/android/services/src/main/res/drawable-hdpi/sync_mobile.png
deleted file mode 100644
index 4b25152..0000000
Binary files a/mobile/android/services/src/main/res/drawable-hdpi/sync_mobile.png and /dev/null differ
diff --git a/mobile/android/services/src/main/res/drawable-hdpi/sync_mobile_inactive.png b/mobile/android/services/src/main/res/drawable-hdpi/sync_mobile_inactive.png
deleted file mode 100644
index e940179..0000000
Binary files a/mobile/android/services/src/main/res/drawable-hdpi/sync_mobile_inactive.png and /dev/null differ
diff --git a/mobile/android/services/src/main/res/drawable-hdpi/sync_promo.png b/mobile/android/services/src/main/res/drawable-hdpi/sync_promo.png
deleted file mode 100644
index ea21505..0000000
Binary files a/mobile/android/services/src/main/res/drawable-hdpi/sync_promo.png and /dev/null differ
diff --git a/mobile/android/services/src/main/res/drawable-xhdpi/fxaccount_sync_error.png b/mobile/android/services/src/main/res/drawable-xhdpi/fxaccount_sync_error.png
deleted file mode 100644
index f9bf849..0000000
Binary files a/mobile/android/services/src/main/res/drawable-xhdpi/fxaccount_sync_error.png and /dev/null differ
diff --git a/mobile/android/services/src/main/res/drawable-xhdpi/sync_desktop.png b/mobile/android/services/src/main/res/drawable-xhdpi/sync_desktop.png
deleted file mode 100644
index 30d5b5c..0000000
Binary files a/mobile/android/services/src/main/res/drawable-xhdpi/sync_desktop.png and /dev/null differ
diff --git a/mobile/android/services/src/main/res/drawable-xhdpi/sync_desktop_inactive.png b/mobile/android/services/src/main/res/drawable-xhdpi/sync_desktop_inactive.png
deleted file mode 100644
index 1b5b00a..0000000
Binary files a/mobile/android/services/src/main/res/drawable-xhdpi/sync_desktop_inactive.png and /dev/null differ
diff --git a/mobile/android/services/src/main/res/drawable-xhdpi/sync_mobile.png b/mobile/android/services/src/main/res/drawable-xhdpi/sync_mobile.png
deleted file mode 100644
index 2c3f45d..0000000
Binary files a/mobile/android/services/src/main/res/drawable-xhdpi/sync_mobile.png and /dev/null differ
diff --git a/mobile/android/services/src/main/res/drawable-xhdpi/sync_mobile_inactive.png b/mobile/android/services/src/main/res/drawable-xhdpi/sync_mobile_inactive.png
deleted file mode 100644
index 60fd77c..0000000
Binary files a/mobile/android/services/src/main/res/drawable-xhdpi/sync_mobile_inactive.png and /dev/null differ
diff --git a/mobile/android/services/src/main/res/drawable-xhdpi/sync_promo.png b/mobile/android/services/src/main/res/drawable-xhdpi/sync_promo.png
deleted file mode 100644
index 63f1a55..0000000
Binary files a/mobile/android/services/src/main/res/drawable-xhdpi/sync_promo.png and /dev/null differ
diff --git a/mobile/android/services/src/main/res/drawable-xxhdpi/fxaccount_sync_error.png b/mobile/android/services/src/main/res/drawable-xxhdpi/fxaccount_sync_error.png
deleted file mode 100644
index 7555bc9..0000000
Binary files a/mobile/android/services/src/main/res/drawable-xxhdpi/fxaccount_sync_error.png and /dev/null differ
diff --git a/mobile/android/services/src/main/res/drawable-xxhdpi/sync_avatar_default.png b/mobile/android/services/src/main/res/drawable-xxhdpi/sync_avatar_default.png
deleted file mode 100644
index 16d1278..0000000
Binary files a/mobile/android/services/src/main/res/drawable-xxhdpi/sync_avatar_default.png and /dev/null differ
diff --git a/mobile/android/services/src/main/res/drawable-xxhdpi/sync_desktop.png b/mobile/android/services/src/main/res/drawable-xxhdpi/sync_desktop.png
deleted file mode 100644
index 9bb9a55..0000000
Binary files a/mobile/android/services/src/main/res/drawable-xxhdpi/sync_desktop.png and /dev/null differ
diff --git a/mobile/android/services/src/main/res/drawable-xxhdpi/sync_desktop_inactive.png b/mobile/android/services/src/main/res/drawable-xxhdpi/sync_desktop_inactive.png
deleted file mode 100644
index c3fe0ec..0000000
Binary files a/mobile/android/services/src/main/res/drawable-xxhdpi/sync_desktop_inactive.png and /dev/null differ
diff --git a/mobile/android/services/src/main/res/drawable-xxhdpi/sync_mobile.png b/mobile/android/services/src/main/res/drawable-xxhdpi/sync_mobile.png
deleted file mode 100644
index 400ddf6..0000000
Binary files a/mobile/android/services/src/main/res/drawable-xxhdpi/sync_mobile.png and /dev/null differ
diff --git a/mobile/android/services/src/main/res/drawable-xxhdpi/sync_mobile_inactive.png b/mobile/android/services/src/main/res/drawable-xxhdpi/sync_mobile_inactive.png
deleted file mode 100644
index a688b0d..0000000
Binary files a/mobile/android/services/src/main/res/drawable-xxhdpi/sync_mobile_inactive.png and /dev/null differ
diff --git a/mobile/android/services/src/main/res/layout/fxaccount_preference_list_fragment.xml b/mobile/android/services/src/main/res/layout/fxaccount_preference_list_fragment.xml
deleted file mode 100644
index acaafc7..0000000
--- a/mobile/android/services/src/main/res/layout/fxaccount_preference_list_fragment.xml
+++ /dev/null
@@ -1,40 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-** Copyright 2010, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:orientation="vertical"
-    android:layout_height="fill_parent"
-    android:layout_width="fill_parent"
-    android:background="@android:color/transparent">
-
-    <ListView android:id="@android:id/list"
-        android:layout_width="fill_parent"
-        android:layout_height="0px"
-        android:layout_weight="1"
-        android:paddingTop="0dip"
-        android:paddingBottom="@dimen/preference_fragment_padding_bottom"
-        android:paddingLeft="@dimen/preference_fragment_padding_side"
-        android:paddingRight="@dimen/preference_fragment_padding_side"
-        android:scrollbarStyle="@integer/preference_fragment_scrollbarStyle"
-        android:clipToPadding="false"
-        android:drawSelectorOnTop="false"
-        android:cacheColorHint="@android:color/transparent"
-        android:scrollbarAlwaysDrawVerticalTrack="true" />
-
-</LinearLayout>
diff --git a/mobile/android/services/src/main/res/layout/fxaccount_status_error_preference.xml b/mobile/android/services/src/main/res/layout/fxaccount_status_error_preference.xml
deleted file mode 100644
index 4a507cd..0000000
--- a/mobile/android/services/src/main/res/layout/fxaccount_status_error_preference.xml
+++ /dev/null
@@ -1,66 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="fill_parent"
-    android:layout_height="wrap_content"
-    android:background="@color/fxaccount_error_preference_backgroundcolor"
-    android:gravity="center_vertical"
-    android:minHeight="?android:attr/listPreferredItemHeight"
-    android:paddingRight="?android:attr/scrollbarSize" >
-
-    <LinearLayout
-        android:layout_width="wrap_content"
-        android:layout_height="match_parent"
-        android:gravity="center"
-        android:minWidth="0dp"
-        android:orientation="horizontal" >
-
-        <ImageView
-            android:id="@+android:id/icon"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_gravity="center"
-            android:minWidth="48dip"
-            android:padding="10dip" />
-    </LinearLayout>
-
-    <RelativeLayout
-        android:layout_width="0dp"
-        android:layout_height="wrap_content"
-        android:layout_marginBottom="6dip"
-        android:layout_marginLeft="15dip"
-        android:layout_marginRight="6dip"
-        android:layout_marginTop="6dip"
-        android:layout_weight="1" >
-
-        <TextView
-            android:id="@+android:id/title"
-            style="@style/FxAccountTextItem"
-            android:layout_width="fill_parent"
-            android:layout_height="wrap_content"
-            android:layout_gravity="center_horizontal"
-            android:gravity="center_vertical" >
-        </TextView>
-    </RelativeLayout>
-
-    <!-- We ignore summary and widget_frame, but they still need to be present.  We set them to be gone. -->
-
-    <TextView
-        android:id="@+android:id/summary"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:maxLines="4"
-        android:textAppearance="?android:attr/textAppearanceSmall"
-        android:textColor="?android:attr/textColorSecondary"
-        android:visibility="gone" />
-
-    <!-- Preference should place its actual preference widget here. -->
-
-    <LinearLayout
-        android:id="@+android:id/widget_frame"
-        android:layout_width="wrap_content"
-        android:layout_height="match_parent"
-        android:gravity="center"
-        android:orientation="vertical"
-        android:visibility="gone" />
-
-</LinearLayout>
diff --git a/mobile/android/services/src/main/res/layout/homescreen_prompt.xml b/mobile/android/services/src/main/res/layout/homescreen_prompt.xml
deleted file mode 100644
index 26d04ad..0000000
--- a/mobile/android/services/src/main/res/layout/homescreen_prompt.xml
+++ /dev/null
@@ -1,92 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<!-- 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/. -->
-
-<merge xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:tools="http://schemas.android.com/tools"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:clipChildren="false"
-    android:clipToPadding="false">
-
-    <RelativeLayout
-        android:id="@+id/container"
-        android:layout_width="@dimen/overlay_prompt_container_width"
-        android:layout_height="wrap_content"
-        android:layout_gravity="bottom|center"
-        android:background="@android:color/white"
-        android:clickable="true"
-        android:orientation="vertical">
-
-        <ImageView
-            android:id="@+id/close"
-            android:layout_width="24dp"
-            android:layout_height="24dp"
-            android:layout_alignParentRight="true"
-            android:layout_marginLeft="10dp"
-            android:layout_marginRight="30dp"
-            android:layout_marginTop="30dp"
-            android:ellipsize="end"
-            android:maxLines="2"
-            android:padding="6dp"
-            android:src="@drawable/tab_close_active" />
-
-        <TextView
-            android:id="@+id/title"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_marginBottom="6dp"
-            android:layout_marginLeft="30dp"
-            android:layout_marginTop="30dp"
-            android:layout_toLeftOf="@id/close"
-            android:fontFamily="sans-serif-light"
-            android:textColor="@color/text_and_tabs_tray_grey"
-            android:textSize="20sp"
-            tools:text="The Pokedex" />
-
-        <TextView
-            android:id="@+id/host"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_below="@id/title"
-            android:layout_marginBottom="20dp"
-            android:layout_marginLeft="30dp"
-            android:layout_marginRight="30dp"
-            android:ellipsize="end"
-            android:maxLines="1"
-            android:textColor="@color/placeholder_grey"
-            android:textSize="16sp"
-            tools:text="pokedex.org" />
-
-        <ImageView
-            android:id="@+id/icon"
-            android:layout_width="50dp"
-            android:layout_height="50dp"
-            android:layout_below="@id/host"
-            android:layout_marginBottom="20dp"
-            android:layout_marginLeft="30dp"
-            android:src="@drawable/icon" />
-
-        <Button
-            android:id="@+id/add"
-            style="@style/Widget.BaseButton"
-            android:layout_width="wrap_content"
-            android:layout_height="50dp"
-            android:layout_alignParentRight="true"
-            android:layout_below="@id/host"
-            android:layout_marginBottom="20dp"
-            android:layout_marginLeft="100dp"
-            android:layout_marginRight="30dp"
-            android:background="@drawable/button_background_action_orange_round"
-            android:paddingLeft="16dp"
-            android:paddingRight="16dp"
-            android:text="@string/promotion_add_to_homescreen"
-            android:maxLines="2"
-            android:ellipsize="end"
-            android:textColor="@android:color/white"
-            android:textSize="16sp" />
-
-    </RelativeLayout>
-</merge>
diff --git a/mobile/android/services/src/main/res/layout/simple_helper_ui.xml b/mobile/android/services/src/main/res/layout/simple_helper_ui.xml
deleted file mode 100644
index f549d5c..0000000
--- a/mobile/android/services/src/main/res/layout/simple_helper_ui.xml
+++ /dev/null
@@ -1,61 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<!-- 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/. -->
-
-<merge xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:tools="http://schemas.android.com/tools"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:clipChildren="false"
-    android:clipToPadding="false">
-
-    <LinearLayout
-        android:id="@+id/container"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:background="@android:color/white"
-        android:layout_gravity="bottom|center"
-        android:clickable="true"
-        android:orientation="vertical">
-
-        <ImageView
-            android:id="@+id/image"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_marginTop="40dp"
-            android:layout_marginBottom="40dp"
-            android:scaleType="fitCenter"
-            android:layout_gravity="center"
-            android:adjustViewBounds="true"/>
-
-        <TextView
-            android:id="@+id/title"
-            android:layout_width="@dimen/firstrun_content_width"
-            android:layout_height="wrap_content"
-            android:layout_gravity="center"
-            android:gravity="center"
-            android:textAppearance="@style/TextAppearance.FirstrunLight.Main"/>
-
-
-        <TextView
-            android:id="@+id/message"
-            android:layout_width="@dimen/firstrun_content_width"
-            android:layout_height="wrap_content"
-            android:paddingTop="20dp"
-            android:paddingBottom="30dp"
-            android:layout_gravity="center"
-            android:gravity="center"
-            android:textAppearance="@style/TextAppearance.FirstrunRegular.Body"
-            android:singleLine="false"/>
-
-        <Button
-            android:id="@+id/button"
-            style="@style/Widget.Firstrun.Button"
-            android:background="@drawable/button_background_action_orange_round"
-            android:layout_gravity="center"
-            android:layout_marginBottom="30dp"/>
-
-    </LinearLayout>
-</merge>
diff --git a/mobile/android/services/src/main/res/menu/fxaccount_status_menu.xml b/mobile/android/services/src/main/res/menu/fxaccount_status_menu.xml
deleted file mode 100644
index 16f72a7..0000000
--- a/mobile/android/services/src/main/res/menu/fxaccount_status_menu.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<menu xmlns:android="http://schemas.android.com/apk/res/android" >
-  <item
-      android:id="@+id/enable_debug_mode"
-      android:checkable="true"
-      android:checked="false"
-      android:title="@string/fxaccount_enable_debug_mode" />
-</menu>
diff --git a/mobile/android/services/src/main/res/values-v11/fxaccount_styles.xml b/mobile/android/services/src/main/res/values-v11/fxaccount_styles.xml
deleted file mode 100644
index 5c0a23d..0000000
--- a/mobile/android/services/src/main/res/values-v11/fxaccount_styles.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-   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/.
--->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android">
-
-    <!-- FxAccountStatusActivity ActionBar -->
-    <style name="ActionBar.FxAccountStatusActivity">
-         <item name="android:displayOptions">showHome|homeAsUp|showTitle</item>
-    </style>
-
-    <style name="FxAccountTheme" parent="Gecko.Preferences" />
-
-    <style name="FxAccountTheme.FxAccountStatusActivity" parent="Gecko.Preferences">
-         <item name="android:actionBarStyle">@style/ActionBar.FxAccountStatusActivity</item>
-    </style>
-
-</resources>
diff --git a/mobile/android/services/src/main/res/values/fxaccount_colors.xml b/mobile/android/services/src/main/res/values/fxaccount_colors.xml
deleted file mode 100644
index f7140fa..0000000
--- a/mobile/android/services/src/main/res/values/fxaccount_colors.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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/. -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android">
-  <color name="fxaccount_textColor">#424f59</color>
-  <color name="fxaccount_error_preference_backgroundcolor">#fad4d2</color>
-</resources>
diff --git a/mobile/android/services/src/main/res/values/fxaccount_dimens.xml b/mobile/android/services/src/main/res/values/fxaccount_dimens.xml
deleted file mode 100644
index d1d4458..0000000
--- a/mobile/android/services/src/main/res/values/fxaccount_dimens.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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/. -->
-
-<resources>
-  <!-- Preference fragment padding, bottom -->
-  <dimen name="preference_fragment_padding_bottom">0dp</dimen>
-  <!-- Preference fragment padding, sides -->
-  <dimen name="preference_fragment_padding_side">16dp</dimen>
-
-  <integer name="preference_fragment_scrollbarStyle">0x02000000</integer> <!-- outsideOverlay -->
-
-  <!-- Profile avatar image height. -->
-  <dimen name="fxaccount_profile_image_height">48dp</dimen>
-  <!-- Profile avatar image width. -->
-  <dimen name="fxaccount_profile_image_width">48dp</dimen>
-</resources>
diff --git a/mobile/android/services/src/main/res/values/fxaccount_styles.xml b/mobile/android/services/src/main/res/values/fxaccount_styles.xml
deleted file mode 100644
index d74efac..0000000
--- a/mobile/android/services/src/main/res/values/fxaccount_styles.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-   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/.
--->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android">
-
-    <style name="FxAccountTheme" parent="Gecko.Preferences" />
-
-    <style name="FxAccountTheme.FxAccountStatusActivity" parent="@style/FxAccountTheme">
-        <item name="android:windowNoTitle">false</item>
-    </style>
-
-    <style name="FxAccountTextItem" parent="@android:style/TextAppearance.Medium">
-        <item name="android:textColor">@color/fxaccount_textColor</item>
-        <item name="android:layout_width">fill_parent</item>
-        <item name="android:layout_height">wrap_content</item>
-        <item name="android:gravity">center_horizontal</item>
-        <item name="android:textSize">14sp</item>
-        <item name="android:layout_marginBottom">10dp</item>
-        <item name="android:layout_marginLeft">10dp</item>
-        <item name="android:layout_marginRight">10dp</item>
-    </style>
-
-</resources>
diff --git a/mobile/android/services/src/main/res/xml/fxaccount_authenticator.xml b/mobile/android/services/src/main/res/xml/fxaccount_authenticator.xml
deleted file mode 100644
index 7b004e2..0000000
--- a/mobile/android/services/src/main/res/xml/fxaccount_authenticator.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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/. -->
-
-<account-authenticator xmlns:android="http://schemas.android.com/apk/res/android"
-    android:accountType="@string/moz_android_shared_fxaccount_type"
-    android:icon="@drawable/icon"
-    android:smallIcon="@drawable/icon"
-    android:label="@string/fxaccount_label"
-    android:accountPreferences="@xml/fxaccount_options" />
diff --git a/mobile/android/services/src/main/res/xml/fxaccount_options.xml b/mobile/android/services/src/main/res/xml/fxaccount_options.xml
deleted file mode 100644
index 449fc05..0000000
--- a/mobile/android/services/src/main/res/xml/fxaccount_options.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 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/. -->
-
-<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
-  <PreferenceCategory
-    android:title="@string/fxaccount_options_title" />
-  <PreferenceScreen
-    android:key="options"
-    android:title="@string/fxaccount_options_configure_title">
-    <intent
-      android:action="android.intent.action.MAIN"
-      android:targetPackage="@string/android_package_name_for_ui"
-      android:targetClass="org.mozilla.gecko.fxa.activities.FxAccountStatusActivity">
-    </intent>
-  </PreferenceScreen>
-</PreferenceScreen>
diff --git a/mobile/android/services/src/main/res/xml/fxaccount_status_prefscreen.xml b/mobile/android/services/src/main/res/xml/fxaccount_status_prefscreen.xml
deleted file mode 100644
index 570e362..0000000
--- a/mobile/android/services/src/main/res/xml/fxaccount_status_prefscreen.xml
+++ /dev/null
@@ -1,142 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
-                  xmlns:gecko="http://schemas.android.com/apk/res-auto"
-                  android:key="status_screen">
-
-    <PreferenceCategory
-        android:key="signed_in_as_category"
-        android:title="@string/fxaccount_status_signed_in_as" >
-        <Preference
-            android:editable="false"
-            android:key="profile"
-            android:icon="@drawable/sync_avatar_default"
-            android:persistent="false"
-            android:title="" />
-        <Preference
-            android:editable="false"
-            android:key="manage_account"
-            android:persistent="false"
-            android:title="@string/fxaccount_status_manage_account" />
-        <Preference
-            android:editable="false"
-            android:key="auth_server"
-            android:persistent="false"
-            android:title="@string/fxaccount_status_auth_server" />
-    </PreferenceCategory>
-    <PreferenceCategory
-        android:key="sync_category"
-        android:title="@string/fxaccount_status_sync" >
-        <Preference
-            android:editable="false"
-            android:icon="@drawable/fxaccount_sync_error"
-            android:key="needs_credentials"
-            android:layout="@layout/fxaccount_status_error_preference"
-            android:persistent="false"
-            android:title="@string/fxaccount_status_needs_credentials" />
-        <Preference
-            android:editable="false"
-            android:icon="@drawable/fxaccount_sync_error"
-            android:key="needs_upgrade"
-            android:layout="@layout/fxaccount_status_error_preference"
-            android:persistent="false"
-            android:title="@string/fxaccount_status_needs_upgrade" />
-        <Preference
-            android:editable="false"
-            android:icon="@drawable/fxaccount_sync_error"
-            android:key="needs_verification"
-            android:layout="@layout/fxaccount_status_error_preference"
-            android:persistent="false"
-            android:title="@string/fxaccount_status_needs_verification" />
-        <Preference
-            android:editable="false"
-            android:icon="@drawable/fxaccount_sync_error"
-            android:key="needs_master_sync_automatically_enabled"
-            android:layout="@layout/fxaccount_status_error_preference"
-            android:persistent="false"
-            android:title="@string/fxaccount_status_needs_master_sync_automatically_enabled" />
-        <Preference
-            android:editable="false"
-            android:icon="@drawable/fxaccount_sync_error"
-            android:key="needs_finish_migrating"
-            android:layout="@layout/fxaccount_status_error_preference"
-            android:persistent="false"
-            android:title="@string/fxaccount_status_needs_finish_migrating" />
-
-        <Preference
-            android:editable="false"
-            android:key="sync_now"
-            android:defaultValue=""
-            android:persistent="false"
-            android:title="@string/fxaccount_status_sync_now"
-            android:summary="" />
-
-        <CheckBoxPreference
-            android:key="bookmarks"
-            android:persistent="false"
-            android:title="@string/fxaccount_status_bookmarks" />
-        <CheckBoxPreference
-            android:key="history"
-            android:persistent="false"
-            android:title="@string/fxaccount_status_history" />
-        <CheckBoxPreference
-            android:key="tabs"
-            android:persistent="false"
-            android:title="@string/fxaccount_status_tabs" />
-        <CheckBoxPreference
-            android:key="passwords"
-            android:persistent="false"
-            android:title="@string/fxaccount_status_passwords" />
-
-        <EditTextPreference
-            android:singleLine="true"
-            android:key="device_name"
-            android:persistent="false"
-            android:title="@string/fxaccount_status_device_name" />
-
-        <Preference
-            android:editable="false"
-            android:key="sync_server"
-            android:persistent="false"
-            android:title="@string/fxaccount_status_sync_server" />
-        <org.mozilla.gecko.fxa.activities.CustomColorPreference
-            android:editable="false"
-            android:key="remove_account"
-            android:persistent="false"
-            gecko:titleColor="@color/rejection_red"
-            android:title="@string/fxaccount_remove_account" />
-        <Preference
-            android:editable="false"
-            android:key="more"
-            android:persistent="false"
-            android:title="@string/fxaccount_status_more" />
-
-    </PreferenceCategory>
-    <PreferenceCategory
-        android:key="legal_category"
-        android:title="@string/fxaccount_status_legal" >
-        <Preference
-            android:editable="false"
-            android:key="linktos"
-            android:persistent="false"
-            android:title="@string/fxaccount_status_linktos" />
-        <Preference
-            android:editable="false"
-            android:key="linkprivacy"
-            android:persistent="false"
-            android:title="@string/fxaccount_status_linkprivacy" />
-    </PreferenceCategory>
-    <PreferenceCategory
-        android:key="debug_category" >
-        <Preference android:key="debug_refresh" />
-        <Preference android:key="debug_dump" />
-        <Preference android:key="debug_force_sync" />
-        <Preference android:key="debug_invalidate_certificate" />
-        <Preference android:key="debug_forget_certificate" />
-        <Preference android:key="debug_require_password" />
-        <Preference android:key="debug_require_upgrade" />
-        <Preference android:key="debug_migrated_from_sync11" />
-        <Preference android:key="debug_make_account_stage" />
-        <Preference android:key="debug_make_account_default" />
-	</PreferenceCategory>
-
-</PreferenceScreen>
diff --git a/mobile/android/services/src/main/res/xml/fxaccount_syncadapter.xml b/mobile/android/services/src/main/res/xml/fxaccount_syncadapter.xml
deleted file mode 100644
index 7619206..0000000
--- a/mobile/android/services/src/main/res/xml/fxaccount_syncadapter.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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/. -->
-
-<sync-adapter xmlns:android="http://schemas.android.com/apk/res/android"
-    android:accountType="@string/moz_android_shared_fxaccount_type"
-    android:contentAuthority="@string/content_authority_db_browser"
-    android:isAlwaysSyncable="true"
-    android:supportsUploading="true"
-    android:userVisible="true"
-/>
diff --git a/mobile/android/services/strings.xml.in b/mobile/android/services/strings.xml.in
deleted file mode 100644
index 143a3db..0000000
--- a/mobile/android/services/strings.xml.in
+++ /dev/null
@@ -1,86 +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/. -->
-
-<!-- Configure Engines -->
-<string name="sync_configure_engines_title_passwords">&sync.configure.engines.title.passwords2;</string>
-<string name="sync_configure_engines_title_history">&sync.configure.engines.title.history;</string>
-<string name="sync_configure_engines_title_tabs">&sync.configure.engines.title.tabs;</string>
-
-<!-- Bookmark folder strings -->
-<string name="bookmarks_folder_menu">&bookmarks.folder.menu.label;</string>
-<string name="bookmarks_folder_places">&bookmarks.folder.places.label;</string>
-<string name="bookmarks_folder_tags">&bookmarks.folder.tags.label;</string>
-<string name="bookmarks_folder_toolbar">&bookmarks.folder.toolbar.label;</string>
-<string name="bookmarks_folder_unfiled">&bookmarks.folder.other.label;</string>
-<string name="bookmarks_folder_desktop">&bookmarks.folder.desktop.label;</string>
-<string name="bookmarks_folder_mobile">&bookmarks.folder.mobile.label;</string>
-<string name="bookmarks_folder_pinned">&bookmarks.folder.pinned.label;</string>
-
-<!-- Send tab to device. -->
-<string name="sync_default_client_name">&sync.default.client.name;</string>
-
-<!-- Firefox Account links. -->
-<string name="fxaccount_link_tos">https://accounts.firefox.com/legal/terms</string>
-<string name="fxaccount_link_pn">https://accounts.firefox.com/legal/privacy</string>
-
-<string name="fxaccount_getting_started_welcome_to_sync">&fxaccount_getting_started_welcome_to_sync;</string>
-<string name="fxaccount_getting_started_description">&fxaccount_getting_started_description2;</string>
-<string name="fxaccount_getting_started_get_started">&fxaccount_getting_started_get_started;</string>
-
-<string name="fxaccount_status_activity_label">&syncBrand.shortName.label;</string>
-<string name="fxaccount_status_signed_in_as">&fxaccount_status_signed_in_as;</string>
-<string name="fxaccount_status_manage_account">&fxaccount_status_manage_account;</string>
-<string name="fxaccount_status_auth_server">&fxaccount_status_auth_server;</string>
-<string name="fxaccount_status_sync_now">&fxaccount_status_sync_now;</string>
-<string name="fxaccount_status_syncing">&fxaccount_status_syncing2;</string>
-<string name="fxaccount_status_last_synced">&remote_tabs_last_synced;</string>
-<string name="fxaccount_status_never_synced">&remote_tabs_never_synced;</string>
-<string name="fxaccount_status_device_name">&fxaccount_status_device_name;</string>
-<string name="fxaccount_status_sync_server">&fxaccount_status_sync_server;</string>
-<string name="fxaccount_status_sync">&fxaccount_status_sync;</string>
-<string name="fxaccount_status_sync_enabled">&fxaccount_status_sync_enabled;</string>
-<string name="fxaccount_status_needs_verification">&fxaccount_status_needs_verification2;</string>
-<string name="fxaccount_status_needs_credentials">&fxaccount_status_needs_credentials;</string>
-<string name="fxaccount_status_needs_upgrade">&fxaccount_status_needs_upgrade;</string>
-<string name="fxaccount_status_needs_master_sync_automatically_enabled">&fxaccount_status_needs_master_sync_automatically_enabled;</string>
-<string name="fxaccount_status_needs_master_sync_automatically_enabled_v21">&fxaccount_status_needs_master_sync_automatically_enabled_v21;</string>
-<string name="fxaccount_status_needs_finish_migrating">&fxaccount_status_needs_finish_migrating;</string>
-<string name="fxaccount_status_bookmarks">&fxaccount_status_bookmarks;</string>
-<string name="fxaccount_status_history">&fxaccount_status_history;</string>
-<string name="fxaccount_status_passwords">&fxaccount_status_passwords2;</string>
-<string name="fxaccount_status_tabs">&fxaccount_status_tabs;</string>
-<string name="fxaccount_status_legal">&fxaccount_status_legal;</string>
-<string name="fxaccount_status_linktos">&fxaccount_status_linktos2;</string>
-<string name="fxaccount_status_linkprivacy">&fxaccount_status_linkprivacy2;</string>
-<string name="fxaccount_status_more">&fxaccount_status_more;</string>
-<string name="fxaccount_remove_account">&fxaccount_remove_account;</string>
-
-<string name="fxaccount_label">&fxaccount_account_type_label;</string>
-
-<string name="fxaccount_options_title">&fxaccount_options_title;</string>
-<string name="fxaccount_options_configure_title">&fxaccount_options_configure_title;</string>
-
-<string name="fxaccount_remote_error_UPGRADE_REQUIRED">&fxaccount_remote_error_UPGRADE_REQUIRED;</string>
-<string name="fxaccount_remote_error_ATTEMPT_TO_CREATE_AN_ACCOUNT_THAT_ALREADY_EXISTS">&fxaccount_remote_error_ATTEMPT_TO_CREATE_AN_ACCOUNT_THAT_ALREADY_EXISTS_2;</string>
-<string name="fxaccount_remote_error_ATTEMPT_TO_ACCESS_AN_ACCOUNT_THAT_DOES_NOT_EXIST">&fxaccount_remote_error_ATTEMPT_TO_ACCESS_AN_ACCOUNT_THAT_DOES_NOT_EXIST;</string>
-<string name="fxaccount_remote_error_INCORRECT_PASSWORD">&fxaccount_remote_error_INCORRECT_PASSWORD;</string>
-<string name="fxaccount_remote_error_ATTEMPT_TO_OPERATE_ON_AN_UNVERIFIED_ACCOUNT">&fxaccount_remote_error_ATTEMPT_TO_OPERATE_ON_AN_UNVERIFIED_ACCOUNT;</string>
-<string name="fxaccount_remote_error_CLIENT_HAS_SENT_TOO_MANY_REQUESTS">&fxaccount_remote_error_CLIENT_HAS_SENT_TOO_MANY_REQUESTS;</string>
-<string name="fxaccount_remote_error_SERVICE_TEMPORARILY_UNAVAILABLE_TO_DUE_HIGH_LOAD">&fxaccount_remote_error_SERVICE_TEMPORARILY_UNAVAILABLE_TO_DUE_HIGH_LOAD;</string>
-<string name="fxaccount_remote_error_UNKNOWN_ERROR">&fxaccount_remote_error_UNKNOWN_ERROR;</string>
-<string name="fxaccount_remote_error_ACCOUNT_LOCKED">&fxaccount_remote_error_ACCOUNT_LOCKED;</string>
-
-<string name="fxaccount_sync_sign_in_error_notification_title">&fxaccount_sync_sign_in_error_notification_title2;</string>
-<string name="fxaccount_sync_sign_in_error_notification_text">&fxaccount_sync_sign_in_error_notification_text2;</string>
-
-<!-- Remove Account -->
-<string name="fxaccount_remove_account_dialog_title">&fxaccount_remove_account_dialog_title;</string>
-<string name="fxaccount_remove_account_dialog_message">&fxaccount_remove_account_dialog_message;</string>
-<string name="fxaccount_remove_account_toast">&fxaccount_remove_account_toast;</string>
-
-<string name="fxaccount_sync_finish_migrating_notification_title">&fxaccount_sync_finish_migrating_notification_title;</string>
-<string name="fxaccount_sync_finish_migrating_notification_text">&fxaccount_sync_finish_migrating_notification_text;</string>
-
-<!-- Log Personal information -->
-<string name="fxaccount_enable_debug_mode">&fxaccount_enable_debug_mode;</string>
diff --git a/modules/libpref/init/all.js b/modules/libpref/init/all.js
index aed20c8..10f9b99 100644
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -5151,9 +5151,6 @@ pref("layout.accessiblecaret.hide_carets_for_mouse_input", true);
 // Wakelock is disabled by default.
 pref("dom.wakelock.enabled", false);
 
-// The URL of the Firefox Accounts auth server backend
-pref("identity.fxaccounts.auth.uri", "https://api.accounts.firefox.com/v1");
-
 // disable mozsample size for now
 pref("image.mozsamplesize.enabled", false);
 
diff --git a/services/fxaccounts/Credentials.jsm b/services/fxaccounts/Credentials.jsm
deleted file mode 100644
index 56e8b3d..0000000
--- a/services/fxaccounts/Credentials.jsm
+++ /dev/null
@@ -1,136 +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/. */
-
-/**
- * This module implements client-side key stretching for use in Firefox
- * Accounts account creation and login.
- *
- * See https://github.com/mozilla/fxa-auth-server/wiki/onepw-protocol
- */
-
-"use strict";
-
-this.EXPORTED_SYMBOLS = ["Credentials"];
-
-const {utils: Cu, interfaces: Ci} = Components;
-
-Cu.import("resource://gre/modules/Log.jsm");
-Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://gre/modules/Promise.jsm");
-Cu.import("resource://services-crypto/utils.js");
-Cu.import("resource://services-common/utils.js");
-
-const PROTOCOL_VERSION = "identity.mozilla.com/picl/v1/";
-const PBKDF2_ROUNDS = 1000;
-const STRETCHED_PW_LENGTH_BYTES = 32;
-const HKDF_SALT = CommonUtils.hexToBytes("00");
-const HKDF_LENGTH = 32;
-const HMAC_ALGORITHM = Ci.nsICryptoHMAC.SHA256;
-const HMAC_LENGTH = 32;
-
-// loglevel preference should be one of: "FATAL", "ERROR", "WARN", "INFO",
-// "CONFIG", "DEBUG", "TRACE" or "ALL". We will be logging error messages by
-// default.
-const PREF_LOG_LEVEL = "identity.fxaccounts.loglevel";
-try {
-  this.LOG_LEVEL =
-    Services.prefs.getPrefType(PREF_LOG_LEVEL) == Ci.nsIPrefBranch.PREF_STRING
-    && Services.prefs.getCharPref(PREF_LOG_LEVEL);
-} catch (e) {
-  this.LOG_LEVEL = Log.Level.Error;
-}
-
-var log = Log.repository.getLogger("Identity.FxAccounts");
-log.level = LOG_LEVEL;
-log.addAppender(new Log.ConsoleAppender(new Log.BasicFormatter()));
-
-this.Credentials = Object.freeze({
-  /**
-   * Make constants accessible to tests
-   */
-  constants: {
-    PROTOCOL_VERSION: PROTOCOL_VERSION,
-    PBKDF2_ROUNDS: PBKDF2_ROUNDS,
-    STRETCHED_PW_LENGTH_BYTES: STRETCHED_PW_LENGTH_BYTES,
-    HKDF_SALT: HKDF_SALT,
-    HKDF_LENGTH: HKDF_LENGTH,
-    HMAC_ALGORITHM: HMAC_ALGORITHM,
-    HMAC_LENGTH: HMAC_LENGTH,
-  },
-
-  /**
-   * KW function from https://github.com/mozilla/fxa-auth-server/wiki/onepw-protocol
-   *
-   * keyWord derivation for use as a salt.
-   *
-   *
-   *   @param {String} context  String for use in generating salt
-   *
-   *   @return {bitArray} the salt
-   *
-   * Note that PROTOCOL_VERSION does not refer in any way to the version of the
-   * Firefox Accounts API.
-   */
-  keyWord: function(context) {
-    return CommonUtils.stringToBytes(PROTOCOL_VERSION + context);
-  },
-
-  /**
-   * KWE function from https://github.com/mozilla/fxa-auth-server/wiki/onepw-protocol
-   *
-   * keyWord extended with a name and an email.
-   *
-   *   @param {String} name The name of the salt
-   *   @param {String} email The email of the user.
-   *
-   *   @return {bitArray} the salt combination with the namespace
-   *
-   * Note that PROTOCOL_VERSION does not refer in any way to the version of the
-   * Firefox Accounts API.
-   */
-  keyWordExtended: function(name, email) {
-    return CommonUtils.stringToBytes(PROTOCOL_VERSION + name + ':' + email);
-  },
-
-  setup: function(emailInput, passwordInput, options={}) {
-    let deferred = Promise.defer();
-    log.debug("setup credentials for " + emailInput);
-
-    let hkdfSalt = options.hkdfSalt || HKDF_SALT;
-    let hkdfLength = options.hkdfLength || HKDF_LENGTH;
-    let hmacLength = options.hmacLength || HMAC_LENGTH;
-    let hmacAlgorithm = options.hmacAlgorithm || HMAC_ALGORITHM;
-    let stretchedPWLength = options.stretchedPassLength || STRETCHED_PW_LENGTH_BYTES;
-    let pbkdf2Rounds = options.pbkdf2Rounds || PBKDF2_ROUNDS;
-
-    let result = {};
-
-    let password = CommonUtils.encodeUTF8(passwordInput);
-    let salt = this.keyWordExtended("quickStretch", emailInput);
-
-    let runnable = () => {
-      let start = Date.now();
-      let quickStretchedPW = CryptoUtils.pbkdf2Generate(
-          password, salt, pbkdf2Rounds, stretchedPWLength, hmacAlgorithm, hmacLength);
-
-      result.quickStretchedPW = quickStretchedPW;
-
-      result.authPW =
-        CryptoUtils.hkdf(quickStretchedPW, hkdfSalt, this.keyWord("authPW"), hkdfLength);
-
-      result.unwrapBKey =
-        CryptoUtils.hkdf(quickStretchedPW, hkdfSalt, this.keyWord("unwrapBkey"), hkdfLength);
-
-      log.debug("Credentials set up after " + (Date.now() - start) + " ms");
-      deferred.resolve(result);
-    }
-
-    Services.tm.currentThread.dispatch(runnable,
-        Ci.nsIThread.DISPATCH_NORMAL);
-    log.debug("Dispatched thread for credentials setup crypto work");
-
-    return deferred.promise;
-  }
-});
-
diff --git a/services/fxaccounts/FxAccounts.jsm b/services/fxaccounts/FxAccounts.jsm
deleted file mode 100644
index 5bed881..0000000
--- a/services/fxaccounts/FxAccounts.jsm
+++ /dev/null
@@ -1,1735 +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/. */
-"use strict";
-
-this.EXPORTED_SYMBOLS = ["fxAccounts", "FxAccounts"];
-
-const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
-
-Cu.import("resource://gre/modules/Log.jsm");
-Cu.import("resource://gre/modules/Promise.jsm");
-Cu.import("resource://services-common/utils.js");
-Cu.import("resource://services-common/rest.js");
-Cu.import("resource://services-crypto/utils.js");
-Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-Cu.import("resource://gre/modules/Timer.jsm");
-Cu.import("resource://gre/modules/Task.jsm");
-Cu.import("resource://gre/modules/FxAccountsStorage.jsm");
-Cu.import("resource://gre/modules/FxAccountsCommon.js");
-
-XPCOMUtils.defineLazyModuleGetter(this, "FxAccountsClient",
-  "resource://gre/modules/FxAccountsClient.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "FxAccountsConfig",
-  "resource://gre/modules/FxAccountsConfig.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "jwcrypto",
-  "resource://gre/modules/identity/jwcrypto.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "FxAccountsOAuthGrantClient",
-  "resource://gre/modules/FxAccountsOAuthGrantClient.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "FxAccountsProfile",
-  "resource://gre/modules/FxAccountsProfile.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "Utils",
-  "resource://services-sync/util.js");
-
-// All properties exposed by the public FxAccounts API.
-var publicProperties = [
-  "accountStatus",
-  "checkVerificationStatus",
-  "getAccountsClient",
-  "getAssertion",
-  "getDeviceId",
-  "getKeys",
-  "getOAuthToken",
-  "getSignedInUser",
-  "getSignedInUserProfile",
-  "handleDeviceDisconnection",
-  "invalidateCertificate",
-  "loadAndPoll",
-  "localtimeOffsetMsec",
-  "notifyDevices",
-  "now",
-  "promiseAccountsChangeProfileURI",
-  "promiseAccountsForceSigninURI",
-  "promiseAccountsManageURI",
-  "promiseAccountsSignUpURI",
-  "promiseAccountsSignInURI",
-  "removeCachedOAuthToken",
-  "requiresHttps",
-  "resendVerificationEmail",
-  "resetCredentials",
-  "sessionStatus",
-  "setSignedInUser",
-  "signOut",
-  "updateDeviceRegistration",
-  "updateUserAccountData",
-  "whenVerified",
-];
-
-// An AccountState object holds all state related to one specific account.
-// Only one AccountState is ever "current" in the FxAccountsInternal object -
-// whenever a user logs out or logs in, the current AccountState is discarded,
-// making it impossible for the wrong state or state data to be accidentally
-// used.
-// In addition, it has some promise-related helpers to ensure that if an
-// attempt is made to resolve a promise on a "stale" state (eg, if an
-// operation starts, but a different user logs in before the operation
-// completes), the promise will be rejected.
-// It is intended to be used thusly:
-// somePromiseBasedFunction: function() {
-//   let currentState = this.currentAccountState;
-//   return someOtherPromiseFunction().then(
-//     data => currentState.resolve(data)
-//   );
-// }
-// If the state has changed between the function being called and the promise
-// being resolved, the .resolve() call will actually be rejected.
-var AccountState = this.AccountState = function(storageManager) {
-  this.storageManager = storageManager;
-  this.promiseInitialized = this.storageManager.getAccountData().then(data => {
-    this.oauthTokens = data && data.oauthTokens ? data.oauthTokens : {};
-  }).catch(err => {
-    log.error("Failed to initialize the storage manager", err);
-    // Things are going to fall apart, but not much we can do about it here.
-  });
-};
-
-AccountState.prototype = {
-  oauthTokens: null,
-  whenVerifiedDeferred: null,
-  whenKeysReadyDeferred: null,
-
-  // If the storage manager has been nuked then we are no longer current.
-  get isCurrent() {
-    return this.storageManager != null;
-  },
-
-  abort() {
-    if (this.whenVerifiedDeferred) {
-      this.whenVerifiedDeferred.reject(
-        new Error("Verification aborted; Another user signing in"));
-      this.whenVerifiedDeferred = null;
-    }
-
-    if (this.whenKeysReadyDeferred) {
-      this.whenKeysReadyDeferred.reject(
-        new Error("Verification aborted; Another user signing in"));
-      this.whenKeysReadyDeferred = null;
-    }
-
-    this.cert = null;
-    this.keyPair = null;
-    this.oauthTokens = null;
-    // Avoid finalizing the storageManager multiple times (ie, .signOut()
-    // followed by .abort())
-    if (!this.storageManager) {
-      return Promise.resolve();
-    }
-    let storageManager = this.storageManager;
-    this.storageManager = null;
-    return storageManager.finalize();
-  },
-
-  // Clobber all cached data and write that empty data to storage.
-  signOut() {
-    this.cert = null;
-    this.keyPair = null;
-    this.oauthTokens = null;
-    let storageManager = this.storageManager;
-    this.storageManager = null;
-    return storageManager.deleteAccountData().then(() => {
-      return storageManager.finalize();
-    });
-  },
-
-  // Get user account data. Optionally specify explicit field names to fetch
-  // (and note that if you require an in-memory field you *must* specify the
-  // field name(s).)
-  getUserAccountData(fieldNames = null) {
-    if (!this.isCurrent) {
-      return Promise.reject(new Error("Another user has signed in"));
-    }
-    return this.storageManager.getAccountData(fieldNames).then(result => {
-      return this.resolve(result);
-    });
-  },
-
-  updateUserAccountData(updatedFields) {
-    if (!this.isCurrent) {
-      return Promise.reject(new Error("Another user has signed in"));
-    }
-    return this.storageManager.updateAccountData(updatedFields);
-  },
-
-  resolve: function(result) {
-    if (!this.isCurrent) {
-      log.info("An accountState promise was resolved, but was actually rejected" +
-               " due to a different user being signed in. Originally resolved" +
-               " with", result);
-      return Promise.reject(new Error("A different user signed in"));
-    }
-    return Promise.resolve(result);
-  },
-
-  reject: function(error) {
-    // It could be argued that we should just let it reject with the original
-    // error - but this runs the risk of the error being (eg) a 401, which
-    // might cause the consumer to attempt some remediation and cause other
-    // problems.
-    if (!this.isCurrent) {
-      log.info("An accountState promise was rejected, but we are ignoring that" +
-               "reason and rejecting it due to a different user being signed in." +
-               "Originally rejected with", error);
-      return Promise.reject(new Error("A different user signed in"));
-    }
-    return Promise.reject(error);
-  },
-
-  // Abstractions for storage of cached tokens - these are all sync, and don't
-  // handle revocation etc - it's just storage (and the storage itself is async,
-  // but we don't return the storage promises, so it *looks* sync)
-  // These functions are sync simply so we can handle "token races" - when there
-  // are multiple in-flight requests for the same scope, we can detect this
-  // and revoke the redundant token.
-
-  // A preamble for the cache helpers...
-  _cachePreamble() {
-    if (!this.isCurrent) {
-      throw new Error("Another user has signed in");
-    }
-  },
-
-  // Set a cached token. |tokenData| must have a 'token' element, but may also
-  // have additional fields (eg, it probably specifies the server to revoke
-  // from). The 'get' functions below return the entire |tokenData| value.
-  setCachedToken(scopeArray, tokenData) {
-    this._cachePreamble();
-    if (!tokenData.token) {
-      throw new Error("No token");
-    }
-    let key = getScopeKey(scopeArray);
-    this.oauthTokens[key] = tokenData;
-    // And a background save...
-    this._persistCachedTokens();
-  },
-
-  // Return data for a cached token or null (or throws on bad state etc)
-  getCachedToken(scopeArray) {
-    this._cachePreamble();
-    let key = getScopeKey(scopeArray);
-    let result = this.oauthTokens[key];
-    if (result) {
-      // later we might want to check an expiry date - but we currently
-      // have no such concept, so just return it.
-      log.trace("getCachedToken returning cached token");
-      return result;
-    }
-    return null;
-  },
-
-  // Remove a cached token from the cache.  Does *not* revoke it from anywhere.
-  // Returns the entire token entry if found, null otherwise.
-  removeCachedToken(token) {
-    this._cachePreamble();
-    let data = this.oauthTokens;
-    for (let [key, tokenValue] of Object.entries(data)) {
-      if (tokenValue.token == token) {
-        delete data[key];
-        // And a background save...
-        this._persistCachedTokens();
-        return tokenValue;
-      }
-    }
-    return null;
-  },
-
-  // A hook-point for tests.  Returns a promise that's ignored in most cases
-  // (notable exceptions are tests and when we explicitly are saving the entire
-  // set of user data.)
-  _persistCachedTokens() {
-    this._cachePreamble();
-    return this.updateUserAccountData({ oauthTokens: this.oauthTokens }).catch(err => {
-      log.error("Failed to update cached tokens", err);
-    });
-  },
-}
-
-/* Given an array of scopes, make a string key by normalizing. */
-function getScopeKey(scopeArray) {
-  let normalizedScopes = scopeArray.map(item => item.toLowerCase());
-  return normalizedScopes.sort().join("|");
-}
-
-/**
- * Copies properties from a given object to another object.
- *
- * @param from (object)
- *        The object we read property descriptors from.
- * @param to (object)
- *        The object that we set property descriptors on.
- * @param options (object) (optional)
- *        {keys: [...]}
- *          Lets the caller pass the names of all properties they want to be
- *          copied. Will copy all properties of the given source object by
- *          default.
- *        {bind: object}
- *          Lets the caller specify the object that will be used to .bind()
- *          all function properties we find to. Will bind to the given target
- *          object by default.
- */
-function copyObjectProperties(from, to, opts = {}) {
-  let keys = (opts && opts.keys) || Object.keys(from);
-  let thisArg = (opts && opts.bind) || to;
-
-  for (let prop of keys) {
-    let desc = Object.getOwnPropertyDescriptor(from, prop);
-
-    if (typeof(desc.value) == "function") {
-      desc.value = desc.value.bind(thisArg);
-    }
-
-    if (desc.get) {
-      desc.get = desc.get.bind(thisArg);
-    }
-
-    if (desc.set) {
-      desc.set = desc.set.bind(thisArg);
-    }
-
-    Object.defineProperty(to, prop, desc);
-  }
-}
-
-function urlsafeBase64Encode(key) {
-  return ChromeUtils.base64URLEncode(new Uint8Array(key), { pad: false });
-}
-
-/**
- * The public API's constructor.
- */
-this.FxAccounts = function (mockInternal) {
-  let internal = new FxAccountsInternal();
-  let external = {};
-
-  // Copy all public properties to the 'external' object.
-  let prototype = FxAccountsInternal.prototype;
-  let options = {keys: publicProperties, bind: internal};
-  copyObjectProperties(prototype, external, options);
-
-  // Copy all of the mock's properties to the internal object.
-  if (mockInternal && !mockInternal.onlySetInternal) {
-    copyObjectProperties(mockInternal, internal);
-  }
-
-  if (mockInternal) {
-    // Exposes the internal object for testing only.
-    external.internal = internal;
-  }
-
-  if (!internal.fxaPushService) {
-    // internal.fxaPushService option is used in testing.
-    // Otherwise we load the service lazily.
-    XPCOMUtils.defineLazyGetter(internal, "fxaPushService", function () {
-      return Components.classes["@mozilla.org/fxaccounts/push;1"]
-        .getService(Components.interfaces.nsISupports)
-        .wrappedJSObject;
-    });
-  }
-
-  // wait until after the mocks are setup before initializing.
-  internal.initialize();
-
-  return Object.freeze(external);
-}
-
-/**
- * The internal API's constructor.
- */
-function FxAccountsInternal() {
-  // Make a local copy of this constant so we can mock it in testing
-  this.POLL_SESSION = POLL_SESSION;
-
-  // All significant initialization should be done in the initialize() method
-  // below as it helps with testing.
-}
-
-/**
- * The internal API's prototype.
- */
-FxAccountsInternal.prototype = {
-  // The timeout (in ms) we use to poll for a verified mail for the first 2 mins.
-  VERIFICATION_POLL_TIMEOUT_INITIAL: 15000, // 15 seconds
-  // And how often we poll after the first 2 mins.
-  VERIFICATION_POLL_TIMEOUT_SUBSEQUENT: 30000, // 30 seconds.
-  // The current version of the device registration, we use this to re-register
-  // devices after we update what we send on device registration.
-  DEVICE_REGISTRATION_VERSION: 2,
-
-  _fxAccountsClient: null,
-
-  // All significant initialization should be done in this initialize() method,
-  // as it's called after this object has been mocked for tests.
-  initialize() {
-    this.currentTimer = null;
-    this.currentAccountState = this.newAccountState();
-  },
-
-  get fxAccountsClient() {
-    if (!this._fxAccountsClient) {
-      this._fxAccountsClient = new FxAccountsClient();
-    }
-    return this._fxAccountsClient;
-  },
-
-  // The profile object used to fetch the actual user profile.
-  _profile: null,
-  get profile() {
-    if (!this._profile) {
-      let profileServerUrl = Services.urlFormatter.formatURLPref("identity.fxaccounts.remote.profile.uri");
-      this._profile = new FxAccountsProfile({
-        fxa: this,
-        profileServerUrl: profileServerUrl,
-      });
-    }
-    return this._profile;
-  },
-
-  // A hook-point for tests who may want a mocked AccountState or mocked storage.
-  newAccountState(credentials) {
-    let storage = new FxAccountsStorageManager();
-    storage.initialize(credentials);
-    return new AccountState(storage);
-  },
-
-  /**
-   * Send a message to a set of devices in the same account
-   *
-   * @return Promise
-   */
-  notifyDevices: function(deviceIds, payload, TTL) {
-    if (!Array.isArray(deviceIds)) {
-      deviceIds = [deviceIds];
-    }
-    return this.currentAccountState.getUserAccountData()
-      .then(data => {
-        if (!data) {
-          throw this._error(ERROR_NO_ACCOUNT);
-        }
-        if (!data.sessionToken) {
-          throw this._error(ERROR_AUTH_ERROR,
-            "notifyDevices called without a session token");
-        }
-        return this.fxAccountsClient.notifyDevices(data.sessionToken, deviceIds,
-          payload, TTL);
-    });
-  },
-
-  /**
-   * Return the current time in milliseconds as an integer.  Allows tests to
-   * manipulate the date to simulate certificate expiration.
-   */
-  now: function() {
-    return this.fxAccountsClient.now();
-  },
-
-  getAccountsClient: function() {
-    return this.fxAccountsClient;
-  },
-
-  /**
-   * Return clock offset in milliseconds, as reported by the fxAccountsClient.
-   * This can be overridden for testing.
-   *
-   * The offset is the number of milliseconds that must be added to the client
-   * clock to make it equal to the server clock.  For example, if the client is
-   * five minutes ahead of the server, the localtimeOffsetMsec will be -300000.
-   */
-  get localtimeOffsetMsec() {
-    return this.fxAccountsClient.localtimeOffsetMsec;
-  },
-
-  /**
-   * Ask the server whether the user's email has been verified
-   */
-  checkEmailStatus: function checkEmailStatus(sessionToken, options = {}) {
-    if (!sessionToken) {
-      return Promise.reject(new Error(
-        "checkEmailStatus called without a session token"));
-    }
-    return this.fxAccountsClient.recoveryEmailStatus(sessionToken,
-      options).catch(error => this._handleTokenError(error));
-  },
-
-  /**
-   * Once the user's email is verified, we can request the keys
-   */
-  fetchKeys: function fetchKeys(keyFetchToken) {
-    log.debug("fetchKeys: " + !!keyFetchToken);
-    if (logPII) {
-      log.debug("fetchKeys - the token is " + keyFetchToken);
-    }
-    return this.fxAccountsClient.accountKeys(keyFetchToken);
-  },
-
-  // set() makes sure that polling is happening, if necessary.
-  // get() does not wait for verification, and returns an object even if
-  // unverified. The caller of get() must check .verified .
-  // The "fxaccounts:onverified" event will fire only when the verified
-  // state goes from false to true, so callers must register their observer
-  // and then call get(). In particular, it will not fire when the account
-  // was found to be verified in a previous boot: if our stored state says
-  // the account is verified, the event will never fire. So callers must do:
-  //   register notification observer (go)
-  //   userdata = get()
-  //   if (userdata.verified()) {go()}
-
-  /**
-   * Get the user currently signed in to Firefox Accounts.
-   *
-   * @return Promise
-   *        The promise resolves to the credentials object of the signed-in user:
-   *        {
-   *          email: The user's email address
-   *          uid: The user's unique id
-   *          sessionToken: Session for the FxA server
-   *          kA: An encryption key from the FxA server
-   *          kB: An encryption key derived from the user's FxA password
-   *          verified: email verification status
-   *          authAt: The time (seconds since epoch) that this record was
-   *                  authenticated
-   *        }
-   *        or null if no user is signed in.
-   */
-  getSignedInUser: function getSignedInUser() {
-    let currentState = this.currentAccountState;
-    return currentState.getUserAccountData().then(data => {
-      if (!data) {
-        return null;
-      }
-      if (!this.isUserEmailVerified(data)) {
-        // If the email is not verified, start polling for verification,
-        // but return null right away.  We don't want to return a promise
-        // that might not be fulfilled for a long time.
-        this.startVerifiedCheck(data);
-      }
-      return data;
-    }).then(result => currentState.resolve(result));
-  },
-
-  /**
-   * Set the current user signed in to Firefox Accounts.
-   *
-   * @param credentials
-   *        The credentials object obtained by logging in or creating
-   *        an account on the FxA server:
-   *        {
-   *          authAt: The time (seconds since epoch) that this record was
-   *                  authenticated
-   *          email: The users email address
-   *          keyFetchToken: a keyFetchToken which has not yet been used
-   *          sessionToken: Session for the FxA server
-   *          uid: The user's unique id
-   *          unwrapBKey: used to unwrap kB, derived locally from the
-   *                      password (not revealed to the FxA server)
-   *          verified: true/false
-   *        }
-   * @return Promise
-   *         The promise resolves to null when the data is saved
-   *         successfully and is rejected on error.
-   */
-  setSignedInUser: function setSignedInUser(credentials) {
-    log.debug("setSignedInUser - aborting any existing flows");
-    return this.abortExistingFlow().then(() => {
-      let currentAccountState = this.currentAccountState = this.newAccountState(
-        Cu.cloneInto(credentials, {}) // Pass a clone of the credentials object.
-      );
-      // This promise waits for storage, but not for verification.
-      // We're telling the caller that this is durable now (although is that
-      // really something we should commit to? Why not let the write happen in
-      // the background? Already does for updateAccountData ;)
-      return currentAccountState.promiseInitialized.then(() => {
-        // Starting point for polling if new user
-        if (!this.isUserEmailVerified(credentials)) {
-          this.startVerifiedCheck(credentials);
-        }
-
-        return this.updateDeviceRegistration();
-      }).then(() => {
-        Services.telemetry.getHistogramById("FXA_CONFIGURED").add(1);
-        this.notifyObservers(ONLOGIN_NOTIFICATION);
-      }).then(() => {
-        return currentAccountState.resolve();
-      });
-    })
-  },
-
-  /**
-   * Update account data for the currently signed in user.
-   *
-   * @param credentials
-   *        The credentials object containing the fields to be updated.
-   *        This object must contain |email| and |uid| fields and they must
-   *        match the currently signed in user.
-   */
-  updateUserAccountData(credentials) {
-    log.debug("updateUserAccountData called with fields", Object.keys(credentials));
-    if (logPII) {
-      log.debug("updateUserAccountData called with data", credentials);
-    }
-    let currentAccountState = this.currentAccountState;
-    return currentAccountState.promiseInitialized.then(() => {
-      return currentAccountState.getUserAccountData(["email", "uid"]);
-    }).then(existing => {
-      if (existing.email != credentials.email || existing.uid != credentials.uid) {
-        throw new Error("The specified credentials aren't for the current user");
-      }
-      // We need to nuke email and uid as storage will complain if we try and
-      // update them (even when the value is the same)
-      credentials = Cu.cloneInto(credentials, {}); // clone it first
-      delete credentials.email;
-      delete credentials.uid;
-      return currentAccountState.updateUserAccountData(credentials);
-    });
-  },
-
-  /**
-   * returns a promise that fires with the assertion.  If there is no verified
-   * signed-in user, fires with null.
-   */
-  getAssertion: function getAssertion(audience) {
-    return this._getAssertion(audience);
-  },
-
-  // getAssertion() is "public" so screws with our mock story. This
-  // implementation method *can* be (and is) mocked by tests.
-  _getAssertion: function _getAssertion(audience) {
-    log.debug("enter getAssertion()");
-    let currentState = this.currentAccountState;
-    return currentState.getUserAccountData().then(data => {
-      if (!data) {
-        // No signed-in user
-        return null;
-      }
-      if (!this.isUserEmailVerified(data)) {
-        // Signed-in user has not verified email
-        return null;
-      }
-      if (!data.sessionToken) {
-        // can't get a signed certificate without a session token. This
-        // can happen if we request an assertion after clearing an invalid
-        // session token from storage.
-        throw this._error(ERROR_AUTH_ERROR, "getAssertion called without a session token");
-      }
-      return this.getKeypairAndCertificate(currentState).then(
-        ({keyPair, certificate}) => {
-          return this.getAssertionFromCert(data, keyPair, certificate, audience);
-        }
-      );
-    }).catch(err =>
-      this._handleTokenError(err)
-    ).then(result => currentState.resolve(result));
-  },
-
-  /**
-   * Invalidate the FxA certificate, so that it will be refreshed from the server
-   * the next time it is needed.
-   */
-  invalidateCertificate() {
-    return this.currentAccountState.updateUserAccountData({ cert: null });
-  },
-
-  getDeviceId() {
-    return this.currentAccountState.getUserAccountData()
-      .then(data => {
-        if (data) {
-          if (!data.deviceId || !data.deviceRegistrationVersion ||
-              data.deviceRegistrationVersion < this.DEVICE_REGISTRATION_VERSION) {
-            // There is no device id or the device registration is outdated.
-            // Either way, we should register the device with FxA
-            // before returning the id to the caller.
-            return this._registerOrUpdateDevice(data);
-          }
-
-          // Return the device id that we already registered with the server.
-          return data.deviceId;
-        }
-
-        // Without a signed-in user, there can be no device id.
-        return null;
-      });
-  },
-
-  /**
-   * Resend the verification email fot the currently signed-in user.
-   *
-   */
-  resendVerificationEmail: function resendVerificationEmail() {
-    let currentState = this.currentAccountState;
-    return this.getSignedInUser().then(data => {
-      // If the caller is asking for verification to be re-sent, and there is
-      // no signed-in user to begin with, this is probably best regarded as an
-      // error.
-      if (data) {
-        if (!data.sessionToken) {
-          return Promise.reject(new Error(
-            "resendVerificationEmail called without a session token"));
-        }
-        this.pollEmailStatus(currentState, data.sessionToken, "start");
-        return this.fxAccountsClient.resendVerificationEmail(
-          data.sessionToken).catch(err => this._handleTokenError(err));
-      }
-      throw new Error("Cannot resend verification email; no signed-in user");
-    });
-  },
-
-  /*
-   * Reset state such that any previous flow is canceled.
-   */
-  abortExistingFlow: function abortExistingFlow() {
-    if (this.currentTimer) {
-      log.debug("Polling aborted; Another user signing in");
-      clearTimeout(this.currentTimer);
-      this.currentTimer = 0;
-    }
-    if (this._profile) {
-      this._profile.tearDown();
-      this._profile = null;
-    }
-    // We "abort" the accountState and assume our caller is about to throw it
-    // away and replace it with a new one.
-    return this.currentAccountState.abort();
-  },
-
-  accountStatus: function accountStatus() {
-    return this.currentAccountState.getUserAccountData().then(data => {
-      if (!data) {
-        return false;
-      }
-      return this.fxAccountsClient.accountStatus(data.uid);
-    });
-  },
-
-  checkVerificationStatus: function() {
-    log.trace('checkVerificationStatus');
-    let currentState = this.currentAccountState;
-    return currentState.getUserAccountData().then(data => {
-      if (!data) {
-        log.trace("checkVerificationStatus - no user data");
-        return null;
-      }
-
-      // Always check the verification status, even if the local state indicates
-      // we're already verified. If the user changed their password, the check
-      // will fail, and we'll enter the reauth state.
-      log.trace("checkVerificationStatus - forcing verification status check");
-      return this.pollEmailStatus(currentState, data.sessionToken, "push");
-    });
-  },
-
-  _destroyOAuthToken: function(tokenData) {
-    let client = new FxAccountsOAuthGrantClient({
-      serverURL: tokenData.server,
-      client_id: FX_OAUTH_CLIENT_ID
-    });
-    return client.destroyToken(tokenData.token)
-  },
-
-  _destroyAllOAuthTokens: function(tokenInfos) {
-    // let's just destroy them all in parallel...
-    let promises = [];
-    for (let [key, tokenInfo] of Object.entries(tokenInfos || {})) {
-      promises.push(this._destroyOAuthToken(tokenInfo));
-    }
-    return Promise.all(promises);
-  },
-
-  signOut: function signOut(localOnly) {
-    let currentState = this.currentAccountState;
-    let sessionToken;
-    let tokensToRevoke;
-    let deviceId;
-    return currentState.getUserAccountData().then(data => {
-      // Save the session token, tokens to revoke and the
-      // device id for use in the call to signOut below.
-      if (data) {
-        sessionToken = data.sessionToken;
-        tokensToRevoke = data.oauthTokens;
-        deviceId = data.deviceId;
-      }
-      return this._signOutLocal();
-    }).then(() => {
-      // FxAccountsManager calls here, then does its own call
-      // to FxAccountsClient.signOut().
-      if (!localOnly) {
-        // Wrap this in a promise so *any* errors in signOut won't
-        // block the local sign out. This is *not* returned.
-        Promise.resolve().then(() => {
-          // This can happen in the background and shouldn't block
-          // the user from signing out. The server must tolerate
-          // clients just disappearing, so this call should be best effort.
-          if (sessionToken) {
-            return this._signOutServer(sessionToken, deviceId);
-          }
-          log.warn("Missing session token; skipping remote sign out");
-        }).catch(err => {
-          log.error("Error during remote sign out of Firefox Accounts", err);
-        }).then(() => {
-          return this._destroyAllOAuthTokens(tokensToRevoke);
-        }).catch(err => {
-          log.error("Error during destruction of oauth tokens during signout", err);
-        }).then(() => {
-          FxAccountsConfig.resetConfigURLs();
-          // just for testing - notifications are cheap when no observers.
-          this.notifyObservers("testhelper-fxa-signout-complete");
-        })
-      } else {
-        // We want to do this either way -- but if we're signing out remotely we
-        // need to wait until we destroy the oauth tokens if we want that to succeed.
-        FxAccountsConfig.resetConfigURLs();
-      }
-    }).then(() => {
-      this.notifyObservers(ONLOGOUT_NOTIFICATION);
-    });
-  },
-
-  /**
-   * This function should be called in conjunction with a server-side
-   * signOut via FxAccountsClient.
-   */
-  _signOutLocal: function signOutLocal() {
-    let currentAccountState = this.currentAccountState;
-    return currentAccountState.signOut().then(() => {
-      // this "aborts" this.currentAccountState but doesn't make a new one.
-      return this.abortExistingFlow();
-    }).then(() => {
-      this.currentAccountState = this.newAccountState();
-      return this.currentAccountState.promiseInitialized;
-    });
-  },
-
-  _signOutServer(sessionToken, deviceId) {
-    // For now we assume the service being logged out from is Sync, so
-    // we must tell the server to either destroy the device or sign out
-    // (if no device exists). We might need to revisit this when this
-    // FxA code is used in a context that isn't Sync.
-
-    const options = { service: "sync" };
-
-    if (deviceId) {
-      log.debug("destroying device and session");
-      return this.fxAccountsClient.signOutAndDestroyDevice(sessionToken, deviceId, options);
-    }
-
-    log.debug("destroying session");
-    return this.fxAccountsClient.signOut(sessionToken, options);
-  },
-
-  /**
-   * Check the status of the current session using cached credentials.
-   *
-   * @return Promise
-   *        Resolves with a boolean indicating if the session is still valid
-   */
-  sessionStatus() {
-    return this.getSignedInUser().then(data => {
-      if (!data.sessionToken) {
-        return Promise.reject(new Error(
-          "sessionStatus called without a session token"));
-      }
-      return this.fxAccountsClient.sessionStatus(data.sessionToken);
-    });
-  },
-
-  /**
-   * Fetch encryption keys for the signed-in-user from the FxA API server.
-   *
-   * Not for user consumption.  Exists to cause the keys to be fetch.
-   *
-   * Returns user data so that it can be chained with other methods.
-   *
-   * @return Promise
-   *        The promise resolves to the credentials object of the signed-in user:
-   *        {
-   *          email: The user's email address
-   *          uid: The user's unique id
-   *          sessionToken: Session for the FxA server
-   *          kA: An encryption key from the FxA server
-   *          kB: An encryption key derived from the user's FxA password
-   *          verified: email verification status
-   *        }
-   *        or null if no user is signed in
-   */
-  getKeys: function() {
-    let currentState = this.currentAccountState;
-    return currentState.getUserAccountData().then((userData) => {
-      if (!userData) {
-        throw new Error("Can't get keys; User is not signed in");
-      }
-      if (userData.kA && userData.kB) {
-        return userData;
-      }
-      if (!currentState.whenKeysReadyDeferred) {
-        currentState.whenKeysReadyDeferred = Promise.defer();
-        if (userData.keyFetchToken) {
-          this.fetchAndUnwrapKeys(userData.keyFetchToken).then(
-            (dataWithKeys) => {
-              if (!dataWithKeys.kA || !dataWithKeys.kB) {
-                currentState.whenKeysReadyDeferred.reject(
-                  new Error("user data missing kA or kB")
-                );
-                return;
-              }
-              currentState.whenKeysReadyDeferred.resolve(dataWithKeys);
-            },
-            (err) => {
-              currentState.whenKeysReadyDeferred.reject(err);
-            }
-          );
-        } else {
-          currentState.whenKeysReadyDeferred.reject('No keyFetchToken');
-        }
-      }
-      return currentState.whenKeysReadyDeferred.promise;
-    }).catch(err =>
-      this._handleTokenError(err)
-    ).then(result => currentState.resolve(result));
-   },
-
-  fetchAndUnwrapKeys: function(keyFetchToken) {
-    if (logPII) {
-      log.debug("fetchAndUnwrapKeys: token: " + keyFetchToken);
-    }
-    let currentState = this.currentAccountState;
-    return Task.spawn(function* task() {
-      // Sign out if we don't have a key fetch token.
-      if (!keyFetchToken) {
-        log.warn("improper fetchAndUnwrapKeys() call: token missing");
-        yield this.signOut();
-        return null;
-      }
-
-      let {kA, wrapKB} = yield this.fetchKeys(keyFetchToken);
-
-      let data = yield currentState.getUserAccountData();
-
-      // Sanity check that the user hasn't changed out from under us
-      if (data.keyFetchToken !== keyFetchToken) {
-        throw new Error("Signed in user changed while fetching keys!");
-      }
-
-      // Next statements must be synchronous until we setUserAccountData
-      // so that we don't risk getting into a weird state.
-      let kB_hex = CryptoUtils.xor(CommonUtils.hexToBytes(data.unwrapBKey),
-                                   wrapKB);
-
-      if (logPII) {
-        log.debug("kB_hex: " + kB_hex);
-      }
-      let updateData = {
-        kA: CommonUtils.bytesAsHex(kA),
-        kB: CommonUtils.bytesAsHex(kB_hex),
-        keyFetchToken: null, // null values cause the item to be removed.
-        unwrapBKey: null,
-      }
-
-      log.debug("Keys Obtained: kA=" + !!updateData.kA + ", kB=" + !!updateData.kB);
-      if (logPII) {
-        log.debug("Keys Obtained: kA=" + updateData.kA + ", kB=" + updateData.kB);
-      }
-
-      yield currentState.updateUserAccountData(updateData);
-      // We are now ready for business. This should only be invoked once
-      // per setSignedInUser(), regardless of whether we've rebooted since
-      // setSignedInUser() was called.
-      this.notifyObservers(ONVERIFIED_NOTIFICATION);
-      return currentState.getUserAccountData();
-    }.bind(this)).then(result => currentState.resolve(result));
-  },
-
-  getAssertionFromCert: function(data, keyPair, cert, audience) {
-    log.debug("getAssertionFromCert");
-    let payload = {};
-    let d = Promise.defer();
-    let options = {
-      duration: ASSERTION_LIFETIME,
-      localtimeOffsetMsec: this.localtimeOffsetMsec,
-      now: this.now()
-    };
-    let currentState = this.currentAccountState;
-    // "audience" should look like "http://123done.org".
-    // The generated assertion will expire in two minutes.
-    jwcrypto.generateAssertion(cert, keyPair, audience, options, (err, signed) => {
-      if (err) {
-        log.error("getAssertionFromCert: " + err);
-        d.reject(err);
-      } else {
-        log.debug("getAssertionFromCert returning signed: " + !!signed);
-        if (logPII) {
-          log.debug("getAssertionFromCert returning signed: " + signed);
-        }
-        d.resolve(signed);
-      }
-    });
-    return d.promise.then(result => currentState.resolve(result));
-  },
-
-  getCertificateSigned: function(sessionToken, serializedPublicKey, lifetime) {
-    log.debug("getCertificateSigned: " + !!sessionToken + " " + !!serializedPublicKey);
-    if (logPII) {
-      log.debug("getCertificateSigned: " + sessionToken + " " + serializedPublicKey);
-    }
-    return this.fxAccountsClient.signCertificate(
-      sessionToken,
-      JSON.parse(serializedPublicKey),
-      lifetime
-    );
-  },
-
-  /**
-   * returns a promise that fires with {keyPair, certificate}.
-   */
-  getKeypairAndCertificate: Task.async(function* (currentState) {
-    // If the debugging pref to ignore cached authentication credentials is set for Sync,
-    // then don't use any cached key pair/certificate, i.e., generate a new
-    // one and get it signed.
-    // The purpose of this pref is to expedite any auth errors as the result of a
-    // expired or revoked FxA session token, e.g., from resetting or changing the FxA
-    // password.
-    let ignoreCachedAuthCredentials = false;
-    try {
-      ignoreCachedAuthCredentials = Services.prefs.getBoolPref("services.sync.debug.ignoreCachedAuthCredentials");
-    } catch(e) {
-      // Pref doesn't exist
-    }
-    let mustBeValidUntil = this.now() + ASSERTION_USE_PERIOD;
-    let accountData = yield currentState.getUserAccountData(["cert", "keyPair", "sessionToken"]);
-
-    let keyPairValid = !ignoreCachedAuthCredentials &&
-                       accountData.keyPair &&
-                       (accountData.keyPair.validUntil > mustBeValidUntil);
-    let certValid = !ignoreCachedAuthCredentials &&
-                    accountData.cert &&
-                    (accountData.cert.validUntil > mustBeValidUntil);
-    // TODO: get the lifetime from the cert's .exp field
-    if (keyPairValid && certValid) {
-      log.debug("getKeypairAndCertificate: already have keyPair and certificate");
-      return {
-        keyPair: accountData.keyPair.rawKeyPair,
-        certificate: accountData.cert.rawCert
-      }
-    }
-    // We are definately going to generate a new cert, either because it has
-    // already expired, or the keyPair has - and a new keyPair means we must
-    // generate a new cert.
-
-    // A keyPair has a longer lifetime than a cert, so it's possible we will
-    // have a valid keypair but an expired cert, which means we can skip
-    // keypair generation.
-    // Either way, the cert will require hitting the network, so bail now if
-    // we know that's going to fail.
-    if (Services.io.offline) {
-      throw new Error(ERROR_OFFLINE);
-    }
-
-    let keyPair;
-    if (keyPairValid) {
-      keyPair = accountData.keyPair;
-    } else {
-      let keyWillBeValidUntil = this.now() + KEY_LIFETIME;
-      keyPair = yield new Promise((resolve, reject) => {
-        jwcrypto.generateKeyPair("DS160", (err, kp) => {
-          if (err) {
-            return reject(err);
-          }
-          log.debug("got keyPair");
-          resolve({
-            rawKeyPair: kp,
-            validUntil: keyWillBeValidUntil,
-          });
-        });
-      });
-    }
-
-    // and generate the cert.
-    let certWillBeValidUntil = this.now() + CERT_LIFETIME;
-    let certificate = yield this.getCertificateSigned(accountData.sessionToken,
-                                                      keyPair.rawKeyPair.serializedPublicKey,
-                                                      CERT_LIFETIME);
-    log.debug("getCertificate got a new one: " + !!certificate);
-    if (certificate) {
-      // Cache both keypair and cert.
-      let toUpdate = {
-        keyPair,
-        cert: {
-          rawCert: certificate,
-          validUntil: certWillBeValidUntil,
-        },
-      };
-      yield currentState.updateUserAccountData(toUpdate);
-    }
-    return {
-      keyPair: keyPair.rawKeyPair,
-      certificate: certificate,
-    }
-  }),
-
-  getUserAccountData: function() {
-    return this.currentAccountState.getUserAccountData();
-  },
-
-  isUserEmailVerified: function isUserEmailVerified(data) {
-    return !!(data && data.verified);
-  },
-
-  /**
-   * Setup for and if necessary do email verification polling.
-   */
-  loadAndPoll: function() {
-    let currentState = this.currentAccountState;
-    return currentState.getUserAccountData()
-      .then(data => {
-        if (data) {
-          Services.telemetry.getHistogramById("FXA_CONFIGURED").add(1);
-          if (!this.isUserEmailVerified(data)) {
-            this.pollEmailStatus(currentState, data.sessionToken, "start");
-          }
-        }
-        return data;
-      });
-  },
-
-  startVerifiedCheck: function(data) {
-    log.debug("startVerifiedCheck", data && data.verified);
-    if (logPII) {
-      log.debug("startVerifiedCheck with user data", data);
-    }
-
-    // Get us to the verified state, then get the keys. This returns a promise
-    // that will fire when we are completely ready.
-    //
-    // Login is truly complete once keys have been fetched, so once getKeys()
-    // obtains and stores kA and kB, it will fire the onverified observer
-    // notification.
-
-    // The callers of startVerifiedCheck never consume a returned promise (ie,
-    // this is simply kicking off a background fetch) so we must add a rejection
-    // handler to avoid runtime warnings about the rejection not being handled.
-    this.whenVerified(data).then(
-      () => this.getKeys(),
-      err => log.info("startVerifiedCheck promise was rejected: " + err)
-    );
-  },
-
-  whenVerified: function(data) {
-    let currentState = this.currentAccountState;
-    if (data.verified) {
-      log.debug("already verified");
-      return currentState.resolve(data);
-    }
-    if (!currentState.whenVerifiedDeferred) {
-      log.debug("whenVerified promise starts polling for verified email");
-      this.pollEmailStatus(currentState, data.sessionToken, "start");
-    }
-    return currentState.whenVerifiedDeferred.promise.then(
-      result => currentState.resolve(result)
-    );
-  },
-
-  notifyObservers: function(topic, data) {
-    log.debug("Notifying observers of " + topic);
-    Services.obs.notifyObservers(null, topic, data);
-  },
-
-  // XXX - pollEmailStatus should maybe be on the AccountState object?
-  pollEmailStatus: function pollEmailStatus(currentState, sessionToken, why) {
-    log.debug("entering pollEmailStatus: " + why);
-    if (why == "start" || why == "push") {
-      if (this.currentTimer) {
-        log.debug("pollEmailStatus starting while existing timer is running");
-        clearTimeout(this.currentTimer);
-        this.currentTimer = null;
-      }
-
-      // If we were already polling, stop and start again.  This could happen
-      // if the user requested the verification email to be resent while we
-      // were already polling for receipt of an earlier email.
-      this.pollStartDate = Date.now();
-      if (!currentState.whenVerifiedDeferred) {
-        currentState.whenVerifiedDeferred = Promise.defer();
-        // This deferred might not end up with any handlers (eg, if sync
-        // is yet to start up.)  This might cause "A promise chain failed to
-        // handle a rejection" messages, so add an error handler directly
-        // on the promise to log the error.
-        currentState.whenVerifiedDeferred.promise.then(null, err => {
-          log.info("the wait for user verification was stopped: " + err);
-        });
-      }
-    }
-
-    // We return a promise for testing only. Other callers can ignore this,
-    // since verification polling continues in the background.
-    return this.checkEmailStatus(sessionToken, { reason: why })
-      .then((response) => {
-        log.debug("checkEmailStatus -> " + JSON.stringify(response));
-        if (response && response.verified) {
-          currentState.updateUserAccountData({ verified: true })
-            .then(() => {
-              return currentState.getUserAccountData();
-            })
-            .then(data => {
-              // Now that the user is verified, we can proceed to fetch keys
-              if (currentState.whenVerifiedDeferred) {
-                currentState.whenVerifiedDeferred.resolve(data);
-                delete currentState.whenVerifiedDeferred;
-              }
-              // Tell FxAccountsManager to clear its cache
-              this.notifyObservers(ON_FXA_UPDATE_NOTIFICATION, ONVERIFIED_NOTIFICATION);
-            });
-        } else {
-          // Poll email status again after a short delay.
-          this.pollEmailStatusAgain(currentState, sessionToken);
-        }
-      }, error => {
-        let timeoutMs = undefined;
-        if (error && error.retryAfter) {
-          // If the server told us to back off, back off the requested amount.
-          timeoutMs = (error.retryAfter + 3) * 1000;
-        }
-        // The server will return 401 if a request parameter is erroneous or
-        // if the session token expired. Let's continue polling otherwise.
-        if (!error || !error.code || error.code != 401) {
-          this.pollEmailStatusAgain(currentState, sessionToken, timeoutMs);
-        } else {
-          let error = new Error("Verification status check failed");
-          this._rejectWhenVerified(currentState, error);
-        }
-      });
-  },
-
-  _rejectWhenVerified(currentState, error) {
-    currentState.whenVerifiedDeferred.reject(error);
-    delete currentState.whenVerifiedDeferred;
-  },
-
-  // Poll email status using truncated exponential back-off.
-  pollEmailStatusAgain: function (currentState, sessionToken, timeoutMs) {
-    let ageMs = Date.now() - this.pollStartDate;
-    if (ageMs >= this.POLL_SESSION) {
-      if (currentState.whenVerifiedDeferred) {
-        let error = new Error("User email verification timed out.");
-        this._rejectWhenVerified(currentState, error);
-      }
-      log.debug("polling session exceeded, giving up");
-      return;
-    }
-    if (timeoutMs === undefined) {
-      let currentMinute = Math.ceil(ageMs / 60000);
-      timeoutMs = currentMinute <= 2 ? this.VERIFICATION_POLL_TIMEOUT_INITIAL
-                                     : this.VERIFICATION_POLL_TIMEOUT_SUBSEQUENT;
-    }
-    log.debug("polling with timeout = " + timeoutMs);
-    this.currentTimer = setTimeout(() => {
-      this.pollEmailStatus(currentState, sessionToken, "timer");
-    }, timeoutMs);
-  },
-
-  requiresHttps: function() {
-    let allowHttp = false;
-    try {
-      allowHttp = Services.prefs.getBoolPref("identity.fxaccounts.allowHttp");
-    } catch(e) {
-      // Pref doesn't exist
-    }
-    return allowHttp !== true;
-  },
-
-  promiseAccountsSignUpURI() {
-    return FxAccountsConfig.promiseAccountsSignUpURI();
-  },
-
-  promiseAccountsSignInURI() {
-    return FxAccountsConfig.promiseAccountsSignInURI();
-  },
-
-  // Returns a promise that resolves with the URL to use to force a re-signin
-  // of the current account.
-  promiseAccountsForceSigninURI: Task.async(function *() {
-    yield FxAccountsConfig.ensureConfigured();
-    let url = Services.urlFormatter.formatURLPref("identity.fxaccounts.remote.force_auth.uri");
-    if (this.requiresHttps() && !/^https:/.test(url)) { // Comment to un-break emacs js-mode highlighting
-      throw new Error("Firefox Accounts server must use HTTPS");
-    }
-    let currentState = this.currentAccountState;
-    // but we need to append the email address onto a query string.
-    return this.getSignedInUser().then(accountData => {
-      if (!accountData) {
-        return null;
-      }
-      let newQueryPortion = url.indexOf("?") == -1 ? "?" : "&";
-      newQueryPortion += "email=" + encodeURIComponent(accountData.email);
-      return url + newQueryPortion;
-    }).then(result => currentState.resolve(result));
-  }),
-
-  // Returns a promise that resolves with the URL to use to change
-  // the current account's profile image.
-  // if settingToEdit is set, the profile page should hightlight that setting
-  // for the user to edit.
-  promiseAccountsChangeProfileURI: function(entrypoint, settingToEdit = null) {
-    let url = Services.urlFormatter.formatURLPref("identity.fxaccounts.settings.uri");
-
-    if (settingToEdit) {
-      url += (url.indexOf("?") == -1 ? "?" : "&") +
-             "setting=" + encodeURIComponent(settingToEdit);
-    }
-
-    if (this.requiresHttps() && !/^https:/.test(url)) { // Comment to un-break emacs js-mode highlighting
-      throw new Error("Firefox Accounts server must use HTTPS");
-    }
-    let currentState = this.currentAccountState;
-    // but we need to append the email address onto a query string.
-    return this.getSignedInUser().then(accountData => {
-      if (!accountData) {
-        return null;
-      }
-      let newQueryPortion = url.indexOf("?") == -1 ? "?" : "&";
-      newQueryPortion += "email=" + encodeURIComponent(accountData.email);
-      newQueryPortion += "&uid=" + encodeURIComponent(accountData.uid);
-      if (entrypoint) {
-        newQueryPortion += "&entrypoint=" + encodeURIComponent(entrypoint);
-      }
-      return url + newQueryPortion;
-    }).then(result => currentState.resolve(result));
-  },
-
-  // Returns a promise that resolves with the URL to use to manage the current
-  // user's FxA acct.
-  promiseAccountsManageURI: function(entrypoint) {
-    let url = Services.urlFormatter.formatURLPref("identity.fxaccounts.settings.uri");
-    if (this.requiresHttps() && !/^https:/.test(url)) { // Comment to un-break emacs js-mode highlighting
-      throw new Error("Firefox Accounts server must use HTTPS");
-    }
-    let currentState = this.currentAccountState;
-    // but we need to append the uid and email address onto a query string
-    // (if the server has no matching uid it will offer to sign in with the
-    // email address)
-    return this.getSignedInUser().then(accountData => {
-      if (!accountData) {
-        return null;
-      }
-      let newQueryPortion = url.indexOf("?") == -1 ? "?" : "&";
-      newQueryPortion += "uid=" + encodeURIComponent(accountData.uid) +
-                         "&email=" + encodeURIComponent(accountData.email);
-      if (entrypoint) {
-        newQueryPortion += "&entrypoint=" + encodeURIComponent(entrypoint);
-      }
-      return url + newQueryPortion;
-    }).then(result => currentState.resolve(result));
-  },
-
-  /**
-   * Get an OAuth token for the user
-   *
-   * @param options
-   *        {
-   *          scope: (string/array) the oauth scope(s) being requested. As a
-   *                 convenience, you may pass a string if only one scope is
-   *                 required, or an array of strings if multiple are needed.
-   *        }
-   *
-   * @return Promise.<string | Error>
-   *        The promise resolves the oauth token as a string or rejects with
-   *        an error object ({error: ERROR, details: {}}) of the following:
-   *          INVALID_PARAMETER
-   *          NO_ACCOUNT
-   *          UNVERIFIED_ACCOUNT
-   *          NETWORK_ERROR
-   *          AUTH_ERROR
-   *          UNKNOWN_ERROR
-   */
-  getOAuthToken: Task.async(function* (options = {}) {
-    log.debug("getOAuthToken enter");
-    let scope = options.scope;
-    if (typeof scope === "string") {
-      scope = [scope];
-    }
-
-    if (!scope || !scope.length) {
-      throw this._error(ERROR_INVALID_PARAMETER, "Missing or invalid 'scope' option");
-    }
-
-    yield this._getVerifiedAccountOrReject();
-
-    // Early exit for a cached token.
-    let currentState = this.currentAccountState;
-    let cached = currentState.getCachedToken(scope);
-    if (cached) {
-      log.debug("getOAuthToken returning a cached token");
-      return cached.token;
-    }
-
-    // We are going to hit the server - this is the string we pass to it.
-    let scopeString = scope.join(" ");
-    let client = options.client;
-
-    if (!client) {
-      try {
-        let defaultURL = Services.urlFormatter.formatURLPref("identity.fxaccounts.remote.oauth.uri");
-        client = new FxAccountsOAuthGrantClient({
-          serverURL: defaultURL,
-          client_id: FX_OAUTH_CLIENT_ID
-        });
-      } catch (e) {
-        throw this._error(ERROR_INVALID_PARAMETER, e);
-      }
-    }
-    let oAuthURL = client.serverURL.href;
-
-    try {
-      log.debug("getOAuthToken fetching new token from", oAuthURL);
-      let assertion = yield this.getAssertion(oAuthURL);
-      let result = yield client.getTokenFromAssertion(assertion, scopeString);
-      let token = result.access_token;
-      // If we got one, cache it.
-      if (token) {
-        let entry = {token: token, server: oAuthURL};
-        // But before we do, check the cache again - if we find one now, it
-        // means someone else concurrently requested the same scope and beat
-        // us to the cache write. To be nice to the server, we revoke the one
-        // we just got and return the newly cached value.
-        let cached = currentState.getCachedToken(scope);
-        if (cached) {
-          log.debug("Detected a race for this token - revoking the new one.");
-          this._destroyOAuthToken(entry);
-          return cached.token;
-        }
-        currentState.setCachedToken(scope, entry);
-      }
-      return token;
-    } catch (err) {
-      throw this._errorToErrorClass(err);
-    }
-  }),
-
-  /**
-   * Remove an OAuth token from the token cache.  Callers should call this
-   * after they determine a token is invalid, so a new token will be fetched
-   * on the next call to getOAuthToken().
-   *
-   * @param options
-   *        {
-   *          token: (string) A previously fetched token.
-   *        }
-   * @return Promise.<undefined> This function will always resolve, even if
-   *         an unknown token is passed.
-   */
-   removeCachedOAuthToken: Task.async(function* (options) {
-    if (!options.token || typeof options.token !== "string") {
-      throw this._error(ERROR_INVALID_PARAMETER, "Missing or invalid 'token' option");
-    }
-    let currentState = this.currentAccountState;
-    let existing = currentState.removeCachedToken(options.token);
-    if (existing) {
-      // background destroy.
-      this._destroyOAuthToken(existing).catch(err => {
-        log.warn("FxA failed to revoke a cached token", err);
-      });
-    }
-   }),
-
-  _getVerifiedAccountOrReject: Task.async(function* () {
-    let data = yield this.currentAccountState.getUserAccountData();
-    if (!data) {
-      // No signed-in user
-      throw this._error(ERROR_NO_ACCOUNT);
-    }
-    if (!this.isUserEmailVerified(data)) {
-      // Signed-in user has not verified email
-      throw this._error(ERROR_UNVERIFIED_ACCOUNT);
-    }
-  }),
-
-  /*
-   * Coerce an error into one of the general error cases:
-   *          NETWORK_ERROR
-   *          AUTH_ERROR
-   *          UNKNOWN_ERROR
-   *
-   * These errors will pass through:
-   *          INVALID_PARAMETER
-   *          NO_ACCOUNT
-   *          UNVERIFIED_ACCOUNT
-   */
-  _errorToErrorClass: function (aError) {
-    if (aError.errno) {
-      let error = SERVER_ERRNO_TO_ERROR[aError.errno];
-      return this._error(ERROR_TO_GENERAL_ERROR_CLASS[error] || ERROR_UNKNOWN, aError);
-    } else if (aError.message &&
-        (aError.message === "INVALID_PARAMETER" ||
-        aError.message === "NO_ACCOUNT" ||
-        aError.message === "UNVERIFIED_ACCOUNT" ||
-        aError.message === "AUTH_ERROR")) {
-      return aError;
-    }
-    return this._error(ERROR_UNKNOWN, aError);
-  },
-
-  _error: function(aError, aDetails) {
-    log.error("FxA rejecting with error ${aError}, details: ${aDetails}", {aError, aDetails});
-    let reason = new Error(aError);
-    if (aDetails) {
-      reason.details = aDetails;
-    }
-    return reason;
-  },
-
-  /**
-   * Get the user's account and profile data
-   *
-   * @param options
-   *        {
-   *          contentUrl: (string) Used by the FxAccountsWebChannel.
-   *            Defaults to pref identity.fxaccounts.settings.uri
-   *          profileServerUrl: (string) Used by the FxAccountsWebChannel.
-   *            Defaults to pref identity.fxaccounts.remote.profile.uri
-   *        }
-   *
-   * @return Promise.<object | Error>
-   *        The promise resolves to an accountData object with extra profile
-   *        information such as profileImageUrl, or rejects with
-   *        an error object ({error: ERROR, details: {}}) of the following:
-   *          INVALID_PARAMETER
-   *          NO_ACCOUNT
-   *          UNVERIFIED_ACCOUNT
-   *          NETWORK_ERROR
-   *          AUTH_ERROR
-   *          UNKNOWN_ERROR
-   */
-  getSignedInUserProfile: function () {
-    let currentState = this.currentAccountState;
-    return this.profile.getProfile().then(
-      profileData => {
-        let profile = Cu.cloneInto(profileData, {});
-        return currentState.resolve(profile);
-      },
-      error => {
-        log.error("Could not retrieve profile data", error);
-        return currentState.reject(error);
-      }
-    ).catch(err => Promise.reject(this._errorToErrorClass(err)));
-  },
-
-  // Attempt to update the auth server with whatever device details are stored
-  // in the account data. Returns a promise that always resolves, never rejects.
-  // If the promise resolves to a value, that value is the device id.
-  updateDeviceRegistration() {
-    return this.getSignedInUser().then(signedInUser => {
-      if (signedInUser) {
-        return this._registerOrUpdateDevice(signedInUser);
-      }
-    }).catch(error => this._logErrorAndResetDeviceRegistrationVersion(error));
-  },
-
-  handleDeviceDisconnection(deviceId) {
-    return this.currentAccountState.getUserAccountData()
-      .then(data => data ? data.deviceId : null)
-      .then(localDeviceId => {
-        if (deviceId == localDeviceId) {
-          this.notifyObservers(ON_DEVICE_DISCONNECTED_NOTIFICATION, deviceId);
-          return this.signOut(true);
-        }
-        log.error(
-          "The device ID to disconnect doesn't match with the local device ID.\n"
-          + "Local: " + localDeviceId + ", ID to disconnect: " + deviceId);
-    });
-  },
-
-  /**
-   * Delete all the cached persisted credentials we store for FxA.
-   *
-   * @return Promise resolves when the user data has been persisted
-  */
-  resetCredentials() {
-    // Delete all fields except those required for the user to
-    // reauthenticate.
-    let updateData = {};
-    let clearField = field => {
-      if (!FXA_PWDMGR_REAUTH_WHITELIST.has(field)) {
-        updateData[field] = null;
-      }
-    }
-    FXA_PWDMGR_PLAINTEXT_FIELDS.forEach(clearField);
-    FXA_PWDMGR_SECURE_FIELDS.forEach(clearField);
-    FXA_PWDMGR_MEMORY_FIELDS.forEach(clearField);
-
-    let currentState = this.currentAccountState;
-    return currentState.updateUserAccountData(updateData);
-  },
-
-  // If you change what we send to the FxA servers during device registration,
-  // you'll have to bump the DEVICE_REGISTRATION_VERSION number to force older
-  // devices to re-register when Firefox updates
-  _registerOrUpdateDevice(signedInUser) {
-    try {
-      // Allow tests to skip device registration because:
-      //   1. It makes remote requests to the auth server.
-      //   2. _getDeviceName does not work from xpcshell.
-      //   3. The B2G tests fail when attempting to import services-sync/util.js.
-      if (Services.prefs.getBoolPref("identity.fxaccounts.skipDeviceRegistration")) {
-        return Promise.resolve();
-      }
-    } catch(ignore) {}
-
-    if (!signedInUser.sessionToken) {
-      return Promise.reject(new Error(
-        "_registerOrUpdateDevice called without a session token"));
-    }
-
-    return this.fxaPushService.registerPushEndpoint().then(subscription => {
-      const deviceName = this._getDeviceName();
-      let deviceOptions = {};
-
-      // if we were able to obtain a subscription
-      if (subscription && subscription.endpoint) {
-        deviceOptions.pushCallback = subscription.endpoint;
-        let publicKey = subscription.getKey('p256dh');
-        let authKey = subscription.getKey('auth');
-        if (publicKey && authKey) {
-          deviceOptions.pushPublicKey = urlsafeBase64Encode(publicKey);
-          deviceOptions.pushAuthKey = urlsafeBase64Encode(authKey);
-        }
-      }
-
-      if (signedInUser.deviceId) {
-        log.debug("updating existing device details");
-        return this.fxAccountsClient.updateDevice(
-          signedInUser.sessionToken, signedInUser.deviceId, deviceName, deviceOptions);
-      }
-
-      log.debug("registering new device details");
-      return this.fxAccountsClient.registerDevice(
-        signedInUser.sessionToken, deviceName, this._getDeviceType(), deviceOptions);
-    }).then(device =>
-      this.currentAccountState.updateUserAccountData({
-        deviceId: device.id,
-        deviceRegistrationVersion: this.DEVICE_REGISTRATION_VERSION
-      }).then(() => device.id)
-    ).catch(error => this._handleDeviceError(error, signedInUser.sessionToken));
-  },
-
-  _getDeviceName() {
-    return Utils.getDeviceName();
-  },
-
-  _getDeviceType() {
-    return Utils.getDeviceType();
-  },
-
-  _handleDeviceError(error, sessionToken) {
-    return Promise.resolve().then(() => {
-      if (error.code === 400) {
-        if (error.errno === ERRNO_UNKNOWN_DEVICE) {
-          return this._recoverFromUnknownDevice();
-        }
-
-        if (error.errno === ERRNO_DEVICE_SESSION_CONFLICT) {
-          return this._recoverFromDeviceSessionConflict(error, sessionToken);
-        }
-      }
-
-      // `_handleTokenError` re-throws the error.
-      return this._handleTokenError(error);
-    }).catch(error =>
-      this._logErrorAndResetDeviceRegistrationVersion(error)
-    ).catch(() => {});
-  },
-
-  _recoverFromUnknownDevice() {
-    // FxA did not recognise the device id. Handle it by clearing the device
-    // id on the account data. At next sync or next sign-in, registration is
-    // retried and should succeed.
-    log.warn("unknown device id, clearing the local device data");
-    return this.currentAccountState.updateUserAccountData({ deviceId: null })
-      .catch(error => this._logErrorAndResetDeviceRegistrationVersion(error));
-  },
-
-  _recoverFromDeviceSessionConflict(error, sessionToken) {
-    // FxA has already associated this session with a different device id.
-    // Perhaps we were beaten in a race to register. Handle the conflict:
-    //   1. Fetch the list of devices for the current user from FxA.
-    //   2. Look for ourselves in the list.
-    //   3. If we find a match, set the correct device id and device registration
-    //      version on the account data and return the correct device id. At next
-    //      sync or next sign-in, registration is retried and should succeed.
-    //   4. If we don't find a match, log the original error.
-    log.warn("device session conflict, attempting to ascertain the correct device id");
-    return this.fxAccountsClient.getDeviceList(sessionToken)
-      .then(devices => {
-        const matchingDevices = devices.filter(device => device.isCurrentDevice);
-        const length = matchingDevices.length;
-        if (length === 1) {
-          const deviceId = matchingDevices[0].id;
-          return this.currentAccountState.updateUserAccountData({
-            deviceId,
-            deviceRegistrationVersion: null
-          }).then(() => deviceId);
-        }
-        if (length > 1) {
-          log.error("insane server state, " + length + " devices for this session");
-        }
-        return this._logErrorAndResetDeviceRegistrationVersion(error);
-      }).catch(secondError => {
-        log.error("failed to recover from device-session conflict", secondError);
-        this._logErrorAndResetDeviceRegistrationVersion(error)
-      });
-  },
-
-  _logErrorAndResetDeviceRegistrationVersion(error) {
-    // Device registration should never cause other operations to fail.
-    // If we've reached this point, just log the error and reset the device
-    // registration version on the account data. At next sync or next sign-in,
-    // registration will be retried.
-    log.error("device registration failed", error);
-    return this.currentAccountState.updateUserAccountData({
-      deviceRegistrationVersion: null
-    }).catch(secondError => {
-      log.error(
-        "failed to reset the device registration version, device registration won't be retried",
-        secondError);
-    }).then(() => {});
-  },
-
-  _handleTokenError(err) {
-    if (!err || err.code != 401 || err.errno != ERRNO_INVALID_AUTH_TOKEN) {
-      throw err;
-    }
-    log.warn("recovering from invalid token error", err);
-    return this.accountStatus().then(exists => {
-      if (!exists) {
-        // Delete all local account data. Since the account no longer
-        // exists, we can skip the remote calls.
-        log.info("token invalidated because the account no longer exists");
-        return this.signOut(true);
-      }
-      log.info("clearing credentials to handle invalid token error");
-      return this.resetCredentials();
-    }).then(() => Promise.reject(err));
-  },
-};
-
-
-// A getter for the instance to export
-XPCOMUtils.defineLazyGetter(this, "fxAccounts", function() {
-  let a = new FxAccounts();
-
-  // XXX Bug 947061 - We need a strategy for resuming email verification after
-  // browser restart
-  a.loadAndPoll();
-
-  return a;
-});
diff --git a/services/fxaccounts/FxAccountsClient.jsm b/services/fxaccounts/FxAccountsClient.jsm
deleted file mode 100644
index fbe8da2..0000000
--- a/services/fxaccounts/FxAccountsClient.jsm
+++ /dev/null
@@ -1,623 +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/. */
-
-this.EXPORTED_SYMBOLS = ["FxAccountsClient"];
-
-const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
-
-Cu.import("resource://gre/modules/Log.jsm");
-Cu.import("resource://gre/modules/Promise.jsm");
-Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://services-common/utils.js");
-Cu.import("resource://services-common/hawkclient.js");
-Cu.import("resource://services-common/hawkrequest.js");
-Cu.import("resource://services-crypto/utils.js");
-Cu.import("resource://gre/modules/FxAccountsCommon.js");
-Cu.import("resource://gre/modules/Credentials.jsm");
-
-const HOST_PREF = "identity.fxaccounts.auth.uri";
-
-const SIGNIN = "/account/login";
-const SIGNUP = "/account/create";
-
-this.FxAccountsClient = function(host = Services.prefs.getCharPref(HOST_PREF)) {
-  this.host = host;
-
-  // The FxA auth server expects requests to certain endpoints to be authorized
-  // using Hawk.
-  this.hawk = new HawkClient(host);
-  this.hawk.observerPrefix = "FxA:hawk";
-
-  // Manage server backoff state. C.f.
-  // https://github.com/mozilla/fxa-auth-server/blob/master/docs/api.md#backoff-protocol
-  this.backoffError = null;
-};
-
-this.FxAccountsClient.prototype = {
-
-  /**
-   * Return client clock offset, in milliseconds, as determined by hawk client.
-   * Provided because callers should not have to know about hawk
-   * implementation.
-   *
-   * The offset is the number of milliseconds that must be added to the client
-   * clock to make it equal to the server clock.  For example, if the client is
-   * five minutes ahead of the server, the localtimeOffsetMsec will be -300000.
-   */
-  get localtimeOffsetMsec() {
-    return this.hawk.localtimeOffsetMsec;
-  },
-
-  /*
-   * Return current time in milliseconds
-   *
-   * Not used by this module, but made available to the FxAccounts.jsm
-   * that uses this client.
-   */
-  now: function() {
-    return this.hawk.now();
-  },
-
-  /**
-   * Common code from signIn and signUp.
-   *
-   * @param path
-   *        Request URL path. Can be /account/create or /account/login
-   * @param email
-   *        The email address for the account (utf8)
-   * @param password
-   *        The user's password
-   * @param [getKeys=false]
-   *        If set to true the keyFetchToken will be retrieved
-   * @param [retryOK=true]
-   *        If capitalization of the email is wrong and retryOK is set to true,
-   *        we will retry with the suggested capitalization from the server
-   * @return Promise
-   *        Returns a promise that resolves to an object:
-   *        {
-   *          authAt: authentication time for the session (seconds since epoch)
-   *          email: the primary email for this account
-   *          keyFetchToken: a key fetch token (hex)
-   *          sessionToken: a session token (hex)
-   *          uid: the user's unique ID (hex)
-   *          unwrapBKey: used to unwrap kB, derived locally from the
-   *                      password (not revealed to the FxA server)
-   *          verified (optional): flag indicating verification status of the
-   *                               email
-   *        }
-   */
-  _createSession: function(path, email, password, getKeys=false,
-                           retryOK=true) {
-    return Credentials.setup(email, password).then((creds) => {
-      let data = {
-        authPW: CommonUtils.bytesAsHex(creds.authPW),
-        email: email,
-      };
-      let keys = getKeys ? "?keys=true" : "";
-
-      return this._request(path + keys, "POST", null, data).then(
-        // Include the canonical capitalization of the email in the response so
-        // the caller can set its signed-in user state accordingly.
-        result => {
-          result.email = data.email;
-          result.unwrapBKey = CommonUtils.bytesAsHex(creds.unwrapBKey);
-
-          return result;
-        },
-        error => {
-          log.debug("Session creation failed", error);
-          // If the user entered an email with different capitalization from
-          // what's stored in the database (e.g., Greta.Garbo at gmail.COM as
-          // opposed to greta.garbo at gmail.com), the server will respond with a
-          // errno 120 (code 400) and the expected capitalization of the email.
-          // We retry with this email exactly once.  If successful, we use the
-          // server's version of the email as the signed-in-user's email. This
-          // is necessary because the email also serves as salt; so we must be
-          // in agreement with the server on capitalization.
-          //
-          // API reference:
-          // https://github.com/mozilla/fxa-auth-server/blob/master/docs/api.md
-          if (ERRNO_INCORRECT_EMAIL_CASE === error.errno && retryOK) {
-            if (!error.email) {
-              log.error("Server returned errno 120 but did not provide email");
-              throw error;
-            }
-            return this._createSession(path, error.email, password, getKeys,
-                                       false);
-          }
-          throw error;
-        }
-      );
-    });
-  },
-
-  /**
-   * Create a new Firefox Account and authenticate
-   *
-   * @param email
-   *        The email address for the account (utf8)
-   * @param password
-   *        The user's password
-   * @param [getKeys=false]
-   *        If set to true the keyFetchToken will be retrieved
-   * @return Promise
-   *        Returns a promise that resolves to an object:
-   *        {
-   *          uid: the user's unique ID (hex)
-   *          sessionToken: a session token (hex)
-   *          keyFetchToken: a key fetch token (hex),
-   *          unwrapBKey: used to unwrap kB, derived locally from the
-   *                      password (not revealed to the FxA server)
-   *        }
-   */
-  signUp: function(email, password, getKeys=false) {
-    return this._createSession(SIGNUP, email, password, getKeys,
-                               false /* no retry */);
-  },
-
-  /**
-   * Authenticate and create a new session with the Firefox Account API server
-   *
-   * @param email
-   *        The email address for the account (utf8)
-   * @param password
-   *        The user's password
-   * @param [getKeys=false]
-   *        If set to true the keyFetchToken will be retrieved
-   * @return Promise
-   *        Returns a promise that resolves to an object:
-   *        {
-   *          authAt: authentication time for the session (seconds since epoch)
-   *          email: the primary email for this account
-   *          keyFetchToken: a key fetch token (hex)
-   *          sessionToken: a session token (hex)
-   *          uid: the user's unique ID (hex)
-   *          unwrapBKey: used to unwrap kB, derived locally from the
-   *                      password (not revealed to the FxA server)
-   *          verified: flag indicating verification status of the email
-   *        }
-   */
-  signIn: function signIn(email, password, getKeys=false) {
-    return this._createSession(SIGNIN, email, password, getKeys,
-                               true /* retry */);
-  },
-
-  /**
-   * Check the status of a session given a session token
-   *
-   * @param sessionTokenHex
-   *        The session token encoded in hex
-   * @return Promise
-   *        Resolves with a boolean indicating if the session is still valid
-   */
-  sessionStatus: function (sessionTokenHex) {
-    return this._request("/session/status", "GET",
-      deriveHawkCredentials(sessionTokenHex, "sessionToken")).then(
-        () => Promise.resolve(true),
-        error => {
-          if (isInvalidTokenError(error)) {
-            return Promise.resolve(false);
-          }
-          throw error;
-        }
-      );
-  },
-
-  /**
-   * Destroy the current session with the Firefox Account API server
-   *
-   * @param sessionTokenHex
-   *        The session token encoded in hex
-   * @return Promise
-   */
-  signOut: function (sessionTokenHex, options = {}) {
-    let path = "/session/destroy";
-    if (options.service) {
-      path += "?service=" + encodeURIComponent(options.service);
-    }
-    return this._request(path, "POST",
-      deriveHawkCredentials(sessionTokenHex, "sessionToken"));
-  },
-
-  /**
-   * Check the verification status of the user's FxA email address
-   *
-   * @param sessionTokenHex
-   *        The current session token encoded in hex
-   * @return Promise
-   */
-  recoveryEmailStatus: function (sessionTokenHex, options = {}) {
-    let path = "/recovery_email/status";
-    if (options.reason) {
-      path += "?reason=" + encodeURIComponent(options.reason);
-    }
-
-    return this._request(path, "GET",
-      deriveHawkCredentials(sessionTokenHex, "sessionToken"));
-  },
-
-  /**
-   * Resend the verification email for the user
-   *
-   * @param sessionTokenHex
-   *        The current token encoded in hex
-   * @return Promise
-   */
-  resendVerificationEmail: function(sessionTokenHex) {
-    return this._request("/recovery_email/resend_code", "POST",
-      deriveHawkCredentials(sessionTokenHex, "sessionToken"));
-  },
-
-  /**
-   * Retrieve encryption keys
-   *
-   * @param keyFetchTokenHex
-   *        A one-time use key fetch token encoded in hex
-   * @return Promise
-   *        Returns a promise that resolves to an object:
-   *        {
-   *          kA: an encryption key for recevorable data (bytes)
-   *          wrapKB: an encryption key that requires knowledge of the
-   *                  user's password (bytes)
-   *        }
-   */
-  accountKeys: function (keyFetchTokenHex) {
-    let creds = deriveHawkCredentials(keyFetchTokenHex, "keyFetchToken");
-    let keyRequestKey = creds.extra.slice(0, 32);
-    let morecreds = CryptoUtils.hkdf(keyRequestKey, undefined,
-                                     Credentials.keyWord("account/keys"), 3 * 32);
-    let respHMACKey = morecreds.slice(0, 32);
-    let respXORKey = morecreds.slice(32, 96);
-
-    return this._request("/account/keys", "GET", creds).then(resp => {
-      if (!resp.bundle) {
-        throw new Error("failed to retrieve keys");
-      }
-
-      let bundle = CommonUtils.hexToBytes(resp.bundle);
-      let mac = bundle.slice(-32);
-
-      let hasher = CryptoUtils.makeHMACHasher(Ci.nsICryptoHMAC.SHA256,
-        CryptoUtils.makeHMACKey(respHMACKey));
-
-      let bundleMAC = CryptoUtils.digestBytes(bundle.slice(0, -32), hasher);
-      if (mac !== bundleMAC) {
-        throw new Error("error unbundling encryption keys");
-      }
-
-      let keyAWrapB = CryptoUtils.xor(respXORKey, bundle.slice(0, 64));
-
-      return {
-        kA: keyAWrapB.slice(0, 32),
-        wrapKB: keyAWrapB.slice(32)
-      };
-    });
-  },
-
-  /**
-   * Sends a public key to the FxA API server and returns a signed certificate
-   *
-   * @param sessionTokenHex
-   *        The current session token encoded in hex
-   * @param serializedPublicKey
-   *        A public key (usually generated by jwcrypto)
-   * @param lifetime
-   *        The lifetime of the certificate
-   * @return Promise
-   *        Returns a promise that resolves to the signed certificate.
-   *        The certificate can be used to generate a Persona assertion.
-   * @throws a new Error
-   *         wrapping any of these HTTP code/errno pairs:
-   *           https://github.com/mozilla/fxa-auth-server/blob/master/docs/api.md#response-12
-   */
-  signCertificate: function (sessionTokenHex, serializedPublicKey, lifetime) {
-    let creds = deriveHawkCredentials(sessionTokenHex, "sessionToken");
-
-    let body = { publicKey: serializedPublicKey,
-                 duration: lifetime };
-    return Promise.resolve()
-      .then(_ => this._request("/certificate/sign", "POST", creds, body))
-      .then(resp => resp.cert,
-            err => {
-              log.error("HAWK.signCertificate error: " + JSON.stringify(err));
-              throw err;
-            });
-  },
-
-  /**
-   * Determine if an account exists
-   *
-   * @param email
-   *        The email address to check
-   * @return Promise
-   *        The promise resolves to true if the account exists, or false
-   *        if it doesn't. The promise is rejected on other errors.
-   */
-  accountExists: function (email) {
-    return this.signIn(email, "").then(
-      (cantHappen) => {
-        throw new Error("How did I sign in with an empty password?");
-      },
-      (expectedError) => {
-        switch (expectedError.errno) {
-          case ERRNO_ACCOUNT_DOES_NOT_EXIST:
-            return false;
-            break;
-          case ERRNO_INCORRECT_PASSWORD:
-            return true;
-            break;
-          default:
-            // not so expected, any more ...
-            throw expectedError;
-            break;
-        }
-      }
-    );
-  },
-
-  /**
-   * Given the uid of an existing account (not an arbitrary email), ask
-   * the server if it still exists via /account/status.
-   *
-   * Used for differentiating between password change and account deletion.
-   */
-  accountStatus: function(uid) {
-    return this._request("/account/status?uid="+uid, "GET").then(
-      (result) => {
-        return result.exists;
-      },
-      (error) => {
-        log.error("accountStatus failed with: " + error);
-        return Promise.reject(error);
-      }
-    );
-  },
-
-  /**
-   * Register a new device
-   *
-   * @method registerDevice
-   * @param  sessionTokenHex
-   *         Session token obtained from signIn
-   * @param  name
-   *         Device name
-   * @param  type
-   *         Device type (mobile|desktop)
-   * @param  [options]
-   *         Extra device options
-   * @param  [options.pushCallback]
-   *         `pushCallback` push endpoint callback
-   * @param  [options.pushPublicKey]
-   *         `pushPublicKey` push public key (URLSafe Base64 string)
-   * @param  [options.pushAuthKey]
-   *         `pushAuthKey` push auth secret (URLSafe Base64 string)
-   * @return Promise
-   *         Resolves to an object:
-   *         {
-   *           id: Device identifier
-   *           createdAt: Creation time (milliseconds since epoch)
-   *           name: Name of device
-   *           type: Type of device (mobile|desktop)
-   *         }
-   */
-  registerDevice(sessionTokenHex, name, type, options = {}) {
-    let path = "/account/device";
-
-    let creds = deriveHawkCredentials(sessionTokenHex, "sessionToken");
-    let body = { name, type };
-
-    if (options.pushCallback) {
-      body.pushCallback = options.pushCallback;
-    }
-    if (options.pushPublicKey && options.pushAuthKey) {
-      body.pushPublicKey = options.pushPublicKey;
-      body.pushAuthKey = options.pushAuthKey;
-    }
-
-    return this._request(path, "POST", creds, body);
-  },
-
-  /**
-   * Sends a message to other devices. Must conform with the push payload schema:
-   * https://github.com/mozilla/fxa-auth-server/blob/master/docs/pushpayloads.schema.json
-   *
-   * @method notifyDevice
-   * @param  sessionTokenHex
-   *         Session token obtained from signIn
-   * @param  deviceIds
-   *         Devices to send the message to
-   * @param  payload
-   *         Data to send with the message
-   * @return Promise
-   *         Resolves to an empty object:
-   *         {}
-   */
-  notifyDevices(sessionTokenHex, deviceIds, payload, TTL = 0) {
-    const body = {
-      to: deviceIds,
-      payload,
-      TTL
-    };
-    return this._request("/account/devices/notify", "POST",
-      deriveHawkCredentials(sessionTokenHex, "sessionToken"), body);
-  },
-
-  /**
-   * Update the session or name for an existing device
-   *
-   * @method updateDevice
-   * @param  sessionTokenHex
-   *         Session token obtained from signIn
-   * @param  id
-   *         Device identifier
-   * @param  name
-   *         Device name
-   * @param  [options]
-   *         Extra device options
-   * @param  [options.pushCallback]
-   *         `pushCallback` push endpoint callback
-   * @param  [options.pushPublicKey]
-   *         `pushPublicKey` push public key (URLSafe Base64 string)
-   * @param  [options.pushAuthKey]
-   *         `pushAuthKey` push auth secret (URLSafe Base64 string)
-   * @return Promise
-   *         Resolves to an object:
-   *         {
-   *           id: Device identifier
-   *           name: Device name
-   *         }
-   */
-  updateDevice(sessionTokenHex, id, name, options = {}) {
-    let path = "/account/device";
-
-    let creds = deriveHawkCredentials(sessionTokenHex, "sessionToken");
-    let body = { id, name };
-    if (options.pushCallback) {
-      body.pushCallback = options.pushCallback;
-    }
-    if (options.pushPublicKey && options.pushAuthKey) {
-      body.pushPublicKey = options.pushPublicKey;
-      body.pushAuthKey = options.pushAuthKey;
-    }
-
-    return this._request(path, "POST", creds, body);
-  },
-
-  /**
-   * Delete a device and its associated session token, signing the user
-   * out of the server.
-   *
-   * @method signOutAndDestroyDevice
-   * @param  sessionTokenHex
-   *         Session token obtained from signIn
-   * @param  id
-   *         Device identifier
-   * @param  [options]
-   *         Options object
-   * @param  [options.service]
-   *         `service` query parameter
-   * @return Promise
-   *         Resolves to an empty object:
-   *         {}
-   */
-  signOutAndDestroyDevice(sessionTokenHex, id, options = {}) {
-    let path = "/account/device/destroy";
-
-    if (options.service) {
-      path += "?service=" + encodeURIComponent(options.service);
-    }
-
-    let creds = deriveHawkCredentials(sessionTokenHex, "sessionToken");
-    let body = { id };
-
-    return this._request(path, "POST", creds, body);
-  },
-
-  /**
-   * Get a list of currently registered devices
-   *
-   * @method getDeviceList
-   * @param  sessionTokenHex
-   *         Session token obtained from signIn
-   * @return Promise
-   *         Resolves to an array of objects:
-   *         [
-   *           {
-   *             id: Device id
-   *             isCurrentDevice: Boolean indicating whether the item
-   *                              represents the current device
-   *             name: Device name
-   *             type: Device type (mobile|desktop)
-   *           },
-   *           ...
-   *         ]
-   */
-  getDeviceList(sessionTokenHex) {
-    let path = "/account/devices";
-    let creds = deriveHawkCredentials(sessionTokenHex, "sessionToken");
-
-    return this._request(path, "GET", creds, {});
-  },
-
-  _clearBackoff: function() {
-      this.backoffError = null;
-  },
-
-  /**
-   * A general method for sending raw API calls to the FxA auth server.
-   * All request bodies and responses are JSON.
-   *
-   * @param path
-   *        API endpoint path
-   * @param method
-   *        The HTTP request method
-   * @param credentials
-   *        Hawk credentials
-   * @param jsonPayload
-   *        A JSON payload
-   * @return Promise
-   *        Returns a promise that resolves to the JSON response of the API call,
-   *        or is rejected with an error. Error responses have the following properties:
-   *        {
-   *          "code": 400, // matches the HTTP status code
-   *          "errno": 107, // stable application-level error number
-   *          "error": "Bad Request", // string description of the error type
-   *          "message": "the value of salt is not allowed to be undefined",
-   *          "info": "https://docs.dev.lcip.og/errors/1234" // link to more info on the error
-   *        }
-   */
-  _request: function hawkRequest(path, method, credentials, jsonPayload) {
-    let deferred = Promise.defer();
-
-    // We were asked to back off.
-    if (this.backoffError) {
-      log.debug("Received new request during backoff, re-rejecting.");
-      deferred.reject(this.backoffError);
-      return deferred.promise;
-    }
-
-    this.hawk.request(path, method, credentials, jsonPayload).then(
-      (response) => {
-        try {
-          let responseObj = JSON.parse(response.body);
-          deferred.resolve(responseObj);
-        } catch (err) {
-          log.error("json parse error on response: " + response.body);
-          deferred.reject({error: err});
-        }
-      },
-
-      (error) => {
-        log.error("error " + method + "ing " + path + ": " + JSON.stringify(error));
-        if (error.retryAfter) {
-          log.debug("Received backoff response; caching error as flag.");
-          this.backoffError = error;
-          // Schedule clearing of cached-error-as-flag.
-          CommonUtils.namedTimer(
-            this._clearBackoff,
-            error.retryAfter * 1000,
-            this,
-            "fxaBackoffTimer"
-           );
-        }
-        deferred.reject(error);
-      }
-    );
-
-    return deferred.promise;
-  },
-};
-
-function isInvalidTokenError(error) {
-  if (error.code != 401) {
-    return false;
-  }
-  switch (error.errno) {
-    case ERRNO_INVALID_AUTH_TOKEN:
-    case ERRNO_INVALID_AUTH_TIMESTAMP:
-    case ERRNO_INVALID_AUTH_NONCE:
-      return true;
-  }
-  return false;
-}
diff --git a/services/fxaccounts/FxAccountsCommon.js b/services/fxaccounts/FxAccountsCommon.js
deleted file mode 100644
index 71fe78a..0000000
--- a/services/fxaccounts/FxAccountsCommon.js
+++ /dev/null
@@ -1,368 +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/. */
-
-var { interfaces: Ci, utils: Cu } = Components;
-
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://gre/modules/Log.jsm");
-
-// loglevel should be one of "Fatal", "Error", "Warn", "Info", "Config",
-// "Debug", "Trace" or "All". If none is specified, "Debug" will be used by
-// default.  Note "Debug" is usually appropriate so that when this log is
-// included in the Sync file logs we get verbose output.
-const PREF_LOG_LEVEL = "identity.fxaccounts.loglevel";
-// The level of messages that will be dumped to the console.  If not specified,
-// "Error" will be used.
-const PREF_LOG_LEVEL_DUMP = "identity.fxaccounts.log.appender.dump";
-
-// A pref that can be set so "sensitive" information (eg, personally
-// identifiable info, credentials, etc) will be logged.
-const PREF_LOG_SENSITIVE_DETAILS = "identity.fxaccounts.log.sensitive";
-
-var exports = Object.create(null);
-
-XPCOMUtils.defineLazyGetter(exports, 'log', function() {
-  let log = Log.repository.getLogger("FirefoxAccounts");
-  // We set the log level to debug, but the default dump appender is set to
-  // the level reflected in the pref.  Other code that consumes FxA may then
-  // choose to add another appender at a different level.
-  log.level = Log.Level.Debug;
-  let appender = new Log.DumpAppender();
-  appender.level = Log.Level.Error;
-
-  log.addAppender(appender);
-  try {
-    // The log itself.
-    let level =
-      Services.prefs.getPrefType(PREF_LOG_LEVEL) == Ci.nsIPrefBranch.PREF_STRING
-      && Services.prefs.getCharPref(PREF_LOG_LEVEL);
-    log.level = Log.Level[level] || Log.Level.Debug;
-
-    // The appender.
-    level =
-      Services.prefs.getPrefType(PREF_LOG_LEVEL_DUMP) == Ci.nsIPrefBranch.PREF_STRING
-      && Services.prefs.getCharPref(PREF_LOG_LEVEL_DUMP);
-    appender.level = Log.Level[level] || Log.Level.Error;
-  } catch (e) {
-    log.error(e);
-  }
-
-  return log;
-});
-
-// A boolean to indicate if personally identifiable information (or anything
-// else sensitive, such as credentials) should be logged.
-XPCOMUtils.defineLazyGetter(exports, 'logPII', function() {
-  try {
-    return Services.prefs.getBoolPref(PREF_LOG_SENSITIVE_DETAILS);
-  } catch (_) {
-    return false;
-  }
-});
-
-exports.FXACCOUNTS_PERMISSION = "firefox-accounts";
-
-exports.DATA_FORMAT_VERSION = 1;
-exports.DEFAULT_STORAGE_FILENAME = "signedInUser.json";
-
-// Token life times.
-// Having this parameter be short has limited security value and can cause
-// spurious authentication values if the client's clock is skewed and
-// we fail to adjust. See Bug 983256.
-exports.ASSERTION_LIFETIME = 1000 * 3600 * 24 * 365 * 25; // 25 years
-// This is a time period we want to guarantee that the assertion will be
-// valid after we generate it (e.g., the signed cert won't expire in this
-// period).
-exports.ASSERTION_USE_PERIOD = 1000 * 60 * 5; // 5 minutes
-exports.CERT_LIFETIME      = 1000 * 3600 * 6;  // 6 hours
-exports.KEY_LIFETIME       = 1000 * 3600 * 12; // 12 hours
-
-// After we start polling for account verification, we stop polling when this
-// many milliseconds have elapsed.
-exports.POLL_SESSION       = 1000 * 60 * 20;   // 20 minutes
-
-// Observer notifications.
-exports.ONLOGIN_NOTIFICATION = "fxaccounts:onlogin";
-exports.ONVERIFIED_NOTIFICATION = "fxaccounts:onverified";
-exports.ONLOGOUT_NOTIFICATION = "fxaccounts:onlogout";
-// Internal to services/fxaccounts only
-exports.ON_FXA_UPDATE_NOTIFICATION = "fxaccounts:update";
-exports.ON_DEVICE_DISCONNECTED_NOTIFICATION = "fxaccounts:device_disconnected";
-exports.ON_PASSWORD_CHANGED_NOTIFICATION = "fxaccounts:password_changed";
-exports.ON_PASSWORD_RESET_NOTIFICATION = "fxaccounts:password_reset";
-exports.ON_COLLECTION_CHANGED_NOTIFICATION = "sync:collection_changed";
-
-exports.FXA_PUSH_SCOPE_ACCOUNT_UPDATE = "chrome://fxa-device-update";
-
-exports.ON_PROFILE_CHANGE_NOTIFICATION = "fxaccounts:profilechange";
-exports.ON_ACCOUNT_STATE_CHANGE_NOTIFICATION = "fxaccounts:statechange";
-
-// UI Requests.
-exports.UI_REQUEST_SIGN_IN_FLOW = "signInFlow";
-exports.UI_REQUEST_REFRESH_AUTH = "refreshAuthentication";
-
-// The OAuth client ID for Firefox Desktop
-exports.FX_OAUTH_CLIENT_ID = "5882386c6d801776";
-
-// Firefox Accounts WebChannel ID
-exports.WEBCHANNEL_ID = "account_updates";
-
-// Server errno.
-// From https://github.com/mozilla/fxa-auth-server/blob/master/docs/api.md#response-format
-exports.ERRNO_ACCOUNT_ALREADY_EXISTS         = 101;
-exports.ERRNO_ACCOUNT_DOES_NOT_EXIST         = 102;
-exports.ERRNO_INCORRECT_PASSWORD             = 103;
-exports.ERRNO_UNVERIFIED_ACCOUNT             = 104;
-exports.ERRNO_INVALID_VERIFICATION_CODE      = 105;
-exports.ERRNO_NOT_VALID_JSON_BODY            = 106;
-exports.ERRNO_INVALID_BODY_PARAMETERS        = 107;
-exports.ERRNO_MISSING_BODY_PARAMETERS        = 108;
-exports.ERRNO_INVALID_REQUEST_SIGNATURE      = 109;
-exports.ERRNO_INVALID_AUTH_TOKEN             = 110;
-exports.ERRNO_INVALID_AUTH_TIMESTAMP         = 111;
-exports.ERRNO_MISSING_CONTENT_LENGTH         = 112;
-exports.ERRNO_REQUEST_BODY_TOO_LARGE         = 113;
-exports.ERRNO_TOO_MANY_CLIENT_REQUESTS       = 114;
-exports.ERRNO_INVALID_AUTH_NONCE             = 115;
-exports.ERRNO_ENDPOINT_NO_LONGER_SUPPORTED   = 116;
-exports.ERRNO_INCORRECT_LOGIN_METHOD         = 117;
-exports.ERRNO_INCORRECT_KEY_RETRIEVAL_METHOD = 118;
-exports.ERRNO_INCORRECT_API_VERSION          = 119;
-exports.ERRNO_INCORRECT_EMAIL_CASE           = 120;
-exports.ERRNO_ACCOUNT_LOCKED                 = 121;
-exports.ERRNO_ACCOUNT_UNLOCKED               = 122;
-exports.ERRNO_UNKNOWN_DEVICE                 = 123;
-exports.ERRNO_DEVICE_SESSION_CONFLICT        = 124;
-exports.ERRNO_SERVICE_TEMP_UNAVAILABLE       = 201;
-exports.ERRNO_PARSE                          = 997;
-exports.ERRNO_NETWORK                        = 998;
-exports.ERRNO_UNKNOWN_ERROR                  = 999;
-
-// Offset oauth server errnos so they don't conflict with auth server errnos
-exports.OAUTH_SERVER_ERRNO_OFFSET = 1000;
-
-// OAuth Server errno.
-exports.ERRNO_UNKNOWN_CLIENT_ID              = 101 + exports.OAUTH_SERVER_ERRNO_OFFSET;
-exports.ERRNO_INCORRECT_CLIENT_SECRET        = 102 + exports.OAUTH_SERVER_ERRNO_OFFSET;
-exports.ERRNO_INCORRECT_REDIRECT_URI         = 103 + exports.OAUTH_SERVER_ERRNO_OFFSET;
-exports.ERRNO_INVALID_FXA_ASSERTION          = 104 + exports.OAUTH_SERVER_ERRNO_OFFSET;
-exports.ERRNO_UNKNOWN_CODE                   = 105 + exports.OAUTH_SERVER_ERRNO_OFFSET;
-exports.ERRNO_INCORRECT_CODE                 = 106 + exports.OAUTH_SERVER_ERRNO_OFFSET;
-exports.ERRNO_EXPIRED_CODE                   = 107 + exports.OAUTH_SERVER_ERRNO_OFFSET;
-exports.ERRNO_OAUTH_INVALID_TOKEN            = 108 + exports.OAUTH_SERVER_ERRNO_OFFSET;
-exports.ERRNO_INVALID_REQUEST_PARAM          = 109 + exports.OAUTH_SERVER_ERRNO_OFFSET;
-exports.ERRNO_INVALID_RESPONSE_TYPE          = 110 + exports.OAUTH_SERVER_ERRNO_OFFSET;
-exports.ERRNO_UNAUTHORIZED                   = 111 + exports.OAUTH_SERVER_ERRNO_OFFSET;
-exports.ERRNO_FORBIDDEN                      = 112 + exports.OAUTH_SERVER_ERRNO_OFFSET;
-exports.ERRNO_INVALID_CONTENT_TYPE           = 113 + exports.OAUTH_SERVER_ERRNO_OFFSET;
-
-// Errors.
-exports.ERROR_ACCOUNT_ALREADY_EXISTS         = "ACCOUNT_ALREADY_EXISTS";
-exports.ERROR_ACCOUNT_DOES_NOT_EXIST         = "ACCOUNT_DOES_NOT_EXIST ";
-exports.ERROR_ACCOUNT_LOCKED                 = "ACCOUNT_LOCKED";
-exports.ERROR_ACCOUNT_UNLOCKED               = "ACCOUNT_UNLOCKED";
-exports.ERROR_ALREADY_SIGNED_IN_USER         = "ALREADY_SIGNED_IN_USER";
-exports.ERROR_DEVICE_SESSION_CONFLICT        = "DEVICE_SESSION_CONFLICT";
-exports.ERROR_ENDPOINT_NO_LONGER_SUPPORTED   = "ENDPOINT_NO_LONGER_SUPPORTED";
-exports.ERROR_INCORRECT_API_VERSION          = "INCORRECT_API_VERSION";
-exports.ERROR_INCORRECT_EMAIL_CASE           = "INCORRECT_EMAIL_CASE";
-exports.ERROR_INCORRECT_KEY_RETRIEVAL_METHOD = "INCORRECT_KEY_RETRIEVAL_METHOD";
-exports.ERROR_INCORRECT_LOGIN_METHOD         = "INCORRECT_LOGIN_METHOD";
-exports.ERROR_INVALID_EMAIL                  = "INVALID_EMAIL";
-exports.ERROR_INVALID_AUDIENCE               = "INVALID_AUDIENCE";
-exports.ERROR_INVALID_AUTH_TOKEN             = "INVALID_AUTH_TOKEN";
-exports.ERROR_INVALID_AUTH_TIMESTAMP         = "INVALID_AUTH_TIMESTAMP";
-exports.ERROR_INVALID_AUTH_NONCE             = "INVALID_AUTH_NONCE";
-exports.ERROR_INVALID_BODY_PARAMETERS        = "INVALID_BODY_PARAMETERS";
-exports.ERROR_INVALID_PASSWORD               = "INVALID_PASSWORD";
-exports.ERROR_INVALID_VERIFICATION_CODE      = "INVALID_VERIFICATION_CODE";
-exports.ERROR_INVALID_REFRESH_AUTH_VALUE     = "INVALID_REFRESH_AUTH_VALUE";
-exports.ERROR_INVALID_REQUEST_SIGNATURE      = "INVALID_REQUEST_SIGNATURE";
-exports.ERROR_INTERNAL_INVALID_USER          = "INTERNAL_ERROR_INVALID_USER";
-exports.ERROR_MISSING_BODY_PARAMETERS        = "MISSING_BODY_PARAMETERS";
-exports.ERROR_MISSING_CONTENT_LENGTH         = "MISSING_CONTENT_LENGTH";
-exports.ERROR_NO_TOKEN_SESSION               = "NO_TOKEN_SESSION";
-exports.ERROR_NO_SILENT_REFRESH_AUTH         = "NO_SILENT_REFRESH_AUTH";
-exports.ERROR_NOT_VALID_JSON_BODY            = "NOT_VALID_JSON_BODY";
-exports.ERROR_OFFLINE                        = "OFFLINE";
-exports.ERROR_PERMISSION_DENIED              = "PERMISSION_DENIED";
-exports.ERROR_REQUEST_BODY_TOO_LARGE         = "REQUEST_BODY_TOO_LARGE";
-exports.ERROR_SERVER_ERROR                   = "SERVER_ERROR";
-exports.ERROR_SYNC_DISABLED                  = "SYNC_DISABLED";
-exports.ERROR_TOO_MANY_CLIENT_REQUESTS       = "TOO_MANY_CLIENT_REQUESTS";
-exports.ERROR_SERVICE_TEMP_UNAVAILABLE       = "SERVICE_TEMPORARY_UNAVAILABLE";
-exports.ERROR_UI_ERROR                       = "UI_ERROR";
-exports.ERROR_UI_REQUEST                     = "UI_REQUEST";
-exports.ERROR_PARSE                          = "PARSE_ERROR";
-exports.ERROR_NETWORK                        = "NETWORK_ERROR";
-exports.ERROR_UNKNOWN                        = "UNKNOWN_ERROR";
-exports.ERROR_UNKNOWN_DEVICE                 = "UNKNOWN_DEVICE";
-exports.ERROR_UNVERIFIED_ACCOUNT             = "UNVERIFIED_ACCOUNT";
-
-// OAuth errors.
-exports.ERROR_UNKNOWN_CLIENT_ID              = "UNKNOWN_CLIENT_ID";
-exports.ERROR_INCORRECT_CLIENT_SECRET        = "INCORRECT_CLIENT_SECRET";
-exports.ERROR_INCORRECT_REDIRECT_URI         = "INCORRECT_REDIRECT_URI";
-exports.ERROR_INVALID_FXA_ASSERTION          = "INVALID_FXA_ASSERTION";
-exports.ERROR_UNKNOWN_CODE                   = "UNKNOWN_CODE";
-exports.ERROR_INCORRECT_CODE                 = "INCORRECT_CODE";
-exports.ERROR_EXPIRED_CODE                   = "EXPIRED_CODE";
-exports.ERROR_OAUTH_INVALID_TOKEN            = "OAUTH_INVALID_TOKEN";
-exports.ERROR_INVALID_REQUEST_PARAM          = "INVALID_REQUEST_PARAM";
-exports.ERROR_INVALID_RESPONSE_TYPE          = "INVALID_RESPONSE_TYPE";
-exports.ERROR_UNAUTHORIZED                   = "UNAUTHORIZED";
-exports.ERROR_FORBIDDEN                      = "FORBIDDEN";
-exports.ERROR_INVALID_CONTENT_TYPE           = "INVALID_CONTENT_TYPE";
-
-// Additional generic error classes for external consumers
-exports.ERROR_NO_ACCOUNT                     = "NO_ACCOUNT";
-exports.ERROR_AUTH_ERROR                     = "AUTH_ERROR";
-exports.ERROR_INVALID_PARAMETER              = "INVALID_PARAMETER";
-
-// Status code errors
-exports.ERROR_CODE_METHOD_NOT_ALLOWED        = 405;
-exports.ERROR_MSG_METHOD_NOT_ALLOWED         = "METHOD_NOT_ALLOWED";
-
-// FxAccounts has the ability to "split" the credentials between a plain-text
-// JSON file in the profile dir and in the login manager.
-// In order to prevent new fields accidentally ending up in the "wrong" place,
-// all fields stored are listed here.
-
-// The fields we save in the plaintext JSON.
-// See bug 1013064 comments 23-25 for why the sessionToken is "safe"
-exports.FXA_PWDMGR_PLAINTEXT_FIELDS = new Set(
-  ["email", "verified", "authAt", "sessionToken", "uid", "oauthTokens", "profile",
-  "deviceId", "deviceRegistrationVersion"]);
-
-// Fields we store in secure storage if it exists.
-exports.FXA_PWDMGR_SECURE_FIELDS = new Set(
-  ["kA", "kB", "keyFetchToken", "unwrapBKey", "assertion"]);
-
-// Fields we keep in memory and don't persist anywhere.
-exports.FXA_PWDMGR_MEMORY_FIELDS = new Set(
-  ["cert", "keyPair"]);
-
-// A whitelist of fields that remain in storage when the user needs to
-// reauthenticate. All other fields will be removed.
-exports.FXA_PWDMGR_REAUTH_WHITELIST = new Set(
-  ["email", "uid", "profile", "deviceId", "deviceRegistrationVersion", "verified"]);
-
-// The pseudo-host we use in the login manager
-exports.FXA_PWDMGR_HOST = "chrome://FirefoxAccounts";
-// The realm we use in the login manager.
-exports.FXA_PWDMGR_REALM = "Firefox Accounts credentials";
-
-// Error matching.
-exports.SERVER_ERRNO_TO_ERROR = {};
-
-// Error mapping
-exports.ERROR_TO_GENERAL_ERROR_CLASS = {};
-
-for (let id in exports) {
-  this[id] = exports[id];
-}
-
-// Allow this file to be imported via Components.utils.import().
-this.EXPORTED_SYMBOLS = Object.keys(exports);
-
-// Set these up now that everything has been loaded into |this|.
-SERVER_ERRNO_TO_ERROR[ERRNO_ACCOUNT_ALREADY_EXISTS]         = ERROR_ACCOUNT_ALREADY_EXISTS;
-SERVER_ERRNO_TO_ERROR[ERRNO_ACCOUNT_DOES_NOT_EXIST]         = ERROR_ACCOUNT_DOES_NOT_EXIST;
-SERVER_ERRNO_TO_ERROR[ERRNO_INCORRECT_PASSWORD]             = ERROR_INVALID_PASSWORD;
-SERVER_ERRNO_TO_ERROR[ERRNO_UNVERIFIED_ACCOUNT]             = ERROR_UNVERIFIED_ACCOUNT;
-SERVER_ERRNO_TO_ERROR[ERRNO_INVALID_VERIFICATION_CODE]      = ERROR_INVALID_VERIFICATION_CODE;
-SERVER_ERRNO_TO_ERROR[ERRNO_NOT_VALID_JSON_BODY]            = ERROR_NOT_VALID_JSON_BODY;
-SERVER_ERRNO_TO_ERROR[ERRNO_INVALID_BODY_PARAMETERS]        = ERROR_INVALID_BODY_PARAMETERS;
-SERVER_ERRNO_TO_ERROR[ERRNO_MISSING_BODY_PARAMETERS]        = ERROR_MISSING_BODY_PARAMETERS;
-SERVER_ERRNO_TO_ERROR[ERRNO_INVALID_REQUEST_SIGNATURE]      = ERROR_INVALID_REQUEST_SIGNATURE;
-SERVER_ERRNO_TO_ERROR[ERRNO_INVALID_AUTH_TOKEN]             = ERROR_INVALID_AUTH_TOKEN;
-SERVER_ERRNO_TO_ERROR[ERRNO_INVALID_AUTH_TIMESTAMP]         = ERROR_INVALID_AUTH_TIMESTAMP;
-SERVER_ERRNO_TO_ERROR[ERRNO_MISSING_CONTENT_LENGTH]         = ERROR_MISSING_CONTENT_LENGTH;
-SERVER_ERRNO_TO_ERROR[ERRNO_REQUEST_BODY_TOO_LARGE]         = ERROR_REQUEST_BODY_TOO_LARGE;
-SERVER_ERRNO_TO_ERROR[ERRNO_TOO_MANY_CLIENT_REQUESTS]       = ERROR_TOO_MANY_CLIENT_REQUESTS;
-SERVER_ERRNO_TO_ERROR[ERRNO_INVALID_AUTH_NONCE]             = ERROR_INVALID_AUTH_NONCE;
-SERVER_ERRNO_TO_ERROR[ERRNO_ENDPOINT_NO_LONGER_SUPPORTED]   = ERROR_ENDPOINT_NO_LONGER_SUPPORTED;
-SERVER_ERRNO_TO_ERROR[ERRNO_INCORRECT_LOGIN_METHOD]         = ERROR_INCORRECT_LOGIN_METHOD;
-SERVER_ERRNO_TO_ERROR[ERRNO_INCORRECT_KEY_RETRIEVAL_METHOD] = ERROR_INCORRECT_KEY_RETRIEVAL_METHOD;
-SERVER_ERRNO_TO_ERROR[ERRNO_INCORRECT_API_VERSION]          = ERROR_INCORRECT_API_VERSION;
-SERVER_ERRNO_TO_ERROR[ERRNO_INCORRECT_EMAIL_CASE]           = ERROR_INCORRECT_EMAIL_CASE;
-SERVER_ERRNO_TO_ERROR[ERRNO_ACCOUNT_LOCKED]                 = ERROR_ACCOUNT_LOCKED;
-SERVER_ERRNO_TO_ERROR[ERRNO_ACCOUNT_UNLOCKED]               = ERROR_ACCOUNT_UNLOCKED;
-SERVER_ERRNO_TO_ERROR[ERRNO_UNKNOWN_DEVICE]                 = ERROR_UNKNOWN_DEVICE;
-SERVER_ERRNO_TO_ERROR[ERRNO_DEVICE_SESSION_CONFLICT]        = ERROR_DEVICE_SESSION_CONFLICT;
-SERVER_ERRNO_TO_ERROR[ERRNO_SERVICE_TEMP_UNAVAILABLE]       = ERROR_SERVICE_TEMP_UNAVAILABLE;
-SERVER_ERRNO_TO_ERROR[ERRNO_UNKNOWN_ERROR]                  = ERROR_UNKNOWN;
-SERVER_ERRNO_TO_ERROR[ERRNO_NETWORK]                        = ERROR_NETWORK;
-
-// oauth
-SERVER_ERRNO_TO_ERROR[ERRNO_UNKNOWN_CLIENT_ID]              = ERROR_UNKNOWN_CLIENT_ID;
-SERVER_ERRNO_TO_ERROR[ERRNO_INCORRECT_CLIENT_SECRET]        = ERROR_INCORRECT_CLIENT_SECRET;
-SERVER_ERRNO_TO_ERROR[ERRNO_INCORRECT_REDIRECT_URI]         = ERROR_INCORRECT_REDIRECT_URI;
-SERVER_ERRNO_TO_ERROR[ERRNO_INVALID_FXA_ASSERTION]          = ERROR_INVALID_FXA_ASSERTION;
-SERVER_ERRNO_TO_ERROR[ERRNO_UNKNOWN_CODE]                   = ERROR_UNKNOWN_CODE;
-SERVER_ERRNO_TO_ERROR[ERRNO_INCORRECT_CODE]                 = ERROR_INCORRECT_CODE;
-SERVER_ERRNO_TO_ERROR[ERRNO_EXPIRED_CODE]                   = ERROR_EXPIRED_CODE;
-SERVER_ERRNO_TO_ERROR[ERRNO_OAUTH_INVALID_TOKEN]            = ERROR_OAUTH_INVALID_TOKEN;
-SERVER_ERRNO_TO_ERROR[ERRNO_INVALID_REQUEST_PARAM]          = ERROR_INVALID_REQUEST_PARAM;
-SERVER_ERRNO_TO_ERROR[ERRNO_INVALID_RESPONSE_TYPE]          = ERROR_INVALID_RESPONSE_TYPE;
-SERVER_ERRNO_TO_ERROR[ERRNO_UNAUTHORIZED]                   = ERROR_UNAUTHORIZED;
-SERVER_ERRNO_TO_ERROR[ERRNO_FORBIDDEN]                      = ERROR_FORBIDDEN;
-SERVER_ERRNO_TO_ERROR[ERRNO_INVALID_CONTENT_TYPE]           = ERROR_INVALID_CONTENT_TYPE;
-
-
-// Map internal errors to more generic error classes for consumers
-ERROR_TO_GENERAL_ERROR_CLASS[ERROR_ACCOUNT_ALREADY_EXISTS]         = ERROR_AUTH_ERROR;
-ERROR_TO_GENERAL_ERROR_CLASS[ERROR_ACCOUNT_DOES_NOT_EXIST]         = ERROR_AUTH_ERROR;
-ERROR_TO_GENERAL_ERROR_CLASS[ERROR_ACCOUNT_LOCKED]                 = ERROR_AUTH_ERROR;
-ERROR_TO_GENERAL_ERROR_CLASS[ERROR_ACCOUNT_UNLOCKED]               = ERROR_AUTH_ERROR;
-ERROR_TO_GENERAL_ERROR_CLASS[ERROR_ALREADY_SIGNED_IN_USER]         = ERROR_AUTH_ERROR;
-ERROR_TO_GENERAL_ERROR_CLASS[ERROR_DEVICE_SESSION_CONFLICT]        = ERROR_AUTH_ERROR;
-ERROR_TO_GENERAL_ERROR_CLASS[ERROR_ENDPOINT_NO_LONGER_SUPPORTED]   = ERROR_AUTH_ERROR;
-ERROR_TO_GENERAL_ERROR_CLASS[ERROR_INCORRECT_API_VERSION]          = ERROR_AUTH_ERROR;
-ERROR_TO_GENERAL_ERROR_CLASS[ERROR_INCORRECT_EMAIL_CASE]           = ERROR_AUTH_ERROR;
-ERROR_TO_GENERAL_ERROR_CLASS[ERROR_INCORRECT_KEY_RETRIEVAL_METHOD] = ERROR_AUTH_ERROR;
-ERROR_TO_GENERAL_ERROR_CLASS[ERROR_INCORRECT_LOGIN_METHOD]         = ERROR_AUTH_ERROR;
-ERROR_TO_GENERAL_ERROR_CLASS[ERROR_INVALID_EMAIL]                  = ERROR_AUTH_ERROR;
-ERROR_TO_GENERAL_ERROR_CLASS[ERROR_INVALID_AUDIENCE]               = ERROR_AUTH_ERROR;
-ERROR_TO_GENERAL_ERROR_CLASS[ERROR_INVALID_AUTH_TOKEN]             = ERROR_AUTH_ERROR;
-ERROR_TO_GENERAL_ERROR_CLASS[ERROR_INVALID_AUTH_TIMESTAMP]         = ERROR_AUTH_ERROR;
-ERROR_TO_GENERAL_ERROR_CLASS[ERROR_INVALID_AUTH_NONCE]             = ERROR_AUTH_ERROR;
-ERROR_TO_GENERAL_ERROR_CLASS[ERROR_INVALID_BODY_PARAMETERS]        = ERROR_AUTH_ERROR;
-ERROR_TO_GENERAL_ERROR_CLASS[ERROR_INVALID_PASSWORD]               = ERROR_AUTH_ERROR;
-ERROR_TO_GENERAL_ERROR_CLASS[ERROR_INVALID_VERIFICATION_CODE]      = ERROR_AUTH_ERROR;
-ERROR_TO_GENERAL_ERROR_CLASS[ERROR_INVALID_REFRESH_AUTH_VALUE]     = ERROR_AUTH_ERROR;
-ERROR_TO_GENERAL_ERROR_CLASS[ERROR_INVALID_REQUEST_SIGNATURE]      = ERROR_AUTH_ERROR;
-ERROR_TO_GENERAL_ERROR_CLASS[ERROR_INTERNAL_INVALID_USER]          = ERROR_AUTH_ERROR;
-ERROR_TO_GENERAL_ERROR_CLASS[ERROR_MISSING_BODY_PARAMETERS]        = ERROR_AUTH_ERROR;
-ERROR_TO_GENERAL_ERROR_CLASS[ERROR_MISSING_CONTENT_LENGTH]         = ERROR_AUTH_ERROR;
-ERROR_TO_GENERAL_ERROR_CLASS[ERROR_NO_TOKEN_SESSION]               = ERROR_AUTH_ERROR;
-ERROR_TO_GENERAL_ERROR_CLASS[ERROR_NO_SILENT_REFRESH_AUTH]         = ERROR_AUTH_ERROR;
-ERROR_TO_GENERAL_ERROR_CLASS[ERROR_NOT_VALID_JSON_BODY]            = ERROR_AUTH_ERROR;
-ERROR_TO_GENERAL_ERROR_CLASS[ERROR_PERMISSION_DENIED]              = ERROR_AUTH_ERROR;
-ERROR_TO_GENERAL_ERROR_CLASS[ERROR_REQUEST_BODY_TOO_LARGE]         = ERROR_AUTH_ERROR;
-ERROR_TO_GENERAL_ERROR_CLASS[ERROR_UNKNOWN_DEVICE]                 = ERROR_AUTH_ERROR;
-ERROR_TO_GENERAL_ERROR_CLASS[ERROR_UNVERIFIED_ACCOUNT]             = ERROR_AUTH_ERROR;
-ERROR_TO_GENERAL_ERROR_CLASS[ERROR_UI_ERROR]                       = ERROR_AUTH_ERROR;
-ERROR_TO_GENERAL_ERROR_CLASS[ERROR_UI_REQUEST]                     = ERROR_AUTH_ERROR;
-ERROR_TO_GENERAL_ERROR_CLASS[ERROR_OFFLINE]                        = ERROR_NETWORK;
-ERROR_TO_GENERAL_ERROR_CLASS[ERROR_SERVER_ERROR]                   = ERROR_NETWORK;
-ERROR_TO_GENERAL_ERROR_CLASS[ERROR_TOO_MANY_CLIENT_REQUESTS]       = ERROR_NETWORK;
-ERROR_TO_GENERAL_ERROR_CLASS[ERROR_SERVICE_TEMP_UNAVAILABLE]       = ERROR_NETWORK;
-ERROR_TO_GENERAL_ERROR_CLASS[ERROR_PARSE]                          = ERROR_NETWORK;
-ERROR_TO_GENERAL_ERROR_CLASS[ERROR_NETWORK]                        = ERROR_NETWORK;
-
-// oauth
-ERROR_TO_GENERAL_ERROR_CLASS[ERROR_INCORRECT_CLIENT_SECRET]        = ERROR_AUTH_ERROR;
-ERROR_TO_GENERAL_ERROR_CLASS[ERROR_INCORRECT_REDIRECT_URI]         = ERROR_AUTH_ERROR;
-ERROR_TO_GENERAL_ERROR_CLASS[ERROR_INVALID_FXA_ASSERTION]          = ERROR_AUTH_ERROR;
-ERROR_TO_GENERAL_ERROR_CLASS[ERROR_UNKNOWN_CODE]                   = ERROR_AUTH_ERROR;
-ERROR_TO_GENERAL_ERROR_CLASS[ERROR_INCORRECT_CODE]                 = ERROR_AUTH_ERROR;
-ERROR_TO_GENERAL_ERROR_CLASS[ERROR_EXPIRED_CODE]                   = ERROR_AUTH_ERROR;
-ERROR_TO_GENERAL_ERROR_CLASS[ERROR_OAUTH_INVALID_TOKEN]            = ERROR_AUTH_ERROR;
-ERROR_TO_GENERAL_ERROR_CLASS[ERROR_INVALID_REQUEST_PARAM]          = ERROR_AUTH_ERROR;
-ERROR_TO_GENERAL_ERROR_CLASS[ERROR_INVALID_RESPONSE_TYPE]          = ERROR_AUTH_ERROR;
-ERROR_TO_GENERAL_ERROR_CLASS[ERROR_UNAUTHORIZED]                   = ERROR_AUTH_ERROR;
-ERROR_TO_GENERAL_ERROR_CLASS[ERROR_FORBIDDEN]                      = ERROR_AUTH_ERROR;
-ERROR_TO_GENERAL_ERROR_CLASS[ERROR_INVALID_CONTENT_TYPE]           = ERROR_AUTH_ERROR;
diff --git a/services/fxaccounts/FxAccountsComponents.manifest b/services/fxaccounts/FxAccountsComponents.manifest
deleted file mode 100644
index 5069755..0000000
--- a/services/fxaccounts/FxAccountsComponents.manifest
+++ /dev/null
@@ -1,4 +0,0 @@
-# FxAccountsPush.js
-component {1b7db999-2ecd-4abf-bb95-a726896798ca} FxAccountsPush.js process=main
-contract @mozilla.org/fxaccounts/push;1 {1b7db999-2ecd-4abf-bb95-a726896798ca}
-category push chrome://fxa-device-update @mozilla.org/fxaccounts/push;1
diff --git a/services/fxaccounts/FxAccountsConfig.jsm b/services/fxaccounts/FxAccountsConfig.jsm
deleted file mode 100644
index 9dcf532..0000000
--- a/services/fxaccounts/FxAccountsConfig.jsm
+++ /dev/null
@@ -1,179 +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/. */
-"use strict";
-this.EXPORTED_SYMBOLS = ["FxAccountsConfig"];
-
-const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
-
-Cu.import("resource://services-common/rest.js");
-Cu.import("resource://gre/modules/FxAccountsCommon.js");
-Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-Cu.import("resource://gre/modules/Task.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "fxAccounts",
-                                  "resource://gre/modules/FxAccounts.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "EnsureFxAccountsWebChannel",
-                                  "resource://gre/modules/FxAccountsWebChannel.jsm");
-
-const CONFIG_PREFS = [
-  "identity.fxaccounts.auth.uri",
-  "identity.fxaccounts.remote.oauth.uri",
-  "identity.fxaccounts.remote.profile.uri",
-  "identity.sync.tokenserver.uri",
-  "identity.fxaccounts.remote.webchannel.uri",
-  "identity.fxaccounts.settings.uri",
-  "identity.fxaccounts.remote.signup.uri",
-  "identity.fxaccounts.remote.signin.uri",
-  "identity.fxaccounts.remote.force_auth.uri",
-];
-
-this.FxAccountsConfig = {
-
-  // Returns a promise that resolves with the URI of the remote UI flows.
-  promiseAccountsSignUpURI: Task.async(function*() {
-    yield this.ensureConfigured();
-    let url = Services.urlFormatter.formatURLPref("identity.fxaccounts.remote.signup.uri");
-    if (fxAccounts.requiresHttps() && !/^https:/.test(url)) { // Comment to un-break emacs js-mode highlighting
-      throw new Error("Firefox Accounts server must use HTTPS");
-    }
-    return url;
-  }),
-
-  // Returns a promise that resolves with the URI of the remote UI flows.
-  promiseAccountsSignInURI: Task.async(function*() {
-    yield this.ensureConfigured();
-    let url = Services.urlFormatter.formatURLPref("identity.fxaccounts.remote.signin.uri");
-    if (fxAccounts.requiresHttps() && !/^https:/.test(url)) { // Comment to un-break emacs js-mode highlighting
-      throw new Error("Firefox Accounts server must use HTTPS");
-    }
-    return url;
-  }),
-
-  resetConfigURLs() {
-    let autoconfigURL = this.getAutoConfigURL();
-    if (!autoconfigURL) {
-      return;
-    }
-    // They have the autoconfig uri pref set, so we clear all the prefs that we
-    // will have initialized, which will leave them pointing at production.
-    for (let pref of CONFIG_PREFS) {
-      Services.prefs.clearUserPref(pref);
-    }
-    // Reset the webchannel.
-    EnsureFxAccountsWebChannel();
-    if (!Services.prefs.prefHasUserValue("webchannel.allowObject.urlWhitelist")) {
-      return;
-    }
-    let whitelistValue = Services.prefs.getCharPref("webchannel.allowObject.urlWhitelist");
-    if (whitelistValue.startsWith(autoconfigURL + " ")) {
-      whitelistValue = whitelistValue.slice(autoconfigURL.length + 1);
-      // Check and see if the value will be the default, and just clear the pref if it would
-      // to avoid it showing up as changed in about:config.
-      let defaultWhitelist;
-      try {
-        defaultWhitelist = Services.prefs.getDefaultBranch("webchannel.allowObject.").getCharPref("urlWhitelist");
-      } catch (e) {
-        // No default value ...
-      }
-
-      if (defaultWhitelist === whitelistValue) {
-        Services.prefs.clearUserPref("webchannel.allowObject.urlWhitelist");
-      } else {
-        Services.prefs.setCharPref("webchannel.allowObject.urlWhitelist", whitelistValue);
-      }
-    }
-  },
-
-  getAutoConfigURL() {
-    let pref;
-    try {
-      pref = Services.prefs.getCharPref("identity.fxaccounts.autoconfig.uri");
-    } catch (e) { /* no pref */ }
-    if (!pref) {
-      // no pref / empty pref means we don't bother here.
-      return "";
-    }
-    let rootURL = Services.urlFormatter.formatURL(pref);
-    if (rootURL.endsWith("/")) {
-      rootURL.slice(0, -1);
-    }
-    return rootURL;
-  },
-
-  ensureConfigured: Task.async(function*() {
-    let isSignedIn = !!(yield fxAccounts.getSignedInUser());
-    if (!isSignedIn) {
-      yield this.fetchConfigURLs();
-    }
-  }),
-
-  // Read expected client configuration from the fxa auth server
-  // (from `identity.fxaccounts.autoconfig.uri`/.well-known/fxa-client-configuration)
-  // and replace all the relevant our prefs with the information found there.
-  // This is only done before sign-in and sign-up, and even then only if the
-  // `identity.fxaccounts.autoconfig.uri` preference is set.
-  fetchConfigURLs: Task.async(function*() {
-    let rootURL = this.getAutoConfigURL();
-    if (!rootURL) {
-      return;
-    }
-    let configURL = rootURL + "/.well-known/fxa-client-configuration";
-    let jsonStr = yield new Promise((resolve, reject) => {
-      let request = new RESTRequest(configURL);
-      request.setHeader("Accept", "application/json");
-      request.get(error => {
-        if (error) {
-          log.error(`Failed to get configuration object from "${configURL}"`, error);
-          return reject(error);
-        }
-        if (!request.response.success) {
-          log.error(`Received HTTP response code ${request.response.status} from configuration object request`);
-          if (request.response && request.response.body) {
-            log.debug("Got error response", request.response.body);
-          }
-          return reject(request.response.status);
-        }
-        resolve(request.response.body);
-      });
-    });
-
-    log.debug("Got successful configuration response", jsonStr);
-    try {
-      // Update the prefs directly specified by the config.
-      let config = JSON.parse(jsonStr)
-      let authServerBase = config.auth_server_base_url;
-      if (!authServerBase.endsWith("/v1")) {
-        authServerBase += "/v1";
-      }
-      Services.prefs.setCharPref("identity.fxaccounts.auth.uri", authServerBase);
-      Services.prefs.setCharPref("identity.fxaccounts.remote.oauth.uri", config.oauth_server_base_url + "/v1");
-      Services.prefs.setCharPref("identity.fxaccounts.remote.profile.uri", config.profile_server_base_url + "/v1");
-      Services.prefs.setCharPref("identity.sync.tokenserver.uri", config.sync_tokenserver_base_url + "/1.0/sync/1.5");
-      // Update the prefs that are based off of the autoconfig url
-
-      let contextParam = encodeURIComponent(
-        Services.prefs.getCharPref("identity.fxaccounts.contextParam"));
-
-      Services.prefs.setCharPref("identity.fxaccounts.remote.webchannel.uri", rootURL);
-      Services.prefs.setCharPref("identity.fxaccounts.settings.uri", rootURL + "/settings?service=sync&context=" + contextParam);
-      Services.prefs.setCharPref("identity.fxaccounts.remote.signup.uri", rootURL + "/signup?service=sync&context=" + contextParam);
-      Services.prefs.setCharPref("identity.fxaccounts.remote.signin.uri", rootURL + "/signin?service=sync&context=" + contextParam);
-      Services.prefs.setCharPref("identity.fxaccounts.remote.force_auth.uri", rootURL + "/force_auth?service=sync&context=" + contextParam);
-
-      let whitelistValue = Services.prefs.getCharPref("webchannel.allowObject.urlWhitelist");
-      if (!whitelistValue.includes(rootURL)) {
-        whitelistValue = `${rootURL} ${whitelistValue}`;
-        Services.prefs.setCharPref("webchannel.allowObject.urlWhitelist", whitelistValue);
-      }
-      // Ensure the webchannel is pointed at the correct uri
-      EnsureFxAccountsWebChannel();
-    } catch (e) {
-      log.error("Failed to initialize configuration preferences from autoconfig object", e);
-      throw e;
-    }
-  }),
-
-};
diff --git a/services/fxaccounts/FxAccountsOAuthClient.jsm b/services/fxaccounts/FxAccountsOAuthClient.jsm
deleted file mode 100644
index c59f1a8..0000000
--- a/services/fxaccounts/FxAccountsOAuthClient.jsm
+++ /dev/null
@@ -1,269 +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/. */
-
-/**
- * Firefox Accounts OAuth browser login helper.
- * Uses the WebChannel component to receive OAuth messages and complete login flows.
- */
-
-this.EXPORTED_SYMBOLS = ["FxAccountsOAuthClient"];
-
-const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
-
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-Cu.import("resource://gre/modules/Log.jsm");
-Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://gre/modules/FxAccountsCommon.js");
-XPCOMUtils.defineLazyModuleGetter(this, "WebChannel",
-                                  "resource://gre/modules/WebChannel.jsm");
-Cu.importGlobalProperties(["URL"]);
-
-/**
- * Create a new FxAccountsOAuthClient for browser some service.
- *
- * @param {Object} options Options
- *   @param {Object} options.parameters
- *   Opaque alphanumeric token to be included in verification links
- *     @param {String} options.parameters.client_id
- *     OAuth id returned from client registration
- *     @param {String} options.parameters.state
- *     A value that will be returned to the client as-is upon redirection
- *     @param {String} options.parameters.oauth_uri
- *     The FxA OAuth server uri
- *     @param {String} options.parameters.content_uri
- *     The FxA Content server uri
- *     @param {String} [options.parameters.scope]
- *     Optional. A colon-separated list of scopes that the user has authorized
- *     @param {String} [options.parameters.action]
- *     Optional. If provided, should be either signup, signin or force_auth.
- *     @param {String} [options.parameters.email]
- *     Optional. Required if options.paramters.action is 'force_auth'.
- *     @param {Boolean} [options.parameters.keys]
- *     Optional. If true then relier-specific encryption keys will be
- *     available in the second argument to onComplete.
- *   @param [authorizationEndpoint] {String}
- *   Optional authorization endpoint for the OAuth server
- * @constructor
- */
-this.FxAccountsOAuthClient = function(options) {
-  this._validateOptions(options);
-  this.parameters = options.parameters;
-  this._configureChannel();
-
-  let authorizationEndpoint = options.authorizationEndpoint || "/authorization";
-
-  try {
-    this._fxaOAuthStartUrl = new URL(this.parameters.oauth_uri + authorizationEndpoint + "?");
-  } catch (e) {
-    throw new Error("Invalid OAuth Url");
-  }
-
-  let params = this._fxaOAuthStartUrl.searchParams;
-  params.append("client_id", this.parameters.client_id);
-  params.append("state", this.parameters.state);
-  params.append("scope", this.parameters.scope || "");
-  params.append("action", this.parameters.action || "signin");
-  params.append("webChannelId", this._webChannelId);
-  if (this.parameters.keys) {
-    params.append("keys", "true");
-  }
-  // Only append if we actually have a value.
-  if (this.parameters.email) {
-    params.append("email", this.parameters.email);
-  }
-};
-
-this.FxAccountsOAuthClient.prototype = {
-  /**
-   * Function that gets called once the OAuth flow is complete.
-   * The callback will receive an object with code and state properties.
-   * If the keys parameter was specified and true, the callback will receive
-   * a second argument with kAr and kBr properties.
-   */
-  onComplete: null,
-  /**
-   * Function that gets called if there is an error during the OAuth flow,
-   * for example due to a state mismatch.
-   * The callback will receive an Error object as its argument.
-   */
-  onError: null,
-  /**
-   * Configuration object that stores all OAuth parameters.
-   */
-  parameters: null,
-  /**
-   * WebChannel that is used to communicate with content page.
-   */
-  _channel: null,
-  /**
-   * Boolean to indicate if this client has completed an OAuth flow.
-   */
-  _complete: false,
-  /**
-   * The url that opens the Firefox Accounts OAuth flow.
-   */
-  _fxaOAuthStartUrl: null,
-  /**
-   * WebChannel id.
-   */
-  _webChannelId: null,
-  /**
-   * WebChannel origin, used to validate origin of messages.
-   */
-  _webChannelOrigin: null,
-  /**
-   * Opens a tab at "this._fxaOAuthStartUrl".
-   * Registers a WebChannel listener and sets up a callback if needed.
-   */
-  launchWebFlow: function () {
-    if (!this._channelCallback) {
-      this._registerChannel();
-    }
-
-    if (this._complete) {
-      throw new Error("This client already completed the OAuth flow");
-    } else {
-      let opener = Services.wm.getMostRecentWindow("navigator:browser").gBrowser;
-      opener.selectedTab = opener.addTab(this._fxaOAuthStartUrl.href);
-    }
-  },
-
-  /**
-   * Release all resources that are in use.
-   */
-  tearDown: function() {
-    this.onComplete = null;
-    this.onError = null;
-    this._complete = true;
-    this._channel.stopListening();
-    this._channel = null;
-  },
-
-  /**
-   * Configures WebChannel id and origin
-   *
-   * @private
-   */
-  _configureChannel: function() {
-    this._webChannelId = "oauth_" + this.parameters.client_id;
-
-    // if this.parameters.content_uri is present but not a valid URI, then this will throw an error.
-    try {
-      this._webChannelOrigin = Services.io.newURI(this.parameters.content_uri, null, null);
-    } catch (e) {
-      throw e;
-    }
-  },
-
-  /**
-   * Create a new channel with the WebChannelBroker, setup a callback listener
-   * @private
-   */
-  _registerChannel: function() {
-    /**
-     * Processes messages that are called back from the FxAccountsChannel
-     *
-     * @param webChannelId {String}
-     *        Command webChannelId
-     * @param message {Object}
-     *        Command message
-     * @param sendingContext {Object}
-     *        Channel message event sendingContext
-     * @private
-     */
-    let listener = function (webChannelId, message, sendingContext) {
-      if (message) {
-        let command = message.command;
-        let data = message.data;
-        let target = sendingContext && sendingContext.browser;
-
-        switch (command) {
-          case "oauth_complete":
-            // validate the returned state and call onComplete or onError
-            let result = null;
-            let err = null;
-
-            if (this.parameters.state !== data.state) {
-              err = new Error("OAuth flow failed. State doesn't match");
-            } else if (this.parameters.keys && !data.keys) {
-              err = new Error("OAuth flow failed. Keys were not returned");
-            } else {
-              result = {
-                code: data.code,
-                state: data.state
-              };
-            }
-
-            // if the message asked to close the tab
-            if (data.closeWindow && target) {
-              // for e10s reasons the best way is to use the TabBrowser to close the tab.
-              let tabbrowser = target.getTabBrowser();
-
-              if (tabbrowser) {
-                let tab = tabbrowser.getTabForBrowser(target);
-
-                if (tab) {
-                  tabbrowser.removeTab(tab);
-                  log.debug("OAuth flow closed the tab.");
-                } else {
-                  log.debug("OAuth flow failed to close the tab. Tab not found in TabBrowser.");
-                }
-              } else {
-                log.debug("OAuth flow failed to close the tab. TabBrowser not found.");
-              }
-            }
-
-            if (err) {
-              log.debug(err.message);
-              if (this.onError) {
-                this.onError(err);
-              }
-            } else {
-              log.debug("OAuth flow completed.");
-              if (this.onComplete) {
-                if (this.parameters.keys) {
-                  this.onComplete(result, data.keys);
-                } else {
-                  this.onComplete(result);
-                }
-              }
-            }
-
-            // onComplete will be called for this client only once
-            // calling onComplete again will result in a failure of the OAuth flow
-            this.tearDown();
-            break;
-        }
-      }
-    };
-
-    this._channelCallback = listener.bind(this);
-    this._channel = new WebChannel(this._webChannelId, this._webChannelOrigin);
-    this._channel.listen(this._channelCallback);
-    log.debug("Channel registered: " + this._webChannelId + " with origin " + this._webChannelOrigin.prePath);
-  },
-
-  /**
-   * Validates the required FxA OAuth parameters
-   *
-   * @param options {Object}
-   *        OAuth client options
-   * @private
-   */
-  _validateOptions: function (options) {
-    if (!options || !options.parameters) {
-      throw new Error("Missing 'parameters' configuration option");
-    }
-
-    ["oauth_uri", "client_id", "content_uri", "state"].forEach(option => {
-      if (!options.parameters[option]) {
-        throw new Error("Missing 'parameters." + option + "' parameter");
-      }
-    });
-
-    if (options.parameters.action == "force_auth" && !options.parameters.email) {
-      throw new Error("parameters.email is required for action 'force_auth'");
-    }
-  },
-};
diff --git a/services/fxaccounts/FxAccountsOAuthGrantClient.jsm b/services/fxaccounts/FxAccountsOAuthGrantClient.jsm
deleted file mode 100644
index 4319a07..0000000
--- a/services/fxaccounts/FxAccountsOAuthGrantClient.jsm
+++ /dev/null
@@ -1,241 +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/. */
-
-/**
- * Firefox Accounts OAuth Grant Client allows clients to obtain
- * an OAuth token from a BrowserID assertion. Only certain client
- * IDs support this privilage.
- */
-
-this.EXPORTED_SYMBOLS = ["FxAccountsOAuthGrantClient", "FxAccountsOAuthGrantClientError"];
-
-const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
-
-Cu.import("resource://gre/modules/Promise.jsm");
-Cu.import("resource://gre/modules/Log.jsm");
-Cu.import("resource://gre/modules/FxAccountsCommon.js");
-Cu.import("resource://services-common/rest.js");
-
-Cu.importGlobalProperties(["URL"]);
-
-const AUTH_ENDPOINT = "/authorization";
-const DESTROY_ENDPOINT = "/destroy";
-
-/**
- * Create a new FxAccountsOAuthClient for browser some service.
- *
- * @param {Object} options Options
- *   @param {Object} options.parameters
- *     @param {String} options.parameters.client_id
- *     OAuth id returned from client registration
- *     @param {String} options.parameters.serverURL
- *     The FxA OAuth server URL
- *   @param [authorizationEndpoint] {String}
- *   Optional authorization endpoint for the OAuth server
- * @constructor
- */
-this.FxAccountsOAuthGrantClient = function(options) {
-
-  this._validateOptions(options);
-  this.parameters = options;
-
-  try {
-    this.serverURL = new URL(this.parameters.serverURL);
-  } catch (e) {
-    throw new Error("Invalid 'serverURL'");
-  }
-
-  log.debug("FxAccountsOAuthGrantClient Initialized");
-};
-
-this.FxAccountsOAuthGrantClient.prototype = {
-
-  /**
-   * Retrieves an OAuth access token for the signed in user
-   *
-   * @param {Object} assertion BrowserID assertion
-   * @param {String} scope OAuth scope
-   * @return Promise
-   *        Resolves: {Object} Object with access_token property
-   */
-  getTokenFromAssertion: function (assertion, scope) {
-    if (!assertion) {
-      throw new Error("Missing 'assertion' parameter");
-    }
-    if (!scope) {
-      throw new Error("Missing 'scope' parameter");
-    }
-    let params = {
-      scope: scope,
-      client_id: this.parameters.client_id,
-      assertion: assertion,
-      response_type: "token"
-    };
-
-    return this._createRequest(AUTH_ENDPOINT, "POST", params);
-  },
-
-  /**
-   * Destroys a previously fetched OAuth access token.
-   *
-   * @param {String} token The previously fetched token
-   * @return Promise
-   *        Resolves: {Object} with the server response, which is typically
-   *        ignored.
-   */
-  destroyToken: function (token) {
-    if (!token) {
-      throw new Error("Missing 'token' parameter");
-    }
-    let params = {
-      token: token,
-    };
-
-    return this._createRequest(DESTROY_ENDPOINT, "POST", params);
-  },
-
-  /**
-   * Validates the required FxA OAuth parameters
-   *
-   * @param options {Object}
-   *        OAuth client options
-   * @private
-   */
-  _validateOptions: function (options) {
-    if (!options) {
-      throw new Error("Missing configuration options");
-    }
-
-    ["serverURL", "client_id"].forEach(option => {
-      if (!options[option]) {
-        throw new Error("Missing '" + option + "' parameter");
-      }
-    });
-  },
-
-  /**
-   * Interface for making remote requests.
-   */
-  _Request: RESTRequest,
-
-  /**
-   * Remote request helper
-   *
-   * @param {String} path
-   *        Profile server path, i.e "/profile".
-   * @param {String} [method]
-   *        Type of request, i.e "GET".
-   * @return Promise
-   *         Resolves: {Object} Successful response from the Profile server.
-   *         Rejects: {FxAccountsOAuthGrantClientError} Profile client error.
-   * @private
-   */
-  _createRequest: function(path, method = "POST", params) {
-    return new Promise((resolve, reject) => {
-      let profileDataUrl = this.serverURL + path;
-      let request = new this._Request(profileDataUrl);
-      method = method.toUpperCase();
-
-      request.setHeader("Accept", "application/json");
-      request.setHeader("Content-Type", "application/json");
-
-      request.onComplete = function (error) {
-        if (error) {
-          return reject(new FxAccountsOAuthGrantClientError({
-            error: ERROR_NETWORK,
-            errno: ERRNO_NETWORK,
-            message: error.toString(),
-          }));
-        }
-
-        let body = null;
-        try {
-          body = JSON.parse(request.response.body);
-        } catch (e) {
-          return reject(new FxAccountsOAuthGrantClientError({
-            error: ERROR_PARSE,
-            errno: ERRNO_PARSE,
-            code: request.response.status,
-            message: request.response.body,
-          }));
-        }
-
-        // "response.success" means status code is 200
-        if (request.response.success) {
-          return resolve(body);
-        }
-
-        if (typeof body.errno === 'number') {
-          // Offset oauth server errnos to avoid conflict with other FxA server errnos
-          body.errno += OAUTH_SERVER_ERRNO_OFFSET;
-        } else if (body.errno) {
-          body.errno = ERRNO_UNKNOWN_ERROR;
-        }
-        return reject(new FxAccountsOAuthGrantClientError(body));
-      };
-
-      if (method === "POST") {
-        request.post(params);
-      } else {
-        // method not supported
-        return reject(new FxAccountsOAuthGrantClientError({
-          error: ERROR_NETWORK,
-          errno: ERRNO_NETWORK,
-          code: ERROR_CODE_METHOD_NOT_ALLOWED,
-          message: ERROR_MSG_METHOD_NOT_ALLOWED,
-        }));
-      }
-    });
-  },
-
-};
-
-/**
- * Normalized profile client errors
- * @param {Object} [details]
- *        Error details object
- *   @param {number} [details.code]
- *          Error code
- *   @param {number} [details.errno]
- *          Error number
- *   @param {String} [details.error]
- *          Error description
- *   @param {String|null} [details.message]
- *          Error message
- * @constructor
- */
-this.FxAccountsOAuthGrantClientError = function(details) {
-  details = details || {};
-
-  this.name = "FxAccountsOAuthGrantClientError";
-  this.code = details.code || null;
-  this.errno = details.errno || ERRNO_UNKNOWN_ERROR;
-  this.error = details.error || ERROR_UNKNOWN;
-  this.message = details.message || null;
-};
-
-/**
- * Returns error object properties
- *
- * @returns {{name: *, code: *, errno: *, error: *, message: *}}
- * @private
- */
-FxAccountsOAuthGrantClientError.prototype._toStringFields = function() {
-  return {
-    name: this.name,
-    code: this.code,
-    errno: this.errno,
-    error: this.error,
-    message: this.message,
-  };
-};
-
-/**
- * String representation of a oauth grant client error
- *
- * @returns {String}
- */
-FxAccountsOAuthGrantClientError.prototype.toString = function() {
-  return this.name + "(" + JSON.stringify(this._toStringFields()) + ")";
-};
diff --git a/services/fxaccounts/FxAccountsProfile.jsm b/services/fxaccounts/FxAccountsProfile.jsm
deleted file mode 100644
index b63cd64..0000000
--- a/services/fxaccounts/FxAccountsProfile.jsm
+++ /dev/null
@@ -1,191 +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/. */
-
-"use strict";
-
-/**
- * Firefox Accounts Profile helper.
- *
- * This class abstracts interaction with the profile server for an account.
- * It will handle things like fetching profile data, listening for updates to
- * the user's profile in open browser tabs, and cacheing/invalidating profile data.
- */
-
-this.EXPORTED_SYMBOLS = ["FxAccountsProfile"];
-
-const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
-
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://gre/modules/FxAccountsCommon.js");
-Cu.import("resource://gre/modules/FxAccounts.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "FxAccountsProfileClient",
-  "resource://gre/modules/FxAccountsProfileClient.jsm");
-
-// Based off of deepEqual from Assert.jsm
-function deepEqual(actual, expected) {
-  if (actual === expected) {
-    return true;
-  } else if (typeof actual != "object" && typeof expected != "object") {
-    return actual == expected;
-  } else {
-    return objEquiv(actual, expected);
-  }
-}
-
-function isUndefinedOrNull(value) {
-  return value === null || value === undefined;
-}
-
-function objEquiv(a, b) {
-  if (isUndefinedOrNull(a) || isUndefinedOrNull(b)) {
-    return false;
-  }
-  if (a.prototype !== b.prototype) {
-    return false;
-  }
-  let ka, kb, key, i;
-  try {
-    ka = Object.keys(a);
-    kb = Object.keys(b);
-  } catch (e) {
-    return false;
-  }
-  if (ka.length != kb.length) {
-    return false;
-  }
-  ka.sort();
-  kb.sort();
-  for (i = ka.length - 1; i >= 0; i--) {
-    key = ka[i];
-    if (!deepEqual(a[key], b[key])) {
-      return false;
-    }
-  }
-  return true;
-}
-
-function hasChanged(oldData, newData) {
-  return !deepEqual(oldData, newData);
-}
-
-this.FxAccountsProfile = function (options = {}) {
-  this._cachedProfile = null;
-  this._cachedAt = 0; // when we saved the cached version.
-  this._currentFetchPromise = null;
-  this._isNotifying = false; // are we sending a notification?
-  this.fxa = options.fxa || fxAccounts;
-  this.client = options.profileClient || new FxAccountsProfileClient({
-    fxa: this.fxa,
-    serverURL: options.profileServerUrl,
-  });
-
-  // An observer to invalidate our _cachedAt optimization. We use a weak-ref
-  // just incase this.tearDown isn't called in some cases.
-  Services.obs.addObserver(this, ON_PROFILE_CHANGE_NOTIFICATION, true);
-  // for testing
-  if (options.channel) {
-    this.channel = options.channel;
-  }
-}
-
-this.FxAccountsProfile.prototype = {
-  // If we get subsequent requests for a profile within this period, don't bother
-  // making another request to determine if it is fresh or not.
-  PROFILE_FRESHNESS_THRESHOLD: 120000, // 2 minutes
-
-  observe(subject, topic, data) {
-    // If we get a profile change notification from our webchannel it means
-    // the user has just changed their profile via the web, so we want to
-    // ignore our "freshness threshold"
-    if (topic == ON_PROFILE_CHANGE_NOTIFICATION && !this._isNotifying) {
-      log.debug("FxAccountsProfile observed profile change");
-      this._cachedAt = 0;
-    }
-  },
-
-  tearDown: function () {
-    this.fxa = null;
-    this.client = null;
-    this._cachedProfile = null;
-    Services.obs.removeObserver(this, ON_PROFILE_CHANGE_NOTIFICATION);
-  },
-
-  _getCachedProfile: function () {
-    // The cached profile will end up back in the generic accountData
-    // once bug 1157529 is fixed.
-    return Promise.resolve(this._cachedProfile);
-  },
-
-  _notifyProfileChange: function (uid) {
-    this._isNotifying = true;
-    Services.obs.notifyObservers(null, ON_PROFILE_CHANGE_NOTIFICATION, uid);
-    this._isNotifying = false;
-  },
-
-  // Cache fetched data if it is different from what's in the cache.
-  // Send out a notification if it has changed so that UI can update.
-  _cacheProfile: function (profileData) {
-    if (!hasChanged(this._cachedProfile, profileData)) {
-      log.debug("fetched profile matches cached copy");
-      return Promise.resolve(null); // indicates no change (but only tests care)
-    }
-    this._cachedProfile = profileData;
-    this._cachedAt = Date.now();
-    return this.fxa.getSignedInUser()
-      .then(userData => {
-        log.debug("notifying profile changed for user ${uid}", userData);
-        this._notifyProfileChange(userData.uid);
-        return profileData;
-      });
-  },
-
-  _fetchAndCacheProfile: function () {
-    if (!this._currentFetchPromise) {
-      this._currentFetchPromise = this.client.fetchProfile().then(profile => {
-        return this._cacheProfile(profile).then(() => {
-          return profile;
-        });
-      }).then(profile => {
-        this._currentFetchPromise = null;
-        return profile;
-      }, err => {
-        this._currentFetchPromise = null;
-        throw err;
-      });
-    }
-    return this._currentFetchPromise
-  },
-
-  // Returns cached data right away if available, then fetches the latest profile
-  // data in the background. After data is fetched a notification will be sent
-  // out if the profile has changed.
-  getProfile: function () {
-    return this._getCachedProfile()
-      .then(cachedProfile => {
-        if (cachedProfile) {
-          if (Date.now() > this._cachedAt + this.PROFILE_FRESHNESS_THRESHOLD) {
-            // Note that _fetchAndCacheProfile isn't returned, so continues
-            // in the background.
-            this._fetchAndCacheProfile().catch(err => {
-              log.error("Background refresh of profile failed", err);
-            });
-          } else {
-            log.trace("not checking freshness of profile as it remains recent");
-          }
-          return cachedProfile;
-        }
-        return this._fetchAndCacheProfile();
-      })
-      .then(profile => {
-        return profile;
-      });
-  },
-
-  QueryInterface: XPCOMUtils.generateQI([
-      Ci.nsIObserver,
-      Ci.nsISupportsWeakReference,
-  ]),
-};
diff --git a/services/fxaccounts/FxAccountsProfileClient.jsm b/services/fxaccounts/FxAccountsProfileClient.jsm
deleted file mode 100644
index 1e5edc6..0000000
--- a/services/fxaccounts/FxAccountsProfileClient.jsm
+++ /dev/null
@@ -1,260 +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/. */
-
-/**
- * A client to fetch profile information for a Firefox Account.
- */
- "use strict;"
-
-this.EXPORTED_SYMBOLS = ["FxAccountsProfileClient", "FxAccountsProfileClientError"];
-
-const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
-
-Cu.import("resource://gre/modules/Promise.jsm");
-Cu.import("resource://gre/modules/Log.jsm");
-Cu.import("resource://gre/modules/FxAccountsCommon.js");
-Cu.import("resource://gre/modules/FxAccounts.jsm");
-Cu.import("resource://gre/modules/Task.jsm");
-Cu.import("resource://services-common/rest.js");
-
-Cu.importGlobalProperties(["URL"]);
-
-/**
- * Create a new FxAccountsProfileClient to be able to fetch Firefox Account profile information.
- *
- * @param {Object} options Options
- *   @param {String} options.serverURL
- *   The URL of the profile server to query.
- *   Example: https://profile.accounts.firefox.com/v1
- *   @param {String} options.token
- *   The bearer token to access the profile server
- * @constructor
- */
-this.FxAccountsProfileClient = function(options) {
-  if (!options || !options.serverURL) {
-    throw new Error("Missing 'serverURL' configuration option");
-  }
-
-  this.fxa = options.fxa || fxAccounts;
-  // This is a work-around for loop that manages its own oauth tokens.
-  // * If |token| is in options we use it and don't attempt any token refresh
-  //  on 401. This is for loop.
-  // * If |token| doesn't exist we will fetch our own token. This is for the
-  //   normal FxAccounts methods for obtaining the profile.
-  // We should nuke all |this.token| support once loop moves closer to FxAccounts.
-  this.token = options.token;
-
-  try {
-    this.serverURL = new URL(options.serverURL);
-  } catch (e) {
-    throw new Error("Invalid 'serverURL'");
-  }
-  this.oauthOptions = {
-    scope: "profile",
-  };
-  log.debug("FxAccountsProfileClient: Initialized");
-};
-
-this.FxAccountsProfileClient.prototype = {
-  /**
-   * {nsIURI}
-   * The server to fetch profile information from.
-   */
-  serverURL: null,
-
-  /**
-   * Interface for making remote requests.
-   */
-  _Request: RESTRequest,
-
-  /**
-   * Remote request helper which abstracts authentication away.
-   *
-   * @param {String} path
-   *        Profile server path, i.e "/profile".
-   * @param {String} [method]
-   *        Type of request, i.e "GET".
-   * @return Promise
-   *         Resolves: {Object} Successful response from the Profile server.
-   *         Rejects: {FxAccountsProfileClientError} Profile client error.
-   * @private
-   */
-  _createRequest: Task.async(function* (path, method = "GET") {
-    let token = this.token;
-    if (!token) {
-      // tokens are cached, so getting them each request is cheap.
-      token = yield this.fxa.getOAuthToken(this.oauthOptions);
-    }
-    try {
-      return (yield this._rawRequest(path, method, token));
-    } catch (ex) {
-      if (!(ex instanceof FxAccountsProfileClientError) || ex.code != 401) {
-        throw ex;
-      }
-      // If this object was instantiated with a token then we don't refresh it.
-      if (this.token) {
-        throw ex;
-      }
-      // it's an auth error - assume our token expired and retry.
-      log.info("Fetching the profile returned a 401 - revoking our token and retrying");
-      yield this.fxa.removeCachedOAuthToken({token});
-      token = yield this.fxa.getOAuthToken(this.oauthOptions);
-      // and try with the new token - if that also fails then we fail after
-      // revoking the token.
-      try {
-        return (yield this._rawRequest(path, method, token));
-      } catch (ex) {
-        if (!(ex instanceof FxAccountsProfileClientError) || ex.code != 401) {
-          throw ex;
-        }
-        log.info("Retry fetching the profile still returned a 401 - revoking our token and failing");
-        yield this.fxa.removeCachedOAuthToken({token});
-        throw ex;
-      }
-    }
-  }),
-
-  /**
-   * Remote "raw" request helper - doesn't handle auth errors and tokens.
-   *
-   * @param {String} path
-   *        Profile server path, i.e "/profile".
-   * @param {String} method
-   *        Type of request, i.e "GET".
-   * @param {String} token
-   * @return Promise
-   *         Resolves: {Object} Successful response from the Profile server.
-   *         Rejects: {FxAccountsProfileClientError} Profile client error.
-   * @private
-   */
-  _rawRequest: function(path, method, token) {
-    return new Promise((resolve, reject) => {
-      let profileDataUrl = this.serverURL + path;
-      let request = new this._Request(profileDataUrl);
-      method = method.toUpperCase();
-
-      request.setHeader("Authorization", "Bearer " + token);
-      request.setHeader("Accept", "application/json");
-
-      request.onComplete = function (error) {
-        if (error) {
-          return reject(new FxAccountsProfileClientError({
-            error: ERROR_NETWORK,
-            errno: ERRNO_NETWORK,
-            message: error.toString(),
-          }));
-        }
-
-        let body = null;
-        try {
-          body = JSON.parse(request.response.body);
-        } catch (e) {
-          return reject(new FxAccountsProfileClientError({
-            error: ERROR_PARSE,
-            errno: ERRNO_PARSE,
-            code: request.response.status,
-            message: request.response.body,
-          }));
-        }
-
-        // "response.success" means status code is 200
-        if (request.response.success) {
-          return resolve(body);
-        } else {
-          return reject(new FxAccountsProfileClientError({
-            error: body.error || ERROR_UNKNOWN,
-            errno: body.errno || ERRNO_UNKNOWN_ERROR,
-            code: request.response.status,
-            message: body.message || body,
-          }));
-        }
-      };
-
-      if (method === "GET") {
-        request.get();
-      } else {
-        // method not supported
-        return reject(new FxAccountsProfileClientError({
-          error: ERROR_NETWORK,
-          errno: ERRNO_NETWORK,
-          code: ERROR_CODE_METHOD_NOT_ALLOWED,
-          message: ERROR_MSG_METHOD_NOT_ALLOWED,
-        }));
-      }
-    });
-  },
-
-  /**
-   * Retrieve user's profile from the server
-   *
-   * @return Promise
-   *         Resolves: {Object} Successful response from the '/profile' endpoint.
-   *         Rejects: {FxAccountsProfileClientError} profile client error.
-   */
-  fetchProfile: function () {
-    log.debug("FxAccountsProfileClient: Requested profile");
-    return this._createRequest("/profile", "GET");
-  },
-
-  /**
-   * Retrieve user's profile from the server
-   *
-   * @return Promise
-   *         Resolves: {Object} Successful response from the '/avatar' endpoint.
-   *         Rejects: {FxAccountsProfileClientError} profile client error.
-   */
-  fetchProfileImage: function () {
-    log.debug("FxAccountsProfileClient: Requested avatar");
-    return this._createRequest("/avatar", "GET");
-  }
-};
-
-/**
- * Normalized profile client errors
- * @param {Object} [details]
- *        Error details object
- *   @param {number} [details.code]
- *          Error code
- *   @param {number} [details.errno]
- *          Error number
- *   @param {String} [details.error]
- *          Error description
- *   @param {String|null} [details.message]
- *          Error message
- * @constructor
- */
-this.FxAccountsProfileClientError = function(details) {
-  details = details || {};
-
-  this.name = "FxAccountsProfileClientError";
-  this.code = details.code || null;
-  this.errno = details.errno || ERRNO_UNKNOWN_ERROR;
-  this.error = details.error || ERROR_UNKNOWN;
-  this.message = details.message || null;
-};
-
-/**
- * Returns error object properties
- *
- * @returns {{name: *, code: *, errno: *, error: *, message: *}}
- * @private
- */
-FxAccountsProfileClientError.prototype._toStringFields = function() {
-  return {
-    name: this.name,
-    code: this.code,
-    errno: this.errno,
-    error: this.error,
-    message: this.message,
-  };
-};
-
-/**
- * String representation of a profile client error
- *
- * @returns {String}
- */
-FxAccountsProfileClientError.prototype.toString = function() {
-  return this.name + "(" + JSON.stringify(this._toStringFields()) + ")";
-};
diff --git a/services/fxaccounts/FxAccountsPush.js b/services/fxaccounts/FxAccountsPush.js
deleted file mode 100644
index 358be06..0000000
--- a/services/fxaccounts/FxAccountsPush.js
+++ /dev/null
@@ -1,240 +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/. */
-
-const Cc = Components.classes;
-const Ci = Components.interfaces;
-const Cu = Components.utils;
-
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://services-sync/util.js");
-Cu.import("resource://gre/modules/FxAccountsCommon.js");
-Cu.import("resource://gre/modules/Task.jsm");
-
-/**
- * FxAccountsPushService manages Push notifications for Firefox Accounts in the browser
- *
- * @param [options]
- *        Object, custom options that used for testing
- * @constructor
- */
-function FxAccountsPushService(options = {}) {
-  this.log = log;
-
-  if (options.log) {
-    // allow custom log for testing purposes
-    this.log = options.log;
-  }
-
-  this.log.debug("FxAccountsPush loading service");
-  this.wrappedJSObject = this;
-  this.initialize(options);
-}
-
-FxAccountsPushService.prototype = {
-  /**
-   * Helps only initialize observers once.
-   */
-  _initialized: false,
-  /**
-   * Instance of the nsIPushService or a mocked object.
-   */
-  pushService: null,
-  /**
-   * Instance of FxAccounts or a mocked object.
-   */
-  fxAccounts: null,
-  /**
-   * Component ID of this service, helps register this component.
-   */
-  classID: Components.ID("{1b7db999-2ecd-4abf-bb95-a726896798ca}"),
-  /**
-   * Register used interfaces in this service
-   */
-  QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]),
-  /**
-   * Initialize the service and register all the required observers.
-   *
-   * @param [options]
-   */
-  initialize(options) {
-    if (this._initialized) {
-      return false;
-    }
-
-    this._initialized = true;
-
-    if (options.pushService) {
-      this.pushService = options.pushService;
-    } else {
-      this.pushService = Cc["@mozilla.org/push/Service;1"].getService(Ci.nsIPushService);
-    }
-
-    if (options.fxAccounts) {
-      this.fxAccounts = options.fxAccounts;
-    } else {
-      XPCOMUtils.defineLazyModuleGetter(this, "fxAccounts",
-        "resource://gre/modules/FxAccounts.jsm");
-    }
-
-    // listen to new push messages, push changes and logout events
-    Services.obs.addObserver(this, this.pushService.pushTopic, false);
-    Services.obs.addObserver(this, this.pushService.subscriptionChangeTopic, false);
-    Services.obs.addObserver(this, ONLOGOUT_NOTIFICATION, false);
-
-    this.log.debug("FxAccountsPush initialized");
-  },
-  /**
-   * Registers a new endpoint with the Push Server
-   *
-   * @returns {Promise}
-   *          Promise always resolves with a subscription or a null if failed to subscribe.
-   */
-  registerPushEndpoint() {
-    this.log.trace("FxAccountsPush registerPushEndpoint");
-
-    return new Promise((resolve) => {
-      this.pushService.subscribe(FXA_PUSH_SCOPE_ACCOUNT_UPDATE,
-        Services.scriptSecurityManager.getSystemPrincipal(),
-        (result, subscription) => {
-          if (Components.isSuccessCode(result)) {
-            this.log.debug("FxAccountsPush got subscription");
-            resolve(subscription);
-          } else {
-            this.log.warn("FxAccountsPush failed to subscribe", result);
-            resolve(null);
-          }
-        });
-    });
-  },
-  /**
-   * Standard observer interface to listen to push messages, changes and logout.
-   *
-   * @param subject
-   * @param topic
-   * @param data
-   * @returns {Promise}
-   */
-  _observe(subject, topic, data) {
-    this.log.trace(`observed topic=${topic}, data=${data}, subject=${subject}`);
-    switch (topic) {
-      case this.pushService.pushTopic:
-        if (data === FXA_PUSH_SCOPE_ACCOUNT_UPDATE) {
-          let message = subject.QueryInterface(Ci.nsIPushMessage);
-          return this._onPushMessage(message);
-        }
-        break;
-      case this.pushService.subscriptionChangeTopic:
-        if (data === FXA_PUSH_SCOPE_ACCOUNT_UPDATE) {
-          return this._onPushSubscriptionChange();
-        }
-        break;
-      case ONLOGOUT_NOTIFICATION:
-        // user signed out, we need to stop polling the Push Server
-        return this.unsubscribe().catch(err => {
-          this.log.error("Error during unsubscribe", err);
-        });
-        break;
-      default:
-        break;
-    }
-  },
-  /**
-   * Wrapper around _observe that catches errors
-   */
-  observe(subject, topic, data) {
-    Promise.resolve()
-      .then(() => this._observe(subject, topic, data))
-      .catch(err => this.log.error(err));
-  },
-  /**
-   * Fired when the Push server sends a notification.
-   *
-   * @private
-   * @returns {Promise}
-   */
-  _onPushMessage(message) {
-    this.log.trace("FxAccountsPushService _onPushMessage");
-    if (!message.data) {
-      // Use the empty signal to check the verification state of the account right away
-      this.log.debug("empty push message - checking account status");
-      return this.fxAccounts.checkVerificationStatus();
-    }
-    let payload = message.data.json();
-    this.log.debug(`push command: ${payload.command}`);
-    switch (payload.command) {
-      case ON_DEVICE_DISCONNECTED_NOTIFICATION:
-        return this.fxAccounts.handleDeviceDisconnection(payload.data.id);
-        break;
-      case ON_PASSWORD_CHANGED_NOTIFICATION:
-      case ON_PASSWORD_RESET_NOTIFICATION:
-        return this._onPasswordChanged();
-        break;
-      case ON_COLLECTION_CHANGED_NOTIFICATION:
-        Services.obs.notifyObservers(null, ON_COLLECTION_CHANGED_NOTIFICATION, payload.data.collections);
-      default:
-        this.log.warn("FxA Push command unrecognized: " + payload.command);
-    }
-  },
-  /**
-   * Check the FxA session status after a password change/reset event.
-   * If the session is invalid, reset credentials and notify listeners of
-   * ON_ACCOUNT_STATE_CHANGE_NOTIFICATION that the account may have changed
-   *
-   * @returns {Promise}
-   * @private
-   */
-  _onPasswordChanged: Task.async(function* () {
-    if (!(yield this.fxAccounts.sessionStatus())) {
-      yield this.fxAccounts.resetCredentials();
-      Services.obs.notifyObservers(null, ON_ACCOUNT_STATE_CHANGE_NOTIFICATION, null);
-    }
-  }),
-  /**
-   * Fired when the Push server drops a subscription, or the subscription identifier changes.
-   *
-   * https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM/Reference/Interface/nsIPushService#Receiving_Push_Messages
-   *
-   * @returns {Promise}
-   * @private
-   */
-  _onPushSubscriptionChange() {
-    this.log.trace("FxAccountsPushService _onPushSubscriptionChange");
-    return this.fxAccounts.updateDeviceRegistration();
-  },
-  /**
-   * Unsubscribe from the Push server
-   *
-   * Ref: https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM/Reference/Interface/nsIPushService#unsubscribe()
-   *
-   * @returns {Promise}
-   * @private
-   */
-  unsubscribe() {
-    this.log.trace("FxAccountsPushService unsubscribe");
-    return new Promise((resolve) => {
-      this.pushService.unsubscribe(FXA_PUSH_SCOPE_ACCOUNT_UPDATE,
-        Services.scriptSecurityManager.getSystemPrincipal(),
-        (result, ok) => {
-          if (Components.isSuccessCode(result)) {
-            if (ok === true) {
-              this.log.debug("FxAccountsPushService unsubscribed");
-            } else {
-              this.log.debug("FxAccountsPushService had no subscription to unsubscribe");
-            }
-          } else {
-            this.log.warn("FxAccountsPushService failed to unsubscribe", result);
-          }
-          return resolve(ok);
-        });
-    });
-  },
-};
-
-// Service registration below registers with FxAccountsComponents.manifest
-const components = [FxAccountsPushService];
-this.NSGetFactory = XPCOMUtils.generateNSGetFactory(components);
-
-// The following registration below helps with testing this service.
-this.EXPORTED_SYMBOLS=["FxAccountsPushService"];
diff --git a/services/fxaccounts/FxAccountsStorage.jsm b/services/fxaccounts/FxAccountsStorage.jsm
deleted file mode 100644
index 4362cdf..0000000
--- a/services/fxaccounts/FxAccountsStorage.jsm
+++ /dev/null
@@ -1,606 +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/. */
-"use strict";
-
-this.EXPORTED_SYMBOLS = [
-  "FxAccountsStorageManagerCanStoreField",
-  "FxAccountsStorageManager",
-];
-
-const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
-
-Cu.import("resource://gre/modules/AppConstants.jsm");
-Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://gre/modules/Task.jsm");
-Cu.import("resource://gre/modules/FxAccountsCommon.js");
-Cu.import("resource://gre/modules/osfile.jsm");
-Cu.import("resource://services-common/utils.js");
-
-var haveLoginManager = true;
-
-// A helper function so code can check what fields are able to be stored by
-// the storage manager without having a reference to a manager instance.
-function FxAccountsStorageManagerCanStoreField(fieldName) {
-  return FXA_PWDMGR_MEMORY_FIELDS.has(fieldName) ||
-         FXA_PWDMGR_PLAINTEXT_FIELDS.has(fieldName) ||
-         FXA_PWDMGR_SECURE_FIELDS.has(fieldName);
-}
-
-// The storage manager object.
-this.FxAccountsStorageManager = function(options = {}) {
-  this.options = {
-    filename: options.filename || DEFAULT_STORAGE_FILENAME,
-    baseDir: options.baseDir || OS.Constants.Path.profileDir,
-  }
-  this.plainStorage = new JSONStorage(this.options);
-  // On b2g we have no loginManager for secure storage, and tests may want
-  // to pretend secure storage isn't available.
-  let useSecure = 'useSecure' in options ? options.useSecure : haveLoginManager;
-  if (useSecure) {
-    this.secureStorage = new LoginManagerStorage();
-  } else {
-    this.secureStorage = null;
-  }
-  this._clearCachedData();
-  // See .initialize() below - this protects against it not being called.
-  this._promiseInitialized = Promise.reject("initialize not called");
-  // A promise to avoid storage races - see _queueStorageOperation
-  this._promiseStorageComplete = Promise.resolve();
-}
-
-this.FxAccountsStorageManager.prototype = {
-  _initialized: false,
-  _needToReadSecure: true,
-
-  // An initialization routine that *looks* synchronous to the callers, but
-  // is actually async as everything else waits for it to complete.
-  initialize(accountData) {
-    if (this._initialized) {
-      throw new Error("already initialized");
-    }
-    this._initialized = true;
-    // If we just throw away our pre-rejected promise it is reported as an
-    // unhandled exception when it is GCd - so add an empty .catch handler here
-    // to prevent this.
-    this._promiseInitialized.catch(() => {});
-    this._promiseInitialized = this._initialize(accountData);
-  },
-
-  _initialize: Task.async(function* (accountData) {
-    log.trace("initializing new storage manager");
-    try {
-      if (accountData) {
-        // If accountData is passed we don't need to read any storage.
-        this._needToReadSecure = false;
-        // split it into the 2 parts, write it and we are done.
-        for (let [name, val] of Object.entries(accountData)) {
-          if (FXA_PWDMGR_PLAINTEXT_FIELDS.has(name)) {
-            this.cachedPlain[name] = val;
-          } else if (FXA_PWDMGR_SECURE_FIELDS.has(name)) {
-            this.cachedSecure[name] = val;
-          } else {
-            // Hopefully it's an "in memory" field. If it's not we log a warning
-            // but still treat it as such (so it will still be available in this
-            // session but isn't persisted anywhere.)
-            if (!FXA_PWDMGR_MEMORY_FIELDS.has(name)) {
-              log.warn("Unknown FxA field name in user data, treating as in-memory", name);
-            }
-            this.cachedMemory[name] = val;
-          }
-        }
-        // write it out and we are done.
-        yield this._write();
-        return;
-      }
-      // So we were initialized without account data - that means we need to
-      // read the state from storage. We try and read plain storage first and
-      // only attempt to read secure storage if the plain storage had a user.
-      this._needToReadSecure = yield this._readPlainStorage();
-      if (this._needToReadSecure && this.secureStorage) {
-        yield this._doReadAndUpdateSecure();
-      }
-    } finally {
-      log.trace("initializing of new storage manager done");
-    }
-  }),
-
-  finalize() {
-    // We can't throw this instance away while it is still writing or we may
-    // end up racing with the newly created one.
-    log.trace("StorageManager finalizing");
-    return this._promiseInitialized.then(() => {
-      return this._promiseStorageComplete;
-    }).then(() => {
-      this._promiseStorageComplete = null;
-      this._promiseInitialized = null;
-      this._clearCachedData();
-      log.trace("StorageManager finalized");
-    })
-  },
-
-  // We want to make sure we don't end up doing multiple storage requests
-  // concurrently - which has a small window for reads if the master-password
-  // is locked at initialization time and becomes unlocked later, and always
-  // has an opportunity for updates.
-  // We also want to make sure we finished writing when finalizing, so we
-  // can't accidentally end up with the previous user's write finishing after
-  // a signOut attempts to clear it.
-  // So all such operations "queue" themselves via this.
-  _queueStorageOperation(func) {
-    // |result| is the promise we return - it has no .catch handler, so callers
-    // of the storage operation still see failure as a normal rejection.
-    let result = this._promiseStorageComplete.then(func);
-    // But the promise we assign to _promiseStorageComplete *does* have a catch
-    // handler so that rejections in one storage operation does not prevent
-    // future operations from starting (ie, _promiseStorageComplete must never
-    // be in a rejected state)
-    this._promiseStorageComplete = result.catch(err => {
-      log.error("${func} failed: ${err}", {func, err});
-    });
-    return result;
-  },
-
-  // Get the account data by combining the plain and secure storage.
-  // If fieldNames is specified, it may be a string or an array of strings,
-  // and only those fields are returned. If not specified the entire account
-  // data is returned except for "in memory" fields. Note that not specifying
-  // field names will soon be deprecated/removed - we want all callers to
-  // specify the fields they care about.
-  getAccountData: Task.async(function* (fieldNames = null) {
-    yield this._promiseInitialized;
-    // We know we are initialized - this means our .cachedPlain is accurate
-    // and doesn't need to be read (it was read if necessary by initialize).
-    // So if there's no uid, there's no user signed in.
-    if (!('uid' in this.cachedPlain)) {
-      return null;
-    }
-    let result = {};
-    if (fieldNames === null) {
-      // The "old" deprecated way of fetching a logged in user.
-      for (let [name, value] of Object.entries(this.cachedPlain)) {
-        result[name] = value;
-      }
-      // But the secure data may not have been read, so try that now.
-      yield this._maybeReadAndUpdateSecure();
-      // .cachedSecure now has as much as it possibly can (which is possibly
-      // nothing if (a) secure storage remains locked and (b) we've never updated
-      // a field to be stored in secure storage.)
-      for (let [name, value] of Object.entries(this.cachedSecure)) {
-        result[name] = value;
-      }
-      // Note we don't return cachedMemory fields here - they must be explicitly
-      // requested.
-      return result;
-    }
-    // The new explicit way of getting attributes.
-    if (!Array.isArray(fieldNames)) {
-      fieldNames = [fieldNames];
-    }
-    let checkedSecure = false;
-    for (let fieldName of fieldNames) {
-      if (FXA_PWDMGR_MEMORY_FIELDS.has(fieldName)) {
-        if (this.cachedMemory[fieldName] !== undefined) {
-          result[fieldName] = this.cachedMemory[fieldName];
-        }
-      } else if (FXA_PWDMGR_PLAINTEXT_FIELDS.has(fieldName)) {
-        if (this.cachedPlain[fieldName] !== undefined) {
-          result[fieldName] = this.cachedPlain[fieldName];
-        }
-      } else if (FXA_PWDMGR_SECURE_FIELDS.has(fieldName)) {
-        // We may not have read secure storage yet.
-        if (!checkedSecure) {
-          yield this._maybeReadAndUpdateSecure();
-          checkedSecure = true;
-        }
-        if (this.cachedSecure[fieldName] !== undefined) {
-          result[fieldName] = this.cachedSecure[fieldName];
-        }
-      } else {
-        throw new Error("unexpected field '" + name + "'");
-      }
-    }
-    return result;
-  }),
-
-  // Update just the specified fields. This DOES NOT allow you to change to
-  // a different user, nor to set the user as signed-out.
-  updateAccountData: Task.async(function* (newFields) {
-    yield this._promiseInitialized;
-    if (!('uid' in this.cachedPlain)) {
-      // If this storage instance shows no logged in user, then you can't
-      // update fields.
-      throw new Error("No user is logged in");
-    }
-    if (!newFields || 'uid' in newFields || 'email' in newFields) {
-      // Once we support
-      // user changing email address this may need to change, but it's not
-      // clear how we would be told of such a change anyway...
-      throw new Error("Can't change uid or email address");
-    }
-    log.debug("_updateAccountData with items", Object.keys(newFields));
-    // work out what bucket.
-    for (let [name, value] of Object.entries(newFields)) {
-      if (FXA_PWDMGR_MEMORY_FIELDS.has(name)) {
-        if (value == null) {
-          delete this.cachedMemory[name];
-        } else {
-          this.cachedMemory[name] = value;
-        }
-      } else if (FXA_PWDMGR_PLAINTEXT_FIELDS.has(name)) {
-        if (value == null) {
-          delete this.cachedPlain[name];
-        } else {
-          this.cachedPlain[name] = value;
-        }
-      } else if (FXA_PWDMGR_SECURE_FIELDS.has(name)) {
-        // don't do the "delete on null" thing here - we need to keep it until
-        // we have managed to read so we can nuke it on write.
-        this.cachedSecure[name] = value;
-      } else {
-        // Throwing seems reasonable here as some client code has explicitly
-        // specified the field name, so it's either confused or needs to update
-        // how this field is to be treated.
-        throw new Error("unexpected field '" + name + "'");
-      }
-    }
-    // If we haven't yet read the secure data, do so now, else we may write
-    // out partial data.
-    yield this._maybeReadAndUpdateSecure();
-    // Now save it - but don't wait on the _write promise - it's queued up as
-    // a storage operation, so .finalize() will wait for completion, but no need
-    // for us to.
-    this._write();
-  }),
-
-  _clearCachedData() {
-    this.cachedMemory = {};
-    this.cachedPlain = {};
-    // If we don't have secure storage available we have cachedPlain and
-    // cachedSecure be the same object.
-    this.cachedSecure = this.secureStorage == null ? this.cachedPlain : {};
-  },
-
-  /* Reads the plain storage and caches the read values in this.cachedPlain.
-     Only ever called once and unlike the "secure" storage, is expected to never
-     fail (ie, plain storage is considered always available, whereas secure
-     storage may be unavailable if it is locked).
-
-     Returns a promise that resolves with true if valid account data was found,
-     false otherwise.
-
-     Note: _readPlainStorage is only called during initialize, so isn't
-     protected via _queueStorageOperation() nor _promiseInitialized.
-  */
-  _readPlainStorage: Task.async(function* () {
-    let got;
-    try {
-      got = yield this.plainStorage.get();
-    } catch(err) {
-      // File hasn't been created yet.  That will be done
-      // when write is called.
-      if (!(err instanceof OS.File.Error) || !err.becauseNoSuchFile) {
-        log.error("Failed to read plain storage", err);
-      }
-      // either way, we return null.
-      got = null;
-    }
-    if (!got || !got.accountData || !got.accountData.uid ||
-        got.version != DATA_FORMAT_VERSION) {
-      return false;
-    }
-    // We need to update our .cachedPlain, but can't just assign to it as
-    // it may need to be the exact same object as .cachedSecure
-    // As a sanity check, .cachedPlain must be empty (as we are called by init)
-    // XXX - this would be a good use-case for a RuntimeAssert or similar, as
-    // being added in bug 1080457.
-    if (Object.keys(this.cachedPlain).length != 0) {
-      throw new Error("should be impossible to have cached data already.")
-    }
-    for (let [name, value] of Object.entries(got.accountData)) {
-      this.cachedPlain[name] = value;
-    }
-    return true;
-  }),
-
-  /* If we haven't managed to read the secure storage, try now, so
-     we can merge our cached data with the data that's already been set.
-  */
-  _maybeReadAndUpdateSecure: Task.async(function* () {
-    if (this.secureStorage == null || !this._needToReadSecure) {
-      return;
-    }
-    return this._queueStorageOperation(() => {
-      if (this._needToReadSecure) { // we might have read it by now!
-        return this._doReadAndUpdateSecure();
-      }
-    });
-  }),
-
-  /* Unconditionally read the secure storage and merge our cached data (ie, data
-     which has already been set while the secure storage was locked) with
-     the read data
-  */
-  _doReadAndUpdateSecure: Task.async(function* () {
-    let { uid, email } = this.cachedPlain;
-    try {
-      log.debug("reading secure storage with existing", Object.keys(this.cachedSecure));
-      // If we already have anything in .cachedSecure it means something has
-      // updated cachedSecure before we've read it. That means that after we do
-      // manage to read we must write back the merged data.
-      let needWrite = Object.keys(this.cachedSecure).length != 0;
-      let readSecure = yield this.secureStorage.get(uid, email);
-      // and update our cached data with it - anything already in .cachedSecure
-      // wins (including the fact it may be null or undefined, the latter
-      // which means it will be removed from storage.
-      if (readSecure && readSecure.version != DATA_FORMAT_VERSION) {
-        log.warn("got secure data but the data format version doesn't match");
-        readSecure = null;
-      }
-      if (readSecure && readSecure.accountData) {
-        log.debug("secure read fetched items", Object.keys(readSecure.accountData));
-        for (let [name, value] of Object.entries(readSecure.accountData)) {
-          if (!(name in this.cachedSecure)) {
-            this.cachedSecure[name] = value;
-          }
-        }
-        if (needWrite) {
-          log.debug("successfully read secure data; writing updated data back")
-          yield this._doWriteSecure();
-        }
-      }
-      this._needToReadSecure = false;
-    } catch (ex) {
-      if (ex instanceof this.secureStorage.STORAGE_LOCKED) {
-        log.debug("setAccountData: secure storage is locked trying to read");
-      } else {
-        log.error("failed to read secure storage", ex);
-        throw ex;
-      }
-    }
-  }),
-
-  _write() {
-    // We don't want multiple writes happening concurrently, and we also need to
-    // know when an "old" storage manager is done (this.finalize() waits for this)
-    return this._queueStorageOperation(() => this.__write());
-  },
-
-  __write: Task.async(function* () {
-    // Write everything back - later we could track what's actually dirty,
-    // but for now we write it all.
-    log.debug("writing plain storage", Object.keys(this.cachedPlain));
-    let toWritePlain = {
-      version: DATA_FORMAT_VERSION,
-      accountData: this.cachedPlain,
-    }
-    yield this.plainStorage.set(toWritePlain);
-
-    // If we have no secure storage manager we are done.
-    if (this.secureStorage == null) {
-      return;
-    }
-    // and only attempt to write to secure storage if we've managed to read it,
-    // otherwise we might clobber data that's already there.
-    if (!this._needToReadSecure) {
-      yield this._doWriteSecure();
-    }
-  }),
-
-  /* Do the actual write of secure data. Caller is expected to check if we actually
-     need to write and to ensure we are in a queued storage operation.
-  */
-  _doWriteSecure: Task.async(function* () {
-    // We need to remove null items here.
-    for (let [name, value] of Object.entries(this.cachedSecure)) {
-      if (value == null) {
-        delete this.cachedSecure[name];
-      }
-    }
-    log.debug("writing secure storage", Object.keys(this.cachedSecure));
-    let toWriteSecure = {
-      version: DATA_FORMAT_VERSION,
-      accountData: this.cachedSecure,
-    }
-    try {
-      yield this.secureStorage.set(this.cachedPlain.uid, toWriteSecure);
-    } catch (ex) {
-      if (!(ex instanceof this.secureStorage.STORAGE_LOCKED)) {
-        throw ex;
-      }
-      // This shouldn't be possible as once it is unlocked it can't be
-      // re-locked, and we can only be here if we've previously managed to
-      // read.
-      log.error("setAccountData: secure storage is locked trying to write");
-    }
-  }),
-
-  // Delete the data for an account - ie, called on "sign out".
-  deleteAccountData() {
-    return this._queueStorageOperation(() => this._deleteAccountData());
-  },
-
-  _deleteAccountData: Task.async(function* () {
-    log.debug("removing account data");
-    yield this._promiseInitialized;
-    yield this.plainStorage.set(null);
-    if (this.secureStorage) {
-      yield this.secureStorage.set(null);
-    }
-    this._clearCachedData();
-    log.debug("account data reset");
-  }),
-}
-
-/**
- * JSONStorage constructor that creates instances that may set/get
- * to a specified file, in a directory that will be created if it
- * doesn't exist.
- *
- * @param options {
- *                  filename: of the file to write to
- *                  baseDir: directory where the file resides
- *                }
- * @return instance
- */
-function JSONStorage(options) {
-  this.baseDir = options.baseDir;
-  this.path = OS.Path.join(options.baseDir, options.filename);
-};
-
-JSONStorage.prototype = {
-  set: function(contents) {
-    log.trace("starting write of json user data", contents ? Object.keys(contents.accountData) : "null");
-    let start = Date.now();
-    return OS.File.makeDir(this.baseDir, {ignoreExisting: true})
-      .then(CommonUtils.writeJSON.bind(null, contents, this.path))
-      .then(result => {
-        log.trace("finished write of json user data - took", Date.now()-start);
-        return result;
-      });
-  },
-
-  get: function() {
-    log.trace("starting fetch of json user data");
-    let start = Date.now();
-    return CommonUtils.readJSON(this.path).then(result => {
-      log.trace("finished fetch of json user data - took", Date.now()-start);
-      return result;
-    });
-  },
-};
-
-function StorageLockedError() {
-}
-/**
- * LoginManagerStorage constructor that creates instances that set/get
- * data stored securely in the nsILoginManager.
- *
- * @return instance
- */
-
-function LoginManagerStorage() {
-}
-
-LoginManagerStorage.prototype = {
-  STORAGE_LOCKED: StorageLockedError,
-  // The fields in the credentials JSON object that are stored in plain-text
-  // in the profile directory.  All other fields are stored in the login manager,
-  // and thus are only available when the master-password is unlocked.
-
-  // a hook point for testing.
-  get _isLoggedIn() {
-    return Services.logins.isLoggedIn;
-  },
-
-  // Clear any data from the login manager.  Returns true if the login manager
-  // was unlocked (even if no existing logins existed) or false if it was
-  // locked (meaning we don't even know if it existed or not.)
-  _clearLoginMgrData: Task.async(function* () {
-    try { // Services.logins might be third-party and broken...
-      yield Services.logins.initializationPromise;
-      if (!this._isLoggedIn) {
-        return false;
-      }
-      let logins = Services.logins.findLogins({}, FXA_PWDMGR_HOST, null, FXA_PWDMGR_REALM);
-      for (let login of logins) {
-        Services.logins.removeLogin(login);
-      }
-      return true;
-    } catch (ex) {
-      log.error("Failed to clear login data: ${}", ex);
-      return false;
-    }
-  }),
-
-  set: Task.async(function* (uid, contents) {
-    if (!contents) {
-      // Nuke it from the login manager.
-      let cleared = yield this._clearLoginMgrData();
-      if (!cleared) {
-        // just log a message - we verify that the uid matches when
-        // we reload it, so having a stale entry doesn't really hurt.
-        log.info("not removing credentials from login manager - not logged in");
-      }
-      log.trace("storage set finished clearing account data");
-      return;
-    }
-
-    // We are saving actual data.
-    log.trace("starting write of user data to the login manager");
-    try { // Services.logins might be third-party and broken...
-      // and the stuff into the login manager.
-      yield Services.logins.initializationPromise;
-      // If MP is locked we silently fail - the user may need to re-auth
-      // next startup.
-      if (!this._isLoggedIn) {
-        log.info("not saving credentials to login manager - not logged in");
-        throw new this.STORAGE_LOCKED();
-      }
-      // write the data to the login manager.
-      let loginInfo = new Components.Constructor(
-         "@mozilla.org/login-manager/loginInfo;1", Ci.nsILoginInfo, "init");
-      let login = new loginInfo(FXA_PWDMGR_HOST,
-                                null, // aFormSubmitURL,
-                                FXA_PWDMGR_REALM, // aHttpRealm,
-                                uid, // aUsername
-                                JSON.stringify(contents), // aPassword
-                                "", // aUsernameField
-                                "");// aPasswordField
-
-      let existingLogins = Services.logins.findLogins({}, FXA_PWDMGR_HOST, null,
-                                                      FXA_PWDMGR_REALM);
-      if (existingLogins.length) {
-        Services.logins.modifyLogin(existingLogins[0], login);
-      } else {
-        Services.logins.addLogin(login);
-      }
-      log.trace("finished write of user data to the login manager");
-    } catch (ex) {
-      if (ex instanceof this.STORAGE_LOCKED) {
-        throw ex;
-      }
-      // just log and consume the error here - it may be a 3rd party login
-      // manager replacement that's simply broken.
-      log.error("Failed to save data to the login manager", ex);
-    }
-  }),
-
-  get: Task.async(function* (uid, email) {
-    log.trace("starting fetch of user data from the login manager");
-
-    try { // Services.logins might be third-party and broken...
-      // read the data from the login manager and merge it for return.
-      yield Services.logins.initializationPromise;
-
-      if (!this._isLoggedIn) {
-        log.info("returning partial account data as the login manager is locked.");
-        throw new this.STORAGE_LOCKED();
-      }
-
-      let logins = Services.logins.findLogins({}, FXA_PWDMGR_HOST, null, FXA_PWDMGR_REALM);
-      if (logins.length == 0) {
-        // This could happen if the MP was locked when we wrote the data.
-        log.info("Can't find any credentials in the login manager");
-        return null;
-      }
-      let login = logins[0];
-      // Support either the uid or the email as the username - as of bug 1183951
-      // we store the uid, but we support having either for b/w compat.
-      if (login.username == uid || login.username == email) {
-        return JSON.parse(login.password);
-      }
-      log.info("username in the login manager doesn't match - ignoring it");
-      yield this._clearLoginMgrData();
-    } catch (ex) {
-      if (ex instanceof this.STORAGE_LOCKED) {
-        throw ex;
-      }
-      // just log and consume the error here - it may be a 3rd party login
-      // manager replacement that's simply broken.
-      log.error("Failed to get data from the login manager", ex);
-    }
-    return null;
-  }),
-}
-
diff --git a/services/fxaccounts/FxAccountsWebChannel.jsm b/services/fxaccounts/FxAccountsWebChannel.jsm
deleted file mode 100644
index 810d93c..0000000
--- a/services/fxaccounts/FxAccountsWebChannel.jsm
+++ /dev/null
@@ -1,474 +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/. */
-
-/**
- * Firefox Accounts Web Channel.
- *
- * Uses the WebChannel component to receive messages
- * about account state changes.
- */
-
-this.EXPORTED_SYMBOLS = ["EnsureFxAccountsWebChannel"];
-
-const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
-
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-Cu.import("resource://gre/modules/FxAccountsCommon.js");
-
-XPCOMUtils.defineLazyModuleGetter(this, "Services",
-                                  "resource://gre/modules/Services.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "WebChannel",
-                                  "resource://gre/modules/WebChannel.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "fxAccounts",
-                                  "resource://gre/modules/FxAccounts.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "FxAccountsStorageManagerCanStoreField",
-                                  "resource://gre/modules/FxAccountsStorage.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "Weave",
-                                  "resource://services-sync/main.js");
-
-const COMMAND_PROFILE_CHANGE       = "profile:change";
-const COMMAND_CAN_LINK_ACCOUNT     = "fxaccounts:can_link_account";
-const COMMAND_LOGIN                = "fxaccounts:login";
-const COMMAND_LOGOUT               = "fxaccounts:logout";
-const COMMAND_DELETE               = "fxaccounts:delete";
-const COMMAND_SYNC_PREFERENCES     = "fxaccounts:sync_preferences";
-const COMMAND_CHANGE_PASSWORD      = "fxaccounts:change_password";
-
-const PREF_LAST_FXA_USER           = "identity.fxaccounts.lastSignedInUserHash";
-const PREF_SYNC_SHOW_CUSTOMIZATION = "services.sync-setup.ui.showCustomizationDialog";
-
-/**
- * A helper function that extracts the message and stack from an error object.
- * Returns a `{ message, stack }` tuple. `stack` will be null if the error
- * doesn't have a stack trace.
- */
-function getErrorDetails(error) {
-  let details = { message: String(error), stack: null };
-
-  // Adapted from Console.jsm.
-  if (error.stack) {
-    let frames = [];
-    for (let frame = error.stack; frame; frame = frame.caller) {
-      frames.push(String(frame).padStart(4));
-    }
-    details.stack = frames.join("\n");
-  }
-
-  return details;
-}
-
-/**
- * Create a new FxAccountsWebChannel to listen for account updates
- *
- * @param {Object} options Options
- *   @param {Object} options
- *     @param {String} options.content_uri
- *     The FxA Content server uri
- *     @param {String} options.channel_id
- *     The ID of the WebChannel
- *     @param {String} options.helpers
- *     Helpers functions. Should only be passed in for testing.
- * @constructor
- */
-this.FxAccountsWebChannel = function(options) {
-  if (!options) {
-    throw new Error("Missing configuration options");
-  }
-  if (!options["content_uri"]) {
-    throw new Error("Missing 'content_uri' option");
-  }
-  this._contentUri = options.content_uri;
-
-  if (!options["channel_id"]) {
-    throw new Error("Missing 'channel_id' option");
-  }
-  this._webChannelId = options.channel_id;
-
-  // options.helpers is only specified by tests.
-  this._helpers = options.helpers || new FxAccountsWebChannelHelpers(options);
-
-  this._setupChannel();
-};
-
-this.FxAccountsWebChannel.prototype = {
-  /**
-   * WebChannel that is used to communicate with content page
-   */
-  _channel: null,
-
-  /**
-   * Helpers interface that does the heavy lifting.
-   */
-  _helpers: null,
-
-  /**
-   * WebChannel ID.
-   */
-  _webChannelId: null,
-  /**
-   * WebChannel origin, used to validate origin of messages
-   */
-  _webChannelOrigin: null,
-
-  /**
-   * Release all resources that are in use.
-   */
-  tearDown() {
-    this._channel.stopListening();
-    this._channel = null;
-    this._channelCallback = null;
-  },
-
-  /**
-   * Configures and registers a new WebChannel
-   *
-   * @private
-   */
-  _setupChannel() {
-    // if this.contentUri is present but not a valid URI, then this will throw an error.
-    try {
-      this._webChannelOrigin = Services.io.newURI(this._contentUri, null, null);
-      this._registerChannel();
-    } catch (e) {
-      log.error(e);
-      throw e;
-    }
-  },
-
-  _receiveMessage(message, sendingContext) {
-    let command = message.command;
-    let data = message.data;
-
-    switch (command) {
-      case COMMAND_PROFILE_CHANGE:
-        Services.obs.notifyObservers(null, ON_PROFILE_CHANGE_NOTIFICATION, data.uid);
-        break;
-      case COMMAND_LOGIN:
-        this._helpers.login(data).catch(error =>
-          this._sendError(error, message, sendingContext));
-        break;
-      case COMMAND_LOGOUT:
-      case COMMAND_DELETE:
-        this._helpers.logout(data.uid).catch(error =>
-          this._sendError(error, message, sendingContext));
-        break;
-      case COMMAND_CAN_LINK_ACCOUNT:
-        let canLinkAccount = this._helpers.shouldAllowRelink(data.email);
-
-        let response = {
-          command: command,
-          messageId: message.messageId,
-          data: { ok: canLinkAccount }
-        };
-
-        log.debug("FxAccountsWebChannel response", response);
-        this._channel.send(response, sendingContext);
-        break;
-      case COMMAND_SYNC_PREFERENCES:
-        this._helpers.openSyncPreferences(sendingContext.browser, data.entryPoint);
-        break;
-      case COMMAND_CHANGE_PASSWORD:
-        this._helpers.changePassword(data).catch(error =>
-          this._sendError(error, message, sendingContext));
-        break;
-      default:
-        log.warn("Unrecognized FxAccountsWebChannel command", command);
-        break;
-    }
-  },
-
-  _sendError(error, incomingMessage, sendingContext) {
-    log.error("Failed to handle FxAccountsWebChannel message", error);
-    this._channel.send({
-      command: incomingMessage.command,
-      messageId: incomingMessage.messageId,
-      data: {
-        error: getErrorDetails(error),
-      },
-    }, sendingContext);
-  },
-
-  /**
-   * Create a new channel with the WebChannelBroker, setup a callback listener
-   * @private
-   */
-  _registerChannel() {
-    /**
-     * Processes messages that are called back from the FxAccountsChannel
-     *
-     * @param webChannelId {String}
-     *        Command webChannelId
-     * @param message {Object}
-     *        Command message
-     * @param sendingContext {Object}
-     *        Message sending context.
-     *        @param sendingContext.browser {browser}
-     *               The <browser> object that captured the
-     *               WebChannelMessageToChrome.
-     *        @param sendingContext.eventTarget {EventTarget}
-     *               The <EventTarget> where the message was sent.
-     *        @param sendingContext.principal {Principal}
-     *               The <Principal> of the EventTarget where the message was sent.
-     * @private
-     *
-     */
-    let listener = (webChannelId, message, sendingContext) => {
-      if (message) {
-        log.debug("FxAccountsWebChannel message received", message.command);
-        if (logPII) {
-          log.debug("FxAccountsWebChannel message details", message);
-        }
-        try {
-          this._receiveMessage(message, sendingContext);
-        } catch (error) {
-          this._sendError(error, message, sendingContext);
-        }
-      }
-    };
-
-    this._channelCallback = listener;
-    this._channel = new WebChannel(this._webChannelId, this._webChannelOrigin);
-    this._channel.listen(listener);
-    log.debug("FxAccountsWebChannel registered: " + this._webChannelId + " with origin " + this._webChannelOrigin.prePath);
-  }
-};
-
-this.FxAccountsWebChannelHelpers = function(options) {
-  options = options || {};
-
-  this._fxAccounts = options.fxAccounts || fxAccounts;
-};
-
-this.FxAccountsWebChannelHelpers.prototype = {
-  // If the last fxa account used for sync isn't this account, we display
-  // a modal dialog checking they really really want to do this...
-  // (This is sync-specific, so ideally would be in sync's identity module,
-  // but it's a little more seamless to do here, and sync is currently the
-  // only fxa consumer, so...
-  shouldAllowRelink(acctName) {
-    return !this._needRelinkWarning(acctName) ||
-            this._promptForRelink(acctName);
-  },
-
-  /**
-   * New users are asked in the content server whether they want to
-   * customize which data should be synced. The user is only shown
-   * the dialog listing the possible data types upon verification.
-   *
-   * Save a bit into prefs that is read on verification to see whether
-   * to show the list of data types that can be saved.
-   */
-  setShowCustomizeSyncPref(showCustomizeSyncPref) {
-    Services.prefs.setBoolPref(PREF_SYNC_SHOW_CUSTOMIZATION, showCustomizeSyncPref);
-  },
-
-  getShowCustomizeSyncPref() {
-    return Services.prefs.getBoolPref(PREF_SYNC_SHOW_CUSTOMIZATION);
-  },
-
-  /**
-   * stores sync login info it in the fxaccounts service
-   *
-   * @param accountData the user's account data and credentials
-   */
-  login(accountData) {
-    if (accountData.customizeSync) {
-      this.setShowCustomizeSyncPref(true);
-      delete accountData.customizeSync;
-    }
-
-    if (accountData.declinedSyncEngines) {
-      let declinedSyncEngines = accountData.declinedSyncEngines;
-      log.debug("Received declined engines", declinedSyncEngines);
-      Weave.Service.engineManager.setDeclined(declinedSyncEngines);
-      declinedSyncEngines.forEach(engine => {
-        Services.prefs.setBoolPref("services.sync.engine." + engine, false);
-      });
-
-      // if we got declinedSyncEngines that means we do not need to show the customize screen.
-      this.setShowCustomizeSyncPref(false);
-      delete accountData.declinedSyncEngines;
-    }
-
-    // the user has already been shown the "can link account"
-    // screen. No need to keep this data around.
-    delete accountData.verifiedCanLinkAccount;
-
-    // Remember who it was so we can log out next time.
-    this.setPreviousAccountNameHashPref(accountData.email);
-
-    // A sync-specific hack - we want to ensure sync has been initialized
-    // before we set the signed-in user.
-    let xps = Cc["@mozilla.org/weave/service;1"]
-              .getService(Ci.nsISupports)
-              .wrappedJSObject;
-    return xps.whenLoaded().then(() => {
-      return this._fxAccounts.setSignedInUser(accountData);
-    });
-  },
-
-  /**
-   * logout the fxaccounts service
-   *
-   * @param the uid of the account which have been logged out
-   */
-  logout(uid) {
-    return fxAccounts.getSignedInUser().then(userData => {
-      if (userData.uid === uid) {
-        // true argument is `localOnly`, because server-side stuff
-        // has already been taken care of by the content server
-        return fxAccounts.signOut(true);
-      }
-    });
-  },
-
-  changePassword(credentials) {
-    // If |credentials| has fields that aren't handled by accounts storage,
-    // updateUserAccountData will throw - mainly to prevent errors in code
-    // that hard-codes field names.
-    // However, in this case the field names aren't really in our control.
-    // We *could* still insist the server know what fields names are valid,
-    // but that makes life difficult for the server when Firefox adds new
-    // features (ie, new fields) - forcing the server to track a map of
-    // versions to supported field names doesn't buy us much.
-    // So we just remove field names we know aren't handled.
-    let newCredentials = {
-      deviceId: null
-    };
-    for (let name of Object.keys(credentials)) {
-      if (name == "email" || name == "uid" || FxAccountsStorageManagerCanStoreField(name)) {
-        newCredentials[name] = credentials[name];
-      } else {
-        log.info("changePassword ignoring unsupported field", name);
-      }
-    }
-    return this._fxAccounts.updateUserAccountData(newCredentials)
-      .then(() => this._fxAccounts.updateDeviceRegistration());
-  },
-
-  /**
-   * Get the hash of account name of the previously signed in account
-   */
-  getPreviousAccountNameHashPref() {
-    try {
-      return Services.prefs.getComplexValue(PREF_LAST_FXA_USER, Ci.nsISupportsString).data;
-    } catch (_) {
-      return "";
-    }
-  },
-
-  /**
-   * Given an account name, set the hash of the previously signed in account
-   *
-   * @param acctName the account name of the user's account.
-   */
-  setPreviousAccountNameHashPref(acctName) {
-    let string = Cc["@mozilla.org/supports-string;1"]
-                 .createInstance(Ci.nsISupportsString);
-    string.data = this.sha256(acctName);
-    Services.prefs.setComplexValue(PREF_LAST_FXA_USER, Ci.nsISupportsString, string);
-  },
-
-  /**
-   * Given a string, returns the SHA265 hash in base64
-   */
-  sha256(str) {
-    let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
-                      .createInstance(Ci.nsIScriptableUnicodeConverter);
-    converter.charset = "UTF-8";
-    // Data is an array of bytes.
-    let data = converter.convertToByteArray(str, {});
-    let hasher = Cc["@mozilla.org/security/hash;1"]
-                   .createInstance(Ci.nsICryptoHash);
-    hasher.init(hasher.SHA256);
-    hasher.update(data, data.length);
-
-    return hasher.finish(true);
-  },
-
-  /**
-   * Open Sync Preferences in the current tab of the browser
-   *
-   * @param {Object} browser the browser in which to open preferences
-   * @param {String} [entryPoint] entryPoint to use for logging
-   */
-  openSyncPreferences(browser, entryPoint) {
-    let uri = "about:preferences";
-    if (entryPoint) {
-      uri += "?entrypoint=" + encodeURIComponent(entryPoint);
-    }
-    uri += "#sync";
-
-    browser.loadURI(uri);
-  },
-
-  /**
-   * If a user signs in using a different account, the data from the
-   * previous account and the new account will be merged. Ask the user
-   * if they want to continue.
-   *
-   * @private
-   */
-  _needRelinkWarning(acctName) {
-    let prevAcctHash = this.getPreviousAccountNameHashPref();
-    return prevAcctHash && prevAcctHash != this.sha256(acctName);
-  },
-
-  /**
-   * Show the user a warning dialog that the data from the previous account
-   * and the new account will be merged.
-   *
-   * @private
-   */
-  _promptForRelink(acctName) {
-    let sb = Services.strings.createBundle("chrome://browser/locale/syncSetup.properties");
-    let continueLabel = sb.GetStringFromName("continue.label");
-    let title = sb.GetStringFromName("relinkVerify.title");
-    let description = sb.formatStringFromName("relinkVerify.description",
-                                              [acctName], 1);
-    let body = sb.GetStringFromName("relinkVerify.heading") +
-               "\n\n" + description;
-    let ps = Services.prompt;
-    let buttonFlags = (ps.BUTTON_POS_0 * ps.BUTTON_TITLE_IS_STRING) +
-                      (ps.BUTTON_POS_1 * ps.BUTTON_TITLE_CANCEL) +
-                      ps.BUTTON_POS_1_DEFAULT;
-
-    // If running in context of the browser chrome, window does not exist.
-    var targetWindow = typeof window === 'undefined' ? null : window;
-    let pressed = Services.prompt.confirmEx(targetWindow, title, body, buttonFlags,
-                                       continueLabel, null, null, null,
-                                       {});
-    return pressed === 0; // 0 is the "continue" button
-  }
-};
-
-var singleton;
-// The entry-point for this module, which ensures only one of our channels is
-// ever created - we require this because the WebChannel is global in scope
-// (eg, it uses the observer service to tell interested parties of interesting
-// things) and allowing multiple channels would cause such notifications to be
-// sent multiple times.
-this.EnsureFxAccountsWebChannel = function() {
-  let contentUri = Services.urlFormatter.formatURLPref("identity.fxaccounts.remote.webchannel.uri");
-  if (singleton && singleton._contentUri !== contentUri) {
-    singleton.tearDown();
-    singleton = null;
-  }
-  if (!singleton) {
-    try {
-      if (contentUri) {
-        // The FxAccountsWebChannel listens for events and updates
-        // the state machine accordingly.
-        singleton = new this.FxAccountsWebChannel({
-          content_uri: contentUri,
-          channel_id: WEBCHANNEL_ID,
-        });
-      } else {
-        log.warn("FxA WebChannel functionaly is disabled due to no URI pref.");
-      }
-    } catch (ex) {
-      log.error("Failed to create FxA WebChannel", ex);
-    }
-  }
-}
diff --git a/services/fxaccounts/interfaces/moz.build b/services/fxaccounts/interfaces/moz.build
deleted file mode 100644
index ac80b3e..0000000
--- a/services/fxaccounts/interfaces/moz.build
+++ /dev/null
@@ -1,11 +0,0 @@
-# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
-# vim: set filetype=python:
-# 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/.
-
-XPIDL_SOURCES += [
-    'nsIFxAccountsUIGlue.idl'
-]
-
-XPIDL_MODULE = 'services_fxaccounts'
diff --git a/services/fxaccounts/interfaces/nsIFxAccountsUIGlue.idl b/services/fxaccounts/interfaces/nsIFxAccountsUIGlue.idl
deleted file mode 100644
index 950fdbc..0000000
--- a/services/fxaccounts/interfaces/nsIFxAccountsUIGlue.idl
+++ /dev/null
@@ -1,15 +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/. */
-
-#include "nsISupports.idl"
-
-[scriptable, uuid(ab8d0700-9577-11e3-a5e2-0800200c9a66)]
-interface nsIFxAccountsUIGlue : nsISupports
-{
-  // Returns a Promise.
-  jsval signInFlow();
-
-  // Returns a Promise.
-  jsval refreshAuthentication(in DOMString email);
-};
diff --git a/services/fxaccounts/moz.build b/services/fxaccounts/moz.build
deleted file mode 100644
index b1cd3b5..0000000
--- a/services/fxaccounts/moz.build
+++ /dev/null
@@ -1,32 +0,0 @@
-# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
-# vim: set filetype=python:
-# 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/.
-
-DIRS += ['interfaces']
-
-MOCHITEST_CHROME_MANIFESTS += ['tests/mochitest/chrome.ini']
-
-XPCSHELL_TESTS_MANIFESTS += ['tests/xpcshell/xpcshell.ini']
-
-EXTRA_COMPONENTS += [
-  'FxAccountsComponents.manifest',
-  'FxAccountsPush.js',
-]
-
-EXTRA_JS_MODULES += [
-  'Credentials.jsm',
-  'FxAccounts.jsm',
-  'FxAccountsClient.jsm',
-  'FxAccountsCommon.js',
-  'FxAccountsConfig.jsm',
-  'FxAccountsOAuthClient.jsm',
-  'FxAccountsOAuthGrantClient.jsm',
-  'FxAccountsProfile.jsm',
-  'FxAccountsProfileClient.jsm',
-  'FxAccountsPush.js',
-  'FxAccountsStorage.jsm',
-  'FxAccountsWebChannel.jsm',
-]
-
diff --git a/services/fxaccounts/tests/mochitest/chrome.ini b/services/fxaccounts/tests/mochitest/chrome.ini
deleted file mode 100644
index ab2e770..0000000
--- a/services/fxaccounts/tests/mochitest/chrome.ini
+++ /dev/null
@@ -1,7 +0,0 @@
-[DEFAULT]
-skip-if = os == 'android'
-support-files=
-  file_invalidEmailCase.sjs
-
-[test_invalidEmailCase.html]
-
diff --git a/services/fxaccounts/tests/mochitest/file_invalidEmailCase.sjs b/services/fxaccounts/tests/mochitest/file_invalidEmailCase.sjs
deleted file mode 100644
index 9d97ac7..0000000
--- a/services/fxaccounts/tests/mochitest/file_invalidEmailCase.sjs
+++ /dev/null
@@ -1,80 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
-   http://creativecommons.org/publicdomain/zero/1.0/ */
-
-/**
- * This server simulates the behavior of /account/login on the Firefox Accounts
- * auth server in the case where the user is trying to sign in with an email
- * with the wrong capitalization.
- *
- * https://github.com/mozilla/fxa-auth-server/blob/master/docs/api.md#post-v1accountlogin
- *
- * The expected behavior is that on the first attempt, with the wrong email,
- * the server will respond with a 400 and the canonical email capitalization
- * that the client should use.  The client then has one chance to sign in with
- * this different capitalization.
- *
- * In this test, the user with the account id "Greta.Garbo at gmail.COM" initially
- * tries to sign in as "greta.garbo at gmail.com".
- *
- * On success, the client is responsible for updating its sign-in user state
- * and recording the proper email capitalization.
- */
-
-const CC = Components.Constructor;
-const BinaryInputStream = CC("@mozilla.org/binaryinputstream;1",
-                             "nsIBinaryInputStream",
-                             "setInputStream");
-
-const goodEmail = "Greta.Garbo at gmail.COM";
-const badEmail = "greta.garbo at gmail.com";
-
-function handleRequest(request, response) {
-  let body = new BinaryInputStream(request.bodyInputStream);
-  let bytes = [];
-  let available;
-  while ((available = body.available()) > 0) {
-    Array.prototype.push.apply(bytes, body.readByteArray(available));
-  }
-
-  let data = JSON.parse(String.fromCharCode.apply(null, bytes));
-  let message;
-
-  switch (data.email) {
-    case badEmail:
-      // Almost - try again with fixed email case
-      message = {
-        code: 400,
-        errno: 120,
-        error: "Incorrect email case",
-        email: goodEmail,
-      };
-      response.setStatusLine(request.httpVersion, 400, "Almost");
-      break;
-
-    case goodEmail:
-      // Successful login.
-      message = {
-        uid: "your-uid",
-        sessionToken: "your-sessionToken",
-        keyFetchToken: "your-keyFetchToken",
-        verified: true,
-        authAt: 1392144866,
-      };
-      response.setStatusLine(request.httpVersion, 200, "Yay");
-      break;
-
-    default:
-      // Anything else happening in this test is a failure.
-      message = {
-        code: 400,
-        errno: 999,
-        error: "What happened!?",
-      };
-      response.setStatusLine(request.httpVersion, 400, "Ouch");
-      break;
-  }
-
-  messageStr = JSON.stringify(message);
-  response.bodyOutputStream.write(messageStr, messageStr.length);
-}
-
diff --git a/services/fxaccounts/tests/mochitest/test_invalidEmailCase.html b/services/fxaccounts/tests/mochitest/test_invalidEmailCase.html
deleted file mode 100644
index 52866cc..0000000
--- a/services/fxaccounts/tests/mochitest/test_invalidEmailCase.html
+++ /dev/null
@@ -1,131 +0,0 @@
-<!--
-     Any copyright is dedicated to the Public Domain.
-     http://creativecommons.org/publicdomain/zero/1.0/
--->
-<!DOCTYPE HTML>
-<html>
-<!--
-Tests for Firefox Accounts signin with invalid email case
-https://bugzilla.mozilla.org/show_bug.cgi?id=963835
--->
-<head>
-  <title>Test for Firefox Accounts (Bug 963835)</title>
-  <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
-  <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
-</head>
-<body>
-
-<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=963835">Mozilla Bug 963835</a>
-<p id="display"></p>
-<div id="content" style="display: none">
-  Test for correction of invalid email case in Fx Accounts signIn
-</div>
-<pre id="test">
-<script class="testbody" type="text/javascript;version=1.8">
-
-SimpleTest.waitForExplicitFinish();
-
-Components.utils.import("resource://gre/modules/Promise.jsm");
-Components.utils.import("resource://gre/modules/Services.jsm");
-Components.utils.import("resource://gre/modules/FxAccounts.jsm");
-Components.utils.import("resource://gre/modules/FxAccountsClient.jsm");
-Components.utils.import("resource://services-common/hawkclient.js");
-
-const TEST_SERVER =
-  "http://mochi.test:8888/chrome/services/fxaccounts/tests/mochitest/file_invalidEmailCase.sjs?path=";
-
-let MockStorage = function() {
-  this.data = null;
-};
-MockStorage.prototype = Object.freeze({
-  set: function (contents) {
-    this.data = contents;
-    return Promise.resolve(null);
-  },
-  get: function () {
-    return Promise.resolve(this.data);
-  },
-  getOAuthTokens() {
-    return Promise.resolve(null);
-  },
-  setOAuthTokens(contents) {
-    return Promise.resolve();
-  },
-});
-
-function MockFxAccounts() {
-  return new FxAccounts({
-    _now_is: new Date(),
-
-    now: function() {
-      return this._now_is;
-    },
-
-    signedInUserStorage: new MockStorage(),
-
-    fxAccountsClient: new FxAccountsClient(TEST_SERVER),
-  });
-}
-
-let wrongEmail = "greta.garbo at gmail.com";
-let rightEmail = "Greta.Garbo at gmail.COM";
-let password = "123456";
-
-function runTest() {
-  is(Services.prefs.getCharPref("identity.fxaccounts.auth.uri"), TEST_SERVER,
-     "Pref for auth.uri should be set to test server");
-
-  let fxa = new MockFxAccounts();
-  let client = fxa.internal.fxAccountsClient;
-
-  ok(true, !!fxa, "Couldn't mock fxa");
-  ok(true, !!client, "Couldn't mock fxa client");
-  is(client.host, TEST_SERVER, "Should be using the test auth server uri");
-
-  // First try to sign in using the email with the wrong capitalization.  The
-  // FxAccountsClient will receive a 400 from the server with the corrected email.
-  // It will automatically try to sign in again.  We expect this to succeed.
-  client.signIn(wrongEmail, password).then(
-    user => {
-
-      // Now store the signed-in user state.  This will include the correct
-      // email capitalization.
-      fxa.setSignedInUser(user).then(
-        () => {
-
-          // Confirm that the correct email got stored.
-          fxa.getSignedInUser().then(
-            data => {
-              is(data.email, rightEmail);
-              SimpleTest.finish();
-            },
-            getUserError => {
-              ok(false, JSON.stringify(getUserError));
-            }
-          );
-        },
-        setSignedInUserError => {
-          ok(false, JSON.stringify(setSignedInUserError));
-        }
-      );
-    },
-    signInError => {
-      ok(false, JSON.stringify(signInError));
-    }
-  );
-};
-
-SpecialPowers.pushPrefEnv({"set": [
-    ["identity.fxaccounts.enabled", true],         // fx accounts
-    ["identity.fxaccounts.auth.uri", TEST_SERVER], // our sjs server
-    ["toolkit.identity.debug", true],              // verbose identity logging
-    ["browser.dom.window.dump.enabled", true],
-  ]},
-  function () { runTest(); }
-);
-
-</script>
-</pre>
-</body>
-</html>
-
diff --git a/services/fxaccounts/tests/xpcshell/head.js b/services/fxaccounts/tests/xpcshell/head.js
deleted file mode 100644
index ed70fda..0000000
--- a/services/fxaccounts/tests/xpcshell/head.js
+++ /dev/null
@@ -1,18 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/ */
-
-var {classes: Cc, interfaces: Ci, results: Cr, utils: Cu} = Components;
-
-"use strict";
-
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-
-(function initFxAccountsTestingInfrastructure() {
-  do_get_profile();
-
-  let ns = {};
-  Cu.import("resource://testing-common/services/common/logging.js", ns);
-
-  ns.initTestLogging("Trace");
-}).call(this);
-
diff --git a/services/fxaccounts/tests/xpcshell/test_accounts.js b/services/fxaccounts/tests/xpcshell/test_accounts.js
deleted file mode 100644
index d6139a0..0000000
--- a/services/fxaccounts/tests/xpcshell/test_accounts.js
+++ /dev/null
@@ -1,1531 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/ */
-
-"use strict";
-
-Cu.import("resource://services-common/utils.js");
-Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://gre/modules/FxAccounts.jsm");
-Cu.import("resource://gre/modules/FxAccountsClient.jsm");
-Cu.import("resource://gre/modules/FxAccountsCommon.js");
-Cu.import("resource://gre/modules/FxAccountsOAuthGrantClient.jsm");
-Cu.import("resource://gre/modules/Promise.jsm");
-Cu.import("resource://gre/modules/Log.jsm");
-
-// We grab some additional stuff via backstage passes.
-var {AccountState} = Cu.import("resource://gre/modules/FxAccounts.jsm", {});
-
-const ONE_HOUR_MS = 1000 * 60 * 60;
-const ONE_DAY_MS = ONE_HOUR_MS * 24;
-const TWO_MINUTES_MS = 1000 * 60 * 2;
-
-initTestLogging("Trace");
-
-// XXX until bug 937114 is fixed
-Cu.importGlobalProperties(['atob']);
-
-var log = Log.repository.getLogger("Services.FxAccounts.test");
-log.level = Log.Level.Debug;
-
-// See verbose logging from FxAccounts.jsm
-Services.prefs.setCharPref("identity.fxaccounts.loglevel", "Trace");
-Log.repository.getLogger("FirefoxAccounts").level = Log.Level.Trace;
-
-// The oauth server is mocked, but set these prefs to pass param checks
-Services.prefs.setCharPref("identity.fxaccounts.remote.oauth.uri", "https://example.com/v1");
-Services.prefs.setCharPref("identity.fxaccounts.oauth.client_id", "abc123");
-
-
-const PROFILE_SERVER_URL = "http://example.com/v1";
-const CONTENT_URL = "http://accounts.example.com/";
-
-Services.prefs.setCharPref("identity.fxaccounts.remote.profile.uri", PROFILE_SERVER_URL);
-Services.prefs.setCharPref("identity.fxaccounts.settings.uri", CONTENT_URL);
-
-/*
- * The FxAccountsClient communicates with the remote Firefox
- * Accounts auth server.  Mock the server calls, with a little
- * lag time to simulate some latency.
- *
- * We add the _verified attribute to mock the change in verification
- * state on the FXA server.
- */
-
-function MockStorageManager() {
-}
-
-MockStorageManager.prototype = {
-  promiseInitialized: Promise.resolve(),
-
-  initialize(accountData) {
-    this.accountData = accountData;
-  },
-
-  finalize() {
-    return Promise.resolve();
-  },
-
-  getAccountData() {
-    return Promise.resolve(this.accountData);
-  },
-
-  updateAccountData(updatedFields) {
-    for (let [name, value] of Object.entries(updatedFields)) {
-      if (value == null) {
-        delete this.accountData[name];
-      } else {
-        this.accountData[name] = value;
-      }
-    }
-    return Promise.resolve();
-  },
-
-  deleteAccountData() {
-    this.accountData = null;
-    return Promise.resolve();
-  }
-}
-
-function MockFxAccountsClient() {
-  this._email = "nobody at example.com";
-  this._verified = false;
-  this._deletedOnServer = false; // for testing accountStatus
-
-  // mock calls up to the auth server to determine whether the
-  // user account has been verified
-  this.recoveryEmailStatus = function (sessionToken) {
-    // simulate a call to /recovery_email/status
-    return Promise.resolve({
-      email: this._email,
-      verified: this._verified
-    });
-  };
-
-  this.accountStatus = function(uid) {
-    let deferred = Promise.defer();
-    deferred.resolve(!!uid && (!this._deletedOnServer));
-    return deferred.promise;
-  };
-
-  this.accountKeys = function (keyFetchToken) {
-    let deferred = Promise.defer();
-
-    do_timeout(50, () => {
-      let response = {
-        kA: expandBytes("11"),
-        wrapKB: expandBytes("22")
-      };
-      deferred.resolve(response);
-    });
-    return deferred.promise;
-  };
-
-  this.resendVerificationEmail = function(sessionToken) {
-    // Return the session token to show that we received it in the first place
-    return Promise.resolve(sessionToken);
-  };
-
-  this.signCertificate = function() { throw "no" };
-
-  this.signOut = () => Promise.resolve();
-  this.signOutAndDestroyDevice = () => Promise.resolve({});
-
-  FxAccountsClient.apply(this);
-}
-MockFxAccountsClient.prototype = {
-  __proto__: FxAccountsClient.prototype
-}
-
-/*
- * We need to mock the FxAccounts module's interfaces to external
- * services, such as storage and the FxAccounts client.  We also
- * mock the now() method, so that we can simulate the passing of
- * time and verify that signatures expire correctly.
- */
-function MockFxAccounts() {
-  return new FxAccounts({
-    VERIFICATION_POLL_TIMEOUT_INITIAL: 100, // 100ms
-
-    _getCertificateSigned_calls: [],
-    _d_signCertificate: Promise.defer(),
-    _now_is: new Date(),
-    now: function () {
-      return this._now_is;
-    },
-    newAccountState(credentials) {
-      // we use a real accountState but mocked storage.
-      let storage = new MockStorageManager();
-      storage.initialize(credentials);
-      return new AccountState(storage);
-    },
-    getCertificateSigned: function (sessionToken, serializedPublicKey) {
-      _("mock getCertificateSigned\n");
-      this._getCertificateSigned_calls.push([sessionToken, serializedPublicKey]);
-      return this._d_signCertificate.promise;
-    },
-    _registerOrUpdateDevice() {
-      return Promise.resolve();
-    },
-    fxAccountsClient: new MockFxAccountsClient()
-  });
-}
-
-/*
- * Some tests want a "real" fxa instance - however, we still mock the storage
- * to keep the tests fast on b2g.
- */
-function MakeFxAccounts(internal = {}) {
-  if (!internal.newAccountState) {
-    // we use a real accountState but mocked storage.
-    internal.newAccountState = function(credentials) {
-      let storage = new MockStorageManager();
-      storage.initialize(credentials);
-      return new AccountState(storage);
-    };
-  }
-  if (!internal._signOutServer) {
-    internal._signOutServer = () => Promise.resolve();
-  }
-  if (!internal._registerOrUpdateDevice) {
-    internal._registerOrUpdateDevice = () => Promise.resolve();
-  }
-  return new FxAccounts(internal);
-}
-
-add_task(function* test_non_https_remote_server_uri_with_requireHttps_false() {
-  Services.prefs.setBoolPref(
-    "identity.fxaccounts.allowHttp",
-    true);
-  Services.prefs.setCharPref(
-    "identity.fxaccounts.remote.signup.uri",
-    "http://example.com/browser/browser/base/content/test/general/accounts_testRemoteCommands.html");
-  do_check_eq(yield fxAccounts.promiseAccountsSignUpURI(),
-              "http://example.com/browser/browser/base/content/test/general/accounts_testRemoteCommands.html");
-
-  Services.prefs.clearUserPref("identity.fxaccounts.remote.signup.uri");
-  Services.prefs.clearUserPref("identity.fxaccounts.allowHttp");
-});
-
-add_task(function* test_non_https_remote_server_uri() {
-  Services.prefs.setCharPref(
-    "identity.fxaccounts.remote.signup.uri",
-    "http://example.com/browser/browser/base/content/test/general/accounts_testRemoteCommands.html");
-  rejects(fxAccounts.promiseAccountsSignUpURI(), null, "Firefox Accounts server must use HTTPS");
-  Services.prefs.clearUserPref("identity.fxaccounts.remote.signup.uri");
-});
-
-add_task(function* test_get_signed_in_user_initially_unset() {
-  _("Check getSignedInUser initially and after signout reports no user");
-  let account = MakeFxAccounts();
-  let credentials = {
-    email: "foo at example.com",
-    uid: "1234 at lcip.org",
-    assertion: "foobar",
-    sessionToken: "dead",
-    kA: "beef",
-    kB: "cafe",
-    verified: true
-  };
-  let result = yield account.getSignedInUser();
-  do_check_eq(result, null);
-
-  yield account.setSignedInUser(credentials);
-  let histogram = Services.telemetry.getHistogramById("FXA_CONFIGURED");
-  do_check_eq(histogram.snapshot().sum, 1);
-  histogram.clear();
-
-  result = yield account.getSignedInUser();
-  do_check_eq(result.email, credentials.email);
-  do_check_eq(result.assertion, credentials.assertion);
-  do_check_eq(result.kB, credentials.kB);
-
-  // Delete the memory cache and force the user
-  // to be read and parsed from storage (e.g. disk via JSONStorage).
-  delete account.internal.signedInUser;
-  result = yield account.getSignedInUser();
-  do_check_eq(result.email, credentials.email);
-  do_check_eq(result.assertion, credentials.assertion);
-  do_check_eq(result.kB, credentials.kB);
-
-  // sign out
-  let localOnly = true;
-  yield account.signOut(localOnly);
-
-  // user should be undefined after sign out
-  result = yield account.getSignedInUser();
-  do_check_eq(result, null);
-});
-
-add_task(function* test_update_account_data() {
-  _("Check updateUserAccountData does the right thing.");
-  let account = MakeFxAccounts();
-  let credentials = {
-    email: "foo at example.com",
-    uid: "1234 at lcip.org",
-    assertion: "foobar",
-    sessionToken: "dead",
-    kA: "beef",
-    kB: "cafe",
-    verified: true
-  };
-  yield account.setSignedInUser(credentials);
-
-  let newCreds = {
-    email: credentials.email,
-    uid: credentials.uid,
-    assertion: "new_assertion",
-  }
-  yield account.updateUserAccountData(newCreds);
-  do_check_eq((yield account.getSignedInUser()).assertion, "new_assertion",
-              "new field value was saved");
-
-  // but we should fail attempting to change email or uid.
-  newCreds = {
-    email: "someoneelse at example.com",
-    uid: credentials.uid,
-    assertion: "new_assertion",
-  }
-  yield Assert.rejects(account.updateUserAccountData(newCreds));
-  newCreds = {
-    email: credentials.email,
-    uid: "another_uid",
-    assertion: "new_assertion",
-  }
-  yield Assert.rejects(account.updateUserAccountData(newCreds));
-
-  // should fail without email or uid.
-  newCreds = {
-    assertion: "new_assertion",
-  }
-  yield Assert.rejects(account.updateUserAccountData(newCreds));
-
-  // and should fail with a field name that's not known by storage.
-  newCreds = {
-    email: credentials.email,
-    uid: "another_uid",
-    foo: "bar",
-  }
-  yield Assert.rejects(account.updateUserAccountData(newCreds));
-});
-
-add_task(function* test_getCertificateOffline() {
-  _("getCertificateOffline()");
-  let fxa = MakeFxAccounts();
-  let credentials = {
-    email: "foo at example.com",
-    uid: "1234 at lcip.org",
-    sessionToken: "dead",
-    verified: true,
-  };
-
-  yield fxa.setSignedInUser(credentials);
-
-  // Test that an expired cert throws if we're offline.
-  let offline = Services.io.offline;
-  Services.io.offline = true;
-  yield fxa.internal.getKeypairAndCertificate(fxa.internal.currentAccountState).then(
-    result => {
-      Services.io.offline = offline;
-      do_throw("Unexpected success");
-    },
-    err => {
-      Services.io.offline = offline;
-      // ... so we have to check the error string.
-      do_check_eq(err, "Error: OFFLINE");
-    }
-  );
-  yield fxa.signOut(/*localOnly = */true);
-});
-
-add_task(function* test_getCertificateCached() {
-  _("getCertificateCached()");
-  let fxa = MakeFxAccounts();
-  let credentials = {
-    email: "foo at example.com",
-    uid: "1234 at lcip.org",
-    sessionToken: "dead",
-    verified: true,
-    // A cached keypair and cert that remain valid.
-    keyPair: {
-      validUntil: Date.now() + KEY_LIFETIME + 10000,
-      rawKeyPair: "good-keypair",
-    },
-    cert: {
-      validUntil: Date.now() + CERT_LIFETIME + 10000,
-      rawCert: "good-cert",
-    },
-  };
-
-  yield fxa.setSignedInUser(credentials);
-  let {keyPair, certificate} = yield fxa.internal.getKeypairAndCertificate(fxa.internal.currentAccountState);
-  // should have the same keypair and cert.
-  do_check_eq(keyPair, credentials.keyPair.rawKeyPair);
-  do_check_eq(certificate, credentials.cert.rawCert);
-  yield fxa.signOut(/*localOnly = */true);
-});
-
-add_task(function* test_getCertificateExpiredCert() {
-  _("getCertificateExpiredCert()");
-  let fxa = MakeFxAccounts({
-    getCertificateSigned() {
-      return "new cert";
-    }
-  });
-  let credentials = {
-    email: "foo at example.com",
-    uid: "1234 at lcip.org",
-    sessionToken: "dead",
-    verified: true,
-    // A cached keypair that remains valid.
-    keyPair: {
-      validUntil: Date.now() + KEY_LIFETIME + 10000,
-      rawKeyPair: "good-keypair",
-    },
-    // A cached certificate which has expired.
-    cert: {
-      validUntil: Date.parse("Mon, 13 Jan 2000 21:45:06 GMT"),
-      rawCert: "expired-cert",
-    },
-  };
-  yield fxa.setSignedInUser(credentials);
-  let {keyPair, certificate} = yield fxa.internal.getKeypairAndCertificate(fxa.internal.currentAccountState);
-  // should have the same keypair but a new cert.
-  do_check_eq(keyPair, credentials.keyPair.rawKeyPair);
-  do_check_neq(certificate, credentials.cert.rawCert);
-  yield fxa.signOut(/*localOnly = */true);
-});
-
-add_task(function* test_getCertificateExpiredKeypair() {
-  _("getCertificateExpiredKeypair()");
-  let fxa = MakeFxAccounts({
-    getCertificateSigned() {
-      return "new cert";
-    },
-  });
-  let credentials = {
-    email: "foo at example.com",
-    uid: "1234 at lcip.org",
-    sessionToken: "dead",
-    verified: true,
-    // A cached keypair that has expired.
-    keyPair: {
-      validUntil: Date.now() - 1000,
-      rawKeyPair: "expired-keypair",
-    },
-    // A cached certificate which remains valid.
-    cert: {
-      validUntil: Date.now() + CERT_LIFETIME + 10000,
-      rawCert: "expired-cert",
-    },
-  };
-
-  yield fxa.setSignedInUser(credentials);
-  let {keyPair, certificate} = yield fxa.internal.getKeypairAndCertificate(fxa.internal.currentAccountState);
-  // even though the cert was valid, the fact the keypair was not means we
-  // should have fetched both.
-  do_check_neq(keyPair, credentials.keyPair.rawKeyPair);
-  do_check_neq(certificate, credentials.cert.rawCert);
-  yield fxa.signOut(/*localOnly = */true);
-});
-
-// Sanity-check that our mocked client is working correctly
-add_test(function test_client_mock() {
-  let fxa = new MockFxAccounts();
-  let client = fxa.internal.fxAccountsClient;
-  do_check_eq(client._verified, false);
-  do_check_eq(typeof client.signIn, "function");
-
-  // The recoveryEmailStatus function eventually fulfills its promise
-  client.recoveryEmailStatus()
-    .then(response => {
-      do_check_eq(response.verified, false);
-      run_next_test();
-    });
-});
-
-// Sign in a user, and after a little while, verify the user's email.
-// Right after signing in the user, we should get the 'onlogin' notification.
-// Polling should detect that the email is verified, and eventually
-// 'onverified' should be observed
-add_test(function test_verification_poll() {
-  let fxa = new MockFxAccounts();
-  let test_user = getTestUser("francine");
-  let login_notification_received = false;
-
-  makeObserver(ONVERIFIED_NOTIFICATION, function() {
-    log.debug("test_verification_poll observed onverified");
-    // Once email verification is complete, we will observe onverified
-    fxa.internal.getUserAccountData().then(user => {
-      // And confirm that the user's state has changed
-      do_check_eq(user.verified, true);
-      do_check_eq(user.email, test_user.email);
-      do_check_true(login_notification_received);
-      run_next_test();
-    });
-  });
-
-  makeObserver(ONLOGIN_NOTIFICATION, function() {
-    log.debug("test_verification_poll observer onlogin");
-    login_notification_received = true;
-  });
-
-  fxa.setSignedInUser(test_user).then(() => {
-    fxa.internal.getUserAccountData().then(user => {
-      // The user is signing in, but email has not been verified yet
-      do_check_eq(user.verified, false);
-      do_timeout(200, function() {
-        log.debug("Mocking verification of francine's email");
-        fxa.internal.fxAccountsClient._email = test_user.email;
-        fxa.internal.fxAccountsClient._verified = true;
-      });
-    });
-  });
-});
-
-// Sign in the user, but never verify the email.  The check-email
-// poll should time out.  No verifiedlogin event should be observed, and the
-// internal whenVerified promise should be rejected
-add_test(function test_polling_timeout() {
-  // This test could be better - the onverified observer might fire on
-  // somebody else's stack, and we're not making sure that we're not receiving
-  // such a message. In other words, this tests either failure, or success, but
-  // not both.
-
-  let fxa = new MockFxAccounts();
-  let test_user = getTestUser("carol");
-
-  let removeObserver = makeObserver(ONVERIFIED_NOTIFICATION, function() {
-    do_throw("We should not be getting a login event!");
-  });
-
-  fxa.internal.POLL_SESSION = 1;
-
-  let p = fxa.internal.whenVerified({});
-
-  fxa.setSignedInUser(test_user).then(() => {
-    p.then(
-      (success) => {
-        do_throw("this should not succeed");
-      },
-      (fail) => {
-        removeObserver();
-        fxa.signOut().then(run_next_test);
-      }
-    );
-  });
-});
-
-add_test(function test_getKeys() {
-  let fxa = new MockFxAccounts();
-  let user = getTestUser("eusebius");
-
-  // Once email has been verified, we will be able to get keys
-  user.verified = true;
-
-  fxa.setSignedInUser(user).then(() => {
-    fxa.getSignedInUser().then((user) => {
-      // Before getKeys, we have no keys
-      do_check_eq(!!user.kA, false);
-      do_check_eq(!!user.kB, false);
-      // And we still have a key-fetch token and unwrapBKey to use
-      do_check_eq(!!user.keyFetchToken, true);
-      do_check_eq(!!user.unwrapBKey, true);
-
-      fxa.internal.getKeys().then(() => {
-        fxa.getSignedInUser().then((user) => {
-          // Now we should have keys
-          do_check_eq(fxa.internal.isUserEmailVerified(user), true);
-          do_check_eq(!!user.verified, true);
-          do_check_eq(user.kA, expandHex("11"));
-          do_check_eq(user.kB, expandHex("66"));
-          do_check_eq(user.keyFetchToken, undefined);
-          do_check_eq(user.unwrapBKey, undefined);
-          run_next_test();
-        });
-      });
-    });
-  });
-});
-
-add_task(function* test_getKeys_nonexistent_account() {
-  let fxa = new MockFxAccounts();
-  let bismarck = getTestUser("bismarck");
-
-  let client = fxa.internal.fxAccountsClient;
-  client.accountStatus = () => Promise.resolve(false);
-  client.accountKeys = () => {
-    return Promise.reject({
-      code: 401,
-      errno: ERRNO_INVALID_AUTH_TOKEN,
-    });
-  };
-
-  yield fxa.setSignedInUser(bismarck);
-
-  let promiseLogout = new Promise(resolve => {
-    makeObserver(ONLOGOUT_NOTIFICATION, function() {
-      log.debug("test_getKeys_nonexistent_account observed logout");
-      resolve();
-    });
-  });
-
-  try {
-    yield fxa.internal.getKeys();
-    do_check_true(false);
-  } catch (err) {
-    do_check_eq(err.code, 401);
-    do_check_eq(err.errno, ERRNO_INVALID_AUTH_TOKEN);
-  }
-
-  yield promiseLogout;
-
-  let user = yield fxa.internal.getUserAccountData();
-  do_check_eq(user, null);
-});
-
-// getKeys with invalid keyFetchToken should delete keyFetchToken from storage
-add_task(function* test_getKeys_invalid_token() {
-  let fxa = new MockFxAccounts();
-  let yusuf = getTestUser("yusuf");
-
-  let client = fxa.internal.fxAccountsClient;
-  client.accountStatus = () => Promise.resolve(true);
-  client.accountKeys = () => {
-    return Promise.reject({
-      code: 401,
-      errno: ERRNO_INVALID_AUTH_TOKEN,
-    });
-  };
-
-  yield fxa.setSignedInUser(yusuf);
-
-  try {
-    yield fxa.internal.getKeys();
-    do_check_true(false);
-  } catch (err) {
-    do_check_eq(err.code, 401);
-    do_check_eq(err.errno, ERRNO_INVALID_AUTH_TOKEN);
-  }
-
-  let user = yield fxa.internal.getUserAccountData();
-  do_check_eq(user.email, yusuf.email);
-  do_check_eq(user.keyFetchToken, null);
-});
-
-//  fetchAndUnwrapKeys with no keyFetchToken should trigger signOut
-add_test(function test_fetchAndUnwrapKeys_no_token() {
-  let fxa = new MockFxAccounts();
-  let user = getTestUser("lettuce.protheroe");
-  delete user.keyFetchToken
-
-  makeObserver(ONLOGOUT_NOTIFICATION, function() {
-    log.debug("test_fetchAndUnwrapKeys_no_token observed logout");
-    fxa.internal.getUserAccountData().then(user => {
-      run_next_test();
-    });
-  });
-
-  fxa.setSignedInUser(user).then(
-    user => {
-      return fxa.internal.fetchAndUnwrapKeys();
-    }
-  ).then(
-    null,
-    error => {
-      log.info("setSignedInUser correctly rejected");
-    }
-  )
-});
-
-// Alice (User A) signs up but never verifies her email.  Then Bob (User B)
-// signs in with a verified email.  Ensure that no sign-in events are triggered
-// on Alice's behalf.  In the end, Bob should be the signed-in user.
-add_test(function test_overlapping_signins() {
-  let fxa = new MockFxAccounts();
-  let alice = getTestUser("alice");
-  let bob = getTestUser("bob");
-
-  makeObserver(ONVERIFIED_NOTIFICATION, function() {
-    log.debug("test_overlapping_signins observed onverified");
-    // Once email verification is complete, we will observe onverified
-    fxa.internal.getUserAccountData().then(user => {
-      do_check_eq(user.email, bob.email);
-      do_check_eq(user.verified, true);
-      run_next_test();
-    });
-  });
-
-  // Alice is the user signing in; her email is unverified.
-  fxa.setSignedInUser(alice).then(() => {
-    log.debug("Alice signing in ...");
-    fxa.internal.getUserAccountData().then(user => {
-      do_check_eq(user.email, alice.email);
-      do_check_eq(user.verified, false);
-      log.debug("Alice has not verified her email ...");
-
-      // Now Bob signs in instead and actually verifies his email
-      log.debug("Bob signing in ...");
-      fxa.setSignedInUser(bob).then(() => {
-        do_timeout(200, function() {
-          // Mock email verification ...
-          log.debug("Bob verifying his email ...");
-          fxa.internal.fxAccountsClient._verified = true;
-        });
-      });
-    });
-  });
-});
-
-add_task(function* test_getAssertion_invalid_token() {
-  let fxa = new MockFxAccounts();
-
-  let client = fxa.internal.fxAccountsClient;
-  client.accountStatus = () => Promise.resolve(true);
-
-  let creds = {
-    sessionToken: "sessionToken",
-    kA: expandHex("11"),
-    kB: expandHex("66"),
-    verified: true,
-    email: "sonia at example.com",
-  };
-  yield fxa.setSignedInUser(creds);
-
-  try {
-    let promiseAssertion = fxa.getAssertion("audience.example.com");
-    fxa.internal._d_signCertificate.reject({
-      code: 401,
-      errno: ERRNO_INVALID_AUTH_TOKEN,
-    });
-    yield promiseAssertion;
-    do_check_true(false, "getAssertion should reject invalid session token");
-  } catch (err) {
-    do_check_eq(err.code, 401);
-    do_check_eq(err.errno, ERRNO_INVALID_AUTH_TOKEN);
-  }
-
-  let user = yield fxa.internal.getUserAccountData();
-  do_check_eq(user.email, creds.email);
-  do_check_eq(user.sessionToken, null);
-});
-
-add_task(function* test_getAssertion() {
-  let fxa = new MockFxAccounts();
-
-  do_check_throws(function* () {
-    yield fxa.getAssertion("nonaudience");
-  });
-
-  let creds = {
-    sessionToken: "sessionToken",
-    kA: expandHex("11"),
-    kB: expandHex("66"),
-    verified: true
-  };
-  // By putting kA/kB/verified in "creds", we skip ahead
-  // to the "we're ready" stage.
-  yield fxa.setSignedInUser(creds);
-
-  _("== ready to go\n");
-  // Start with a nice arbitrary but realistic date.  Here we use a nice RFC
-  // 1123 date string like we would get from an HTTP header. Over the course of
-  // the test, we will update 'now', but leave 'start' where it is.
-  let now = Date.parse("Mon, 13 Jan 2014 21:45:06 GMT");
-  let start = now;
-  fxa.internal._now_is = now;
-
-  let d = fxa.getAssertion("audience.example.com");
-  // At this point, a thread has been spawned to generate the keys.
-  _("-- back from fxa.getAssertion\n");
-  fxa.internal._d_signCertificate.resolve("cert1");
-  let assertion = yield d;
-  do_check_eq(fxa.internal._getCertificateSigned_calls.length, 1);
-  do_check_eq(fxa.internal._getCertificateSigned_calls[0][0], "sessionToken");
-  do_check_neq(assertion, null);
-  _("ASSERTION: " + assertion + "\n");
-  let pieces = assertion.split("~");
-  do_check_eq(pieces[0], "cert1");
-  let userData = yield fxa.getSignedInUser();
-  let keyPair = userData.keyPair;
-  let cert = userData.cert;
-  do_check_neq(keyPair, undefined);
-  _(keyPair.validUntil + "\n");
-  let p2 = pieces[1].split(".");
-  let header = JSON.parse(atob(p2[0]));
-  _("HEADER: " + JSON.stringify(header) + "\n");
-  do_check_eq(header.alg, "DS128");
-  let payload = JSON.parse(atob(p2[1]));
-  _("PAYLOAD: " + JSON.stringify(payload) + "\n");
-  do_check_eq(payload.aud, "audience.example.com");
-  do_check_eq(keyPair.validUntil, start + KEY_LIFETIME);
-  do_check_eq(cert.validUntil, start + CERT_LIFETIME);
-  _("delta: " + Date.parse(payload.exp - start) + "\n");
-  let exp = Number(payload.exp);
-
-  do_check_eq(exp, now + ASSERTION_LIFETIME);
-
-  // Reset for next call.
-  fxa.internal._d_signCertificate = Promise.defer();
-
-  // Getting a new assertion "soon" (i.e., w/o incrementing "now"), even for
-  // a new audience, should not provoke key generation or a signing request.
-  assertion = yield fxa.getAssertion("other.example.com");
-
-  // There were no additional calls - same number of getcert calls as before
-  do_check_eq(fxa.internal._getCertificateSigned_calls.length, 1);
-
-  // Wait an hour; assertion use period expires, but not the certificate
-  now += ONE_HOUR_MS;
-  fxa.internal._now_is = now;
-
-  // This won't block on anything - will make an assertion, but not get a
-  // new certificate.
-  assertion = yield fxa.getAssertion("third.example.com");
-
-  // Test will time out if that failed (i.e., if that had to go get a new cert)
-  pieces = assertion.split("~");
-  do_check_eq(pieces[0], "cert1");
-  p2 = pieces[1].split(".");
-  header = JSON.parse(atob(p2[0]));
-  payload = JSON.parse(atob(p2[1]));
-  do_check_eq(payload.aud, "third.example.com");
-
-  // The keypair and cert should have the same validity as before, but the
-  // expiration time of the assertion should be different.  We compare this to
-  // the initial start time, to which they are relative, not the current value
-  // of "now".
-  userData = yield fxa.getSignedInUser();
-
-  keyPair = userData.keyPair;
-  cert = userData.cert;
-  do_check_eq(keyPair.validUntil, start + KEY_LIFETIME);
-  do_check_eq(cert.validUntil, start + CERT_LIFETIME);
-  exp = Number(payload.exp);
-  do_check_eq(exp, now + ASSERTION_LIFETIME);
-
-  // Now we wait even longer, and expect both assertion and cert to expire.  So
-  // we will have to get a new keypair and cert.
-  now += ONE_DAY_MS;
-  fxa.internal._now_is = now;
-  d = fxa.getAssertion("fourth.example.com");
-  fxa.internal._d_signCertificate.resolve("cert2");
-  assertion = yield d;
-  do_check_eq(fxa.internal._getCertificateSigned_calls.length, 2);
-  do_check_eq(fxa.internal._getCertificateSigned_calls[1][0], "sessionToken");
-  pieces = assertion.split("~");
-  do_check_eq(pieces[0], "cert2");
-  p2 = pieces[1].split(".");
-  header = JSON.parse(atob(p2[0]));
-  payload = JSON.parse(atob(p2[1]));
-  do_check_eq(payload.aud, "fourth.example.com");
-  userData = yield fxa.getSignedInUser();
-  keyPair = userData.keyPair;
-  cert = userData.cert;
-  do_check_eq(keyPair.validUntil, now + KEY_LIFETIME);
-  do_check_eq(cert.validUntil, now + CERT_LIFETIME);
-  exp = Number(payload.exp);
-
-  do_check_eq(exp, now + ASSERTION_LIFETIME);
-  _("----- DONE ----\n");
-});
-
-add_task(function* test_resend_email_not_signed_in() {
-  let fxa = new MockFxAccounts();
-
-  try {
-    yield fxa.resendVerificationEmail();
-  } catch(err) {
-    do_check_eq(err.message,
-      "Cannot resend verification email; no signed-in user");
-    return;
-  }
-  do_throw("Should not be able to resend email when nobody is signed in");
-});
-
-add_test(function test_accountStatus() {
-  let fxa = new MockFxAccounts();
-  let alice = getTestUser("alice");
-
-  // If we have no user, we have no account server-side
-  fxa.accountStatus().then(
-    (result) => {
-      do_check_false(result);
-    }
-  ).then(
-    () => {
-      fxa.setSignedInUser(alice).then(
-        () => {
-          fxa.accountStatus().then(
-            (result) => {
-               // FxAccounts.accountStatus() should match Client.accountStatus()
-               do_check_true(result);
-               fxa.internal.fxAccountsClient._deletedOnServer = true;
-               fxa.accountStatus().then(
-                 (result) => {
-                   do_check_false(result);
-                   fxa.internal.fxAccountsClient._deletedOnServer = false;
-                   fxa.signOut().then(run_next_test);
-                 }
-               );
-            }
-          )
-        }
-      );
-    }
-  );
-});
-
-add_task(function* test_resend_email_invalid_token() {
-  let fxa = new MockFxAccounts();
-  let sophia = getTestUser("sophia");
-  do_check_neq(sophia.sessionToken, null);
-
-  let client = fxa.internal.fxAccountsClient;
-  client.resendVerificationEmail = () => {
-    return Promise.reject({
-      code: 401,
-      errno: ERRNO_INVALID_AUTH_TOKEN,
-    });
-  };
-  client.accountStatus = () => Promise.resolve(true);
-
-  yield fxa.setSignedInUser(sophia);
-  let user = yield fxa.internal.getUserAccountData();
-  do_check_eq(user.email, sophia.email);
-  do_check_eq(user.verified, false);
-  log.debug("Sophia wants verification email resent");
-
-  try {
-    yield fxa.resendVerificationEmail();
-    do_check_true(false, "resendVerificationEmail should reject invalid session token");
-  } catch (err) {
-    do_check_eq(err.code, 401);
-    do_check_eq(err.errno, ERRNO_INVALID_AUTH_TOKEN);
-  }
-
-  user = yield fxa.internal.getUserAccountData();
-  do_check_eq(user.email, sophia.email);
-  do_check_eq(user.sessionToken, null);
-});
-
-add_test(function test_resend_email() {
-  let fxa = new MockFxAccounts();
-  let alice = getTestUser("alice");
-
-  let initialState = fxa.internal.currentAccountState;
-
-  // Alice is the user signing in; her email is unverified.
-  fxa.setSignedInUser(alice).then(() => {
-    log.debug("Alice signing in");
-
-    // We're polling for the first email
-    do_check_true(fxa.internal.currentAccountState !== initialState);
-    let aliceState = fxa.internal.currentAccountState;
-
-    // The polling timer is ticking
-    do_check_true(fxa.internal.currentTimer > 0);
-
-    fxa.internal.getUserAccountData().then(user => {
-      do_check_eq(user.email, alice.email);
-      do_check_eq(user.verified, false);
-      log.debug("Alice wants verification email resent");
-
-      fxa.resendVerificationEmail().then((result) => {
-        // Mock server response; ensures that the session token actually was
-        // passed to the client to make the hawk call
-        do_check_eq(result, "alice's session token");
-
-        // Timer was not restarted
-        do_check_true(fxa.internal.currentAccountState === aliceState);
-
-        // Timer is still ticking
-        do_check_true(fxa.internal.currentTimer > 0);
-
-        // Ok abort polling before we go on to the next test
-        fxa.internal.abortExistingFlow();
-        run_next_test();
-      });
-    });
-  });
-});
-
-add_task(function* test_sign_out_with_device() {
-  const fxa = new MockFxAccounts();
-
-  const credentials = getTestUser("alice");
-  yield fxa.internal.setSignedInUser(credentials);
-
-  const user = yield fxa.internal.getUserAccountData();
-  do_check_true(user);
-  Object.keys(credentials).forEach(key => do_check_eq(credentials[key], user[key]));
-
-  const spy = {
-    signOut: { count: 0 },
-    signOutAndDeviceDestroy: { count: 0, args: [] }
-  };
-  const client = fxa.internal.fxAccountsClient;
-  client.signOut = function () {
-    spy.signOut.count += 1;
-    return Promise.resolve();
-  };
-  client.signOutAndDestroyDevice = function () {
-    spy.signOutAndDeviceDestroy.count += 1;
-    spy.signOutAndDeviceDestroy.args.push(arguments);
-    return Promise.resolve();
-  };
-
-  const promise = new Promise(resolve => {
-    makeObserver(ONLOGOUT_NOTIFICATION, () => {
-      log.debug("test_sign_out_with_device observed onlogout");
-      // user should be undefined after sign out
-      fxa.internal.getUserAccountData().then(user2 => {
-        do_check_eq(user2, null);
-        do_check_eq(spy.signOut.count, 0);
-        do_check_eq(spy.signOutAndDeviceDestroy.count, 1);
-        do_check_eq(spy.signOutAndDeviceDestroy.args[0].length, 3);
-        do_check_eq(spy.signOutAndDeviceDestroy.args[0][0], credentials.sessionToken);
-        do_check_eq(spy.signOutAndDeviceDestroy.args[0][1], credentials.deviceId);
-        do_check_true(spy.signOutAndDeviceDestroy.args[0][2]);
-        do_check_eq(spy.signOutAndDeviceDestroy.args[0][2].service, "sync");
-        resolve();
-      });
-    });
-  });
-
-  yield fxa.signOut();
-
-  yield promise;
-});
-
-add_task(function* test_sign_out_without_device() {
-  const fxa = new MockFxAccounts();
-
-  const credentials = getTestUser("alice");
-  delete credentials.deviceId;
-  yield fxa.internal.setSignedInUser(credentials);
-
-  const user = yield fxa.internal.getUserAccountData();
-
-  const spy = {
-    signOut: { count: 0, args: [] },
-    signOutAndDeviceDestroy: { count: 0 }
-  };
-  const client = fxa.internal.fxAccountsClient;
-  client.signOut = function () {
-    spy.signOut.count += 1;
-    spy.signOut.args.push(arguments);
-    return Promise.resolve();
-  };
-  client.signOutAndDestroyDevice = function () {
-    spy.signOutAndDeviceDestroy.count += 1;
-    return Promise.resolve();
-  };
-
-  const promise = new Promise(resolve => {
-    makeObserver(ONLOGOUT_NOTIFICATION, () => {
-      log.debug("test_sign_out_without_device observed onlogout");
-      // user should be undefined after sign out
-      fxa.internal.getUserAccountData().then(user2 => {
-        do_check_eq(user2, null);
-        do_check_eq(spy.signOut.count, 1);
-        do_check_eq(spy.signOut.args[0].length, 2);
-        do_check_eq(spy.signOut.args[0][0], credentials.sessionToken);
-        do_check_true(spy.signOut.args[0][1]);
-        do_check_eq(spy.signOut.args[0][1].service, "sync");
-        do_check_eq(spy.signOutAndDeviceDestroy.count, 0);
-        resolve();
-      });
-    });
-  });
-
-  yield fxa.signOut();
-
-  yield promise;
-});
-
-add_task(function* test_sign_out_with_remote_error() {
-  let fxa = new MockFxAccounts();
-  let client = fxa.internal.fxAccountsClient;
-  let remoteSignOutCalled = false;
-  // Force remote sign out to trigger an error
-  client.signOutAndDestroyDevice = function() { remoteSignOutCalled = true; throw "Remote sign out error"; };
-  let promiseLogout = new Promise(resolve => {
-    makeObserver(ONLOGOUT_NOTIFICATION, function() {
-      log.debug("test_sign_out_with_remote_error observed onlogout");
-      resolve();
-    });
-  });
-
-  let jane = getTestUser("jane");
-  yield fxa.setSignedInUser(jane);
-  yield fxa.signOut();
-  yield promiseLogout;
-
-  let user = yield fxa.internal.getUserAccountData();
-  do_check_eq(user, null);
-  do_check_true(remoteSignOutCalled);
-});
-
-add_test(function test_getOAuthToken() {
-  let fxa = new MockFxAccounts();
-  let alice = getTestUser("alice");
-  alice.verified = true;
-  let getTokenFromAssertionCalled = false;
-
-  fxa.internal._d_signCertificate.resolve("cert1");
-
-  // create a mock oauth client
-  let client = new FxAccountsOAuthGrantClient({
-    serverURL: "http://example.com/v1",
-    client_id: "abc123"
-  });
-  client.getTokenFromAssertion = function () {
-    getTokenFromAssertionCalled = true;
-    return Promise.resolve({ access_token: "token" });
-  };
-
-  fxa.setSignedInUser(alice).then(
-    () => {
-      fxa.getOAuthToken({ scope: "profile", client: client }).then(
-        (result) => {
-           do_check_true(getTokenFromAssertionCalled);
-           do_check_eq(result, "token");
-           run_next_test();
-        }
-      )
-    }
-  );
-
-});
-
-add_test(function test_getOAuthTokenScoped() {
-  let fxa = new MockFxAccounts();
-  let alice = getTestUser("alice");
-  alice.verified = true;
-  let getTokenFromAssertionCalled = false;
-
-  fxa.internal._d_signCertificate.resolve("cert1");
-
-  // create a mock oauth client
-  let client = new FxAccountsOAuthGrantClient({
-    serverURL: "http://example.com/v1",
-    client_id: "abc123"
-  });
-  client.getTokenFromAssertion = function (assertion, scopeString) {
-    equal(scopeString, "foo bar");
-    getTokenFromAssertionCalled = true;
-    return Promise.resolve({ access_token: "token" });
-  };
-
-  fxa.setSignedInUser(alice).then(
-    () => {
-      fxa.getOAuthToken({ scope: ["foo", "bar"], client: client }).then(
-        (result) => {
-           do_check_true(getTokenFromAssertionCalled);
-           do_check_eq(result, "token");
-           run_next_test();
-        }
-      )
-    }
-  );
-
-});
-
-add_task(function* test_getOAuthTokenCached() {
-  let fxa = new MockFxAccounts();
-  let alice = getTestUser("alice");
-  alice.verified = true;
-  let numTokenFromAssertionCalls = 0;
-
-  fxa.internal._d_signCertificate.resolve("cert1");
-
-  // create a mock oauth client
-  let client = new FxAccountsOAuthGrantClient({
-    serverURL: "http://example.com/v1",
-    client_id: "abc123"
-  });
-  client.getTokenFromAssertion = function () {
-    numTokenFromAssertionCalls += 1;
-    return Promise.resolve({ access_token: "token" });
-  };
-
-  yield fxa.setSignedInUser(alice);
-  let result = yield fxa.getOAuthToken({ scope: "profile", client: client, service: "test-service" });
-  do_check_eq(numTokenFromAssertionCalls, 1);
-  do_check_eq(result, "token");
-
-  // requesting it again should not re-fetch the token.
-  result = yield fxa.getOAuthToken({ scope: "profile", client: client, service: "test-service" });
-  do_check_eq(numTokenFromAssertionCalls, 1);
-  do_check_eq(result, "token");
-  // But requesting the same service and a different scope *will* get a new one.
-  result = yield fxa.getOAuthToken({ scope: "something-else", client: client, service: "test-service" });
-  do_check_eq(numTokenFromAssertionCalls, 2);
-  do_check_eq(result, "token");
-});
-
-add_task(function* test_getOAuthTokenCachedScopeNormalization() {
-  let fxa = new MockFxAccounts();
-  let alice = getTestUser("alice");
-  alice.verified = true;
-  let numTokenFromAssertionCalls = 0;
-
-  fxa.internal._d_signCertificate.resolve("cert1");
-
-  // create a mock oauth client
-  let client = new FxAccountsOAuthGrantClient({
-    serverURL: "http://example.com/v1",
-    client_id: "abc123"
-  });
-  client.getTokenFromAssertion = function () {
-    numTokenFromAssertionCalls += 1;
-    return Promise.resolve({ access_token: "token" });
-  };
-
-  yield fxa.setSignedInUser(alice);
-  let result = yield fxa.getOAuthToken({ scope: ["foo", "bar"], client: client, service: "test-service" });
-  do_check_eq(numTokenFromAssertionCalls, 1);
-  do_check_eq(result, "token");
-
-  // requesting it again with the scope array in a different order not re-fetch the token.
-  result = yield fxa.getOAuthToken({ scope: ["bar", "foo"], client: client, service: "test-service" });
-  do_check_eq(numTokenFromAssertionCalls, 1);
-  do_check_eq(result, "token");
-  // requesting it again with the scope array in different case not re-fetch the token.
-  result = yield fxa.getOAuthToken({ scope: ["Bar", "Foo"], client: client, service: "test-service" });
-  do_check_eq(numTokenFromAssertionCalls, 1);
-  do_check_eq(result, "token");
-  // But requesting with a new entry in the array does fetch one.
-  result = yield fxa.getOAuthToken({ scope: ["foo", "bar", "etc"], client: client, service: "test-service" });
-  do_check_eq(numTokenFromAssertionCalls, 2);
-  do_check_eq(result, "token");
-});
-
-Services.prefs.setCharPref("identity.fxaccounts.remote.oauth.uri", "https://example.com/v1");
-add_test(function test_getOAuthToken_invalid_param() {
-  let fxa = new MockFxAccounts();
-
-  fxa.getOAuthToken()
-    .then(null, err => {
-       do_check_eq(err.message, "INVALID_PARAMETER");
-       fxa.signOut().then(run_next_test);
-    });
-});
-
-add_test(function test_getOAuthToken_invalid_scope_array() {
-  let fxa = new MockFxAccounts();
-
-  fxa.getOAuthToken({scope: []})
-    .then(null, err => {
-       do_check_eq(err.message, "INVALID_PARAMETER");
-       fxa.signOut().then(run_next_test);
-    });
-});
-
-add_test(function test_getOAuthToken_misconfigure_oauth_uri() {
-  let fxa = new MockFxAccounts();
-
-  Services.prefs.deleteBranch("identity.fxaccounts.remote.oauth.uri");
-
-  fxa.getOAuthToken()
-    .then(null, err => {
-       do_check_eq(err.message, "INVALID_PARAMETER");
-       // revert the pref
-       Services.prefs.setCharPref("identity.fxaccounts.remote.oauth.uri", "https://example.com/v1");
-       fxa.signOut().then(run_next_test);
-    });
-});
-
-add_test(function test_getOAuthToken_no_account() {
-  let fxa = new MockFxAccounts();
-
-  fxa.internal.currentAccountState.getUserAccountData = function () {
-    return Promise.resolve(null);
-  };
-
-  fxa.getOAuthToken({ scope: "profile" })
-    .then(null, err => {
-       do_check_eq(err.message, "NO_ACCOUNT");
-       fxa.signOut().then(run_next_test);
-    });
-});
-
-add_test(function test_getOAuthToken_unverified() {
-  let fxa = new MockFxAccounts();
-  let alice = getTestUser("alice");
-
-  fxa.setSignedInUser(alice).then(() => {
-    fxa.getOAuthToken({ scope: "profile" })
-      .then(null, err => {
-         do_check_eq(err.message, "UNVERIFIED_ACCOUNT");
-         fxa.signOut().then(run_next_test);
-      });
-  });
-});
-
-add_test(function test_getOAuthToken_network_error() {
-  let fxa = new MockFxAccounts();
-  let alice = getTestUser("alice");
-  alice.verified = true;
-
-  fxa.internal._d_signCertificate.resolve("cert1");
-
-  // create a mock oauth client
-  let client = new FxAccountsOAuthGrantClient({
-    serverURL: "http://example.com/v1",
-    client_id: "abc123"
-  });
-  client.getTokenFromAssertion = function () {
-    return Promise.reject(new FxAccountsOAuthGrantClientError({
-      error: ERROR_NETWORK,
-      errno: ERRNO_NETWORK
-    }));
-  };
-
-  fxa.setSignedInUser(alice).then(() => {
-    fxa.getOAuthToken({ scope: "profile", client: client })
-      .then(null, err => {
-         do_check_eq(err.message, "NETWORK_ERROR");
-         do_check_eq(err.details.errno, ERRNO_NETWORK);
-         run_next_test();
-      });
-  });
-});
-
-add_test(function test_getOAuthToken_auth_error() {
-  let fxa = new MockFxAccounts();
-  let alice = getTestUser("alice");
-  alice.verified = true;
-
-  fxa.internal._d_signCertificate.resolve("cert1");
-
-  // create a mock oauth client
-  let client = new FxAccountsOAuthGrantClient({
-    serverURL: "http://example.com/v1",
-    client_id: "abc123"
-  });
-  client.getTokenFromAssertion = function () {
-    return Promise.reject(new FxAccountsOAuthGrantClientError({
-      error: ERROR_INVALID_FXA_ASSERTION,
-      errno: ERRNO_INVALID_FXA_ASSERTION
-    }));
-  };
-
-  fxa.setSignedInUser(alice).then(() => {
-    fxa.getOAuthToken({ scope: "profile", client: client })
-      .then(null, err => {
-         do_check_eq(err.message, "AUTH_ERROR");
-         do_check_eq(err.details.errno, ERRNO_INVALID_FXA_ASSERTION);
-         run_next_test();
-      });
-  });
-});
-
-add_test(function test_getOAuthToken_unknown_error() {
-  let fxa = new MockFxAccounts();
-  let alice = getTestUser("alice");
-  alice.verified = true;
-
-  fxa.internal._d_signCertificate.resolve("cert1");
-
-  // create a mock oauth client
-  let client = new FxAccountsOAuthGrantClient({
-    serverURL: "http://example.com/v1",
-    client_id: "abc123"
-  });
-  client.getTokenFromAssertion = function () {
-    return Promise.reject("boom");
-  };
-
-  fxa.setSignedInUser(alice).then(() => {
-    fxa.getOAuthToken({ scope: "profile", client: client })
-      .then(null, err => {
-         do_check_eq(err.message, "UNKNOWN_ERROR");
-         run_next_test();
-      });
-  });
-});
-
-add_test(function test_getSignedInUserProfile() {
-  let alice = getTestUser("alice");
-  alice.verified = true;
-
-  let mockProfile = {
-    getProfile: function () {
-      return Promise.resolve({ avatar: "image" });
-    },
-    tearDown: function() {},
-  };
-  let fxa = new FxAccounts({
-    _signOutServer() { return Promise.resolve(); },
-    _registerOrUpdateDevice() { return Promise.resolve(); }
-  });
-
-  fxa.setSignedInUser(alice).then(() => {
-    fxa.internal._profile = mockProfile;
-    fxa.getSignedInUserProfile()
-      .then(result => {
-         do_check_true(!!result);
-         do_check_eq(result.avatar, "image");
-         run_next_test();
-      });
-  });
-});
-
-add_test(function test_getSignedInUserProfile_error_uses_account_data() {
-  let fxa = new MockFxAccounts();
-  let alice = getTestUser("alice");
-  alice.verified = true;
-
-  fxa.internal.getSignedInUser = function () {
-    return Promise.resolve({ email: "foo at bar.com" });
-  };
-
-  let teardownCalled = false;
-  fxa.setSignedInUser(alice).then(() => {
-    fxa.internal._profile = {
-      getProfile: function () {
-        return Promise.reject("boom");
-      },
-      tearDown: function() {
-        teardownCalled = true;
-      }
-    };
-
-    fxa.getSignedInUserProfile()
-      .catch(error => {
-        do_check_eq(error.message, "UNKNOWN_ERROR");
-        fxa.signOut().then(() => {
-          do_check_true(teardownCalled);
-          run_next_test();
-        });
-      });
-  });
-});
-
-add_test(function test_getSignedInUserProfile_unverified_account() {
-  let fxa = new MockFxAccounts();
-  let alice = getTestUser("alice");
-
-  fxa.setSignedInUser(alice).then(() => {
-    fxa.getSignedInUserProfile()
-      .catch(error => {
-         do_check_eq(error.message, "UNVERIFIED_ACCOUNT");
-         fxa.signOut().then(run_next_test);
-      });
-  });
-
-});
-
-add_test(function test_getSignedInUserProfile_no_account_data() {
-  let fxa = new MockFxAccounts();
-
-  fxa.internal.getSignedInUser = function () {
-    return Promise.resolve(null);
-  };
-
-  fxa.getSignedInUserProfile()
-    .catch(error => {
-       do_check_eq(error.message, "NO_ACCOUNT");
-       fxa.signOut().then(run_next_test);
-    });
-
-});
-
-add_task(function* test_checkVerificationStatusFailed() {
-  let fxa = new MockFxAccounts();
-  let alice = getTestUser("alice");
-  alice.verified = true;
-
-  let client = fxa.internal.fxAccountsClient;
-  client.recoveryEmailStatus = () => {
-    return Promise.reject({
-      code: 401,
-      errno: ERRNO_INVALID_AUTH_TOKEN,
-    });
-  };
-  client.accountStatus = () => Promise.resolve(true);
-
-  yield fxa.setSignedInUser(alice);
-  let user = yield fxa.internal.getUserAccountData();
-  do_check_neq(alice.sessionToken, null);
-  do_check_eq(user.email, alice.email);
-  do_check_eq(user.verified, true);
-
-  yield fxa.checkVerificationStatus();
-
-  user = yield fxa.internal.getUserAccountData();
-  do_check_eq(user.email, alice.email);
-  do_check_eq(user.sessionToken, null);
-});
-
-/*
- * End of tests.
- * Utility functions follow.
- */
-
-function expandHex(two_hex) {
-  // Return a 64-character hex string, encoding 32 identical bytes.
-  let eight_hex = two_hex + two_hex + two_hex + two_hex;
-  let thirtytwo_hex = eight_hex + eight_hex + eight_hex + eight_hex;
-  return thirtytwo_hex + thirtytwo_hex;
-};
-
-function expandBytes(two_hex) {
-  return CommonUtils.hexToBytes(expandHex(two_hex));
-};
-
-function getTestUser(name) {
-  return {
-    email: name + "@example.com",
-    uid: "1ad7f502-4cc7-4ec1-a209-071fd2fae348",
-    deviceId: name + "'s device id",
-    sessionToken: name + "'s session token",
-    keyFetchToken: name + "'s keyfetch token",
-    unwrapBKey: expandHex("44"),
-    verified: false
-  };
-}
-
-function makeObserver(aObserveTopic, aObserveFunc) {
-  let observer = {
-    // nsISupports provides type management in C++
-    // nsIObserver is to be an observer
-    QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports, Ci.nsIObserver]),
-
-    observe: function (aSubject, aTopic, aData) {
-      log.debug("observed " + aTopic + " " + aData);
-      if (aTopic == aObserveTopic) {
-        removeMe();
-        aObserveFunc(aSubject, aTopic, aData);
-      }
-    }
-  };
-
-  function removeMe() {
-    log.debug("removing observer for " + aObserveTopic);
-    Services.obs.removeObserver(observer, aObserveTopic);
-  }
-
-  Services.obs.addObserver(observer, aObserveTopic, false);
-  return removeMe;
-}
-
-function do_check_throws(func, result, stack)
-{
-  if (!stack)
-    stack = Components.stack.caller;
-
-  try {
-    func();
-  } catch (ex) {
-    if (ex.name == result) {
-      return;
-    }
-    do_throw("Expected result " + result + ", caught " + ex.name, stack);
-  }
-
-  if (result) {
-    do_throw("Expected result " + result + ", none thrown", stack);
-  }
-}
diff --git a/services/fxaccounts/tests/xpcshell/test_accounts_device_registration.js b/services/fxaccounts/tests/xpcshell/test_accounts_device_registration.js
deleted file mode 100644
index 9a2d2c1..0000000
--- a/services/fxaccounts/tests/xpcshell/test_accounts_device_registration.js
+++ /dev/null
@@ -1,526 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/ */
-
-"use strict";
-
-Cu.import("resource://services-common/utils.js");
-Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://gre/modules/FxAccounts.jsm");
-Cu.import("resource://gre/modules/FxAccountsClient.jsm");
-Cu.import("resource://gre/modules/FxAccountsCommon.js");
-Cu.import("resource://gre/modules/Promise.jsm");
-Cu.import("resource://gre/modules/Log.jsm");
-
-initTestLogging("Trace");
-
-var log = Log.repository.getLogger("Services.FxAccounts.test");
-log.level = Log.Level.Debug;
-
-const BOGUS_PUBLICKEY = "BBXOKjUb84pzws1wionFpfCBjDuCh4-s_1b52WA46K5wYL2gCWEOmFKWn_NkS5nmJwTBuO8qxxdjAIDtNeklvQc";
-const BOGUS_AUTHKEY = "GSsIiaD2Mr83iPqwFNK4rw";
-
-Services.prefs.setCharPref("identity.fxaccounts.loglevel", "Trace");
-Log.repository.getLogger("FirefoxAccounts").level = Log.Level.Trace;
-
-Services.prefs.setCharPref("identity.fxaccounts.remote.oauth.uri", "https://example.com/v1");
-Services.prefs.setCharPref("identity.fxaccounts.oauth.client_id", "abc123");
-Services.prefs.setCharPref("identity.fxaccounts.remote.profile.uri", "http://example.com/v1");
-Services.prefs.setCharPref("identity.fxaccounts.settings.uri", "http://accounts.example.com/");
-
-const DEVICE_REGISTRATION_VERSION = 42;
-
-function MockStorageManager() {
-}
-
-MockStorageManager.prototype = {
-  initialize(accountData) {
-    this.accountData = accountData;
-  },
-
-  finalize() {
-    return Promise.resolve();
-  },
-
-  getAccountData() {
-    return Promise.resolve(this.accountData);
-  },
-
-  updateAccountData(updatedFields) {
-    for (let [name, value] of Object.entries(updatedFields)) {
-      if (value == null) {
-        delete this.accountData[name];
-      } else {
-        this.accountData[name] = value;
-      }
-    }
-    return Promise.resolve();
-  },
-
-  deleteAccountData() {
-    this.accountData = null;
-    return Promise.resolve();
-  }
-}
-
-function MockFxAccountsClient(device) {
-  this._email = "nobody at example.com";
-  this._verified = false;
-  this._deletedOnServer = false; // for testing accountStatus
-
-  // mock calls up to the auth server to determine whether the
-  // user account has been verified
-  this.recoveryEmailStatus = function (sessionToken) {
-    // simulate a call to /recovery_email/status
-    return Promise.resolve({
-      email: this._email,
-      verified: this._verified
-    });
-  };
-
-  this.accountStatus = function(uid) {
-    let deferred = Promise.defer();
-    deferred.resolve(!!uid && (!this._deletedOnServer));
-    return deferred.promise;
-  };
-
-  const { id: deviceId, name: deviceName, type: deviceType, sessionToken } = device;
-
-  this.registerDevice = (st, name, type) => Promise.resolve({ id: deviceId, name });
-  this.updateDevice = (st, id, name) => Promise.resolve({ id, name });
-  this.signOutAndDestroyDevice = () => Promise.resolve({});
-  this.getDeviceList = (st) =>
-    Promise.resolve([
-      { id: deviceId, name: deviceName, type: deviceType, isCurrentDevice: st === sessionToken }
-    ]);
-
-  FxAccountsClient.apply(this);
-}
-MockFxAccountsClient.prototype = {
-  __proto__: FxAccountsClient.prototype
-}
-
-function MockFxAccounts(device = {}) {
-  return new FxAccounts({
-    _getDeviceName() {
-      return device.name || "mock device name";
-    },
-    fxAccountsClient: new MockFxAccountsClient(device),
-    fxaPushService: {
-      registerPushEndpoint() {
-        return new Promise((resolve) => {
-          resolve({
-            endpoint: "http://mochi.test:8888",
-            getKey: function(type) {
-              return ChromeUtils.base64URLDecode(
-                type === "auth" ? BOGUS_AUTHKEY : BOGUS_PUBLICKEY,
-                { padding: "ignore" });
-            }
-          });
-        });
-      },
-    },
-    DEVICE_REGISTRATION_VERSION
-  });
-}
-
-add_task(function* test_updateDeviceRegistration_with_new_device() {
-  const deviceName = "foo";
-  const deviceType = "bar";
-
-  const credentials = getTestUser("baz");
-  delete credentials.deviceId;
-  const fxa = new MockFxAccounts({ name: deviceName });
-  yield fxa.internal.setSignedInUser(credentials);
-
-  const spy = {
-    registerDevice: { count: 0, args: [] },
-    updateDevice: { count: 0, args: [] },
-    getDeviceList: { count: 0, args: [] }
-  };
-  const client = fxa.internal.fxAccountsClient;
-  client.registerDevice = function () {
-    spy.registerDevice.count += 1;
-    spy.registerDevice.args.push(arguments);
-    return Promise.resolve({
-      id: "newly-generated device id",
-      createdAt: Date.now(),
-      name: deviceName,
-      type: deviceType
-    });
-  };
-  client.updateDevice = function () {
-    spy.updateDevice.count += 1;
-    spy.updateDevice.args.push(arguments);
-    return Promise.resolve({});
-  };
-  client.getDeviceList = function () {
-    spy.getDeviceList.count += 1;
-    spy.getDeviceList.args.push(arguments);
-    return Promise.resolve([]);
-  };
-
-  const result = yield fxa.updateDeviceRegistration();
-
-  do_check_eq(result, "newly-generated device id");
-  do_check_eq(spy.updateDevice.count, 0);
-  do_check_eq(spy.getDeviceList.count, 0);
-  do_check_eq(spy.registerDevice.count, 1);
-  do_check_eq(spy.registerDevice.args[0].length, 4);
-  do_check_eq(spy.registerDevice.args[0][0], credentials.sessionToken);
-  do_check_eq(spy.registerDevice.args[0][1], deviceName);
-  do_check_eq(spy.registerDevice.args[0][2], "desktop");
-  do_check_eq(spy.registerDevice.args[0][3].pushCallback, "http://mochi.test:8888");
-  do_check_eq(spy.registerDevice.args[0][3].pushPublicKey, BOGUS_PUBLICKEY);
-  do_check_eq(spy.registerDevice.args[0][3].pushAuthKey, BOGUS_AUTHKEY);
-
-  const state = fxa.internal.currentAccountState;
-  const data = yield state.getUserAccountData();
-
-  do_check_eq(data.deviceId, "newly-generated device id");
-  do_check_eq(data.deviceRegistrationVersion, DEVICE_REGISTRATION_VERSION);
-});
-
-add_task(function* test_updateDeviceRegistration_with_existing_device() {
-  const deviceName = "phil's device";
-  const deviceType = "desktop";
-
-  const credentials = getTestUser("pb");
-  const fxa = new MockFxAccounts({ name: deviceName });
-  yield fxa.internal.setSignedInUser(credentials);
-
-  const spy = {
-    registerDevice: { count: 0, args: [] },
-    updateDevice: { count: 0, args: [] },
-    getDeviceList: { count: 0, args: [] }
-  };
-  const client = fxa.internal.fxAccountsClient;
-  client.registerDevice = function () {
-    spy.registerDevice.count += 1;
-    spy.registerDevice.args.push(arguments);
-    return Promise.resolve({});
-  };
-  client.updateDevice = function () {
-    spy.updateDevice.count += 1;
-    spy.updateDevice.args.push(arguments);
-    return Promise.resolve({
-      id: credentials.deviceId,
-      name: deviceName
-    });
-  };
-  client.getDeviceList = function () {
-    spy.getDeviceList.count += 1;
-    spy.getDeviceList.args.push(arguments);
-    return Promise.resolve([]);
-  };
-  const result = yield fxa.updateDeviceRegistration();
-
-  do_check_eq(result, credentials.deviceId);
-  do_check_eq(spy.registerDevice.count, 0);
-  do_check_eq(spy.getDeviceList.count, 0);
-  do_check_eq(spy.updateDevice.count, 1);
-  do_check_eq(spy.updateDevice.args[0].length, 4);
-  do_check_eq(spy.updateDevice.args[0][0], credentials.sessionToken);
-  do_check_eq(spy.updateDevice.args[0][1], credentials.deviceId);
-  do_check_eq(spy.updateDevice.args[0][2], deviceName);
-  do_check_eq(spy.updateDevice.args[0][3].pushCallback, "http://mochi.test:8888");
-  do_check_eq(spy.updateDevice.args[0][3].pushPublicKey, BOGUS_PUBLICKEY);
-  do_check_eq(spy.updateDevice.args[0][3].pushAuthKey, BOGUS_AUTHKEY);
-
-  const state = fxa.internal.currentAccountState;
-  const data = yield state.getUserAccountData();
-
-  do_check_eq(data.deviceId, credentials.deviceId);
-  do_check_eq(data.deviceRegistrationVersion, DEVICE_REGISTRATION_VERSION);
-});
-
-add_task(function* test_updateDeviceRegistration_with_unknown_device_error() {
-  const deviceName = "foo";
-  const deviceType = "bar";
-
-  const credentials = getTestUser("baz");
-  const fxa = new MockFxAccounts({ name: deviceName });
-  yield fxa.internal.setSignedInUser(credentials);
-
-  const spy = {
-    registerDevice: { count: 0, args: [] },
-    updateDevice: { count: 0, args: [] },
-    getDeviceList: { count: 0, args: [] }
-  };
-  const client = fxa.internal.fxAccountsClient;
-  client.registerDevice = function () {
-    spy.registerDevice.count += 1;
-    spy.registerDevice.args.push(arguments);
-    return Promise.resolve({
-      id: "a different newly-generated device id",
-      createdAt: Date.now(),
-      name: deviceName,
-      type: deviceType
-    });
-  };
-  client.updateDevice = function () {
-    spy.updateDevice.count += 1;
-    spy.updateDevice.args.push(arguments);
-    return Promise.reject({
-      code: 400,
-      errno: ERRNO_UNKNOWN_DEVICE
-    });
-  };
-  client.getDeviceList = function () {
-    spy.getDeviceList.count += 1;
-    spy.getDeviceList.args.push(arguments);
-    return Promise.resolve([]);
-  };
-
-  const result = yield fxa.updateDeviceRegistration();
-
-  do_check_null(result);
-  do_check_eq(spy.getDeviceList.count, 0);
-  do_check_eq(spy.registerDevice.count, 0);
-  do_check_eq(spy.updateDevice.count, 1);
-  do_check_eq(spy.updateDevice.args[0].length, 4);
-  do_check_eq(spy.updateDevice.args[0][0], credentials.sessionToken);
-  do_check_eq(spy.updateDevice.args[0][1], credentials.deviceId);
-  do_check_eq(spy.updateDevice.args[0][2], deviceName);
-  do_check_eq(spy.updateDevice.args[0][3].pushCallback, "http://mochi.test:8888");
-  do_check_eq(spy.updateDevice.args[0][3].pushPublicKey, BOGUS_PUBLICKEY);
-  do_check_eq(spy.updateDevice.args[0][3].pushAuthKey, BOGUS_AUTHKEY);
-
-
-  const state = fxa.internal.currentAccountState;
-  const data = yield state.getUserAccountData();
-
-  do_check_null(data.deviceId);
-  do_check_eq(data.deviceRegistrationVersion, DEVICE_REGISTRATION_VERSION);
-});
-
-add_task(function* test_updateDeviceRegistration_with_device_session_conflict_error() {
-  const deviceName = "foo";
-  const deviceType = "bar";
-
-  const credentials = getTestUser("baz");
-  const fxa = new MockFxAccounts({ name: deviceName });
-  yield fxa.internal.setSignedInUser(credentials);
-
-  const spy = {
-    registerDevice: { count: 0, args: [] },
-    updateDevice: { count: 0, args: [], times: [] },
-    getDeviceList: { count: 0, args: [] }
-  };
-  const client = fxa.internal.fxAccountsClient;
-  client.registerDevice = function () {
-    spy.registerDevice.count += 1;
-    spy.registerDevice.args.push(arguments);
-    return Promise.resolve({});
-  };
-  client.updateDevice = function () {
-    spy.updateDevice.count += 1;
-    spy.updateDevice.args.push(arguments);
-    spy.updateDevice.time = Date.now();
-    if (spy.updateDevice.count === 1) {
-      return Promise.reject({
-        code: 400,
-        errno: ERRNO_DEVICE_SESSION_CONFLICT
-      });
-    }
-    return Promise.resolve({
-      id: credentials.deviceId,
-      name: deviceName
-    });
-  };
-  client.getDeviceList = function () {
-    spy.getDeviceList.count += 1;
-    spy.getDeviceList.args.push(arguments);
-    spy.getDeviceList.time = Date.now();
-    return Promise.resolve([
-      { id: "ignore", name: "ignore", type: "ignore", isCurrentDevice: false },
-      { id: credentials.deviceId, name: deviceName, type: deviceType, isCurrentDevice: true }
-    ]);
-  };
-
-  const result = yield fxa.updateDeviceRegistration();
-
-  do_check_eq(result, credentials.deviceId);
-  do_check_eq(spy.registerDevice.count, 0);
-  do_check_eq(spy.updateDevice.count, 1);
-  do_check_eq(spy.updateDevice.args[0].length, 4);
-  do_check_eq(spy.updateDevice.args[0][0], credentials.sessionToken);
-  do_check_eq(spy.updateDevice.args[0][1], credentials.deviceId);
-  do_check_eq(spy.updateDevice.args[0][2], deviceName);
-  do_check_eq(spy.updateDevice.args[0][3].pushCallback, "http://mochi.test:8888");
-  do_check_eq(spy.updateDevice.args[0][3].pushPublicKey, BOGUS_PUBLICKEY);
-  do_check_eq(spy.updateDevice.args[0][3].pushAuthKey, BOGUS_AUTHKEY);
-  do_check_eq(spy.getDeviceList.count, 1);
-  do_check_eq(spy.getDeviceList.args[0].length, 1);
-  do_check_eq(spy.getDeviceList.args[0][0], credentials.sessionToken);
-  do_check_true(spy.getDeviceList.time >= spy.updateDevice.time);
-
-  const state = fxa.internal.currentAccountState;
-  const data = yield state.getUserAccountData();
-
-  do_check_eq(data.deviceId, credentials.deviceId);
-  do_check_eq(data.deviceRegistrationVersion, null);
-});
-
-add_task(function* test_updateDeviceRegistration_with_unrecoverable_error() {
-  const deviceName = "foo";
-  const deviceType = "bar";
-
-  const credentials = getTestUser("baz");
-  delete credentials.deviceId;
-  const fxa = new MockFxAccounts({ name: deviceName });
-  yield fxa.internal.setSignedInUser(credentials);
-
-  const spy = {
-    registerDevice: { count: 0, args: [] },
-    updateDevice: { count: 0, args: [] },
-    getDeviceList: { count: 0, args: [] }
-  };
-  const client = fxa.internal.fxAccountsClient;
-  client.registerDevice = function () {
-    spy.registerDevice.count += 1;
-    spy.registerDevice.args.push(arguments);
-    return Promise.reject({
-      code: 400,
-      errno: ERRNO_TOO_MANY_CLIENT_REQUESTS
-    });
-  };
-  client.updateDevice = function () {
-    spy.updateDevice.count += 1;
-    spy.updateDevice.args.push(arguments);
-    return Promise.resolve({});
-  };
-  client.getDeviceList = function () {
-    spy.getDeviceList.count += 1;
-    spy.getDeviceList.args.push(arguments);
-    return Promise.resolve([]);
-  };
-
-  const result = yield fxa.updateDeviceRegistration();
-
-  do_check_null(result);
-  do_check_eq(spy.getDeviceList.count, 0);
-  do_check_eq(spy.updateDevice.count, 0);
-  do_check_eq(spy.registerDevice.count, 1);
-  do_check_eq(spy.registerDevice.args[0].length, 4);
-
-  const state = fxa.internal.currentAccountState;
-  const data = yield state.getUserAccountData();
-
-  do_check_null(data.deviceId);
-});
-
-add_task(function* test_getDeviceId_with_no_device_id_invokes_device_registration() {
-  const credentials = getTestUser("foo");
-  credentials.verified = true;
-  delete credentials.deviceId;
-  const fxa = new MockFxAccounts();
-  yield fxa.internal.setSignedInUser(credentials);
-
-  const spy = { count: 0, args: [] };
-  fxa.internal.currentAccountState.getUserAccountData =
-    () => Promise.resolve({ email: credentials.email,
-                            deviceRegistrationVersion: DEVICE_REGISTRATION_VERSION });
-  fxa.internal._registerOrUpdateDevice = function () {
-    spy.count += 1;
-    spy.args.push(arguments);
-    return Promise.resolve("bar");
-  };
-
-  const result = yield fxa.internal.getDeviceId();
-
-  do_check_eq(spy.count, 1);
-  do_check_eq(spy.args[0].length, 1);
-  do_check_eq(spy.args[0][0].email, credentials.email);
-  do_check_null(spy.args[0][0].deviceId);
-  do_check_eq(result, "bar");
-});
-
-add_task(function* test_getDeviceId_with_registration_version_outdated_invokes_device_registration() {
-  const credentials = getTestUser("foo");
-  credentials.verified = true;
-  const fxa = new MockFxAccounts();
-  yield fxa.internal.setSignedInUser(credentials);
-
-  const spy = { count: 0, args: [] };
-  fxa.internal.currentAccountState.getUserAccountData =
-    () => Promise.resolve({ deviceId: credentials.deviceId, deviceRegistrationVersion: 0 });
-  fxa.internal._registerOrUpdateDevice = function () {
-    spy.count += 1;
-    spy.args.push(arguments);
-    return Promise.resolve("wibble");
-  };
-
-  const result = yield fxa.internal.getDeviceId();
-
-  do_check_eq(spy.count, 1);
-  do_check_eq(spy.args[0].length, 1);
-  do_check_eq(spy.args[0][0].deviceId, credentials.deviceId);
-  do_check_eq(result, "wibble");
-});
-
-add_task(function* test_getDeviceId_with_device_id_and_uptodate_registration_version_doesnt_invoke_device_registration() {
-  const credentials = getTestUser("foo");
-  credentials.verified = true;
-  const fxa = new MockFxAccounts();
-  yield fxa.internal.setSignedInUser(credentials);
-
-  const spy = { count: 0 };
-  fxa.internal.currentAccountState.getUserAccountData =
-    () => Promise.resolve({ deviceId: credentials.deviceId, deviceRegistrationVersion: DEVICE_REGISTRATION_VERSION });
-  fxa.internal._registerOrUpdateDevice = function () {
-    spy.count += 1;
-    return Promise.resolve("bar");
-  };
-
-  const result = yield fxa.internal.getDeviceId();
-
-  do_check_eq(spy.count, 0);
-  do_check_eq(result, "foo's device id");
-});
-
-add_task(function* test_getDeviceId_with_device_id_and_with_no_registration_version_invokes_device_registration() {
-  const credentials = getTestUser("foo");
-  credentials.verified = true;
-  const fxa = new MockFxAccounts();
-  yield fxa.internal.setSignedInUser(credentials);
-
-  const spy = { count: 0, args: [] };
-  fxa.internal.currentAccountState.getUserAccountData =
-    () => Promise.resolve({ deviceId: credentials.deviceId });
-  fxa.internal._registerOrUpdateDevice = function () {
-    spy.count += 1;
-    spy.args.push(arguments);
-    return Promise.resolve("wibble");
-  };
-
-  const result = yield fxa.internal.getDeviceId();
-
-  do_check_eq(spy.count, 1);
-  do_check_eq(spy.args[0].length, 1);
-  do_check_eq(spy.args[0][0].deviceId, credentials.deviceId);
-  do_check_eq(result, "wibble");
-});
-
-function expandHex(two_hex) {
-  // Return a 64-character hex string, encoding 32 identical bytes.
-  let eight_hex = two_hex + two_hex + two_hex + two_hex;
-  let thirtytwo_hex = eight_hex + eight_hex + eight_hex + eight_hex;
-  return thirtytwo_hex + thirtytwo_hex;
-};
-
-function expandBytes(two_hex) {
-  return CommonUtils.hexToBytes(expandHex(two_hex));
-};
-
-function getTestUser(name) {
-  return {
-    email: name + "@example.com",
-    uid: "1ad7f502-4cc7-4ec1-a209-071fd2fae348",
-    deviceId: name + "'s device id",
-    sessionToken: name + "'s session token",
-    keyFetchToken: name + "'s keyfetch token",
-    unwrapBKey: expandHex("44"),
-    verified: false
-  };
-}
-
diff --git a/services/fxaccounts/tests/xpcshell/test_client.js b/services/fxaccounts/tests/xpcshell/test_client.js
deleted file mode 100644
index 83f42bd..0000000
--- a/services/fxaccounts/tests/xpcshell/test_client.js
+++ /dev/null
@@ -1,917 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/ */
-
-"use strict";
-
-Cu.import("resource://gre/modules/FxAccountsClient.jsm");
-Cu.import("resource://gre/modules/Promise.jsm");
-Cu.import("resource://services-common/utils.js");
-Cu.import("resource://services-common/hawkrequest.js");
-Cu.import("resource://services-crypto/utils.js");
-
-const FAKE_SESSION_TOKEN = "a0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebf";
-
-function run_test() {
-  run_next_test();
-}
-
-// https://wiki.mozilla.org/Identity/AttachedServices/KeyServerProtocol#.2Faccount.2Fkeys
-var ACCOUNT_KEYS = {
-  keyFetch:     h("8081828384858687 88898a8b8c8d8e8f"+
-                  "9091929394959697 98999a9b9c9d9e9f"),
-
-  response:     h("ee5c58845c7c9412 b11bbd20920c2fdd"+
-                  "d83c33c9cd2c2de2 d66b222613364636"+
-                  "c2c0f8cfbb7c6304 72c0bd88451342c6"+
-                  "c05b14ce342c5ad4 6ad89e84464c993c"+
-                  "3927d30230157d08 17a077eef4b20d97"+
-                  "6f7a97363faf3f06 4c003ada7d01aa70"),
-
-  kA:           h("2021222324252627 28292a2b2c2d2e2f"+
-                  "3031323334353637 38393a3b3c3d3e3f"),
-
-  wrapKB:       h("4041424344454647 48494a4b4c4d4e4f"+
-                  "5051525354555657 58595a5b5c5d5e5f"),
-};
-
-function deferredStop(server) {
-  let deferred = Promise.defer();
-  server.stop(deferred.resolve);
-  return deferred.promise;
-}
-
-add_task(function* test_authenticated_get_request() {
-  let message = "{\"msg\": \"Great Success!\"}";
-  let credentials = {
-    id: "eyJleHBpcmVzIjogMTM2NTAxMDg5OC4x",
-    key: "qTZf4ZFpAMpMoeSsX3zVRjiqmNs=",
-    algorithm: "sha256"
-  };
-  let method = "GET";
-
-  let server = httpd_setup({"/foo": function(request, response) {
-      do_check_true(request.hasHeader("Authorization"));
-
-      response.setStatusLine(request.httpVersion, 200, "OK");
-      response.bodyOutputStream.write(message, message.length);
-    }
-  });
-
-  let client = new FxAccountsClient(server.baseURI);
-
-  let result = yield client._request("/foo", method, credentials);
-  do_check_eq("Great Success!", result.msg);
-
-  yield deferredStop(server);
-});
-
-add_task(function* test_authenticated_post_request() {
-  let credentials = {
-    id: "eyJleHBpcmVzIjogMTM2NTAxMDg5OC4x",
-    key: "qTZf4ZFpAMpMoeSsX3zVRjiqmNs=",
-    algorithm: "sha256"
-  };
-  let method = "POST";
-
-  let server = httpd_setup({"/foo": function(request, response) {
-      do_check_true(request.hasHeader("Authorization"));
-
-      response.setStatusLine(request.httpVersion, 200, "OK");
-      response.setHeader("Content-Type", "application/json");
-      response.bodyOutputStream.writeFrom(request.bodyInputStream, request.bodyInputStream.available());
-    }
-  });
-
-  let client = new FxAccountsClient(server.baseURI);
-
-  let result = yield client._request("/foo", method, credentials, {foo: "bar"});
-  do_check_eq("bar", result.foo);
-
-  yield deferredStop(server);
-});
-
-add_task(function* test_500_error() {
-  let message = "<h1>Ooops!</h1>";
-  let method = "GET";
-
-  let server = httpd_setup({"/foo": function(request, response) {
-      response.setStatusLine(request.httpVersion, 500, "Internal Server Error");
-      response.bodyOutputStream.write(message, message.length);
-    }
-  });
-
-  let client = new FxAccountsClient(server.baseURI);
-
-  try {
-    yield client._request("/foo", method);
-    do_throw("Expected to catch an exception");
-  } catch (e) {
-    do_check_eq(500, e.code);
-    do_check_eq("Internal Server Error", e.message);
-  }
-
-  yield deferredStop(server);
-});
-
-add_task(function* test_backoffError() {
-  let method = "GET";
-  let server = httpd_setup({
-    "/retryDelay": function(request, response) {
-      response.setHeader("Retry-After", "30");
-      response.setStatusLine(request.httpVersion, 429, "Client has sent too many requests");
-      let message = "<h1>Ooops!</h1>";
-      response.bodyOutputStream.write(message, message.length);
-    },
-    "/duringDelayIShouldNotBeCalled": function(request, response) {
-      response.setStatusLine(request.httpVersion, 200, "OK");
-      let jsonMessage = "{\"working\": \"yes\"}";
-      response.bodyOutputStream.write(jsonMessage, jsonMessage.length);
-    },
-  });
-
-  let client = new FxAccountsClient(server.baseURI);
-
-  // Retry-After header sets client.backoffError
-  do_check_eq(client.backoffError, null);
-  try {
-    yield client._request("/retryDelay", method);
-  } catch (e) {
-    do_check_eq(429, e.code);
-    do_check_eq(30, e.retryAfter);
-    do_check_neq(typeof(client.fxaBackoffTimer), "undefined");
-    do_check_neq(client.backoffError, null);
-  }
-  // While delay is in effect, client short-circuits any requests
-  // and re-rejects with previous error.
-  try {
-    yield client._request("/duringDelayIShouldNotBeCalled", method);
-    throw new Error("I should not be reached");
-  } catch (e) {
-    do_check_eq(e.retryAfter, 30);
-    do_check_eq(e.message, "Client has sent too many requests");
-    do_check_neq(client.backoffError, null);
-  }
-  // Once timer fires, client nulls error out and HTTP calls work again.
-  client._clearBackoff();
-  let result = yield client._request("/duringDelayIShouldNotBeCalled", method);
-  do_check_eq(client.backoffError, null);
-  do_check_eq(result.working, "yes");
-
-  yield deferredStop(server);
-});
-
-add_task(function* test_signUp() {
-  let creationMessage_noKey = JSON.stringify({
-    uid: "uid",
-    sessionToken: "sessionToken"
-  });
-  let creationMessage_withKey = JSON.stringify({
-    uid: "uid",
-    sessionToken: "sessionToken",
-    keyFetchToken: "keyFetchToken"
-  });
-  let errorMessage = JSON.stringify({code: 400, errno: 101, error: "account exists"});
-  let created = false;
-
-  // Note these strings must be unicode and not already utf-8 encoded.
-  let unicodeUsername = "andr\xe9 at example.org"; // 'andré@example.org'
-  let unicodePassword = "p\xe4ssw\xf6rd"; // 'pässwörd'
-  let server = httpd_setup({
-    "/account/create": function(request, response) {
-      let body = CommonUtils.readBytesFromInputStream(request.bodyInputStream);
-      body = CommonUtils.decodeUTF8(body);
-      let jsonBody = JSON.parse(body);
-
-      // https://github.com/mozilla/fxa-auth-server/wiki/onepw-protocol#wiki-test-vectors
-
-      if (created) {
-        // Error trying to create same account a second time
-        response.setStatusLine(request.httpVersion, 400, "Bad request");
-        response.bodyOutputStream.write(errorMessage, errorMessage.length);
-        return;
-      }
-
-      if (jsonBody.email == unicodeUsername) {
-        do_check_eq("", request._queryString);
-        do_check_eq(jsonBody.authPW, "247b675ffb4c46310bc87e26d712153abe5e1c90ef00a4784594f97ef54f2375");
-
-        response.setStatusLine(request.httpVersion, 200, "OK");
-        response.bodyOutputStream.write(creationMessage_noKey,
-                                        creationMessage_noKey.length);
-        return;
-      }
-
-      if (jsonBody.email == "you at example.org") {
-        do_check_eq("keys=true", request._queryString);
-        do_check_eq(jsonBody.authPW, "e5c1cdfdaa5fcee06142db865b212cc8ba8abee2a27d639d42c139f006cdb930");
-        created = true;
-
-        response.setStatusLine(request.httpVersion, 200, "OK");
-        response.bodyOutputStream.write(creationMessage_withKey,
-                                        creationMessage_withKey.length);
-        return;
-      }
-      // just throwing here doesn't make any log noise, so have an assertion
-      // fail instead.
-      do_check_true(false, "unexpected email: " + jsonBody.email);
-    },
-  });
-
-  // Try to create an account without retrieving optional keys.
-  let client = new FxAccountsClient(server.baseURI);
-  let result = yield client.signUp(unicodeUsername, unicodePassword);
-  do_check_eq("uid", result.uid);
-  do_check_eq("sessionToken", result.sessionToken);
-  do_check_eq(undefined, result.keyFetchToken);
-  do_check_eq(result.unwrapBKey,
-              "de6a2648b78284fcb9ffa81ba95803309cfba7af583c01a8a1a63e567234dd28");
-
-  // Try to create an account retrieving optional keys.
-  result = yield client.signUp('you at example.org', 'pässwörd', true);
-  do_check_eq("uid", result.uid);
-  do_check_eq("sessionToken", result.sessionToken);
-  do_check_eq("keyFetchToken", result.keyFetchToken);
-  do_check_eq(result.unwrapBKey,
-              "f589225b609e56075d76eb74f771ff9ab18a4dc0e901e131ba8f984c7fb0ca8c");
-
-  // Try to create an existing account.  Triggers error path.
-  try {
-    result = yield client.signUp(unicodeUsername, unicodePassword);
-    do_throw("Expected to catch an exception");
-  } catch(expectedError) {
-    do_check_eq(101, expectedError.errno);
-  }
-
-  yield deferredStop(server);
-});
-
-add_task(function* test_signIn() {
-  let sessionMessage_noKey = JSON.stringify({
-    sessionToken: FAKE_SESSION_TOKEN
-  });
-  let sessionMessage_withKey = JSON.stringify({
-    sessionToken: FAKE_SESSION_TOKEN,
-    keyFetchToken: "keyFetchToken"
-  });
-  let errorMessage_notExistent = JSON.stringify({
-    code: 400,
-    errno: 102,
-    error: "doesn't exist"
-  });
-  let errorMessage_wrongCap = JSON.stringify({
-    code: 400,
-    errno: 120,
-    error: "Incorrect email case",
-    email: "you at example.com"
-  });
-
-  // Note this strings must be unicode and not already utf-8 encoded.
-  let unicodeUsername = "m\xe9 at example.com" // 'mé@example.com'
-  let server = httpd_setup({
-    "/account/login": function(request, response) {
-      let body = CommonUtils.readBytesFromInputStream(request.bodyInputStream);
-      body = CommonUtils.decodeUTF8(body);
-      let jsonBody = JSON.parse(body);
-
-      if (jsonBody.email == unicodeUsername) {
-        do_check_eq("", request._queryString);
-        do_check_eq(jsonBody.authPW, "08b9d111196b8408e8ed92439da49206c8ecfbf343df0ae1ecefcd1e0174a8b6");
-        response.setStatusLine(request.httpVersion, 200, "OK");
-        response.bodyOutputStream.write(sessionMessage_noKey,
-                                        sessionMessage_noKey.length);
-        return;
-      }
-      else if (jsonBody.email == "you at example.com") {
-        do_check_eq("keys=true", request._queryString);
-        do_check_eq(jsonBody.authPW, "93d20ec50304d496d0707ec20d7e8c89459b6396ec5dd5b9e92809c5e42856c7");
-        response.setStatusLine(request.httpVersion, 200, "OK");
-        response.bodyOutputStream.write(sessionMessage_withKey,
-                                        sessionMessage_withKey.length);
-        return;
-      }
-      else if (jsonBody.email == "You at example.com") {
-        // Error trying to sign in with a wrong capitalization
-        response.setStatusLine(request.httpVersion, 400, "Bad request");
-        response.bodyOutputStream.write(errorMessage_wrongCap,
-                                        errorMessage_wrongCap.length);
-        return;
-      }
-      else {
-        // Error trying to sign in to nonexistent account
-        response.setStatusLine(request.httpVersion, 400, "Bad request");
-        response.bodyOutputStream.write(errorMessage_notExistent,
-                                        errorMessage_notExistent.length);
-        return;
-      }
-    },
-  });
-
-  // Login without retrieving optional keys
-  let client = new FxAccountsClient(server.baseURI);
-  let result = yield client.signIn(unicodeUsername, 'bigsecret');
-  do_check_eq(FAKE_SESSION_TOKEN, result.sessionToken);
-  do_check_eq(result.unwrapBKey,
-              "c076ec3f4af123a615157154c6e1d0d6293e514fd7b0221e32d50517ecf002b8");
-  do_check_eq(undefined, result.keyFetchToken);
-
-  // Login with retrieving optional keys
-  result = yield client.signIn('you at example.com', 'bigsecret', true);
-  do_check_eq(FAKE_SESSION_TOKEN, result.sessionToken);
-  do_check_eq(result.unwrapBKey,
-              "65970516211062112e955d6420bebe020269d6b6a91ebd288319fc8d0cb49624");
-  do_check_eq("keyFetchToken", result.keyFetchToken);
-
-  // Retry due to wrong email capitalization
-  result = yield client.signIn('You at example.com', 'bigsecret', true);
-  do_check_eq(FAKE_SESSION_TOKEN, result.sessionToken);
-  do_check_eq(result.unwrapBKey,
-              "65970516211062112e955d6420bebe020269d6b6a91ebd288319fc8d0cb49624");
-  do_check_eq("keyFetchToken", result.keyFetchToken);
-
-  // Trigger error path
-  try {
-    result = yield client.signIn("yøü@bad.example.org", "nofear");
-    do_throw("Expected to catch an exception");
-  } catch (expectedError) {
-    do_check_eq(102, expectedError.errno);
-  }
-
-  yield deferredStop(server);
-});
-
-add_task(function* test_signOut() {
-  let signoutMessage = JSON.stringify({});
-  let errorMessage = JSON.stringify({code: 400, errno: 102, error: "doesn't exist"});
-  let signedOut = false;
-
-  let server = httpd_setup({
-    "/session/destroy": function(request, response) {
-      if (!signedOut) {
-        signedOut = true;
-        do_check_true(request.hasHeader("Authorization"));
-        response.setStatusLine(request.httpVersion, 200, "OK");
-        response.bodyOutputStream.write(signoutMessage, signoutMessage.length);
-        return;
-      }
-
-      // Error trying to sign out of nonexistent account
-      response.setStatusLine(request.httpVersion, 400, "Bad request");
-      response.bodyOutputStream.write(errorMessage, errorMessage.length);
-      return;
-    },
-  });
-
-  let client = new FxAccountsClient(server.baseURI);
-  let result = yield client.signOut("FakeSession");
-  do_check_eq(typeof result, "object");
-
-  // Trigger error path
-  try {
-    result = yield client.signOut("FakeSession");
-    do_throw("Expected to catch an exception");
-  } catch(expectedError) {
-    do_check_eq(102, expectedError.errno);
-  }
-
-  yield deferredStop(server);
-});
-
-add_task(function* test_recoveryEmailStatus() {
-  let emailStatus = JSON.stringify({verified: true});
-  let errorMessage = JSON.stringify({code: 400, errno: 102, error: "doesn't exist"});
-  let tries = 0;
-
-  let server = httpd_setup({
-    "/recovery_email/status": function(request, response) {
-      do_check_true(request.hasHeader("Authorization"));
-      do_check_eq("", request._queryString);
-
-      if (tries === 0) {
-        tries += 1;
-        response.setStatusLine(request.httpVersion, 200, "OK");
-        response.bodyOutputStream.write(emailStatus, emailStatus.length);
-        return;
-      }
-
-      // Second call gets an error trying to query a nonexistent account
-      response.setStatusLine(request.httpVersion, 400, "Bad request");
-      response.bodyOutputStream.write(errorMessage, errorMessage.length);
-      return;
-    },
-  });
-
-  let client = new FxAccountsClient(server.baseURI);
-  let result = yield client.recoveryEmailStatus(FAKE_SESSION_TOKEN);
-  do_check_eq(result.verified, true);
-
-  // Trigger error path
-  try {
-    result = yield client.recoveryEmailStatus("some bogus session");
-    do_throw("Expected to catch an exception");
-  } catch(expectedError) {
-    do_check_eq(102, expectedError.errno);
-  }
-
-  yield deferredStop(server);
-});
-
-add_task(function* test_recoveryEmailStatusWithReason() {
-  let emailStatus = JSON.stringify({verified: true});
-  let server = httpd_setup({
-    "/recovery_email/status": function(request, response) {
-      do_check_true(request.hasHeader("Authorization"));
-      // if there is a query string then it will have a reason
-      do_check_eq("reason=push", request._queryString);
-
-      response.setStatusLine(request.httpVersion, 200, "OK");
-      response.bodyOutputStream.write(emailStatus, emailStatus.length);
-      return;
-    },
-  });
-
-  let client = new FxAccountsClient(server.baseURI);
-  let result = yield client.recoveryEmailStatus(FAKE_SESSION_TOKEN, {
-    reason: "push",
-  });
-  do_check_eq(result.verified, true);
-  yield deferredStop(server);
-});
-
-add_task(function* test_resendVerificationEmail() {
-  let emptyMessage = "{}";
-  let errorMessage = JSON.stringify({code: 400, errno: 102, error: "doesn't exist"});
-  let tries = 0;
-
-  let server = httpd_setup({
-    "/recovery_email/resend_code": function(request, response) {
-      do_check_true(request.hasHeader("Authorization"));
-      if (tries === 0) {
-        tries += 1;
-        response.setStatusLine(request.httpVersion, 200, "OK");
-        response.bodyOutputStream.write(emptyMessage, emptyMessage.length);
-        return;
-      }
-
-      // Second call gets an error trying to query a nonexistent account
-      response.setStatusLine(request.httpVersion, 400, "Bad request");
-      response.bodyOutputStream.write(errorMessage, errorMessage.length);
-      return;
-    },
-  });
-
-  let client = new FxAccountsClient(server.baseURI);
-  let result = yield client.resendVerificationEmail(FAKE_SESSION_TOKEN);
-  do_check_eq(JSON.stringify(result), emptyMessage);
-
-  // Trigger error path
-  try {
-    result = yield client.resendVerificationEmail("some bogus session");
-    do_throw("Expected to catch an exception");
-  } catch(expectedError) {
-    do_check_eq(102, expectedError.errno);
-  }
-
-  yield deferredStop(server);
-});
-
-add_task(function* test_accountKeys() {
-  // Four calls to accountKeys().  The first one should work correctly, and we
-  // should get a valid bundle back, in exchange for our keyFetch token, from
-  // which we correctly derive kA and wrapKB.  The subsequent three calls
-  // should all trigger separate error paths.
-  let responseMessage = JSON.stringify({bundle: ACCOUNT_KEYS.response});
-  let errorMessage = JSON.stringify({code: 400, errno: 102, error: "doesn't exist"});
-  let emptyMessage = "{}";
-  let attempt = 0;
-
-  let server = httpd_setup({
-    "/account/keys": function(request, response) {
-      do_check_true(request.hasHeader("Authorization"));
-      attempt += 1;
-
-      switch(attempt) {
-        case 1:
-          // First time succeeds
-          response.setStatusLine(request.httpVersion, 200, "OK");
-          response.bodyOutputStream.write(responseMessage, responseMessage.length);
-          break;
-
-        case 2:
-          // Second time, return no bundle to trigger client error
-          response.setStatusLine(request.httpVersion, 200, "OK");
-          response.bodyOutputStream.write(emptyMessage, emptyMessage.length);
-          break;
-
-        case 3:
-          // Return gibberish to trigger client MAC error
-          // Tweak a byte
-          let garbageResponse = JSON.stringify({
-            bundle: ACCOUNT_KEYS.response.slice(0, -1) + "1"
-          });
-          response.setStatusLine(request.httpVersion, 200, "OK");
-          response.bodyOutputStream.write(garbageResponse, garbageResponse.length);
-          break;
-
-        case 4:
-          // Trigger error for nonexistent account
-          response.setStatusLine(request.httpVersion, 400, "Bad request");
-          response.bodyOutputStream.write(errorMessage, errorMessage.length);
-          break;
-      }
-    },
-  });
-
-  let client = new FxAccountsClient(server.baseURI);
-
-  // First try, all should be good
-  let result = yield client.accountKeys(ACCOUNT_KEYS.keyFetch);
-  do_check_eq(CommonUtils.hexToBytes(ACCOUNT_KEYS.kA), result.kA);
-  do_check_eq(CommonUtils.hexToBytes(ACCOUNT_KEYS.wrapKB), result.wrapKB);
-
-  // Second try, empty bundle should trigger error
-  try {
-    result = yield client.accountKeys(ACCOUNT_KEYS.keyFetch);
-    do_throw("Expected to catch an exception");
-  } catch(expectedError) {
-    do_check_eq(expectedError.message, "failed to retrieve keys");
-  }
-
-  // Third try, bad bundle results in MAC error
-  try {
-    result = yield client.accountKeys(ACCOUNT_KEYS.keyFetch);
-    do_throw("Expected to catch an exception");
-  } catch(expectedError) {
-    do_check_eq(expectedError.message, "error unbundling encryption keys");
-  }
-
-  // Fourth try, pretend account doesn't exist
-  try {
-    result = yield client.accountKeys(ACCOUNT_KEYS.keyFetch);
-    do_throw("Expected to catch an exception");
-  } catch(expectedError) {
-    do_check_eq(102, expectedError.errno);
-  }
-
-  yield deferredStop(server);
-});
-
-add_task(function* test_signCertificate() {
-  let certSignMessage = JSON.stringify({cert: {bar: "baz"}});
-  let errorMessage = JSON.stringify({code: 400, errno: 102, error: "doesn't exist"});
-  let tries = 0;
-
-  let server = httpd_setup({
-    "/certificate/sign": function(request, response) {
-      do_check_true(request.hasHeader("Authorization"));
-
-      if (tries === 0) {
-        tries += 1;
-        let body = CommonUtils.readBytesFromInputStream(request.bodyInputStream);
-        let jsonBody = JSON.parse(body);
-        do_check_eq(JSON.parse(jsonBody.publicKey).foo, "bar");
-        do_check_eq(jsonBody.duration, 600);
-        response.setStatusLine(request.httpVersion, 200, "OK");
-        response.bodyOutputStream.write(certSignMessage, certSignMessage.length);
-        return;
-      }
-
-      // Second attempt, trigger error
-      response.setStatusLine(request.httpVersion, 400, "Bad request");
-      response.bodyOutputStream.write(errorMessage, errorMessage.length);
-      return;
-    },
-  });
-
-  let client = new FxAccountsClient(server.baseURI);
-  let result = yield client.signCertificate(FAKE_SESSION_TOKEN, JSON.stringify({foo: "bar"}), 600);
-  do_check_eq("baz", result.bar);
-
-  // Account doesn't exist
-  try {
-    result = yield client.signCertificate("bogus", JSON.stringify({foo: "bar"}), 600);
-    do_throw("Expected to catch an exception");
-  } catch(expectedError) {
-    do_check_eq(102, expectedError.errno);
-  }
-
-  yield deferredStop(server);
-});
-
-add_task(function* test_accountExists() {
-  let sessionMessage = JSON.stringify({sessionToken: FAKE_SESSION_TOKEN});
-  let existsMessage = JSON.stringify({error: "wrong password", code: 400, errno: 103});
-  let doesntExistMessage = JSON.stringify({error: "no such account", code: 400, errno: 102});
-  let emptyMessage = "{}";
-
-  let server = httpd_setup({
-    "/account/login": function(request, response) {
-      let body = CommonUtils.readBytesFromInputStream(request.bodyInputStream);
-      let jsonBody = JSON.parse(body);
-
-      switch (jsonBody.email) {
-        // We'll test that these users' accounts exist
-        case "i.exist at example.com":
-        case "i.also.exist at example.com":
-          response.setStatusLine(request.httpVersion, 400, "Bad request");
-          response.bodyOutputStream.write(existsMessage, existsMessage.length);
-          break;
-
-        // This user's account doesn't exist
-        case "i.dont.exist at example.com":
-          response.setStatusLine(request.httpVersion, 400, "Bad request");
-          response.bodyOutputStream.write(doesntExistMessage, doesntExistMessage.length);
-          break;
-
-        // This user throws an unexpected response
-        // This will reject the client signIn promise
-        case "i.break.things at example.com":
-          response.setStatusLine(request.httpVersion, 500, "Alas");
-          response.bodyOutputStream.write(emptyMessage, emptyMessage.length);
-          break;
-
-        default:
-          throw new Error("Unexpected login from " + jsonBody.email);
-          break;
-      }
-    },
-  });
-
-  let client = new FxAccountsClient(server.baseURI);
-  let result;
-
-  result = yield client.accountExists("i.exist at example.com");
-  do_check_true(result);
-
-  result = yield client.accountExists("i.also.exist at example.com");
-  do_check_true(result);
-
-  result = yield client.accountExists("i.dont.exist at example.com");
-  do_check_false(result);
-
-  try {
-    result = yield client.accountExists("i.break.things at example.com");
-    do_throw("Expected to catch an exception");
-  } catch(unexpectedError) {
-    do_check_eq(unexpectedError.code, 500);
-  }
-
-  yield deferredStop(server);
-});
-
-add_task(function* test_registerDevice() {
-  const DEVICE_ID = "device id";
-  const DEVICE_NAME = "device name";
-  const DEVICE_TYPE = "device type";
-  const ERROR_NAME = "test that the client promise rejects";
-
-  const server = httpd_setup({
-    "/account/device": function(request, response) {
-      const body = JSON.parse(CommonUtils.readBytesFromInputStream(request.bodyInputStream));
-
-      if (body.id || !body.name || !body.type || Object.keys(body).length !== 2) {
-        response.setStatusLine(request.httpVersion, 400, "Invalid request");
-        return response.bodyOutputStream.write("{}", 2);
-      }
-
-      if (body.name === ERROR_NAME) {
-        response.setStatusLine(request.httpVersion, 500, "Alas");
-        return response.bodyOutputStream.write("{}", 2);
-      }
-
-      body.id = DEVICE_ID;
-      body.createdAt = Date.now();
-
-      const responseMessage = JSON.stringify(body);
-
-      response.setStatusLine(request.httpVersion, 200, "OK");
-      response.bodyOutputStream.write(responseMessage, responseMessage.length);
-    },
-  });
-
-  const client = new FxAccountsClient(server.baseURI);
-  const result = yield client.registerDevice(FAKE_SESSION_TOKEN, DEVICE_NAME, DEVICE_TYPE);
-
-  do_check_true(result);
-  do_check_eq(Object.keys(result).length, 4);
-  do_check_eq(result.id, DEVICE_ID);
-  do_check_eq(typeof result.createdAt, 'number');
-  do_check_true(result.createdAt > 0);
-  do_check_eq(result.name, DEVICE_NAME);
-  do_check_eq(result.type, DEVICE_TYPE);
-
-  try {
-    yield client.registerDevice(FAKE_SESSION_TOKEN, ERROR_NAME, DEVICE_TYPE);
-    do_throw("Expected to catch an exception");
-  } catch(unexpectedError) {
-    do_check_eq(unexpectedError.code, 500);
-  }
-
-  yield deferredStop(server);
-});
-
-add_task(function* test_updateDevice() {
-  const DEVICE_ID = "some other id";
-  const DEVICE_NAME = "some other name";
-  const ERROR_ID = "test that the client promise rejects";
-
-  const server = httpd_setup({
-    "/account/device": function(request, response) {
-      const body = JSON.parse(CommonUtils.readBytesFromInputStream(request.bodyInputStream));
-
-      if (!body.id || !body.name || body.type || Object.keys(body).length !== 2) {
-        response.setStatusLine(request.httpVersion, 400, "Invalid request");
-        return response.bodyOutputStream.write("{}", 2);
-      }
-
-      if (body.id === ERROR_ID) {
-        response.setStatusLine(request.httpVersion, 500, "Alas");
-        return response.bodyOutputStream.write("{}", 2);
-      }
-
-      const responseMessage = JSON.stringify(body);
-
-      response.setStatusLine(request.httpVersion, 200, "OK");
-      response.bodyOutputStream.write(responseMessage, responseMessage.length);
-    },
-  });
-
-  const client = new FxAccountsClient(server.baseURI);
-  const result = yield client.updateDevice(FAKE_SESSION_TOKEN, DEVICE_ID, DEVICE_NAME);
-
-  do_check_true(result);
-  do_check_eq(Object.keys(result).length, 2);
-  do_check_eq(result.id, DEVICE_ID);
-  do_check_eq(result.name, DEVICE_NAME);
-
-  try {
-    yield client.updateDevice(FAKE_SESSION_TOKEN, ERROR_ID, DEVICE_NAME);
-    do_throw("Expected to catch an exception");
-  } catch(unexpectedError) {
-    do_check_eq(unexpectedError.code, 500);
-  }
-
-  yield deferredStop(server);
-});
-
-add_task(function* test_signOutAndDestroyDevice() {
-  const DEVICE_ID = "device id";
-  const ERROR_ID = "test that the client promise rejects";
-
-  const server = httpd_setup({
-    "/account/device/destroy": function(request, response) {
-      const body = JSON.parse(CommonUtils.readBytesFromInputStream(request.bodyInputStream));
-
-      if (!body.id) {
-        response.setStatusLine(request.httpVersion, 400, "Invalid request");
-        return response.bodyOutputStream.write(emptyMessage, emptyMessage.length);
-      }
-
-      if (body.id === ERROR_ID) {
-        response.setStatusLine(request.httpVersion, 500, "Alas");
-        return response.bodyOutputStream.write("{}", 2);
-      }
-
-      response.setStatusLine(request.httpVersion, 200, "OK");
-      response.bodyOutputStream.write("{}", 2);
-    },
-  });
-
-  const client = new FxAccountsClient(server.baseURI);
-  const result = yield client.signOutAndDestroyDevice(FAKE_SESSION_TOKEN, DEVICE_ID);
-
-  do_check_true(result);
-  do_check_eq(Object.keys(result).length, 0);
-
-  try {
-    yield client.signOutAndDestroyDevice(FAKE_SESSION_TOKEN, ERROR_ID);
-    do_throw("Expected to catch an exception");
-  } catch(unexpectedError) {
-    do_check_eq(unexpectedError.code, 500);
-  }
-
-  yield deferredStop(server);
-});
-
-add_task(function* test_getDeviceList() {
-  let canReturnDevices;
-
-  const server = httpd_setup({
-    "/account/devices": function(request, response) {
-      if (canReturnDevices) {
-        response.setStatusLine(request.httpVersion, 200, "OK");
-        response.bodyOutputStream.write("[]", 2);
-      } else {
-        response.setStatusLine(request.httpVersion, 500, "Alas");
-        response.bodyOutputStream.write("{}", 2);
-      }
-    },
-  });
-
-  const client = new FxAccountsClient(server.baseURI);
-
-  canReturnDevices = true;
-  const result = yield client.getDeviceList(FAKE_SESSION_TOKEN);
-  do_check_true(Array.isArray(result));
-  do_check_eq(result.length, 0);
-
-  try {
-    canReturnDevices = false;
-    yield client.getDeviceList(FAKE_SESSION_TOKEN);
-    do_throw("Expected to catch an exception");
-  } catch(unexpectedError) {
-    do_check_eq(unexpectedError.code, 500);
-  }
-
-  yield deferredStop(server);
-});
-
-add_task(function* test_client_metrics() {
-  function writeResp(response, msg) {
-    if (typeof msg === "object") {
-      msg = JSON.stringify(msg);
-    }
-    response.bodyOutputStream.write(msg, msg.length);
-  }
-
-  let server = httpd_setup(
-    {
-      "/session/destroy": function(request, response) {
-        response.setHeader("Content-Type", "application/json; charset=utf-8");
-        response.setStatusLine(request.httpVersion, 401, "Unauthorized");
-        writeResp(response, {
-          error: "invalid authentication timestamp",
-          code: 401,
-          errno: 111,
-        });
-      },
-    }
-  );
-
-  let client = new FxAccountsClient(server.baseURI);
-
-  yield rejects(client.signOut(FAKE_SESSION_TOKEN, {
-    service: "sync",
-  }), function(err) {
-    return err.errno == 111;
-  });
-
-  yield deferredStop(server);
-});
-
-add_task(function* test_email_case() {
-  let canonicalEmail = "greta.garbo at gmail.com";
-  let clientEmail = "Greta.Garbo at gmail.COM";
-  let attempts = 0;
-
-  function writeResp(response, msg) {
-    if (typeof msg === "object") {
-      msg = JSON.stringify(msg);
-    }
-    response.bodyOutputStream.write(msg, msg.length);
-  }
-
-  let server = httpd_setup(
-    {
-      "/account/login": function(request, response) {
-        response.setHeader("Content-Type", "application/json; charset=utf-8");
-        attempts += 1;
-        if (attempts > 2) {
-          response.setStatusLine(request.httpVersion, 429, "Sorry, you had your chance");
-          return writeResp(response, "");
-        }
-
-        let body = CommonUtils.readBytesFromInputStream(request.bodyInputStream);
-        let jsonBody = JSON.parse(body);
-        let email = jsonBody.email;
-
-        // If the client has the wrong case on the email, we return a 400, with
-        // the capitalization of the email as saved in the accounts database.
-        if (email == canonicalEmail) {
-          response.setStatusLine(request.httpVersion, 200, "Yay");
-          return writeResp(response, {areWeHappy: "yes"});
-        }
-
-        response.setStatusLine(request.httpVersion, 400, "Incorrect email case");
-        return writeResp(response, {
-          code: 400,
-          errno: 120,
-          error: "Incorrect email case",
-          email: canonicalEmail
-        });
-      },
-    }
-  );
-
-  let client = new FxAccountsClient(server.baseURI);
-
-  let result = yield client.signIn(clientEmail, "123456");
-  do_check_eq(result.areWeHappy, "yes");
-  do_check_eq(attempts, 2);
-
-  yield deferredStop(server);
-});
-
-// turn formatted test vectors into normal hex strings
-function h(hexStr) {
-  return hexStr.replace(/\s+/g, "");
-}
diff --git a/services/fxaccounts/tests/xpcshell/test_credentials.js b/services/fxaccounts/tests/xpcshell/test_credentials.js
deleted file mode 100644
index cbd9e4c..0000000
--- a/services/fxaccounts/tests/xpcshell/test_credentials.js
+++ /dev/null
@@ -1,110 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/ */
-
-Cu.import("resource://gre/modules/Credentials.jsm");
-Cu.import("resource://gre/modules/Promise.jsm");
-Cu.import("resource://services-common/utils.js");
-Cu.import("resource://services-crypto/utils.js");
-
-var {hexToBytes: h2b,
-     hexAsString: h2s,
-     stringAsHex: s2h,
-     bytesAsHex: b2h} = CommonUtils;
-
-// Test vectors for the "onepw" protocol:
-// https://github.com/mozilla/fxa-auth-server/wiki/onepw-protocol#wiki-test-vectors
-var vectors = {
-  "client stretch-KDF": {
-    email:
-      h("616e6472c3a94065 78616d706c652e6f 7267"),
-    password:
-      h("70c3a4737377c3b6 7264"),
-    quickStretchedPW:
-      h("e4e8889bd8bd61ad 6de6b95c059d56e7 b50dacdaf62bd846 44af7e2add84345d"),
-    authPW:
-      h("247b675ffb4c4631 0bc87e26d712153a be5e1c90ef00a478 4594f97ef54f2375"),
-    authSalt:
-      h("00f0000000000000 0000000000000000 0000000000000000 0000000000000000"),
-  },
-};
-
-// A simple test suite with no utf8 encoding madness.
-add_task(function* test_onepw_setup_credentials() {
-  let email = "francine at example.org";
-  let password = CommonUtils.encodeUTF8("i like pie");
-
-  let pbkdf2 = CryptoUtils.pbkdf2Generate;
-  let hkdf = CryptoUtils.hkdf;
-
-  // quickStretch the email
-  let saltyEmail = Credentials.keyWordExtended("quickStretch", email);
-
-  do_check_eq(b2h(saltyEmail), "6964656e746974792e6d6f7a696c6c612e636f6d2f7069636c2f76312f717569636b537472657463683a6672616e63696e65406578616d706c652e6f7267");
-
-  let pbkdf2Rounds = 1000;
-  let pbkdf2Len = 32;
-
-  let quickStretchedPW = pbkdf2(password, saltyEmail, pbkdf2Rounds, pbkdf2Len, Ci.nsICryptoHMAC.SHA256, 32);
-  let quickStretchedActual = "6b88094c1c73bbf133223f300d101ed70837af48d9d2c1b6e7d38804b20cdde4";
-  do_check_eq(b2h(quickStretchedPW), quickStretchedActual);
-
-  // obtain hkdf info
-  let authKeyInfo = Credentials.keyWord('authPW');
-  do_check_eq(b2h(authKeyInfo), "6964656e746974792e6d6f7a696c6c612e636f6d2f7069636c2f76312f617574685057");
-
-  // derive auth password
-  let hkdfSalt = h2b("00");
-  let hkdfLen = 32;
-  let authPW = hkdf(quickStretchedPW, hkdfSalt, authKeyInfo, hkdfLen);
-
-  do_check_eq(b2h(authPW), "4b8dec7f48e7852658163601ff766124c312f9392af6c3d4e1a247eb439be342");
-
-  // derive unwrap key
-  let unwrapKeyInfo = Credentials.keyWord('unwrapBkey');
-  let unwrapKey = hkdf(quickStretchedPW, hkdfSalt, unwrapKeyInfo, hkdfLen);
-
-  do_check_eq(b2h(unwrapKey), "8ff58975be391338e4ec5d7138b5ed7b65c7d1bfd1f3a4f93e05aa47d5b72be9");
-});
-
-add_task(function* test_client_stretch_kdf() {
-  let pbkdf2 = CryptoUtils.pbkdf2Generate;
-  let hkdf = CryptoUtils.hkdf;
-  let expected = vectors["client stretch-KDF"];
-
-  let email = h2s(expected.email);
-  let password = h2s(expected.password);
-
-  // Intermediate value from sjcl implementation in fxa-js-client
-  // The key thing is the c3a9 sequence in "andré"
-  let salt = Credentials.keyWordExtended("quickStretch", email);
-  do_check_eq(b2h(salt), "6964656e746974792e6d6f7a696c6c612e636f6d2f7069636c2f76312f717569636b537472657463683a616e6472c3a9406578616d706c652e6f7267");
-
-  let options = {
-    stretchedPassLength: 32,
-    pbkdf2Rounds: 1000,
-    hmacAlgorithm: Ci.nsICryptoHMAC.SHA256,
-    hmacLength: 32,
-    hkdfSalt: h2b("00"),
-    hkdfLength: 32,
-  };
-
-  let results = yield Credentials.setup(email, password, options);
-
-  do_check_eq(expected.quickStretchedPW, b2h(results.quickStretchedPW),
-      "quickStretchedPW is wrong");
-
-  do_check_eq(expected.authPW, b2h(results.authPW),
-      "authPW is wrong");
-});
-
-// End of tests
-// Utility functions follow
-
-function run_test() {
-  run_next_test();
-}
-
-// turn formatted test vectors into normal hex strings
-function h(hexStr) {
-  return hexStr.replace(/\s+/g, "");
-}
diff --git a/services/fxaccounts/tests/xpcshell/test_loginmgr_storage.js b/services/fxaccounts/tests/xpcshell/test_loginmgr_storage.js
deleted file mode 100644
index 64ddb1f..0000000
--- a/services/fxaccounts/tests/xpcshell/test_loginmgr_storage.js
+++ /dev/null
@@ -1,214 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/ */
-
-"use strict";
-
-// Tests for FxAccounts, storage and the master password.
-
-// Stop us hitting the real auth server.
-Services.prefs.setCharPref("identity.fxaccounts.auth.uri", "http://localhost");
-// See verbose logging from FxAccounts.jsm
-Services.prefs.setCharPref("identity.fxaccounts.loglevel", "Trace");
-
-Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://gre/modules/FxAccounts.jsm");
-Cu.import("resource://gre/modules/FxAccountsClient.jsm");
-Cu.import("resource://gre/modules/FxAccountsCommon.js");
-Cu.import("resource://gre/modules/osfile.jsm");
-Cu.import("resource://services-common/utils.js");
-Cu.import("resource://gre/modules/FxAccountsCommon.js");
-
-// Use a backstage pass to get at our LoginManagerStorage object, so we can
-// mock the prototype.
-var {LoginManagerStorage} = Cu.import("resource://gre/modules/FxAccountsStorage.jsm", {});
-var isLoggedIn = true;
-LoginManagerStorage.prototype.__defineGetter__("_isLoggedIn", () => isLoggedIn);
-
-function setLoginMgrLoggedInState(loggedIn) {
-  isLoggedIn = loggedIn;
-}
-
-
-initTestLogging("Trace");
-
-function run_test() {
-  run_next_test();
-}
-
-function getLoginMgrData() {
-  let logins = Services.logins.findLogins({}, FXA_PWDMGR_HOST, null, FXA_PWDMGR_REALM);
-  if (logins.length == 0) {
-    return null;
-  }
-  Assert.equal(logins.length, 1, "only 1 login available");
-  return logins[0];
-}
-
-function createFxAccounts() {
-  return new FxAccounts({
-    _getDeviceName() {
-      return "mock device name";
-    },
-    fxaPushService: {
-      registerPushEndpoint() {
-        return new Promise((resolve) => {
-          resolve({
-            endpoint: "http://mochi.test:8888"
-          });
-        });
-      },
-    }
-  });
-}
-
-add_task(function* test_simple() {
-  let fxa = createFxAccounts();
-
-  let creds = {
-    uid: "abcd",
-    email: "test at example.com",
-    sessionToken: "sessionToken",
-    kA: "the kA value",
-    kB: "the kB value",
-    verified: true
-  };
-  yield fxa.setSignedInUser(creds);
-
-  // This should have stored stuff in both the .json file in the profile
-  // dir, and the login dir.
-  let path = OS.Path.join(OS.Constants.Path.profileDir, "signedInUser.json");
-  let data = yield CommonUtils.readJSON(path);
-
-  Assert.strictEqual(data.accountData.email, creds.email, "correct email in the clear text");
-  Assert.strictEqual(data.accountData.sessionToken, creds.sessionToken, "correct sessionToken in the clear text");
-  Assert.strictEqual(data.accountData.verified, creds.verified, "correct verified flag");
-
-  Assert.ok(!("kA" in data.accountData), "kA not stored in clear text");
-  Assert.ok(!("kB" in data.accountData), "kB not stored in clear text");
-
-  let login = getLoginMgrData();
-  Assert.strictEqual(login.username, creds.uid, "uid used for username");
-  let loginData = JSON.parse(login.password);
-  Assert.strictEqual(loginData.version, data.version, "same version flag in both places");
-  Assert.strictEqual(loginData.accountData.kA, creds.kA, "correct kA in the login mgr");
-  Assert.strictEqual(loginData.accountData.kB, creds.kB, "correct kB in the login mgr");
-
-  Assert.ok(!("email" in loginData), "email not stored in the login mgr json");
-  Assert.ok(!("sessionToken" in loginData), "sessionToken not stored in the login mgr json");
-  Assert.ok(!("verified" in loginData), "verified not stored in the login mgr json");
-
-  yield fxa.signOut(/* localOnly = */ true);
-  Assert.strictEqual(getLoginMgrData(), null, "login mgr data deleted on logout");
-});
-
-add_task(function* test_MPLocked() {
-  let fxa = createFxAccounts();
-
-  let creds = {
-    uid: "abcd",
-    email: "test at example.com",
-    sessionToken: "sessionToken",
-    kA: "the kA value",
-    kB: "the kB value",
-    verified: true
-  };
-
-  Assert.strictEqual(getLoginMgrData(), null, "no login mgr at the start");
-  // tell the storage that the MP is locked.
-  setLoginMgrLoggedInState(false);
-  yield fxa.setSignedInUser(creds);
-
-  // This should have stored stuff in the .json, and the login manager stuff
-  // will not exist.
-  let path = OS.Path.join(OS.Constants.Path.profileDir, "signedInUser.json");
-  let data = yield CommonUtils.readJSON(path);
-
-  Assert.strictEqual(data.accountData.email, creds.email, "correct email in the clear text");
-  Assert.strictEqual(data.accountData.sessionToken, creds.sessionToken, "correct sessionToken in the clear text");
-  Assert.strictEqual(data.accountData.verified, creds.verified, "correct verified flag");
-
-  Assert.ok(!("kA" in data.accountData), "kA not stored in clear text");
-  Assert.ok(!("kB" in data.accountData), "kB not stored in clear text");
-
-  Assert.strictEqual(getLoginMgrData(), null, "login mgr data doesn't exist");
-  yield fxa.signOut(/* localOnly = */ true)
-});
-
-
-add_task(function* test_consistentWithMPEdgeCases() {
-  setLoginMgrLoggedInState(true);
-
-  let fxa = createFxAccounts();
-
-  let creds1 = {
-    uid: "uid1",
-    email: "test at example.com",
-    sessionToken: "sessionToken",
-    kA: "the kA value",
-    kB: "the kB value",
-    verified: true
-  };
-
-  let creds2 = {
-    uid: "uid2",
-    email: "test2 at example.com",
-    sessionToken: "sessionToken2",
-    kA: "the kA value2",
-    kB: "the kB value2",
-    verified: false,
-  };
-
-  // Log a user in while MP is unlocked.
-  yield fxa.setSignedInUser(creds1);
-
-  // tell the storage that the MP is locked - this will prevent logout from
-  // being able to clear the data.
-  setLoginMgrLoggedInState(false);
-
-  // now set the second credentials.
-  yield fxa.setSignedInUser(creds2);
-
-  // We should still have creds1 data in the login manager.
-  let login = getLoginMgrData();
-  Assert.strictEqual(login.username, creds1.uid);
-  // and that we do have the first kA in the login manager.
-  Assert.strictEqual(JSON.parse(login.password).accountData.kA, creds1.kA,
-                     "stale data still in login mgr");
-
-  // Make a new FxA instance (otherwise the values in memory will be used)
-  // and we want the login manager to be unlocked.
-  setLoginMgrLoggedInState(true);
-  fxa = createFxAccounts();
-
-  let accountData = yield fxa.getSignedInUser();
-  Assert.strictEqual(accountData.email, creds2.email);
-  // we should have no kA at all.
-  Assert.strictEqual(accountData.kA, undefined, "stale kA wasn't used");
-  yield fxa.signOut(/* localOnly = */ true)
-});
-
-// A test for the fact we will accept either a UID or email when looking in
-// the login manager.
-add_task(function* test_uidMigration() {
-  setLoginMgrLoggedInState(true);
-  Assert.strictEqual(getLoginMgrData(), null, "expect no logins at the start");
-
-  // create the login entry using email as a key.
-  let contents = {kA: "kA"};
-
-  let loginInfo = new Components.Constructor(
-   "@mozilla.org/login-manager/loginInfo;1", Ci.nsILoginInfo, "init");
-  let login = new loginInfo(FXA_PWDMGR_HOST,
-                            null, // aFormSubmitURL,
-                            FXA_PWDMGR_REALM, // aHttpRealm,
-                            "foo at bar.com", // aUsername
-                            JSON.stringify(contents), // aPassword
-                            "", // aUsernameField
-                            "");// aPasswordField
-  Services.logins.addLogin(login);
-
-  // ensure we read it.
-  let storage = new LoginManagerStorage();
-  let got = yield storage.get("uid", "foo at bar.com");
-  Assert.deepEqual(got, contents);
-});
diff --git a/services/fxaccounts/tests/xpcshell/test_oauth_client.js b/services/fxaccounts/tests/xpcshell/test_oauth_client.js
deleted file mode 100644
index 9bcb1b1..0000000
--- a/services/fxaccounts/tests/xpcshell/test_oauth_client.js
+++ /dev/null
@@ -1,55 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/ */
-
-"use strict";
-
-Cu.import("resource://gre/modules/FxAccountsOAuthClient.jsm");
-
-function run_test() {
-  validationHelper(undefined,
-  "Error: Missing 'parameters' configuration option");
-
-  validationHelper({},
-  "Error: Missing 'parameters' configuration option");
-
-  validationHelper({ parameters: {} },
-  "Error: Missing 'parameters.oauth_uri' parameter");
-
-  validationHelper({ parameters: {
-    oauth_uri: "http://oauth.test/v1"
-  }},
-  "Error: Missing 'parameters.client_id' parameter");
-
-  validationHelper({ parameters: {
-    oauth_uri: "http://oauth.test/v1",
-    client_id: "client_id"
-  }},
-  "Error: Missing 'parameters.content_uri' parameter");
-
-  validationHelper({ parameters: {
-    oauth_uri: "http://oauth.test/v1",
-    client_id: "client_id",
-    content_uri: "http://content.test"
-  }},
-  "Error: Missing 'parameters.state' parameter");
-
-  validationHelper({ parameters: {
-    oauth_uri: "http://oauth.test/v1",
-    client_id: "client_id",
-    content_uri: "http://content.test",
-    state: "complete",
-    action: "force_auth"
-  }},
-  "Error: parameters.email is required for action 'force_auth'");
-
-  run_next_test();
-}
-
-function validationHelper(params, expected) {
-  try {
-    new FxAccountsOAuthClient(params);
-  } catch (e) {
-    return do_check_eq(e.toString(), expected);
-  }
-  throw new Error("Validation helper error");
-}
diff --git a/services/fxaccounts/tests/xpcshell/test_oauth_grant_client.js b/services/fxaccounts/tests/xpcshell/test_oauth_grant_client.js
deleted file mode 100644
index 710a65e..0000000
--- a/services/fxaccounts/tests/xpcshell/test_oauth_grant_client.js
+++ /dev/null
@@ -1,292 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/ */
-
-"use strict";
-
-Cu.import("resource://gre/modules/FxAccountsCommon.js");
-Cu.import("resource://gre/modules/FxAccountsOAuthGrantClient.jsm");
-Cu.import("resource://gre/modules/Services.jsm");
-
-const CLIENT_OPTIONS = {
-  serverURL: "http://127.0.0.1:9010/v1",
-  client_id: 'abc123'
-};
-
-const STATUS_SUCCESS = 200;
-
-/**
- * Mock request responder
- * @param {String} response
- *        Mocked raw response from the server
- * @returns {Function}
- */
-var mockResponse = function (response) {
-  return function () {
-    return {
-      setHeader: function () {},
-      post: function () {
-        this.response = response;
-        this.onComplete();
-      }
-    };
-  };
-};
-
-/**
- * Mock request error responder
- * @param {Error} error
- *        Error object
- * @returns {Function}
- */
-var mockResponseError = function (error) {
-  return function () {
-    return {
-      setHeader: function () {},
-      post: function () {
-        this.onComplete(error);
-      }
-    };
-  };
-};
-
-add_test(function missingParams () {
-  let client = new FxAccountsOAuthGrantClient(CLIENT_OPTIONS);
-  try {
-    client.getTokenFromAssertion()
-  } catch (e) {
-    do_check_eq(e.message, "Missing 'assertion' parameter");
-  }
-
-  try {
-    client.getTokenFromAssertion("assertion")
-  } catch (e) {
-    do_check_eq(e.message, "Missing 'scope' parameter");
-  }
-
-  run_next_test();
-});
-
-add_test(function successfulResponse () {
-  let client = new FxAccountsOAuthGrantClient(CLIENT_OPTIONS);
-  let response = {
-    success: true,
-    status: STATUS_SUCCESS,
-    body: "{\"access_token\":\"http://example.com/image.jpeg\",\"id\":\"0d5c1a89b8c54580b8e3e8adadae864a\"}",
-  };
-
-  client._Request = new mockResponse(response);
-  client.getTokenFromAssertion("assertion", "scope")
-    .then(
-      function (result) {
-        do_check_eq(result.access_token, "http://example.com/image.jpeg");
-        run_next_test();
-      }
-    );
-});
-
-add_test(function successfulDestroy () {
-  let client = new FxAccountsOAuthGrantClient(CLIENT_OPTIONS);
-  let response = {
-    success: true,
-    status: STATUS_SUCCESS,
-    body: "{}",
-  };
-
-  client._Request = new mockResponse(response);
-  client.destroyToken("deadbeef").then(run_next_test);
-});
-
-add_test(function parseErrorResponse () {
-  let client = new FxAccountsOAuthGrantClient(CLIENT_OPTIONS);
-  let response = {
-    success: true,
-    status: STATUS_SUCCESS,
-    body: "unexpected",
-  };
-
-  client._Request = new mockResponse(response);
-  client.getTokenFromAssertion("assertion", "scope")
-    .then(
-      null,
-      function (e) {
-        do_check_eq(e.name, "FxAccountsOAuthGrantClientError");
-        do_check_eq(e.code, STATUS_SUCCESS);
-        do_check_eq(e.errno, ERRNO_PARSE);
-        do_check_eq(e.error, ERROR_PARSE);
-        do_check_eq(e.message, "unexpected");
-        run_next_test();
-      }
-    );
-});
-
-add_test(function serverErrorResponse () {
-  let client = new FxAccountsOAuthGrantClient(CLIENT_OPTIONS);
-  let response = {
-    status: 400,
-    body: "{ \"code\": 400, \"errno\": 104, \"error\": \"Bad Request\", \"message\": \"Unauthorized\", \"reason\": \"Invalid fxa assertion\" }",
-  };
-
-  client._Request = new mockResponse(response);
-  client.getTokenFromAssertion("blah", "scope")
-    .then(
-    null,
-    function (e) {
-      do_check_eq(e.name, "FxAccountsOAuthGrantClientError");
-      do_check_eq(e.code, 400);
-      do_check_eq(e.errno, ERRNO_INVALID_FXA_ASSERTION);
-      do_check_eq(e.error, "Bad Request");
-      do_check_eq(e.message, "Unauthorized");
-      run_next_test();
-    }
-  );
-});
-
-add_test(function networkErrorResponse () {
-  let client = new FxAccountsOAuthGrantClient({
-    serverURL: "http://domain.dummy",
-    client_id: "abc123"
-  });
-  Services.prefs.setBoolPref("identity.fxaccounts.skipDeviceRegistration", true);
-  client.getTokenFromAssertion("assertion", "scope")
-    .then(
-      null,
-      function (e) {
-        do_check_eq(e.name, "FxAccountsOAuthGrantClientError");
-        do_check_eq(e.code, null);
-        do_check_eq(e.errno, ERRNO_NETWORK);
-        do_check_eq(e.error, ERROR_NETWORK);
-        run_next_test();
-      }
-    ).catch(() => {}).then(() =>
-      Services.prefs.clearUserPref("identity.fxaccounts.skipDeviceRegistration"));
-});
-
-add_test(function unsupportedMethod () {
-  let client = new FxAccountsOAuthGrantClient(CLIENT_OPTIONS);
-
-  return client._createRequest("/", "PUT")
-    .then(
-      null,
-      function (e) {
-        do_check_eq(e.name, "FxAccountsOAuthGrantClientError");
-        do_check_eq(e.code, ERROR_CODE_METHOD_NOT_ALLOWED);
-        do_check_eq(e.errno, ERRNO_NETWORK);
-        do_check_eq(e.error, ERROR_NETWORK);
-        do_check_eq(e.message, ERROR_MSG_METHOD_NOT_ALLOWED);
-        run_next_test();
-      }
-    );
-});
-
-add_test(function onCompleteRequestError () {
-  let client = new FxAccountsOAuthGrantClient(CLIENT_OPTIONS);
-  client._Request = new mockResponseError(new Error("onComplete error"));
-  client.getTokenFromAssertion("assertion", "scope")
-    .then(
-      null,
-      function (e) {
-        do_check_eq(e.name, "FxAccountsOAuthGrantClientError");
-        do_check_eq(e.code, null);
-        do_check_eq(e.errno, ERRNO_NETWORK);
-        do_check_eq(e.error, ERROR_NETWORK);
-        do_check_eq(e.message, "Error: onComplete error");
-        run_next_test();
-      }
-  );
-});
-
-add_test(function incorrectErrno() {
-  let client = new FxAccountsOAuthGrantClient(CLIENT_OPTIONS);
-  let response = {
-    status: 400,
-    body: "{ \"code\": 400, \"errno\": \"bad errno\", \"error\": \"Bad Request\", \"message\": \"Unauthorized\", \"reason\": \"Invalid fxa assertion\" }",
-  };
-
-  client._Request = new mockResponse(response);
-  client.getTokenFromAssertion("blah", "scope")
-    .then(
-    null,
-    function (e) {
-      do_check_eq(e.name, "FxAccountsOAuthGrantClientError");
-      do_check_eq(e.code, 400);
-      do_check_eq(e.errno, ERRNO_UNKNOWN_ERROR);
-      do_check_eq(e.error, "Bad Request");
-      do_check_eq(e.message, "Unauthorized");
-      run_next_test();
-    }
-  );
-});
-
-add_test(function constructorTests() {
-  validationHelper(undefined,
-    "Error: Missing configuration options");
-
-  validationHelper({},
-    "Error: Missing 'serverURL' parameter");
-
-  validationHelper({ serverURL: "http://example.com" },
-    "Error: Missing 'client_id' parameter");
-
-  validationHelper({ client_id: "123ABC" },
-    "Error: Missing 'serverURL' parameter");
-
-  validationHelper({ client_id: "123ABC", serverURL: "badUrl" },
-    "Error: Invalid 'serverURL'");
-
-  run_next_test();
-});
-
-add_test(function errorTests() {
-  let error1 = new FxAccountsOAuthGrantClientError();
-  do_check_eq(error1.name, "FxAccountsOAuthGrantClientError");
-  do_check_eq(error1.code, null);
-  do_check_eq(error1.errno, ERRNO_UNKNOWN_ERROR);
-  do_check_eq(error1.error, ERROR_UNKNOWN);
-  do_check_eq(error1.message, null);
-
-  let error2 = new FxAccountsOAuthGrantClientError({
-    code: STATUS_SUCCESS,
-    errno: 1,
-    error: "Error",
-    message: "Something",
-  });
-  let fields2 = error2._toStringFields();
-  let statusCode = 1;
-
-  do_check_eq(error2.name, "FxAccountsOAuthGrantClientError");
-  do_check_eq(error2.code, STATUS_SUCCESS);
-  do_check_eq(error2.errno, statusCode);
-  do_check_eq(error2.error, "Error");
-  do_check_eq(error2.message, "Something");
-
-  do_check_eq(fields2.name, "FxAccountsOAuthGrantClientError");
-  do_check_eq(fields2.code, STATUS_SUCCESS);
-  do_check_eq(fields2.errno, statusCode);
-  do_check_eq(fields2.error, "Error");
-  do_check_eq(fields2.message, "Something");
-
-  do_check_true(error2.toString().indexOf("Something") >= 0);
-  run_next_test();
-});
-
-function run_test() {
-  run_next_test();
-}
-
-/**
- * Quick way to test the "FxAccountsOAuthGrantClient" constructor.
- *
- * @param {Object} options
- *        FxAccountsOAuthGrantClient constructor options
- * @param {String} expected
- *        Expected error message
- * @returns {*}
- */
-function validationHelper(options, expected) {
-  try {
-    new FxAccountsOAuthGrantClient(options);
-  } catch (e) {
-    return do_check_eq(e.toString(), expected);
-  }
-  throw new Error("Validation helper error");
-}
diff --git a/services/fxaccounts/tests/xpcshell/test_oauth_grant_client_server.js b/services/fxaccounts/tests/xpcshell/test_oauth_grant_client_server.js
deleted file mode 100644
index bd44651..0000000
--- a/services/fxaccounts/tests/xpcshell/test_oauth_grant_client_server.js
+++ /dev/null
@@ -1,73 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/ */
-
-// A test of FxAccountsOAuthGrantClient but using a real server it can
-// hit.
-"use strict";
-
-Cu.import("resource://gre/modules/FxAccountsCommon.js");
-Cu.import("resource://gre/modules/FxAccountsOAuthGrantClient.jsm");
-
-// handlers for our server.
-var numTokenFetches;
-var activeTokens;
-
-function authorize(request, response) {
-  response.setStatusLine("1.1", 200, "OK");
-  let token = "token" + numTokenFetches;
-  numTokenFetches += 1;
-  activeTokens.add(token);
-  response.write(JSON.stringify({access_token: token}));
-}
-
-function destroy(request, response) {
-  // Getting the body seems harder than it should be!
-  let sis = Cc["@mozilla.org/scriptableinputstream;1"]
-            .createInstance(Ci.nsIScriptableInputStream);
-  sis.init(request.bodyInputStream);
-  let body = JSON.parse(sis.read(sis.available()));
-  sis.close();
-  let token = body.token;
-  ok(activeTokens.delete(token));
-  print("after destroy have", activeTokens.size, "tokens left.")
-  response.setStatusLine("1.1", 200, "OK");
-  response.write('{}');
-}
-
-function startServer() {
-  numTokenFetches = 0;
-  activeTokens = new Set();
-  let srv = new HttpServer();
-  srv.registerPathHandler("/v1/authorization", authorize);
-  srv.registerPathHandler("/v1/destroy", destroy);
-  srv.start(-1);
-  return srv;
-}
-
-function promiseStopServer(server) {
-  return new Promise(resolve => {
-    server.stop(resolve);
-  });
-}
-
-add_task(function* getAndRevokeToken () {
-  let server = startServer();
-  let clientOptions = {
-    serverURL: "http://localhost:" + server.identity.primaryPort + "/v1",
-    client_id: 'abc123',
-  }
-
-  let client = new FxAccountsOAuthGrantClient(clientOptions);
-  let result = yield client.getTokenFromAssertion("assertion", "scope");
-  equal(result.access_token, "token0");
-  equal(numTokenFetches, 1, "we hit the server to fetch a token");
-  yield client.destroyToken("token0");
-  equal(activeTokens.size, 0, "We hit the server to revoke it");
-  yield promiseStopServer(server);
-});
-
-// XXX - TODO - we should probably add more tests for unexpected responses etc.
-
-function run_test() {
-  run_next_test();
-}
diff --git a/services/fxaccounts/tests/xpcshell/test_oauth_token_storage.js b/services/fxaccounts/tests/xpcshell/test_oauth_token_storage.js
deleted file mode 100644
index 0864284..0000000
--- a/services/fxaccounts/tests/xpcshell/test_oauth_token_storage.js
+++ /dev/null
@@ -1,165 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/ */
-
-"use strict";
-
-Cu.import("resource://gre/modules/FxAccounts.jsm");
-Cu.import("resource://gre/modules/FxAccountsClient.jsm");
-Cu.import("resource://gre/modules/FxAccountsCommon.js");
-Cu.import("resource://gre/modules/osfile.jsm");
-
-// We grab some additional stuff via backstage passes.
-var {AccountState} = Cu.import("resource://gre/modules/FxAccounts.jsm", {});
-
-function promiseNotification(topic) {
-  return new Promise(resolve => {
-    let observe = () => {
-      Services.obs.removeObserver(observe, topic);
-      resolve();
-    }
-    Services.obs.addObserver(observe, topic, false);
-  });
-}
-
-// A storage manager that doesn't actually write anywhere.
-function MockStorageManager() {
-}
-
-MockStorageManager.prototype = {
-  promiseInitialized: Promise.resolve(),
-
-  initialize(accountData) {
-    this.accountData = accountData;
-  },
-
-  finalize() {
-    return Promise.resolve();
-  },
-
-  getAccountData() {
-    return Promise.resolve(this.accountData);
-  },
-
-  updateAccountData(updatedFields) {
-    for (let [name, value] of Object.entries(updatedFields)) {
-      if (value == null) {
-        delete this.accountData[name];
-      } else {
-        this.accountData[name] = value;
-      }
-    }
-    return Promise.resolve();
-  },
-
-  deleteAccountData() {
-    this.accountData = null;
-    return Promise.resolve();
-  }
-}
-
-
-// Just enough mocks so we can avoid hawk etc.
-function MockFxAccountsClient() {
-  this._email = "nobody at example.com";
-  this._verified = false;
-
-  this.accountStatus = function(uid) {
-    let deferred = Promise.defer();
-    deferred.resolve(!!uid && (!this._deletedOnServer));
-    return deferred.promise;
-  };
-
-  this.signOut = function() { return Promise.resolve(); };
-  this.registerDevice = function() { return Promise.resolve(); };
-  this.updateDevice = function() { return Promise.resolve(); };
-  this.signOutAndDestroyDevice = function() { return Promise.resolve(); };
-  this.getDeviceList = function() { return Promise.resolve(); };
-
-  FxAccountsClient.apply(this);
-}
-
-MockFxAccountsClient.prototype = {
-  __proto__: FxAccountsClient.prototype
-}
-
-function MockFxAccounts(device={}) {
-  return new FxAccounts({
-    fxAccountsClient: new MockFxAccountsClient(),
-    newAccountState(credentials) {
-      // we use a real accountState but mocked storage.
-      let storage = new MockStorageManager();
-      storage.initialize(credentials);
-      return new AccountState(storage);
-    },
-    _getDeviceName() {
-      return "mock device name";
-    },
-    fxaPushService: {
-      registerPushEndpoint() {
-        return new Promise((resolve) => {
-          resolve({
-            endpoint: "http://mochi.test:8888"
-          });
-        });
-      },
-    },
-  });
-}
-
-function* createMockFxA() {
-  let fxa = new MockFxAccounts();
-  let credentials = {
-    email: "foo at example.com",
-    uid: "1234 at lcip.org",
-    assertion: "foobar",
-    sessionToken: "dead",
-    kA: "beef",
-    kB: "cafe",
-    verified: true
-  };
-  yield fxa.setSignedInUser(credentials);
-  return fxa;
-}
-
-// The tests.
-function run_test() {
-  run_next_test();
-}
-
-add_task(function* testCacheStorage() {
-  let fxa = yield createMockFxA();
-
-  // Hook what the impl calls to save to disk.
-  let cas = fxa.internal.currentAccountState;
-  let origPersistCached = cas._persistCachedTokens.bind(cas)
-  cas._persistCachedTokens = function() {
-    return origPersistCached().then(() => {
-      Services.obs.notifyObservers(null, "testhelper-fxa-cache-persist-done", null);
-    });
-  };
-
-  let promiseWritten = promiseNotification("testhelper-fxa-cache-persist-done");
-  let tokenData = {token: "token1", somethingelse: "something else"};
-  let scopeArray = ["foo", "bar"];
-  cas.setCachedToken(scopeArray, tokenData);
-  deepEqual(cas.getCachedToken(scopeArray), tokenData);
-
-  deepEqual(cas.oauthTokens, {"bar|foo": tokenData});
-  // wait for background write to complete.
-  yield promiseWritten;
-
-  // Check the token cache made it to our mocked storage.
-  deepEqual(cas.storageManager.accountData.oauthTokens, {"bar|foo": tokenData});
-
-  // Drop the token from the cache and ensure it is removed from the json.
-  promiseWritten = promiseNotification("testhelper-fxa-cache-persist-done");
-  yield cas.removeCachedToken("token1");
-  deepEqual(cas.oauthTokens, {});
-  yield promiseWritten;
-  deepEqual(cas.storageManager.accountData.oauthTokens, {});
-
-  // sign out and the token storage should end up with null.
-  let storageManager = cas.storageManager; // .signOut() removes the attribute.
-  yield fxa.signOut( /* localOnly = */ true);
-  deepEqual(storageManager.accountData, null);
-});
diff --git a/services/fxaccounts/tests/xpcshell/test_oauth_tokens.js b/services/fxaccounts/tests/xpcshell/test_oauth_tokens.js
deleted file mode 100644
index f758bf4..0000000
--- a/services/fxaccounts/tests/xpcshell/test_oauth_tokens.js
+++ /dev/null
@@ -1,251 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/ */
-
-"use strict";
-
-Cu.import("resource://gre/modules/FxAccounts.jsm");
-Cu.import("resource://gre/modules/FxAccountsClient.jsm");
-Cu.import("resource://gre/modules/FxAccountsCommon.js");
-Cu.import("resource://gre/modules/FxAccountsOAuthGrantClient.jsm");
-Cu.import("resource://services-common/utils.js");
-var {AccountState} = Cu.import("resource://gre/modules/FxAccounts.jsm", {});
-
-function promiseNotification(topic) {
-  return new Promise(resolve => {
-    let observe = () => {
-      Services.obs.removeObserver(observe, topic);
-      resolve();
-    }
-    Services.obs.addObserver(observe, topic, false);
-  });
-}
-
-// Just enough mocks so we can avoid hawk and storage.
-function MockStorageManager() {
-}
-
-MockStorageManager.prototype = {
-  promiseInitialized: Promise.resolve(),
-
-  initialize(accountData) {
-    this.accountData = accountData;
-  },
-
-  finalize() {
-    return Promise.resolve();
-  },
-
-  getAccountData() {
-    return Promise.resolve(this.accountData);
-  },
-
-  updateAccountData(updatedFields) {
-    for (let [name, value] of Object.entries(updatedFields)) {
-      if (value == null) {
-        delete this.accountData[name];
-      } else {
-        this.accountData[name] = value;
-      }
-    }
-    return Promise.resolve();
-  },
-
-  deleteAccountData() {
-    this.accountData = null;
-    return Promise.resolve();
-  }
-}
-
-function MockFxAccountsClient() {
-  this._email = "nobody at example.com";
-  this._verified = false;
-
-  this.accountStatus = function(uid) {
-    let deferred = Promise.defer();
-    deferred.resolve(!!uid && (!this._deletedOnServer));
-    return deferred.promise;
-  };
-
-  this.signOut = function() { return Promise.resolve(); };
-  this.registerDevice = function() { return Promise.resolve(); };
-  this.updateDevice = function() { return Promise.resolve(); };
-  this.signOutAndDestroyDevice = function() { return Promise.resolve(); };
-  this.getDeviceList = function() { return Promise.resolve(); };
-
-  FxAccountsClient.apply(this);
-}
-
-MockFxAccountsClient.prototype = {
-  __proto__: FxAccountsClient.prototype
-}
-
-function MockFxAccounts(mockGrantClient) {
-  return new FxAccounts({
-    fxAccountsClient: new MockFxAccountsClient(),
-    getAssertion: () => Promise.resolve("assertion"),
-    newAccountState(credentials) {
-      // we use a real accountState but mocked storage.
-      let storage = new MockStorageManager();
-      storage.initialize(credentials);
-      return new AccountState(storage);
-    },
-    _destroyOAuthToken: function(tokenData) {
-      // somewhat sad duplication of _destroyOAuthToken, but hard to avoid.
-      return mockGrantClient.destroyToken(tokenData.token).then( () => {
-        Services.obs.notifyObservers(null, "testhelper-fxa-revoke-complete", null);
-      });
-    },
-    _getDeviceName() {
-      return "mock device name";
-    },
-    fxaPushService: {
-      registerPushEndpoint() {
-        return new Promise((resolve) => {
-          resolve({
-            endpoint: "http://mochi.test:8888"
-          });
-        });
-      },
-    },
-  });
-}
-
-function* createMockFxA(mockGrantClient) {
-  let fxa = new MockFxAccounts(mockGrantClient);
-  let credentials = {
-    email: "foo at example.com",
-    uid: "1234 at lcip.org",
-    assertion: "foobar",
-    sessionToken: "dead",
-    kA: "beef",
-    kB: "cafe",
-    verified: true
-  };
-
-  yield fxa.setSignedInUser(credentials);
-  return fxa;
-}
-
-// The tests.
-function run_test() {
-  run_next_test();
-}
-
-function MockFxAccountsOAuthGrantClient() {
-  this.activeTokens = new Set();
-}
-
-MockFxAccountsOAuthGrantClient.prototype = {
-  serverURL: {href: "http://localhost"},
-  getTokenFromAssertion(assertion, scope) {
-    let token = "token" + this.numTokenFetches;
-    this.numTokenFetches += 1;
-    this.activeTokens.add(token);
-    print("getTokenFromAssertion returning token", token);
-    return Promise.resolve({access_token: token});
-  },
-  destroyToken(token) {
-    ok(this.activeTokens.delete(token));
-    print("after destroy have", this.activeTokens.size, "tokens left.");
-    return Promise.resolve({});
-  },
-  // and some stuff used only for tests.
-  numTokenFetches: 0,
-  activeTokens: null,
-}
-
-add_task(function* testRevoke() {
-  let client = new MockFxAccountsOAuthGrantClient();
-  let tokenOptions = { scope: "test-scope", client: client };
-  let fxa = yield createMockFxA(client);
-
-  // get our first token and check we hit the mock.
-  let token1 = yield fxa.getOAuthToken(tokenOptions);
-  equal(client.numTokenFetches, 1);
-  equal(client.activeTokens.size, 1);
-  ok(token1, "got a token");
-  equal(token1, "token0");
-
-  // drop the new token from our cache.
-  yield fxa.removeCachedOAuthToken({token: token1});
-
-  // FxA fires an observer when the "background" revoke is complete.
-  yield promiseNotification("testhelper-fxa-revoke-complete");
-  // the revoke should have been successful.
-  equal(client.activeTokens.size, 0);
-  // fetching it again hits the server.
-  let token2 = yield fxa.getOAuthToken(tokenOptions);
-  equal(client.numTokenFetches, 2);
-  equal(client.activeTokens.size, 1);
-  ok(token2, "got a token");
-  notEqual(token1, token2, "got a different token");
-});
-
-add_task(function* testSignOutDestroysTokens() {
-  let client = new MockFxAccountsOAuthGrantClient();
-  let fxa = yield createMockFxA(client);
-
-  // get our first token and check we hit the mock.
-  let token1 = yield fxa.getOAuthToken({ scope: "test-scope", client: client });
-  equal(client.numTokenFetches, 1);
-  equal(client.activeTokens.size, 1);
-  ok(token1, "got a token");
-
-  // get another
-  let token2 = yield fxa.getOAuthToken({ scope: "test-scope-2", client: client });
-  equal(client.numTokenFetches, 2);
-  equal(client.activeTokens.size, 2);
-  ok(token2, "got a token");
-  notEqual(token1, token2, "got a different token");
-
-  // now sign out - they should be removed.
-  yield fxa.signOut();
-  // FxA fires an observer when the "background" signout is complete.
-  yield promiseNotification("testhelper-fxa-signout-complete");
-  // No active tokens left.
-  equal(client.activeTokens.size, 0);
-});
-
-add_task(function* testTokenRaces() {
-  // Here we do 2 concurrent fetches each for 2 different token scopes (ie,
-  // 4 token fetches in total).
-  // This should provoke a potential race in the token fetching but we should
-  // handle and detect that leaving us with one of the fetch tokens being
-  // revoked and the same token value returned to both calls.
-  let client = new MockFxAccountsOAuthGrantClient();
-  let fxa = yield createMockFxA(client);
-
-  // We should see 2 notifications as part of this - set up the listeners
-  // now (and wait on them later)
-  let notifications = Promise.all([
-    promiseNotification("testhelper-fxa-revoke-complete"),
-    promiseNotification("testhelper-fxa-revoke-complete"),
-  ]);
-  let results = yield Promise.all([
-    fxa.getOAuthToken({scope: "test-scope", client: client}),
-    fxa.getOAuthToken({scope: "test-scope", client: client}),
-    fxa.getOAuthToken({scope: "test-scope-2", client: client}),
-    fxa.getOAuthToken({scope: "test-scope-2", client: client}),
-  ]);
-
-  equal(client.numTokenFetches, 4, "should have fetched 4 tokens.");
-  // We should see 2 of the 4 revoked due to the race.
-  yield notifications;
-
-  // Should have 2 unique tokens
-  results.sort();
-  equal(results[0], results[1]);
-  equal(results[2], results[3]);
-  // should be 2 active.
-  equal(client.activeTokens.size, 2);
-  // Which can each be revoked.
-  notifications = Promise.all([
-    promiseNotification("testhelper-fxa-revoke-complete"),
-    promiseNotification("testhelper-fxa-revoke-complete"),
-  ]);
-  yield fxa.removeCachedOAuthToken({token: results[0]});
-  equal(client.activeTokens.size, 1);
-  yield fxa.removeCachedOAuthToken({token: results[2]});
-  equal(client.activeTokens.size, 0);
-  yield notifications;
-});
diff --git a/services/fxaccounts/tests/xpcshell/test_profile.js b/services/fxaccounts/tests/xpcshell/test_profile.js
deleted file mode 100644
index 13adf8c..0000000
--- a/services/fxaccounts/tests/xpcshell/test_profile.js
+++ /dev/null
@@ -1,409 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/ */
-
-"use strict";
-
-Cu.import("resource://gre/modules/Promise.jsm");
-Cu.import("resource://gre/modules/FxAccountsCommon.js");
-Cu.import("resource://gre/modules/FxAccountsProfileClient.jsm");
-Cu.import("resource://gre/modules/FxAccountsProfile.jsm");
-
-const URL_STRING = "https://example.com";
-Services.prefs.setCharPref("identity.fxaccounts.settings.uri", "https://example.com/settings");
-
-const STATUS_SUCCESS = 200;
-
-/**
- * Mock request responder
- * @param {String} response
- *        Mocked raw response from the server
- * @returns {Function}
- */
-var mockResponse = function (response) {
-  let Request = function (requestUri) {
-    // Store the request uri so tests can inspect it
-    Request._requestUri = requestUri;
-    return {
-      setHeader: function () {},
-      head: function () {
-        this.response = response;
-        this.onComplete();
-      }
-    };
-  };
-
-  return Request;
-};
-
-/**
- * Mock request error responder
- * @param {Error} error
- *        Error object
- * @returns {Function}
- */
-var mockResponseError = function (error) {
-  return function () {
-    return {
-      setHeader: function () {},
-      head: function () {
-        this.onComplete(error);
-      }
-    };
-  };
-};
-
-var mockClient = function (fxa) {
-  let options = {
-    serverURL: "http://127.0.0.1:1111/v1",
-    fxa: fxa,
-  }
-  return new FxAccountsProfileClient(options);
-};
-
-const ACCOUNT_DATA = {
-  uid: "abc123"
-};
-
-function FxaMock() {
-}
-FxaMock.prototype = {
-  currentAccountState: {
-    profile: null,
-    get isCurrent() {
-      return true;
-    }
-  },
-
-  getSignedInUser: function () {
-    return Promise.resolve(ACCOUNT_DATA);
-  }
-};
-
-var mockFxa = function() {
-  return new FxaMock();
-};
-
-function CreateFxAccountsProfile(fxa = null, client = null) {
-  if (!fxa) {
-    fxa = mockFxa();
-  }
-  let options = {
-    fxa: fxa,
-    profileServerUrl: "http://127.0.0.1:1111/v1"
-  }
-  if (client) {
-    options.profileClient = client;
-  }
-  return new FxAccountsProfile(options);
-}
-
-add_test(function getCachedProfile() {
-  let profile = CreateFxAccountsProfile();
-  // a little pointless until bug 1157529 is fixed...
-  profile._cachedProfile = { avatar: "myurl" };
-
-  return profile._getCachedProfile()
-    .then(function (cached) {
-      do_check_eq(cached.avatar, "myurl");
-      run_next_test();
-    });
-});
-
-add_test(function cacheProfile_change() {
-  let fxa = mockFxa();
-/* Saving profile data disabled - bug 1157529
-  let setUserAccountDataCalled = false;
-  fxa.setUserAccountData = function (data) {
-    setUserAccountDataCalled = true;
-    do_check_eq(data.profile.avatar, "myurl");
-    return Promise.resolve();
-  };
-*/
-  let profile = CreateFxAccountsProfile(fxa);
-
-  makeObserver(ON_PROFILE_CHANGE_NOTIFICATION, function (subject, topic, data) {
-    do_check_eq(data, ACCOUNT_DATA.uid);
-//    do_check_true(setUserAccountDataCalled); - bug 1157529
-    run_next_test();
-  });
-
-  return profile._cacheProfile({ avatar: "myurl" });
-});
-
-add_test(function cacheProfile_no_change() {
-  let fxa = mockFxa();
-  let profile = CreateFxAccountsProfile(fxa)
-  profile._cachedProfile = { avatar: "myurl" };
-// XXX - saving is disabled (but we can leave that in for now as we are
-// just checking it is *not* called)
-  fxa.setSignedInUser = function (data) {
-    throw new Error("should not update account data");
-  };
-
-  return profile._cacheProfile({ avatar: "myurl" })
-    .then((result) => {
-      do_check_false(!!result);
-      run_next_test();
-    });
-});
-
-add_test(function fetchAndCacheProfile_ok() {
-  let client = mockClient(mockFxa());
-  client.fetchProfile = function () {
-    return Promise.resolve({ avatar: "myimg"});
-  };
-  let profile = CreateFxAccountsProfile(null, client);
-
-  profile._cacheProfile = function (toCache) {
-    do_check_eq(toCache.avatar, "myimg");
-    return Promise.resolve();
-  };
-
-  return profile._fetchAndCacheProfile()
-    .then(result => {
-      do_check_eq(result.avatar, "myimg");
-      run_next_test();
-    });
-});
-
-// Check that a second profile request when one is already in-flight reuses
-// the in-flight one.
-add_task(function* fetchAndCacheProfileOnce() {
-  // A promise that remains unresolved while we fire off 2 requests for
-  // a profile.
-  let resolveProfile;
-  let promiseProfile = new Promise(resolve => {
-    resolveProfile = resolve;
-  });
-  let numFetches = 0;
-  let client = mockClient(mockFxa());
-  client.fetchProfile = function () {
-    numFetches += 1;
-    return promiseProfile;
-  };
-  let profile = CreateFxAccountsProfile(null, client);
-
-  let request1 = profile._fetchAndCacheProfile();
-  let request2 = profile._fetchAndCacheProfile();
-
-  // should be one request made to fetch the profile (but the promise returned
-  // by it remains unresolved)
-  do_check_eq(numFetches, 1);
-
-  // resolve the promise.
-  resolveProfile({ avatar: "myimg"});
-
-  // both requests should complete with the same data.
-  let got1 = yield request1;
-  do_check_eq(got1.avatar, "myimg");
-  let got2 = yield request1;
-  do_check_eq(got2.avatar, "myimg");
-
-  // and still only 1 request was made.
-  do_check_eq(numFetches, 1);
-});
-
-// Check that sharing a single fetch promise works correctly when the promise
-// is rejected.
-add_task(function* fetchAndCacheProfileOnce() {
-  // A promise that remains unresolved while we fire off 2 requests for
-  // a profile.
-  let rejectProfile;
-  let promiseProfile = new Promise((resolve,reject) => {
-    rejectProfile = reject;
-  });
-  let numFetches = 0;
-  let client = mockClient(mockFxa());
-  client.fetchProfile = function () {
-    numFetches += 1;
-    return promiseProfile;
-  };
-  let profile = CreateFxAccountsProfile(null, client);
-
-  let request1 = profile._fetchAndCacheProfile();
-  let request2 = profile._fetchAndCacheProfile();
-
-  // should be one request made to fetch the profile (but the promise returned
-  // by it remains unresolved)
-  do_check_eq(numFetches, 1);
-
-  // reject the promise.
-  rejectProfile("oh noes");
-
-  // both requests should reject.
-  try {
-    yield request1;
-    throw new Error("should have rejected");
-  } catch (ex) {
-    if (ex != "oh noes") {
-      throw ex;
-    }
-  }
-  try {
-    yield request2;
-    throw new Error("should have rejected");
-  } catch (ex) {
-    if (ex != "oh noes") {
-      throw ex;
-    }
-  }
-
-  // but a new request should work.
-  client.fetchProfile = function () {
-    return Promise.resolve({ avatar: "myimg"});
-  };
-
-  let got = yield profile._fetchAndCacheProfile();
-  do_check_eq(got.avatar, "myimg");
-});
-
-// Check that a new profile request within PROFILE_FRESHNESS_THRESHOLD of the
-// last one doesn't kick off a new request to check the cached copy is fresh.
-add_task(function* fetchAndCacheProfileAfterThreshold() {
-  let numFetches = 0;
-  let client = mockClient(mockFxa());
-  client.fetchProfile = function () {
-    numFetches += 1;
-    return Promise.resolve({ avatar: "myimg"});
-  };
-  let profile = CreateFxAccountsProfile(null, client);
-  profile.PROFILE_FRESHNESS_THRESHOLD = 1000;
-
-  yield profile.getProfile();
-  do_check_eq(numFetches, 1);
-
-  yield profile.getProfile();
-  do_check_eq(numFetches, 1);
-
-  yield new Promise(resolve => {
-    do_timeout(1000, resolve);
-  });
-
-  yield profile.getProfile();
-  do_check_eq(numFetches, 2);
-});
-
-// Check that a new profile request within PROFILE_FRESHNESS_THRESHOLD of the
-// last one *does* kick off a new request if ON_PROFILE_CHANGE_NOTIFICATION
-// is sent.
-add_task(function* fetchAndCacheProfileBeforeThresholdOnNotification() {
-  let numFetches = 0;
-  let client = mockClient(mockFxa());
-  client.fetchProfile = function () {
-    numFetches += 1;
-    return Promise.resolve({ avatar: "myimg"});
-  };
-  let profile = CreateFxAccountsProfile(null, client);
-  profile.PROFILE_FRESHNESS_THRESHOLD = 1000;
-
-  yield profile.getProfile();
-  do_check_eq(numFetches, 1);
-
-  Services.obs.notifyObservers(null, ON_PROFILE_CHANGE_NOTIFICATION, null);
-
-  yield profile.getProfile();
-  do_check_eq(numFetches, 2);
-});
-
-add_test(function tearDown_ok() {
-  let profile = CreateFxAccountsProfile();
-
-  do_check_true(!!profile.client);
-  do_check_true(!!profile.fxa);
-
-  profile.tearDown();
-  do_check_null(profile.fxa);
-  do_check_null(profile.client);
-
-  run_next_test();
-});
-
-add_test(function getProfile_ok() {
-  let cachedUrl = "myurl";
-  let didFetch = false;
-
-  let profile = CreateFxAccountsProfile();
-  profile._getCachedProfile = function () {
-    return Promise.resolve({ avatar: cachedUrl });
-  };
-
-  profile._fetchAndCacheProfile = function () {
-    didFetch = true;
-    return Promise.resolve();
-  };
-
-  return profile.getProfile()
-    .then(result => {
-      do_check_eq(result.avatar, cachedUrl);
-      do_check_true(didFetch);
-      run_next_test();
-    });
-});
-
-add_test(function getProfile_no_cache() {
-  let fetchedUrl = "newUrl";
-  let profile = CreateFxAccountsProfile();
-  profile._getCachedProfile = function () {
-    return Promise.resolve();
-  };
-
-  profile._fetchAndCacheProfile = function () {
-    return Promise.resolve({ avatar: fetchedUrl });
-  };
-
-  return profile.getProfile()
-    .then(result => {
-      do_check_eq(result.avatar, fetchedUrl);
-      run_next_test();
-    });
-});
-
-add_test(function getProfile_has_cached_fetch_deleted() {
-  let cachedUrl = "myurl";
-
-  let fxa = mockFxa();
-  let client = mockClient(fxa);
-  client.fetchProfile = function () {
-    return Promise.resolve({ avatar: null });
-  };
-
-  let profile = CreateFxAccountsProfile(fxa, client);
-  profile._cachedProfile = { avatar: cachedUrl };
-
-// instead of checking this in a mocked "save" function, just check after the
-// observer
-  makeObserver(ON_PROFILE_CHANGE_NOTIFICATION, function (subject, topic, data) {
-    profile.getProfile()
-      .then(profileData => {
-        do_check_null(profileData.avatar);
-        run_next_test();
-      });
-  });
-
-  return profile.getProfile()
-    .then(result => {
-      do_check_eq(result.avatar, "myurl");
-    });
-});
-
-function run_test() {
-  run_next_test();
-}
-
-function makeObserver(aObserveTopic, aObserveFunc) {
-  let callback = function (aSubject, aTopic, aData) {
-    log.debug("observed " + aTopic + " " + aData);
-    if (aTopic == aObserveTopic) {
-      removeMe();
-      aObserveFunc(aSubject, aTopic, aData);
-    }
-  };
-
-  function removeMe() {
-    log.debug("removing observer for " + aObserveTopic);
-    Services.obs.removeObserver(callback, aObserveTopic);
-  }
-
-  Services.obs.addObserver(callback, aObserveTopic, false);
-  return removeMe;
-}
diff --git a/services/fxaccounts/tests/xpcshell/test_profile_client.js b/services/fxaccounts/tests/xpcshell/test_profile_client.js
deleted file mode 100644
index 20ff6ef..0000000
--- a/services/fxaccounts/tests/xpcshell/test_profile_client.js
+++ /dev/null
@@ -1,411 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/ */
-
-"use strict";
-
-Cu.import("resource://gre/modules/FxAccountsCommon.js");
-Cu.import("resource://gre/modules/FxAccountsProfileClient.jsm");
-
-const STATUS_SUCCESS = 200;
-
-/**
- * Mock request responder
- * @param {String} response
- *        Mocked raw response from the server
- * @returns {Function}
- */
-var mockResponse = function (response) {
-  let Request = function (requestUri) {
-    // Store the request uri so tests can inspect it
-    Request._requestUri = requestUri;
-    return {
-      setHeader: function () {},
-      get: function () {
-        this.response = response;
-        this.onComplete();
-      }
-    };
-  };
-
-  return Request;
-};
-
-// A simple mock FxA that hands out tokens without checking them and doesn't
-// expect tokens to be revoked. We have specific token tests further down that
-// has more checks here.
-var mockFxa = {
-  getOAuthToken(options) {
-    do_check_eq(options.scope, "profile");
-    return "token";
-  }
-}
-
-const PROFILE_OPTIONS = {
-  serverURL: "http://127.0.0.1:1111/v1",
-  fxa: mockFxa,
-};
-
-/**
- * Mock request error responder
- * @param {Error} error
- *        Error object
- * @returns {Function}
- */
-var mockResponseError = function (error) {
-  return function () {
-    return {
-      setHeader: function () {},
-      get: function () {
-        this.onComplete(error);
-      }
-    };
-  };
-};
-
-add_test(function successfulResponse () {
-  let client = new FxAccountsProfileClient(PROFILE_OPTIONS);
-  let response = {
-    success: true,
-    status: STATUS_SUCCESS,
-    body: "{\"email\":\"someone at restmail.net\",\"uid\":\"0d5c1a89b8c54580b8e3e8adadae864a\"}",
-  };
-
-  client._Request = new mockResponse(response);
-  client.fetchProfile()
-    .then(
-      function (result) {
-        do_check_eq(client._Request._requestUri, "http://127.0.0.1:1111/v1/profile");
-        do_check_eq(result.email, "someone at restmail.net");
-        do_check_eq(result.uid, "0d5c1a89b8c54580b8e3e8adadae864a");
-        run_next_test();
-      }
-    );
-});
-
-add_test(function parseErrorResponse () {
-  let client = new FxAccountsProfileClient(PROFILE_OPTIONS);
-  let response = {
-    success: true,
-    status: STATUS_SUCCESS,
-    body: "unexpected",
-  };
-
-  client._Request = new mockResponse(response);
-  client.fetchProfile()
-    .then(
-      null,
-      function (e) {
-        do_check_eq(e.name, "FxAccountsProfileClientError");
-        do_check_eq(e.code, STATUS_SUCCESS);
-        do_check_eq(e.errno, ERRNO_PARSE);
-        do_check_eq(e.error, ERROR_PARSE);
-        do_check_eq(e.message, "unexpected");
-        run_next_test();
-      }
-    );
-});
-
-add_test(function serverErrorResponse () {
-  let client = new FxAccountsProfileClient(PROFILE_OPTIONS);
-  let response = {
-    status: 500,
-    body: "{ \"code\": 500, \"errno\": 100, \"error\": \"Bad Request\", \"message\": \"Something went wrong\", \"reason\": \"Because the internet\" }",
-  };
-
-  client._Request = new mockResponse(response);
-  client.fetchProfile()
-    .then(
-    null,
-    function (e) {
-      do_check_eq(e.name, "FxAccountsProfileClientError");
-      do_check_eq(e.code, 500);
-      do_check_eq(e.errno, 100);
-      do_check_eq(e.error, "Bad Request");
-      do_check_eq(e.message, "Something went wrong");
-      run_next_test();
-    }
-  );
-});
-
-// Test that we get a token, then if we get a 401 we revoke it, get a new one
-// and retry.
-add_test(function server401ResponseThenSuccess () {
-  // The last token we handed out.
-  let lastToken = -1;
-  // The number of times our removeCachedOAuthToken function was called.
-  let numTokensRemoved = 0;
-
-  let mockFxa = {
-    getOAuthToken(options) {
-      do_check_eq(options.scope, "profile");
-      return "" + ++lastToken; // tokens are strings.
-    },
-    removeCachedOAuthToken(options) {
-      // This test never has more than 1 token alive at once, so the token
-      // being revoked must always be the last token we handed out.
-      do_check_eq(parseInt(options.token), lastToken);
-      ++numTokensRemoved;
-    }
-  }
-  let profileOptions = {
-    serverURL: "http://127.0.0.1:1111/v1",
-    fxa: mockFxa,
-  };
-  let client = new FxAccountsProfileClient(profileOptions);
-
-  // 2 responses - first one implying the token has expired, second works.
-  let responses = [
-    {
-      status: 401,
-      body: "{ \"code\": 401, \"errno\": 100, \"error\": \"Token expired\", \"message\": \"That token is too old\", \"reason\": \"Because security\" }",
-    },
-    {
-      success: true,
-      status: STATUS_SUCCESS,
-      body: "{\"avatar\":\"http://example.com/image.jpg\",\"id\":\"0d5c1a89b8c54580b8e3e8adadae864a\"}",
-    },
-  ];
-
-  let numRequests = 0;
-  let numAuthHeaders = 0;
-  // Like mockResponse but we want access to headers etc.
-  client._Request = function(requestUri) {
-    return {
-      setHeader: function (name, value) {
-        if (name == "Authorization") {
-          numAuthHeaders++;
-          do_check_eq(value, "Bearer " + lastToken);
-        }
-      },
-      get: function () {
-        this.response = responses[numRequests];
-        ++numRequests;
-        this.onComplete();
-      }
-    };
-  }
-
-  client.fetchProfile()
-    .then(result => {
-      do_check_eq(result.avatar, "http://example.com/image.jpg");
-      do_check_eq(result.id, "0d5c1a89b8c54580b8e3e8adadae864a");
-      // should have been exactly 2 requests and exactly 2 auth headers.
-      do_check_eq(numRequests, 2);
-      do_check_eq(numAuthHeaders, 2);
-      // and we should have seen one token revoked.
-      do_check_eq(numTokensRemoved, 1);
-
-      run_next_test();
-    }
-  );
-});
-
-// Test that we get a token, then if we get a 401 we revoke it, get a new one
-// and retry - but we *still* get a 401 on the retry, so the caller sees that.
-add_test(function server401ResponsePersists () {
-  // The last token we handed out.
-  let lastToken = -1;
-  // The number of times our removeCachedOAuthToken function was called.
-  let numTokensRemoved = 0;
-
-  let mockFxa = {
-    getOAuthToken(options) {
-      do_check_eq(options.scope, "profile");
-      return "" + ++lastToken; // tokens are strings.
-    },
-    removeCachedOAuthToken(options) {
-      // This test never has more than 1 token alive at once, so the token
-      // being revoked must always be the last token we handed out.
-      do_check_eq(parseInt(options.token), lastToken);
-      ++numTokensRemoved;
-    }
-  }
-  let profileOptions = {
-    serverURL: "http://127.0.0.1:1111/v1",
-    fxa: mockFxa,
-  };
-  let client = new FxAccountsProfileClient(profileOptions);
-
-  let response = {
-      status: 401,
-      body: "{ \"code\": 401, \"errno\": 100, \"error\": \"It's not your token, it's you!\", \"message\": \"I don't like you\", \"reason\": \"Because security\" }",
-  };
-
-  let numRequests = 0;
-  let numAuthHeaders = 0;
-  client._Request = function(requestUri) {
-    return {
-      setHeader: function (name, value) {
-        if (name == "Authorization") {
-          numAuthHeaders++;
-          do_check_eq(value, "Bearer " + lastToken);
-        }
-      },
-      get: function () {
-        this.response = response;
-        ++numRequests;
-        this.onComplete();
-      }
-    };
-  }
-
-  client.fetchProfile().then(
-    null,
-    function (e) {
-      do_check_eq(e.name, "FxAccountsProfileClientError");
-      do_check_eq(e.code, 401);
-      do_check_eq(e.errno, 100);
-      do_check_eq(e.error, "It's not your token, it's you!");
-      // should have been exactly 2 requests and exactly 2 auth headers.
-      do_check_eq(numRequests, 2);
-      do_check_eq(numAuthHeaders, 2);
-      // and we should have seen both tokens revoked.
-      do_check_eq(numTokensRemoved, 2);
-      run_next_test();
-    }
-  );
-});
-
-add_test(function networkErrorResponse () {
-  let client = new FxAccountsProfileClient({
-    serverURL: "http://domain.dummy",
-    fxa: mockFxa,
-  });
-  client.fetchProfile()
-    .then(
-      null,
-      function (e) {
-        do_check_eq(e.name, "FxAccountsProfileClientError");
-        do_check_eq(e.code, null);
-        do_check_eq(e.errno, ERRNO_NETWORK);
-        do_check_eq(e.error, ERROR_NETWORK);
-        run_next_test();
-      }
-    );
-});
-
-add_test(function unsupportedMethod () {
-  let client = new FxAccountsProfileClient(PROFILE_OPTIONS);
-
-  return client._createRequest("/profile", "PUT")
-    .then(
-      null,
-      function (e) {
-        do_check_eq(e.name, "FxAccountsProfileClientError");
-        do_check_eq(e.code, ERROR_CODE_METHOD_NOT_ALLOWED);
-        do_check_eq(e.errno, ERRNO_NETWORK);
-        do_check_eq(e.error, ERROR_NETWORK);
-        do_check_eq(e.message, ERROR_MSG_METHOD_NOT_ALLOWED);
-        run_next_test();
-      }
-    );
-});
-
-add_test(function onCompleteRequestError () {
-  let client = new FxAccountsProfileClient(PROFILE_OPTIONS);
-  client._Request = new mockResponseError(new Error("onComplete error"));
-  client.fetchProfile()
-    .then(
-      null,
-      function (e) {
-        do_check_eq(e.name, "FxAccountsProfileClientError");
-        do_check_eq(e.code, null);
-        do_check_eq(e.errno, ERRNO_NETWORK);
-        do_check_eq(e.error, ERROR_NETWORK);
-        do_check_eq(e.message, "Error: onComplete error");
-        run_next_test();
-      }
-  );
-});
-
-add_test(function fetchProfileImage_successfulResponse () {
-  let client = new FxAccountsProfileClient(PROFILE_OPTIONS);
-  let response = {
-    success: true,
-    status: STATUS_SUCCESS,
-    body: "{\"avatar\":\"http://example.com/image.jpg\",\"id\":\"0d5c1a89b8c54580b8e3e8adadae864a\"}",
-  };
-
-  client._Request = new mockResponse(response);
-  client.fetchProfileImage()
-    .then(
-      function (result) {
-        do_check_eq(client._Request._requestUri, "http://127.0.0.1:1111/v1/avatar");
-        do_check_eq(result.avatar, "http://example.com/image.jpg");
-        do_check_eq(result.id, "0d5c1a89b8c54580b8e3e8adadae864a");
-        run_next_test();
-      }
-    );
-});
-
-add_test(function constructorTests() {
-  validationHelper(undefined,
-    "Error: Missing 'serverURL' configuration option");
-
-  validationHelper({},
-    "Error: Missing 'serverURL' configuration option");
-
-  validationHelper({ serverURL: "badUrl" },
-    "Error: Invalid 'serverURL'");
-
-  run_next_test();
-});
-
-add_test(function errorTests() {
-  let error1 = new FxAccountsProfileClientError();
-  do_check_eq(error1.name, "FxAccountsProfileClientError");
-  do_check_eq(error1.code, null);
-  do_check_eq(error1.errno, ERRNO_UNKNOWN_ERROR);
-  do_check_eq(error1.error, ERROR_UNKNOWN);
-  do_check_eq(error1.message, null);
-
-  let error2 = new FxAccountsProfileClientError({
-    code: STATUS_SUCCESS,
-    errno: 1,
-    error: "Error",
-    message: "Something",
-  });
-  let fields2 = error2._toStringFields();
-  let statusCode = 1;
-
-  do_check_eq(error2.name, "FxAccountsProfileClientError");
-  do_check_eq(error2.code, STATUS_SUCCESS);
-  do_check_eq(error2.errno, statusCode);
-  do_check_eq(error2.error, "Error");
-  do_check_eq(error2.message, "Something");
-
-  do_check_eq(fields2.name, "FxAccountsProfileClientError");
-  do_check_eq(fields2.code, STATUS_SUCCESS);
-  do_check_eq(fields2.errno, statusCode);
-  do_check_eq(fields2.error, "Error");
-  do_check_eq(fields2.message, "Something");
-
-  do_check_true(error2.toString().indexOf("Something") >= 0);
-  run_next_test();
-});
-
-function run_test() {
-  run_next_test();
-}
-
-/**
- * Quick way to test the "FxAccountsProfileClient" constructor.
- *
- * @param {Object} options
- *        FxAccountsProfileClient constructor options
- * @param {String} expected
- *        Expected error message
- * @returns {*}
- */
-function validationHelper(options, expected) {
-  // add fxa to options - that missing isn't what we are testing here.
-  if (options) {
-    options.fxa = mockFxa;
-  }
-  try {
-    new FxAccountsProfileClient(options);
-  } catch (e) {
-    return do_check_eq(e.toString(), expected);
-  }
-  throw new Error("Validation helper error");
-}
diff --git a/services/fxaccounts/tests/xpcshell/test_push_service.js b/services/fxaccounts/tests/xpcshell/test_push_service.js
deleted file mode 100644
index 8d66f6f..0000000
--- a/services/fxaccounts/tests/xpcshell/test_push_service.js
+++ /dev/null
@@ -1,236 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/ */
-
-"use strict";
-
-// Tests for the FxA push service.
-
-Cu.import("resource://gre/modules/Task.jsm");
-Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://gre/modules/Promise.jsm");
-Cu.import("resource://gre/modules/FxAccountsCommon.js");
-Cu.import("resource://gre/modules/FxAccountsPush.js");
-Cu.import("resource://gre/modules/Log.jsm");
-
-XPCOMUtils.defineLazyServiceGetter(this, "pushService",
-  "@mozilla.org/push/Service;1", "nsIPushService");
-
-initTestLogging("Trace");
-log.level = Log.Level.Trace;
-
-const MOCK_ENDPOINT = "http://mochi.test:8888";
-
-// tests do not allow external connections, mock the PushService
-let mockPushService = {
-  pushTopic: this.pushService.pushTopic,
-  subscriptionChangeTopic: this.pushService.subscriptionChangeTopic,
-  subscribe(scope, principal, cb) {
-    cb(Components.results.NS_OK, {
-      endpoint: MOCK_ENDPOINT
-    });
-  },
-  unsubscribe(scope, principal, cb) {
-    cb(Components.results.NS_OK, true);
-  }
-};
-
-let mockFxAccounts = {
-  checkVerificationStatus() {},
-  updateDeviceRegistration() {}
-};
-
-let mockLog = {
-  trace() {},
-  debug() {},
-  warn() {},
-  error() {}
-};
-
-
-add_task(function* initialize() {
-  let pushService = new FxAccountsPushService();
-  equal(pushService.initialize(), false);
-});
-
-add_task(function* registerPushEndpointSuccess() {
-  let pushService = new FxAccountsPushService({
-    pushService: mockPushService,
-    fxAccounts: mockFxAccounts,
-  });
-
-  let subscription = yield pushService.registerPushEndpoint();
-  equal(subscription.endpoint, MOCK_ENDPOINT);
-});
-
-add_task(function* registerPushEndpointFailure() {
-  let failPushService = Object.assign(mockPushService, {
-    subscribe(scope, principal, cb) {
-      cb(Components.results.NS_ERROR_ABORT);
-    }
-  });
-
-  let pushService = new FxAccountsPushService({
-    pushService: failPushService,
-    fxAccounts: mockFxAccounts,
-  });
-
-  let subscription = yield pushService.registerPushEndpoint();
-  equal(subscription, null);
-});
-
-add_task(function* unsubscribeSuccess() {
-  let pushService = new FxAccountsPushService({
-    pushService: mockPushService,
-    fxAccounts: mockFxAccounts,
-  });
-
-  let result = yield pushService.unsubscribe();
-  equal(result, true);
-});
-
-add_task(function* unsubscribeFailure() {
-  let failPushService = Object.assign(mockPushService, {
-    unsubscribe(scope, principal, cb) {
-      cb(Components.results.NS_ERROR_ABORT);
-    }
-  });
-
-  let pushService = new FxAccountsPushService({
-    pushService: failPushService,
-    fxAccounts: mockFxAccounts,
-  });
-
-  let result = yield pushService.unsubscribe();
-  equal(result, null);
-});
-
-add_test(function observeLogout() {
-  let customLog = Object.assign(mockLog, {
-    trace: function (msg) {
-      if (msg === "FxAccountsPushService unsubscribe") {
-        // logout means we unsubscribe
-        run_next_test();
-      }
-    }
-  });
-
-  let pushService = new FxAccountsPushService({
-    pushService: mockPushService,
-    log: customLog
-  });
-
-  pushService.observe(null, ONLOGOUT_NOTIFICATION);
-});
-
-add_test(function observePushTopicVerify() {
-  let emptyMsg = {
-    QueryInterface: function() {
-      return this;
-    }
-  };
-  let customAccounts = Object.assign(mockFxAccounts, {
-    checkVerificationStatus: function () {
-      // checking verification status on push messages without data
-      run_next_test();
-    }
-  });
-
-  let pushService = new FxAccountsPushService({
-    pushService: mockPushService,
-    fxAccounts: customAccounts,
-  });
-
-  pushService.observe(emptyMsg, mockPushService.pushTopic, FXA_PUSH_SCOPE_ACCOUNT_UPDATE);
-});
-
-add_test(function observePushTopicDeviceDisconnected() {
-  const deviceId = "bogusid";
-  let msg = {
-    data: {
-      json: () => ({
-        command: ON_DEVICE_DISCONNECTED_NOTIFICATION,
-        data: {
-          id: deviceId
-        }
-      })
-    },
-    QueryInterface: function() {
-      return this;
-    }
-  };
-  let customAccounts = Object.assign(mockFxAccounts, {
-    handleDeviceDisconnection: function () {
-      // checking verification status on push messages without data
-      run_next_test();
-    }
-  });
-
-  let pushService = new FxAccountsPushService({
-    pushService: mockPushService,
-    fxAccounts: customAccounts,
-  });
-
-  pushService.observe(msg, mockPushService.pushTopic, FXA_PUSH_SCOPE_ACCOUNT_UPDATE);
-});
-
-add_test(function observePushTopicPasswordChanged() {
-  let msg = {
-    data: {
-      json: () => ({
-        command: ON_PASSWORD_CHANGED_NOTIFICATION
-      })
-    },
-    QueryInterface: function() {
-      return this;
-    }
-  };
-
-  let pushService = new FxAccountsPushService({
-    pushService: mockPushService,
-  });
-
-  pushService._onPasswordChanged = function () {
-    run_next_test();
-  }
-
-  pushService.observe(msg, mockPushService.pushTopic, FXA_PUSH_SCOPE_ACCOUNT_UPDATE);
-});
-
-add_test(function observePushTopicPasswordReset() {
-  let msg = {
-    data: {
-      json: () => ({
-        command: ON_PASSWORD_RESET_NOTIFICATION
-      })
-    },
-    QueryInterface: function() {
-      return this;
-    }
-  };
-
-  let pushService = new FxAccountsPushService({
-    pushService: mockPushService
-  });
-
-  pushService._onPasswordChanged = function () {
-    run_next_test();
-  }
-
-  pushService.observe(msg, mockPushService.pushTopic, FXA_PUSH_SCOPE_ACCOUNT_UPDATE);
-});
-
-add_test(function observeSubscriptionChangeTopic() {
-  let customAccounts = Object.assign(mockFxAccounts, {
-    updateDeviceRegistration: function () {
-      // subscription change means updating the device registration
-      run_next_test();
-    }
-  });
-
-  let pushService = new FxAccountsPushService({
-    pushService: mockPushService,
-    fxAccounts: customAccounts,
-  });
-
-  pushService.observe(null, mockPushService.subscriptionChangeTopic, FXA_PUSH_SCOPE_ACCOUNT_UPDATE);
-});
diff --git a/services/fxaccounts/tests/xpcshell/test_storage_manager.js b/services/fxaccounts/tests/xpcshell/test_storage_manager.js
deleted file mode 100644
index 6a293a0..0000000
--- a/services/fxaccounts/tests/xpcshell/test_storage_manager.js
+++ /dev/null
@@ -1,477 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/ */
-
-"use strict";
-
-// Tests for the FxA storage manager.
-
-Cu.import("resource://gre/modules/Task.jsm");
-Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://gre/modules/FxAccountsStorage.jsm");
-Cu.import("resource://gre/modules/FxAccountsCommon.js");
-Cu.import("resource://gre/modules/Log.jsm");
-
-initTestLogging("Trace");
-log.level = Log.Level.Trace;
-
-const DEVICE_REGISTRATION_VERSION = 42;
-
-// A couple of mocks we can use.
-function MockedPlainStorage(accountData) {
-  let data = null;
-  if (accountData) {
-    data = {
-      version: DATA_FORMAT_VERSION,
-      accountData: accountData,
-    }
-  }
-  this.data = data;
-  this.numReads = 0;
-}
-MockedPlainStorage.prototype = {
-  get: Task.async(function* () {
-    this.numReads++;
-    Assert.equal(this.numReads, 1, "should only ever be 1 read of acct data");
-    return this.data;
-  }),
-
-  set: Task.async(function* (data) {
-    this.data = data;
-  }),
-};
-
-function MockedSecureStorage(accountData) {
-  let data = null;
-  if (accountData) {
-    data = {
-      version: DATA_FORMAT_VERSION,
-      accountData: accountData,
-    }
-  }
-  this.data = data;
-  this.numReads = 0;
-}
-
-MockedSecureStorage.prototype = {
-  fetchCount: 0,
-  locked: false,
-  STORAGE_LOCKED: function() {},
-  get: Task.async(function* (uid, email) {
-    this.fetchCount++;
-    if (this.locked) {
-      throw new this.STORAGE_LOCKED();
-    }
-    this.numReads++;
-    Assert.equal(this.numReads, 1, "should only ever be 1 read of unlocked data");
-    return this.data;
-  }),
-
-  set: Task.async(function* (uid, contents) {
-    this.data = contents;
-  }),
-}
-
-function add_storage_task(testFunction) {
-  add_task(function* () {
-    print("Starting test with secure storage manager");
-    yield testFunction(new FxAccountsStorageManager());
-  });
-  add_task(function* () {
-    print("Starting test with simple storage manager");
-    yield testFunction(new FxAccountsStorageManager({useSecure: false}));
-  });
-}
-
-// initialized without account data and there's nothing to read. Not logged in.
-add_storage_task(function* checkInitializedEmpty(sm) {
-  if (sm.secureStorage) {
-    sm.secureStorage = new MockedSecureStorage(null);
-  }
-  yield sm.initialize();
-  Assert.strictEqual((yield sm.getAccountData()), null);
-  Assert.rejects(sm.updateAccountData({kA: "kA"}), "No user is logged in")
-});
-
-// Initialized with account data (ie, simulating a new user being logged in).
-// Should reflect the initial data and be written to storage.
-add_storage_task(function* checkNewUser(sm) {
-  let initialAccountData = {
-    uid: "uid",
-    email: "someone at somewhere.com",
-    kA: "kA",
-    deviceId: "device id"
-  };
-  sm.plainStorage = new MockedPlainStorage()
-  if (sm.secureStorage) {
-    sm.secureStorage = new MockedSecureStorage(null);
-  }
-  yield sm.initialize(initialAccountData);
-  let accountData = yield sm.getAccountData();
-  Assert.equal(accountData.uid, initialAccountData.uid);
-  Assert.equal(accountData.email, initialAccountData.email);
-  Assert.equal(accountData.kA, initialAccountData.kA);
-  Assert.equal(accountData.deviceId, initialAccountData.deviceId);
-
-  // and it should have been written to storage.
-  Assert.equal(sm.plainStorage.data.accountData.uid, initialAccountData.uid);
-  Assert.equal(sm.plainStorage.data.accountData.email, initialAccountData.email);
-  Assert.equal(sm.plainStorage.data.accountData.deviceId, initialAccountData.deviceId);
-  // check secure
-  if (sm.secureStorage) {
-    Assert.equal(sm.secureStorage.data.accountData.kA, initialAccountData.kA);
-  } else {
-    Assert.equal(sm.plainStorage.data.accountData.kA, initialAccountData.kA);
-  }
-});
-
-// Initialized without account data but storage has it available.
-add_storage_task(function* checkEverythingRead(sm) {
-  sm.plainStorage = new MockedPlainStorage({
-    uid: "uid",
-    email: "someone at somewhere.com",
-    deviceId: "wibble",
-    deviceRegistrationVersion: null
-  });
-  if (sm.secureStorage) {
-    sm.secureStorage = new MockedSecureStorage(null);
-  }
-  yield sm.initialize();
-  let accountData = yield sm.getAccountData();
-  Assert.ok(accountData, "read account data");
-  Assert.equal(accountData.uid, "uid");
-  Assert.equal(accountData.email, "someone at somewhere.com");
-  Assert.equal(accountData.deviceId, "wibble");
-  Assert.equal(accountData.deviceRegistrationVersion, null);
-  // Update the data - we should be able to fetch it back and it should appear
-  // in our storage.
-  yield sm.updateAccountData({
-    verified: true,
-    kA: "kA",
-    kB: "kB",
-    deviceRegistrationVersion: DEVICE_REGISTRATION_VERSION
-  });
-  accountData = yield sm.getAccountData();
-  Assert.equal(accountData.kB, "kB");
-  Assert.equal(accountData.kA, "kA");
-  Assert.equal(accountData.deviceId, "wibble");
-  Assert.equal(accountData.deviceRegistrationVersion, DEVICE_REGISTRATION_VERSION);
-  // Check the new value was written to storage.
-  yield sm._promiseStorageComplete; // storage is written in the background.
-  // "verified", "deviceId" and "deviceRegistrationVersion" are plain-text fields.
-  Assert.equal(sm.plainStorage.data.accountData.verified, true);
-  Assert.equal(sm.plainStorage.data.accountData.deviceId, "wibble");
-  Assert.equal(sm.plainStorage.data.accountData.deviceRegistrationVersion, DEVICE_REGISTRATION_VERSION);
-  // "kA" and "foo" are secure
-  if (sm.secureStorage) {
-    Assert.equal(sm.secureStorage.data.accountData.kA, "kA");
-    Assert.equal(sm.secureStorage.data.accountData.kB, "kB");
-  } else {
-    Assert.equal(sm.plainStorage.data.accountData.kA, "kA");
-    Assert.equal(sm.plainStorage.data.accountData.kB, "kB");
-  }
-});
-
-add_storage_task(function* checkInvalidUpdates(sm) {
-  sm.plainStorage = new MockedPlainStorage({uid: "uid", email: "someone at somewhere.com"})
-  if (sm.secureStorage) {
-    sm.secureStorage = new MockedSecureStorage(null);
-  }
-  Assert.rejects(sm.updateAccountData({uid: "another"}), "Can't change");
-  Assert.rejects(sm.updateAccountData({email: "someoneelse"}), "Can't change");
-});
-
-add_storage_task(function* checkNullUpdatesRemovedUnlocked(sm) {
-  if (sm.secureStorage) {
-    sm.plainStorage = new MockedPlainStorage({uid: "uid", email: "someone at somewhere.com"})
-    sm.secureStorage = new MockedSecureStorage({kA: "kA", kB: "kB"});
-  } else {
-    sm.plainStorage = new MockedPlainStorage({uid: "uid", email: "someone at somewhere.com",
-                                              kA: "kA", kB: "kB"});
-  }
-  yield sm.initialize();
-
-  yield sm.updateAccountData({kA: null});
-  let accountData = yield sm.getAccountData();
-  Assert.ok(!accountData.kA);
-  Assert.equal(accountData.kB, "kB");
-});
-
-add_storage_task(function* checkDelete(sm) {
-  if (sm.secureStorage) {
-    sm.plainStorage = new MockedPlainStorage({uid: "uid", email: "someone at somewhere.com"})
-    sm.secureStorage = new MockedSecureStorage({kA: "kA", kB: "kB"});
-  } else {
-    sm.plainStorage = new MockedPlainStorage({uid: "uid", email: "someone at somewhere.com",
-                                              kA: "kA", kB: "kB"});
-  }
-  yield sm.initialize();
-
-  yield sm.deleteAccountData();
-  // Storage should have been reset to null.
-  Assert.equal(sm.plainStorage.data, null);
-  if (sm.secureStorage) {
-    Assert.equal(sm.secureStorage.data, null);
-  }
-  // And everything should reflect no user.
-  Assert.equal((yield sm.getAccountData()), null);
-});
-
-// Some tests only for the secure storage manager.
-add_task(function* checkNullUpdatesRemovedLocked() {
-  let sm = new FxAccountsStorageManager();
-  sm.plainStorage = new MockedPlainStorage({uid: "uid", email: "someone at somewhere.com"})
-  sm.secureStorage = new MockedSecureStorage({kA: "kA", kB: "kB"});
-  sm.secureStorage.locked = true;
-  yield sm.initialize();
-
-  yield sm.updateAccountData({kA: null});
-  let accountData = yield sm.getAccountData();
-  Assert.ok(!accountData.kA);
-  // still no kB as we are locked.
-  Assert.ok(!accountData.kB);
-
-  // now unlock - should still be no kA but kB should appear.
-  sm.secureStorage.locked = false;
-  accountData = yield sm.getAccountData();
-  Assert.ok(!accountData.kA);
-  Assert.equal(accountData.kB, "kB");
-  // And secure storage should have been written with our previously-cached
-  // data.
-  Assert.strictEqual(sm.secureStorage.data.accountData.kA, undefined);
-  Assert.strictEqual(sm.secureStorage.data.accountData.kB, "kB");
-});
-
-add_task(function* checkEverythingReadSecure() {
-  let sm = new FxAccountsStorageManager();
-  sm.plainStorage = new MockedPlainStorage({uid: "uid", email: "someone at somewhere.com"})
-  sm.secureStorage = new MockedSecureStorage({kA: "kA"});
-  yield sm.initialize();
-
-  let accountData = yield sm.getAccountData();
-  Assert.ok(accountData, "read account data");
-  Assert.equal(accountData.uid, "uid");
-  Assert.equal(accountData.email, "someone at somewhere.com");
-  Assert.equal(accountData.kA, "kA");
-});
-
-add_task(function* checkMemoryFieldsNotReturnedByDefault() {
-  let sm = new FxAccountsStorageManager();
-  sm.plainStorage = new MockedPlainStorage({uid: "uid", email: "someone at somewhere.com"})
-  sm.secureStorage = new MockedSecureStorage({kA: "kA"});
-  yield sm.initialize();
-
-  // keyPair is a memory field.
-  yield sm.updateAccountData({keyPair: "the keypair value"});
-  let accountData = yield sm.getAccountData();
-
-  // Requesting everything should *not* return in memory fields.
-  Assert.strictEqual(accountData.keyPair, undefined);
-  // But requesting them specifically does get them.
-  accountData = yield sm.getAccountData("keyPair");
-  Assert.strictEqual(accountData.keyPair, "the keypair value");
-});
-
-add_task(function* checkExplicitGet() {
-  let sm = new FxAccountsStorageManager();
-  sm.plainStorage = new MockedPlainStorage({uid: "uid", email: "someone at somewhere.com"})
-  sm.secureStorage = new MockedSecureStorage({kA: "kA"});
-  yield sm.initialize();
-
-  let accountData = yield sm.getAccountData(["uid", "kA"]);
-  Assert.ok(accountData, "read account data");
-  Assert.equal(accountData.uid, "uid");
-  Assert.equal(accountData.kA, "kA");
-  // We didn't ask for email so shouldn't have got it.
-  Assert.strictEqual(accountData.email, undefined);
-});
-
-add_task(function* checkExplicitGetNoSecureRead() {
-  let sm = new FxAccountsStorageManager();
-  sm.plainStorage = new MockedPlainStorage({uid: "uid", email: "someone at somewhere.com"})
-  sm.secureStorage = new MockedSecureStorage({kA: "kA"});
-  yield sm.initialize();
-
-  Assert.equal(sm.secureStorage.fetchCount, 0);
-  // request 2 fields in secure storage - it should have caused a single fetch.
-  let accountData = yield sm.getAccountData(["email", "uid"]);
-  Assert.ok(accountData, "read account data");
-  Assert.equal(accountData.uid, "uid");
-  Assert.equal(accountData.email, "someone at somewhere.com");
-  Assert.strictEqual(accountData.kA, undefined);
-  Assert.equal(sm.secureStorage.fetchCount, 1);
-});
-
-add_task(function* checkLockedUpdates() {
-  let sm = new FxAccountsStorageManager();
-  sm.plainStorage = new MockedPlainStorage({uid: "uid", email: "someone at somewhere.com"})
-  sm.secureStorage = new MockedSecureStorage({kA: "old-kA", kB: "kB"});
-  sm.secureStorage.locked = true;
-  yield sm.initialize();
-
-  let accountData = yield sm.getAccountData();
-  // requesting kA and kB will fail as storage is locked.
-  Assert.ok(!accountData.kA);
-  Assert.ok(!accountData.kB);
-  // While locked we can still update it and see the updated value.
-  sm.updateAccountData({kA: "new-kA"});
-  accountData = yield sm.getAccountData();
-  Assert.equal(accountData.kA, "new-kA");
-  // unlock.
-  sm.secureStorage.locked = false;
-  accountData = yield sm.getAccountData();
-  // should reflect the value we updated and the one we didn't.
-  Assert.equal(accountData.kA, "new-kA");
-  Assert.equal(accountData.kB, "kB");
-  // And storage should also reflect it.
-  Assert.strictEqual(sm.secureStorage.data.accountData.kA, "new-kA");
-  Assert.strictEqual(sm.secureStorage.data.accountData.kB, "kB");
-});
-
-// Some tests for the "storage queue" functionality.
-
-// A helper for our queued tests. It creates a StorageManager and then queues
-// an unresolved promise. The tests then do additional setup and checks, then
-// resolves or rejects the blocked promise.
-var setupStorageManagerForQueueTest = Task.async(function* () {
-  let sm = new FxAccountsStorageManager();
-  sm.plainStorage = new MockedPlainStorage({uid: "uid", email: "someone at somewhere.com"})
-  sm.secureStorage = new MockedSecureStorage({kA: "kA"});
-  sm.secureStorage.locked = true;
-  yield sm.initialize();
-
-  let resolveBlocked, rejectBlocked;
-  let blockedPromise = new Promise((resolve, reject) => {
-    resolveBlocked = resolve;
-    rejectBlocked = reject;
-  });
-
-  sm._queueStorageOperation(() => blockedPromise);
-  return {sm, blockedPromise, resolveBlocked, rejectBlocked}
-});
-
-// First the general functionality.
-add_task(function* checkQueueSemantics() {
-  let { sm, resolveBlocked } = yield setupStorageManagerForQueueTest();
-
-  // We've one unresolved promise in the queue - add another promise.
-  let resolveSubsequent;
-  let subsequentPromise = new Promise(resolve => {
-    resolveSubsequent = resolve;
-  });
-  let subsequentCalled = false;
-
-  sm._queueStorageOperation(() => {
-    subsequentCalled = true;
-    resolveSubsequent();
-    return subsequentPromise;
-  });
-
-  // Our "subsequent" function should not have been called yet.
-  Assert.ok(!subsequentCalled);
-
-  // Release our blocked promise.
-  resolveBlocked();
-
-  // Our subsequent promise should end up resolved.
-  yield subsequentPromise;
-  Assert.ok(subsequentCalled);
-  yield sm.finalize();
-});
-
-// Check that a queued promise being rejected works correctly.
-add_task(function* checkQueueSemanticsOnError() {
-  let { sm, blockedPromise, rejectBlocked } = yield setupStorageManagerForQueueTest();
-
-  let resolveSubsequent;
-  let subsequentPromise = new Promise(resolve => {
-    resolveSubsequent = resolve;
-  });
-  let subsequentCalled = false;
-
-  sm._queueStorageOperation(() => {
-    subsequentCalled = true;
-    resolveSubsequent();
-    return subsequentPromise;
-  });
-
-  // Our "subsequent" function should not have been called yet.
-  Assert.ok(!subsequentCalled);
-
-  // Reject our blocked promise - the subsequent operations should still work
-  // correctly.
-  rejectBlocked("oh no");
-
-  // Our subsequent promise should end up resolved.
-  yield subsequentPromise;
-  Assert.ok(subsequentCalled);
-
-  // But the first promise should reflect the rejection.
-  try {
-    yield blockedPromise;
-    Assert.ok(false, "expected this promise to reject");
-  } catch (ex) {
-    Assert.equal(ex, "oh no");
-  }
-  yield sm.finalize();
-});
-
-
-// And some tests for the specific operations that are queued.
-add_task(function* checkQueuedReadAndUpdate() {
-  let { sm, resolveBlocked } = yield setupStorageManagerForQueueTest();
-  // Mock the underlying operations
-  // _doReadAndUpdateSecure is queued by _maybeReadAndUpdateSecure
-  let _doReadCalled = false;
-  sm._doReadAndUpdateSecure = () => {
-    _doReadCalled = true;
-    return Promise.resolve();
-  }
-
-  let resultPromise = sm._maybeReadAndUpdateSecure();
-  Assert.ok(!_doReadCalled);
-
-  resolveBlocked();
-  yield resultPromise;
-  Assert.ok(_doReadCalled);
-  yield sm.finalize();
-});
-
-add_task(function* checkQueuedWrite() {
-  let { sm, resolveBlocked } = yield setupStorageManagerForQueueTest();
-  // Mock the underlying operations
-  let __writeCalled = false;
-  sm.__write = () => {
-    __writeCalled = true;
-    return Promise.resolve();
-  }
-
-  let writePromise = sm._write();
-  Assert.ok(!__writeCalled);
-
-  resolveBlocked();
-  yield writePromise;
-  Assert.ok(__writeCalled);
-  yield sm.finalize();
-});
-
-add_task(function* checkQueuedDelete() {
-  let { sm, resolveBlocked } = yield setupStorageManagerForQueueTest();
-  // Mock the underlying operations
-  let _deleteCalled = false;
-  sm._deleteAccountData = () => {
-    _deleteCalled = true;
-    return Promise.resolve();
-  }
-
-  let resultPromise = sm.deleteAccountData();
-  Assert.ok(!_deleteCalled);
-
-  resolveBlocked();
-  yield resultPromise;
-  Assert.ok(_deleteCalled);
-  yield sm.finalize();
-});
-
-function run_test() {
-  run_next_test();
-}
diff --git a/services/fxaccounts/tests/xpcshell/test_web_channel.js b/services/fxaccounts/tests/xpcshell/test_web_channel.js
deleted file mode 100644
index 3cf5662..0000000
--- a/services/fxaccounts/tests/xpcshell/test_web_channel.js
+++ /dev/null
@@ -1,499 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/ */
-
-"use strict";
-
-Cu.import("resource://gre/modules/FxAccountsCommon.js");
-const { FxAccountsWebChannel, FxAccountsWebChannelHelpers } =
-    Cu.import("resource://gre/modules/FxAccountsWebChannel.jsm");
-
-const URL_STRING = "https://example.com";
-
-const mockSendingContext = {
-  browser: {},
-  principal: {},
-  eventTarget: {}
-};
-
-add_test(function () {
-  validationHelper(undefined,
-  "Error: Missing configuration options");
-
-  validationHelper({
-    channel_id: WEBCHANNEL_ID
-  },
-  "Error: Missing 'content_uri' option");
-
-  validationHelper({
-    content_uri: 'bad uri',
-    channel_id: WEBCHANNEL_ID
-  },
-  /NS_ERROR_MALFORMED_URI/);
-
-  validationHelper({
-    content_uri: URL_STRING
-  },
-  'Error: Missing \'channel_id\' option');
-
-  run_next_test();
-});
-
-add_task(function* test_rejection_reporting() {
-  let mockMessage = {
-    command: 'fxaccounts:login',
-    messageId: '1234',
-    data: { email: 'testuser at testuser.com' },
-  };
-
-  let channel = new FxAccountsWebChannel({
-    channel_id: WEBCHANNEL_ID,
-    content_uri: URL_STRING,
-    helpers: {
-      login(accountData) {
-        equal(accountData.email, 'testuser at testuser.com',
-          'Should forward incoming message data to the helper');
-        return Promise.reject(new Error('oops'));
-      },
-    },
-  });
-
-  let promiseSend = new Promise(resolve => {
-    channel._channel.send = (message, context) => {
-      resolve({ message, context });
-    };
-  });
-
-  channel._channelCallback(WEBCHANNEL_ID, mockMessage, mockSendingContext);
-
-  let { message, context } = yield promiseSend;
-
-  equal(context, mockSendingContext, 'Should forward the original context');
-  equal(message.command, 'fxaccounts:login',
-    'Should include the incoming command');
-  equal(message.messageId, '1234', 'Should include the message ID');
-  equal(message.data.error.message, 'Error: oops',
-    'Should convert the error message to a string');
-  notStrictEqual(message.data.error.stack, null,
-    'Should include the stack for JS error rejections');
-});
-
-add_test(function test_exception_reporting() {
-  let mockMessage = {
-    command: 'fxaccounts:sync_preferences',
-    messageId: '5678',
-    data: { entryPoint: 'fxa:verification_complete' }
-  };
-
-  let channel = new FxAccountsWebChannel({
-    channel_id: WEBCHANNEL_ID,
-    content_uri: URL_STRING,
-    helpers: {
-      openSyncPreferences(browser, entryPoint) {
-        equal(entryPoint, 'fxa:verification_complete',
-          'Should forward incoming message data to the helper');
-        throw new TypeError('splines not reticulated');
-      },
-    },
-  });
-
-  channel._channel.send = (message, context) => {
-    equal(context, mockSendingContext, 'Should forward the original context');
-    equal(message.command, 'fxaccounts:sync_preferences',
-      'Should include the incoming command');
-    equal(message.messageId, '5678', 'Should include the message ID');
-    equal(message.data.error.message, 'TypeError: splines not reticulated',
-      'Should convert the exception to a string');
-    notStrictEqual(message.data.error.stack, null,
-      'Should include the stack for JS exceptions');
-
-    run_next_test();
-  };
-
-  channel._channelCallback(WEBCHANNEL_ID, mockMessage, mockSendingContext);
-});
-
-add_test(function test_profile_image_change_message() {
-  var mockMessage = {
-    command: "profile:change",
-    data: { uid: "foo" }
-  };
-
-  makeObserver(ON_PROFILE_CHANGE_NOTIFICATION, function (subject, topic, data) {
-    do_check_eq(data, "foo");
-    run_next_test();
-  });
-
-  var channel = new FxAccountsWebChannel({
-    channel_id: WEBCHANNEL_ID,
-    content_uri: URL_STRING
-  });
-
-  channel._channelCallback(WEBCHANNEL_ID, mockMessage, mockSendingContext);
-});
-
-add_test(function test_login_message() {
-  let mockMessage = {
-    command: 'fxaccounts:login',
-    data: { email: 'testuser at testuser.com' }
-  };
-
-  let channel = new FxAccountsWebChannel({
-    channel_id: WEBCHANNEL_ID,
-    content_uri: URL_STRING,
-    helpers: {
-      login: function (accountData) {
-        do_check_eq(accountData.email, 'testuser at testuser.com');
-        run_next_test();
-        return Promise.resolve();
-      }
-    }
-  });
-
-  channel._channelCallback(WEBCHANNEL_ID, mockMessage, mockSendingContext);
-});
-
-add_test(function test_logout_message() {
-  let mockMessage = {
-    command: 'fxaccounts:logout',
-    data: { uid: "foo" }
-  };
-
-  let channel = new FxAccountsWebChannel({
-    channel_id: WEBCHANNEL_ID,
-    content_uri: URL_STRING,
-    helpers: {
-      logout: function (uid) {
-        do_check_eq(uid, 'foo');
-        run_next_test();
-        return Promise.resolve();
-      }
-    }
-  });
-
-  channel._channelCallback(WEBCHANNEL_ID, mockMessage, mockSendingContext);
-});
-
-add_test(function test_delete_message() {
-  let mockMessage = {
-    command: 'fxaccounts:delete',
-    data: { uid: "foo" }
-  };
-
-  let channel = new FxAccountsWebChannel({
-    channel_id: WEBCHANNEL_ID,
-    content_uri: URL_STRING,
-    helpers: {
-      logout: function (uid) {
-        do_check_eq(uid, 'foo');
-        run_next_test();
-        return Promise.resolve();
-      }
-    }
-  });
-
-  channel._channelCallback(WEBCHANNEL_ID, mockMessage, mockSendingContext);
-});
-
-add_test(function test_can_link_account_message() {
-  let mockMessage = {
-    command: 'fxaccounts:can_link_account',
-    data: { email: 'testuser at testuser.com' }
-  };
-
-  let channel = new FxAccountsWebChannel({
-    channel_id: WEBCHANNEL_ID,
-    content_uri: URL_STRING,
-    helpers: {
-      shouldAllowRelink: function (email) {
-        do_check_eq(email, 'testuser at testuser.com');
-        run_next_test();
-      }
-    }
-  });
-
-  channel._channelCallback(WEBCHANNEL_ID, mockMessage, mockSendingContext);
-});
-
-add_test(function test_sync_preferences_message() {
-  let mockMessage = {
-    command: 'fxaccounts:sync_preferences',
-    data: { entryPoint: 'fxa:verification_complete' }
-  };
-
-  let channel = new FxAccountsWebChannel({
-    channel_id: WEBCHANNEL_ID,
-    content_uri: URL_STRING,
-    helpers: {
-      openSyncPreferences: function (browser, entryPoint) {
-        do_check_eq(entryPoint, 'fxa:verification_complete');
-        do_check_eq(browser, mockSendingContext.browser);
-        run_next_test();
-      }
-    }
-  });
-
-  channel._channelCallback(WEBCHANNEL_ID, mockMessage, mockSendingContext);
-});
-
-add_test(function test_unrecognized_message() {
-  let mockMessage = {
-    command: 'fxaccounts:unrecognized',
-    data: {}
-  };
-
-  let channel = new FxAccountsWebChannel({
-    channel_id: WEBCHANNEL_ID,
-    content_uri: URL_STRING
-  });
-
-  // no error is expected.
-  channel._channelCallback(WEBCHANNEL_ID, mockMessage, mockSendingContext);
-  run_next_test();
-});
-
-
-add_test(function test_helpers_should_allow_relink_same_email() {
-  let helpers = new FxAccountsWebChannelHelpers();
-
-  helpers.setPreviousAccountNameHashPref('testuser at testuser.com');
-  do_check_true(helpers.shouldAllowRelink('testuser at testuser.com'));
-
-  run_next_test();
-});
-
-add_test(function test_helpers_should_allow_relink_different_email() {
-  let helpers = new FxAccountsWebChannelHelpers();
-
-  helpers.setPreviousAccountNameHashPref('testuser at testuser.com');
-
-  helpers._promptForRelink = (acctName) => {
-    return acctName === 'allowed_to_relink at testuser.com';
-  };
-
-  do_check_true(helpers.shouldAllowRelink('allowed_to_relink at testuser.com'));
-  do_check_false(helpers.shouldAllowRelink('not_allowed_to_relink at testuser.com'));
-
-  run_next_test();
-});
-
-add_task(function* test_helpers_login_without_customize_sync() {
-  let helpers = new FxAccountsWebChannelHelpers({
-    fxAccounts: {
-      setSignedInUser: function(accountData) {
-        return new Promise(resolve => {
-          // ensure fxAccounts is informed of the new user being signed in.
-          do_check_eq(accountData.email, 'testuser at testuser.com');
-
-          // verifiedCanLinkAccount should be stripped in the data.
-          do_check_false('verifiedCanLinkAccount' in accountData);
-
-          // the customizeSync pref should not update
-          do_check_false(helpers.getShowCustomizeSyncPref());
-
-          // previously signed in user preference is updated.
-          do_check_eq(helpers.getPreviousAccountNameHashPref(), helpers.sha256('testuser at testuser.com'));
-
-          resolve();
-        });
-      }
-    }
-  });
-
-  // the show customize sync pref should stay the same
-  helpers.setShowCustomizeSyncPref(false);
-
-  // ensure the previous account pref is overwritten.
-  helpers.setPreviousAccountNameHashPref('lastuser at testuser.com');
-
-  yield helpers.login({
-    email: 'testuser at testuser.com',
-    verifiedCanLinkAccount: true,
-    customizeSync: false
-  });
-});
-
-add_task(function* test_helpers_login_with_customize_sync() {
-  let helpers = new FxAccountsWebChannelHelpers({
-    fxAccounts: {
-      setSignedInUser: function(accountData) {
-        return new Promise(resolve => {
-          // ensure fxAccounts is informed of the new user being signed in.
-          do_check_eq(accountData.email, 'testuser at testuser.com');
-
-          // customizeSync should be stripped in the data.
-          do_check_false('customizeSync' in accountData);
-
-          // the customizeSync pref should not update
-          do_check_true(helpers.getShowCustomizeSyncPref());
-
-          resolve();
-        });
-      }
-    }
-  });
-
-  // the customize sync pref should be overwritten
-  helpers.setShowCustomizeSyncPref(false);
-
-  yield helpers.login({
-    email: 'testuser at testuser.com',
-    verifiedCanLinkAccount: true,
-    customizeSync: true
-  });
-});
-
-add_task(function* test_helpers_login_with_customize_sync_and_declined_engines() {
-  let helpers = new FxAccountsWebChannelHelpers({
-    fxAccounts: {
-      setSignedInUser: function(accountData) {
-        return new Promise(resolve => {
-          // ensure fxAccounts is informed of the new user being signed in.
-          do_check_eq(accountData.email, 'testuser at testuser.com');
-
-          // customizeSync should be stripped in the data.
-          do_check_false('customizeSync' in accountData);
-          do_check_false('declinedSyncEngines' in accountData);
-          do_check_eq(Services.prefs.getBoolPref("services.sync.engine.addons"), false);
-          do_check_eq(Services.prefs.getBoolPref("services.sync.engine.bookmarks"), true);
-          do_check_eq(Services.prefs.getBoolPref("services.sync.engine.history"), true);
-          do_check_eq(Services.prefs.getBoolPref("services.sync.engine.passwords"), true);
-          do_check_eq(Services.prefs.getBoolPref("services.sync.engine.prefs"), false);
-          do_check_eq(Services.prefs.getBoolPref("services.sync.engine.tabs"), true);
-
-          // the customizeSync pref should be disabled
-          do_check_false(helpers.getShowCustomizeSyncPref());
-
-          resolve();
-        });
-      }
-    }
-  });
-
-  // the customize sync pref should be overwritten
-  helpers.setShowCustomizeSyncPref(true);
-
-  do_check_eq(Services.prefs.getBoolPref("services.sync.engine.addons"), true);
-  do_check_eq(Services.prefs.getBoolPref("services.sync.engine.bookmarks"), true);
-  do_check_eq(Services.prefs.getBoolPref("services.sync.engine.history"), true);
-  do_check_eq(Services.prefs.getBoolPref("services.sync.engine.passwords"), true);
-  do_check_eq(Services.prefs.getBoolPref("services.sync.engine.prefs"), true);
-  do_check_eq(Services.prefs.getBoolPref("services.sync.engine.tabs"), true);
-  yield helpers.login({
-    email: 'testuser at testuser.com',
-    verifiedCanLinkAccount: true,
-    customizeSync: true,
-    declinedSyncEngines: ['addons', 'prefs']
-  });
-});
-
-add_test(function test_helpers_open_sync_preferences() {
-  let helpers = new FxAccountsWebChannelHelpers({
-    fxAccounts: {
-    }
-  });
-
-  let mockBrowser = {
-    loadURI(uri) {
-      do_check_eq(uri, "about:preferences?entrypoint=fxa%3Averification_complete#sync");
-      run_next_test();
-    }
-  };
-
-  helpers.openSyncPreferences(mockBrowser, "fxa:verification_complete");
-});
-
-add_task(function* test_helpers_change_password() {
-  let wasCalled = {
-    updateUserAccountData: false,
-    updateDeviceRegistration: false
-  };
-  let helpers = new FxAccountsWebChannelHelpers({
-    fxAccounts: {
-      updateUserAccountData(credentials) {
-        return new Promise(resolve => {
-          do_check_true(credentials.hasOwnProperty("email"));
-          do_check_true(credentials.hasOwnProperty("uid"));
-          do_check_true(credentials.hasOwnProperty("kA"));
-          do_check_true(credentials.hasOwnProperty("deviceId"));
-          do_check_null(credentials.deviceId);
-          // "foo" isn't a field known by storage, so should be dropped.
-          do_check_false(credentials.hasOwnProperty("foo"));
-          wasCalled.updateUserAccountData = true;
-
-          resolve();
-        });
-      },
-
-      updateDeviceRegistration() {
-        do_check_eq(arguments.length, 0);
-        wasCalled.updateDeviceRegistration = true;
-        return Promise.resolve()
-      }
-    }
-  });
-  yield helpers.changePassword({ email: "email", uid: "uid", kA: "kA", foo: "foo" });
-  do_check_true(wasCalled.updateUserAccountData);
-  do_check_true(wasCalled.updateDeviceRegistration);
-});
-
-add_task(function* test_helpers_change_password_with_error() {
-  let wasCalled = {
-    updateUserAccountData: false,
-    updateDeviceRegistration: false
-  };
-  let helpers = new FxAccountsWebChannelHelpers({
-    fxAccounts: {
-      updateUserAccountData() {
-        wasCalled.updateUserAccountData = true;
-        return Promise.reject();
-      },
-
-      updateDeviceRegistration() {
-        wasCalled.updateDeviceRegistration = true;
-        return Promise.resolve()
-      }
-    }
-  });
-  try {
-    yield helpers.changePassword({});
-    do_check_false('changePassword should have rejected');
-  } catch (_) {
-    do_check_true(wasCalled.updateUserAccountData);
-    do_check_false(wasCalled.updateDeviceRegistration);
-  }
-});
-
-function run_test() {
-  run_next_test();
-}
-
-function makeObserver(aObserveTopic, aObserveFunc) {
-  let callback = function (aSubject, aTopic, aData) {
-    log.debug("observed " + aTopic + " " + aData);
-    if (aTopic == aObserveTopic) {
-      removeMe();
-      aObserveFunc(aSubject, aTopic, aData);
-    }
-  };
-
-  function removeMe() {
-    log.debug("removing observer for " + aObserveTopic);
-    Services.obs.removeObserver(callback, aObserveTopic);
-  }
-
-  Services.obs.addObserver(callback, aObserveTopic, false);
-  return removeMe;
-}
-
-function validationHelper(params, expected) {
-  try {
-    new FxAccountsWebChannel(params);
-  } catch (e) {
-    if (typeof expected === 'string') {
-      return do_check_eq(e.toString(), expected);
-    } else {
-      return do_check_true(e.toString().match(expected));
-    }
-  }
-  throw new Error("Validation helper error");
-}
diff --git a/services/fxaccounts/tests/xpcshell/xpcshell.ini b/services/fxaccounts/tests/xpcshell/xpcshell.ini
deleted file mode 100644
index 56a3d29..0000000
--- a/services/fxaccounts/tests/xpcshell/xpcshell.ini
+++ /dev/null
@@ -1,23 +0,0 @@
-[DEFAULT]
-head = head.js ../../../common/tests/unit/head_helpers.js ../../../common/tests/unit/head_http.js
-tail =
-skip-if = (toolkit == 'android' || appname == 'thunderbird')
-support-files =
-  !/services/common/tests/unit/head_helpers.js
-  !/services/common/tests/unit/head_http.js
-
-[test_accounts.js]
-[test_accounts_device_registration.js]
-[test_client.js]
-[test_credentials.js]
-[test_loginmgr_storage.js]
-[test_oauth_client.js]
-[test_oauth_grant_client.js]
-[test_oauth_grant_client_server.js]
-[test_oauth_tokens.js]
-[test_oauth_token_storage.js]
-[test_profile_client.js]
-[test_push_service.js]
-[test_web_channel.js]
-[test_profile.js]
-[test_storage_manager.js]
diff --git a/services/moz.build b/services/moz.build
index 91f1e28..e98d152 100644
--- a/services/moz.build
+++ b/services/moz.build
@@ -9,8 +9,5 @@ DIRS += [
     'crypto',
 ]
 
-if CONFIG['MOZ_WIDGET_TOOLKIT'] != 'android':
-    DIRS += ['fxaccounts']
-
 if CONFIG['MOZ_SERVICES_SYNC']:
     DIRS += ['sync']
diff --git a/services/sync/modules/util.js b/services/sync/modules/util.js
index 12496d2..7fd5a79 100644
--- a/services/sync/modules/util.js
+++ b/services/sync/modules/util.js
@@ -19,13 +19,6 @@ Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
 Cu.import("resource://gre/modules/osfile.jsm", this);
 Cu.import("resource://gre/modules/Task.jsm", this);
 
-// FxAccountsCommon.js doesn't use a "namespace", so create one here.
-XPCOMUtils.defineLazyGetter(this, "FxAccountsCommon", function() {
-  let FxAccountsCommon = {};
-  Cu.import("resource://gre/modules/FxAccountsCommon.js", FxAccountsCommon);
-  return FxAccountsCommon;
-});
-
 /*
  * Utility functions
  */
@@ -599,9 +592,6 @@ this.Utils = {
    */
   getSyncCredentialsHosts: function() {
     let result = new Set(this.getSyncCredentialsHostsLegacy());
-    for (let host of this.getSyncCredentialsHostsFxA()) {
-      result.add(host);
-    }
     return result;
   },
 
@@ -613,36 +603,6 @@ this.Utils = {
     return new Set([PWDMGR_HOST]);
   },
 
-  /*
-   * Get the FxA identity hosts.
-   */
-  getSyncCredentialsHostsFxA: function() {
-    // This is somewhat expensive and the result static, so we cache the result.
-    if (this._syncCredentialsHostsFxA) {
-      return this._syncCredentialsHostsFxA;
-    }
-    let result = new Set();
-    // the FxA host
-    result.add(FxAccountsCommon.FXA_PWDMGR_HOST);
-    //
-    // The FxA hosts - these almost certainly all have the same hostname, but
-    // better safe than sorry...
-    for (let prefName of ["identity.fxaccounts.remote.force_auth.uri",
-                          "identity.fxaccounts.remote.signup.uri",
-                          "identity.fxaccounts.remote.signin.uri",
-                          "identity.fxaccounts.settings.uri"]) {
-      let prefVal;
-      try {
-        prefVal = Services.prefs.getCharPref(prefName);
-      } catch (_) {
-        continue;
-      }
-      let uri = Services.io.newURI(prefVal, null, null);
-      result.add(uri.prePath);
-    }
-    return this._syncCredentialsHostsFxA = result;
-  },
-
   getDefaultDeviceName() {
     // Generate a client name if we don't have a useful one yet
     let env = Cc["@mozilla.org/process/environment;1"]
diff --git a/services/sync/tests/unit/test_errorhandler.js b/services/sync/tests/unit/test_errorhandler.js
index c087acc..25d7900 100644
--- a/services/sync/tests/unit/test_errorhandler.js
+++ b/services/sync/tests/unit/test_errorhandler.js
@@ -486,8 +486,6 @@ add_identity_test(this, function test_shouldReportLoginFailureWithNoCluster() {
   do_check_false(errorHandler.shouldReportError());
 });
 
-// XXX - how to arrange for 'Service.identity.basicPassword = null;' in
-// an fxaccounts environment?
 add_task(function test_login_syncAndReportErrors_non_network_error() {
   // Test non-network errors are reported
   // when calling syncAndReportErrors
@@ -536,8 +534,6 @@ add_identity_test(this, function test_sync_syncAndReportErrors_non_network_error
   yield deferred.promise;
 });
 
-// XXX - how to arrange for 'Service.identity.basicPassword = null;' in
-// an fxaccounts environment?
 add_task(function test_login_syncAndReportErrors_prolonged_non_network_error() {
   // Test prolonged, non-network errors are
   // reported when calling syncAndReportErrors.
diff --git a/services/sync/tps/extensions/tps/resource/tps.jsm b/services/sync/tps/extensions/tps/resource/tps.jsm
index ca3e4d5..c94112a 100644
--- a/services/sync/tps/extensions/tps/resource/tps.jsm
+++ b/services/sync/tps/extensions/tps/resource/tps.jsm
@@ -74,9 +74,7 @@ const ACTIONS = [
   ACTION_VERIFY_NOT,
 ];
 
-const OBSERVER_TOPICS = ["fxaccounts:onlogin",
-                         "fxaccounts:onlogout",
-                         "private-browsing",
+const OBSERVER_TOPICS = ["private-browsing",
                          "quit-application-requested",
                          "sessionstore-windows-restored",
                          "weave:engine:start-tracking",
diff --git a/testing/profiles/prefs_general.js b/testing/profiles/prefs_general.js
index bf1534c..35680ca 100644
--- a/testing/profiles/prefs_general.js
+++ b/testing/profiles/prefs_general.js
@@ -258,21 +258,6 @@ user_pref('toolkit.telemetry.server', 'https://%(server)s/telemetry-dummy/');
 user_pref('toolkit.telemetry.test.pref1', true);
 user_pref('toolkit.telemetry.test.pref2', false);
 
-// We don't want to hit the real Firefox Accounts server for tests.  We don't
-// actually need a functioning FxA server, so just set it to something that
-// resolves and accepts requests, even if they all fail.
-user_pref('identity.fxaccounts.auth.uri', 'https://%(server)s/fxa-dummy/');
-
-// Ditto for all the other Firefox accounts URIs used for about:accounts et al.:
-user_pref("identity.fxaccounts.remote.signup.uri", "https://%(server)s/fxa-signup");
-user_pref("identity.fxaccounts.remote.force_auth.uri", "https://%(server)s/fxa-force-auth");
-user_pref("identity.fxaccounts.remote.signin.uri", "https://%(server)s/fxa-signin");
-user_pref("identity.fxaccounts.settings.uri", "https://%(server)s/fxa-settings");
-user_pref('identity.fxaccounts.remote.webchannel.uri', 'https://%(server)s/');
-
-// We don't want browser tests to perform FxA device registration.
-user_pref('identity.fxaccounts.skipDeviceRegistration', true);
-
 // Increase the APZ content response timeout in tests to 1 minute.
 // This is to accommodate the fact that test environments tends to be slower
 // than production environments (with the b2g emulator being the slowest of them
diff --git a/testing/runtimes/mochitest-browser-chrome-e10s.runtimes.json b/testing/runtimes/mochitest-browser-chrome-e10s.runtimes.json
index 211e98e..8525931 100644
--- a/testing/runtimes/mochitest-browser-chrome-e10s.runtimes.json
+++ b/testing/runtimes/mochitest-browser-chrome-e10s.runtimes.json
@@ -76,7 +76,6 @@
     "browser/base/content/test/general/browser_fullscreen-window-open.js": 4312, 
     "browser/base/content/test/general/browser_fxa_oauth.js": 5410, 
     "browser/base/content/test/general/browser_fxa_web_channel.js": 4727, 
-    "browser/base/content/test/general/browser_fxaccounts.js": 2909, 
     "browser/base/content/test/general/browser_getshortcutoruri.js": 3083, 
     "browser/base/content/test/general/browser_identity_UI.js": 20930, 
     "browser/base/content/test/general/browser_insecureLoginForms.js": 4482, 
diff --git a/testing/runtimes/mochitest-browser-chrome.runtimes.json b/testing/runtimes/mochitest-browser-chrome.runtimes.json
index 73b2437..73efc2b 100644
--- a/testing/runtimes/mochitest-browser-chrome.runtimes.json
+++ b/testing/runtimes/mochitest-browser-chrome.runtimes.json
@@ -82,7 +82,6 @@
     "browser/base/content/test/general/browser_fullscreen-window-open.js": 2830, 
     "browser/base/content/test/general/browser_fxa_oauth.js": 4120, 
     "browser/base/content/test/general/browser_fxa_web_channel.js": 3535, 
-    "browser/base/content/test/general/browser_fxaccounts.js": 3175, 
     "browser/base/content/test/general/browser_getshortcutoruri.js": 3344, 
     "browser/base/content/test/general/browser_identity_UI.js": 19308, 
     "browser/base/content/test/general/browser_insecureLoginForms.js": 3538, 
diff --git a/testing/talos/talos/config.py b/testing/talos/talos/config.py
index 59b6123..828e68a 100644
--- a/testing/talos/talos/config.py
+++ b/testing/talos/talos/config.py
@@ -153,7 +153,6 @@ DEFAULTS = dict(
         'browser.contentHandlers.types.3.uri': 'http://127.0.0.1/rss?url=%s',
         'browser.contentHandlers.types.4.uri': 'http://127.0.0.1/rss?url=%s',
         'browser.contentHandlers.types.5.uri': 'http://127.0.0.1/rss?url=%s',
-        'identity.fxaccounts.auth.uri': 'https://127.0.0.1/fxa-dummy/',
         'datareporting.healthreport.about.reportUrl':
             'http://127.0.0.1/abouthealthreport/',
         'datareporting.healthreport.documentServerURI':
@@ -176,7 +175,6 @@ DEFAULTS = dict(
         'devtools.debugger.remote-enabled': False,
         'devtools.theme': "light",
         'devtools.timeline.enabled': False,
-        'identity.fxaccounts.migrateToDevEdition': False,
         'media.libavcodec.allow-obsolete': True
     }
 )
diff --git a/toolkit/identity/tests/unit/head_identity.js b/toolkit/identity/tests/unit/head_identity.js
index a266e7a..c63261b 100644
--- a/toolkit/identity/tests/unit/head_identity.js
+++ b/toolkit/identity/tests/unit/head_identity.js
@@ -239,18 +239,10 @@ try {
 } catch (noPref) {}
 Services.prefs.setBoolPref("toolkit.identity.debug", true);
 
-// Switch on firefox accounts
-var initialPrefFXAValue = false;
-try {
-  initialPrefFXAValue = Services.prefs.getBoolPref("identity.fxaccounts.enabled");
-} catch (noPref) {}
-Services.prefs.setBoolPref("identity.fxaccounts.enabled", true);
-
 // after execution, restore prefs
 do_register_cleanup(function() {
   log("restoring prefs to their initial values");
   Services.prefs.setBoolPref("toolkit.identity.debug", initialPrefDebugValue);
-  Services.prefs.setBoolPref("identity.fxaccounts.enabled", initialPrefFXAValue);
 });
 
 
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/import-browserjs-globals.js b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/import-browserjs-globals.js
index 313af2d..00a48f3 100644
--- a/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/import-browserjs-globals.js
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/import-browserjs-globals.js
@@ -48,8 +48,7 @@ const SCRIPTS = [
   "browser/base/content/browser-tabsintitlebar.js",
   "browser/base/content/browser-thumbnails.js",
   "browser/base/content/browser-trackingprotection.js",
-  "browser/base/content/browser-data-submission-info-bar.js",
-  "browser/base/content/browser-fxaccounts.js"
+  "browser/base/content/browser-data-submission-info-bar.js"
 ];
 
 module.exports = function(context) {
diff --git a/tools/lint/eslint/modules.json b/tools/lint/eslint/modules.json
index 158ba5f..30b5d39 100644
--- a/tools/lint/eslint/modules.json
+++ b/tools/lint/eslint/modules.json
@@ -219,7 +219,7 @@
   "UpdateTelemetry.jsm": ["AUSTLMY"],
   "userapi.js": ["UserAPI10Client"],
   "util.js": ["getChromeWindow", "XPCOMUtils", "Services", "Utils", "Async", "Svc", "Str"],
-  "utils.js": ["applicationName", "assert", "Copy", "getBrowserObject", "getChromeWindow", "getWindows", "getWindowByTitle", "getWindowByType", "getWindowId", "getMethodInWindows", "getPreference", "saveDataURL", "setPreference", "sleep", "startTimer", "stopTimer", "takeScreenshot", "unwrapNode", "waitFor", "btoa", "encryptPayload", "isConfiguredWithLegacyIdentity", "ensureLegacyIdentityManager", "setBasicCredentials", "makeIdentityConfig", "makeFxAccountsInternalMock", "configureFxAccou [...]
+  "utils.js": ["applicationName", "assert", "Copy", "getBrowserObject", "getChromeWindow", "getWindows", "getWindowByTitle", "getWindowByType", "getWindowId", "getMethodInWindows", "getPreference", "saveDataURL", "setPreference", "sleep", "startTimer", "stopTimer", "takeScreenshot", "unwrapNode", "waitFor", "btoa", "encryptPayload", "isConfiguredWithLegacyIdentity", "ensureLegacyIdentityManager", "setBasicCredentials", "makeIdentityConfig", "configureIdentity", "SyncTestingInfrastructure [...]
   "Utils.jsm": ["Utils", "Logger", "PivotContext", "PrefCache", "SettingCache"],
   "VariablesView.jsm": ["VariablesView", "escapeHTML"],
   "VariablesViewController.jsm": ["VariablesViewController", "StackFrameUtils"],

--
Alioth's /home/x2go-admin/maintenancescripts/git/hooks/post-receive-email on /srv/git/code.x2go.org/pale-moon.git


More information about the x2go-commits mailing list