Bug 736393 - Don't abort on store failure. r=rnewman
authorNick Alexander <nalexander@mozilla.com>
Thu, 31 May 2012 12:21:08 -0700
changeset 95385 5e22cc18646d49b94b7d1add0f440f98f4f2d982
parent 95384 0a4417ac4889c92ca712d02f4e9f02f77df140fb
child 95387 c4c66d11293524e1a4463fbb6f65fa056bca858f
push id10097
push usernalexander@mozilla.com
push dateThu, 31 May 2012 20:52:28 +0000
treeherdermozilla-inbound@5e22cc18646d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersrnewman
bugs736393
milestone15.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 736393 - Don't abort on store failure. r=rnewman
mobile/android/base/android-sync-files.mk
mobile/android/base/sync/Server11PreviousPostFailedException.java
mobile/android/base/sync/Server11RecordPostFailedException.java
mobile/android/base/sync/middleware/Crypto5MiddlewareRepositorySession.java
mobile/android/base/sync/repositories/FetchFailedException.java
mobile/android/base/sync/repositories/Server11RepositorySession.java
mobile/android/base/sync/repositories/StoreFailedException.java
mobile/android/base/sync/repositories/android/AndroidBrowserBookmarksRepositorySession.java
mobile/android/base/sync/repositories/android/AndroidBrowserHistoryRepositorySession.java
mobile/android/base/sync/repositories/android/AndroidBrowserRepositorySession.java
mobile/android/base/sync/repositories/android/BookmarksDeletionManager.java
mobile/android/base/sync/repositories/android/FennecTabsRepository.java
mobile/android/base/sync/repositories/android/FormHistoryRepositorySession.java
mobile/android/base/sync/repositories/android/PasswordsRepositorySession.java
mobile/android/base/sync/repositories/delegates/DeferredRepositorySessionStoreDelegate.java
mobile/android/base/sync/repositories/delegates/RepositorySessionStoreDelegate.java
mobile/android/base/sync/stage/ServerSyncStage.java
mobile/android/base/sync/synchronizer/ConcurrentRecordConsumer.java
mobile/android/base/sync/synchronizer/RecordsChannel.java
mobile/android/base/sync/synchronizer/RecordsChannelDelegate.java
mobile/android/base/sync/synchronizer/ServerLocalSynchronizer.java
mobile/android/base/sync/synchronizer/ServerLocalSynchronizerSession.java
mobile/android/base/sync/synchronizer/Synchronizer.java
mobile/android/base/sync/synchronizer/SynchronizerDelegate.java
mobile/android/base/sync/synchronizer/SynchronizerSession.java
mobile/android/base/sync/synchronizer/SynchronizerSessionDelegate.java
mobile/android/sync/java-sources.mn
--- a/mobile/android/base/android-sync-files.mk
+++ b/mobile/android/base/android-sync-files.mk
@@ -1,14 +1,14 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 # These files are managed in the android-sync repo. Do not modify directly, or your changes will be lost.
-SYNC_JAVA_FILES := sync/AlreadySyncingException.java sync/CollectionKeys.java sync/CommandProcessor.java sync/CommandRunner.java sync/CredentialsSource.java sync/crypto/CryptoException.java sync/crypto/CryptoInfo.java sync/crypto/HKDF.java sync/crypto/HMACVerificationException.java sync/crypto/KeyBundle.java sync/crypto/MissingCryptoInputException.java sync/crypto/NoKeyBundleException.java sync/crypto/PersistedCrypto5Keys.java sync/CryptoRecord.java sync/DelayedWorkTracker.java sync/delegates/ClientsDataDelegate.java sync/delegates/FreshStartDelegate.java sync/delegates/GlobalSessionCallback.java sync/delegates/InfoCollectionsDelegate.java sync/delegates/KeyUploadDelegate.java sync/delegates/MetaGlobalDelegate.java sync/delegates/WipeServerDelegate.java sync/EngineSettings.java sync/ExtendedJSONObject.java sync/GlobalSession.java sync/HTTPFailureException.java sync/InfoCollections.java sync/jpake/BigIntegerHelper.java sync/jpake/Gx3OrGx4IsZeroOrOneException.java sync/jpake/IncorrectZkpException.java sync/jpake/JPakeClient.java sync/jpake/JPakeCrypto.java sync/jpake/JPakeJson.java sync/jpake/JPakeNoActivePairingException.java sync/jpake/JPakeNumGenerator.java sync/jpake/JPakeNumGeneratorRandom.java sync/jpake/JPakeParty.java sync/jpake/JPakeRequest.java sync/jpake/JPakeRequestDelegate.java sync/jpake/JPakeResponse.java sync/jpake/stage/CompleteStage.java sync/jpake/stage/ComputeFinalStage.java sync/jpake/stage/ComputeKeyVerificationStage.java sync/jpake/stage/ComputeStepOneStage.java sync/jpake/stage/ComputeStepTwoStage.java sync/jpake/stage/DecryptDataStage.java sync/jpake/stage/DeleteChannel.java sync/jpake/stage/GetChannelStage.java sync/jpake/stage/GetRequestStage.java sync/jpake/stage/JPakeStage.java sync/jpake/stage/PutRequestStage.java sync/jpake/stage/VerifyPairingStage.java sync/jpake/Zkp.java sync/KeyBundleProvider.java sync/Logger.java sync/MetaGlobal.java sync/MetaGlobalException.java sync/MetaGlobalMissingEnginesException.java sync/MetaGlobalNotSetException.java sync/middleware/Crypto5MiddlewareRepository.java sync/middleware/Crypto5MiddlewareRepositorySession.java sync/middleware/MiddlewareRepository.java sync/middleware/MiddlewareRepositorySession.java sync/net/BaseResource.java sync/net/CompletedEntity.java sync/net/ConnectionMonitorThread.java sync/net/HandleProgressException.java sync/net/HttpResponseObserver.java sync/net/Resource.java sync/net/ResourceDelegate.java sync/net/SyncResourceDelegate.java sync/net/SyncResponse.java sync/net/SyncStorageCollectionRequest.java sync/net/SyncStorageCollectionRequestDelegate.java sync/net/SyncStorageRecordRequest.java sync/net/SyncStorageRequest.java sync/net/SyncStorageRequestDelegate.java sync/net/SyncStorageRequestIncrementalDelegate.java sync/net/SyncStorageResponse.java sync/net/TLSSocketFactory.java sync/net/WBOCollectionRequestDelegate.java sync/net/WBORequestDelegate.java sync/NoCollectionKeysSetException.java sync/NodeAuthenticationException.java sync/NonArrayJSONException.java sync/NonObjectJSONException.java sync/NullClusterURLException.java sync/PersistedMetaGlobal.java sync/PrefsSource.java sync/receivers/UpgradeReceiver.java sync/repositories/android/AndroidBrowserBookmarksDataAccessor.java sync/repositories/android/AndroidBrowserBookmarksRepository.java sync/repositories/android/AndroidBrowserBookmarksRepositorySession.java sync/repositories/android/AndroidBrowserHistoryDataAccessor.java sync/repositories/android/AndroidBrowserHistoryDataExtender.java sync/repositories/android/AndroidBrowserHistoryRepository.java sync/repositories/android/AndroidBrowserHistoryRepositorySession.java sync/repositories/android/AndroidBrowserRepository.java sync/repositories/android/AndroidBrowserRepositoryDataAccessor.java sync/repositories/android/AndroidBrowserRepositorySession.java sync/repositories/android/BookmarksDeletionManager.java sync/repositories/android/BookmarksInsertionManager.java sync/repositories/android/BrowserContractHelpers.java sync/repositories/android/CachedSQLiteOpenHelper.java sync/repositories/android/ClientsDatabase.java sync/repositories/android/ClientsDatabaseAccessor.java sync/repositories/android/FennecControlHelper.java sync/repositories/android/FennecTabsRepository.java sync/repositories/android/FormHistoryRepositorySession.java sync/repositories/android/PasswordsRepositorySession.java sync/repositories/android/RepoUtils.java sync/repositories/BookmarkNeedsReparentingException.java sync/repositories/BookmarksRepository.java sync/repositories/ConstrainedServer11Repository.java sync/repositories/delegates/DeferrableRepositorySessionCreationDelegate.java sync/repositories/delegates/DeferredRepositorySessionBeginDelegate.java sync/repositories/delegates/DeferredRepositorySessionFetchRecordsDelegate.java sync/repositories/delegates/DeferredRepositorySessionFinishDelegate.java sync/repositories/delegates/DeferredRepositorySessionStoreDelegate.java sync/repositories/delegates/RepositorySessionBeginDelegate.java sync/repositories/delegates/RepositorySessionCleanDelegate.java sync/repositories/delegates/RepositorySessionCreationDelegate.java sync/repositories/delegates/RepositorySessionFetchRecordsDelegate.java sync/repositories/delegates/RepositorySessionFinishDelegate.java sync/repositories/delegates/RepositorySessionGuidsSinceDelegate.java sync/repositories/delegates/RepositorySessionStoreDelegate.java sync/repositories/delegates/RepositorySessionWipeDelegate.java sync/repositories/domain/BookmarkRecord.java sync/repositories/domain/BookmarkRecordFactory.java sync/repositories/domain/ClientRecord.java sync/repositories/domain/ClientRecordFactory.java sync/repositories/domain/FormHistoryRecord.java sync/repositories/domain/HistoryRecord.java sync/repositories/domain/HistoryRecordFactory.java sync/repositories/domain/PasswordRecord.java sync/repositories/domain/Record.java sync/repositories/domain/TabsRecord.java sync/repositories/domain/VersionConstants.java sync/repositories/HashSetStoreTracker.java sync/repositories/HistoryRepository.java sync/repositories/IdentityRecordFactory.java sync/repositories/InactiveSessionException.java sync/repositories/InvalidBookmarkTypeException.java sync/repositories/InvalidRequestException.java sync/repositories/InvalidSessionTransitionException.java sync/repositories/MultipleRecordsForGuidException.java sync/repositories/NoContentProviderException.java sync/repositories/NoGuidForIdException.java sync/repositories/NoStoreDelegateException.java sync/repositories/NullCursorException.java sync/repositories/ParentNotFoundException.java sync/repositories/ProfileDatabaseException.java sync/repositories/RecordFactory.java sync/repositories/RecordFilter.java sync/repositories/Repository.java sync/repositories/RepositorySession.java sync/repositories/RepositorySessionBundle.java sync/repositories/Server11Repository.java sync/repositories/Server11RepositorySession.java sync/repositories/StoreTracker.java sync/repositories/StoreTrackingRepositorySession.java sync/setup/activities/AccountActivity.java sync/setup/activities/ActivityUtils.java sync/setup/activities/SetupFailureActivity.java sync/setup/activities/SetupSuccessActivity.java sync/setup/activities/SetupSyncActivity.java sync/setup/Constants.java sync/setup/InvalidSyncKeyException.java sync/setup/SyncAccounts.java sync/setup/SyncAuthenticatorService.java sync/stage/AbstractNonRepositorySyncStage.java sync/stage/AndroidBrowserBookmarksServerSyncStage.java sync/stage/AndroidBrowserHistoryServerSyncStage.java sync/stage/CheckPreconditionsStage.java sync/stage/CompletedStage.java sync/stage/EnsureClusterURLStage.java sync/stage/EnsureCrypto5KeysStage.java sync/stage/FennecTabsServerSyncStage.java sync/stage/FetchInfoCollectionsStage.java sync/stage/FetchMetaGlobalStage.java sync/stage/FormHistoryServerSyncStage.java sync/stage/GlobalSyncStage.java sync/stage/NoSuchStageException.java sync/stage/NoSyncIDException.java sync/stage/PasswordsServerSyncStage.java sync/stage/ServerSyncStage.java sync/stage/SyncClientsEngineStage.java sync/stage/UploadMetaGlobalStage.java sync/StubActivity.java sync/syncadapter/SyncAdapter.java sync/syncadapter/SyncService.java sync/SyncConfiguration.java sync/SyncConfigurationException.java sync/SyncException.java sync/synchronizer/ConcurrentRecordConsumer.java sync/synchronizer/RecordConsumer.java sync/synchronizer/RecordsChannel.java sync/synchronizer/RecordsChannelDelegate.java sync/synchronizer/RecordsConsumerDelegate.java sync/synchronizer/SerialRecordConsumer.java sync/synchronizer/SessionNotBegunException.java sync/synchronizer/Synchronizer.java sync/synchronizer/SynchronizerDelegate.java sync/synchronizer/SynchronizerSession.java sync/synchronizer/SynchronizerSessionDelegate.java sync/synchronizer/UnbundleError.java sync/synchronizer/UnexpectedSessionException.java sync/SynchronizerConfiguration.java sync/ThreadPool.java sync/UnexpectedJSONException.java sync/UnknownSynchronizerConfigurationVersionException.java sync/Utils.java
+SYNC_JAVA_FILES := sync/AlreadySyncingException.java sync/CollectionKeys.java sync/CommandProcessor.java sync/CommandRunner.java sync/CredentialsSource.java sync/crypto/CryptoException.java sync/crypto/CryptoInfo.java sync/crypto/HKDF.java sync/crypto/HMACVerificationException.java sync/crypto/KeyBundle.java sync/crypto/MissingCryptoInputException.java sync/crypto/NoKeyBundleException.java sync/crypto/PersistedCrypto5Keys.java sync/CryptoRecord.java sync/DelayedWorkTracker.java sync/delegates/ClientsDataDelegate.java sync/delegates/FreshStartDelegate.java sync/delegates/GlobalSessionCallback.java sync/delegates/InfoCollectionsDelegate.java sync/delegates/KeyUploadDelegate.java sync/delegates/MetaGlobalDelegate.java sync/delegates/WipeServerDelegate.java sync/EngineSettings.java sync/ExtendedJSONObject.java sync/GlobalSession.java sync/HTTPFailureException.java sync/InfoCollections.java sync/jpake/BigIntegerHelper.java sync/jpake/Gx3OrGx4IsZeroOrOneException.java sync/jpake/IncorrectZkpException.java sync/jpake/JPakeClient.java sync/jpake/JPakeCrypto.java sync/jpake/JPakeJson.java sync/jpake/JPakeNoActivePairingException.java sync/jpake/JPakeNumGenerator.java sync/jpake/JPakeNumGeneratorRandom.java sync/jpake/JPakeParty.java sync/jpake/JPakeRequest.java sync/jpake/JPakeRequestDelegate.java sync/jpake/JPakeResponse.java sync/jpake/stage/CompleteStage.java sync/jpake/stage/ComputeFinalStage.java sync/jpake/stage/ComputeKeyVerificationStage.java sync/jpake/stage/ComputeStepOneStage.java sync/jpake/stage/ComputeStepTwoStage.java sync/jpake/stage/DecryptDataStage.java sync/jpake/stage/DeleteChannel.java sync/jpake/stage/GetChannelStage.java sync/jpake/stage/GetRequestStage.java sync/jpake/stage/JPakeStage.java sync/jpake/stage/PutRequestStage.java sync/jpake/stage/VerifyPairingStage.java sync/jpake/Zkp.java sync/KeyBundleProvider.java sync/Logger.java sync/MetaGlobal.java sync/MetaGlobalException.java sync/MetaGlobalMissingEnginesException.java sync/MetaGlobalNotSetException.java sync/middleware/Crypto5MiddlewareRepository.java sync/middleware/Crypto5MiddlewareRepositorySession.java sync/middleware/MiddlewareRepository.java sync/middleware/MiddlewareRepositorySession.java sync/net/BaseResource.java sync/net/CompletedEntity.java sync/net/ConnectionMonitorThread.java sync/net/HandleProgressException.java sync/net/HttpResponseObserver.java sync/net/Resource.java sync/net/ResourceDelegate.java sync/net/SyncResourceDelegate.java sync/net/SyncResponse.java sync/net/SyncStorageCollectionRequest.java sync/net/SyncStorageCollectionRequestDelegate.java sync/net/SyncStorageRecordRequest.java sync/net/SyncStorageRequest.java sync/net/SyncStorageRequestDelegate.java sync/net/SyncStorageRequestIncrementalDelegate.java sync/net/SyncStorageResponse.java sync/net/TLSSocketFactory.java sync/net/WBOCollectionRequestDelegate.java sync/net/WBORequestDelegate.java sync/NoCollectionKeysSetException.java sync/NodeAuthenticationException.java sync/NonArrayJSONException.java sync/NonObjectJSONException.java sync/NullClusterURLException.java sync/PersistedMetaGlobal.java sync/PrefsSource.java sync/receivers/UpgradeReceiver.java sync/repositories/android/AndroidBrowserBookmarksDataAccessor.java sync/repositories/android/AndroidBrowserBookmarksRepository.java sync/repositories/android/AndroidBrowserBookmarksRepositorySession.java sync/repositories/android/AndroidBrowserHistoryDataAccessor.java sync/repositories/android/AndroidBrowserHistoryDataExtender.java sync/repositories/android/AndroidBrowserHistoryRepository.java sync/repositories/android/AndroidBrowserHistoryRepositorySession.java sync/repositories/android/AndroidBrowserRepository.java sync/repositories/android/AndroidBrowserRepositoryDataAccessor.java sync/repositories/android/AndroidBrowserRepositorySession.java sync/repositories/android/BookmarksDeletionManager.java sync/repositories/android/BookmarksInsertionManager.java sync/repositories/android/BrowserContractHelpers.java sync/repositories/android/CachedSQLiteOpenHelper.java sync/repositories/android/ClientsDatabase.java sync/repositories/android/ClientsDatabaseAccessor.java sync/repositories/android/FennecControlHelper.java sync/repositories/android/FennecTabsRepository.java sync/repositories/android/FormHistoryRepositorySession.java sync/repositories/android/PasswordsRepositorySession.java sync/repositories/android/RepoUtils.java sync/repositories/BookmarkNeedsReparentingException.java sync/repositories/BookmarksRepository.java sync/repositories/ConstrainedServer11Repository.java sync/repositories/delegates/DeferrableRepositorySessionCreationDelegate.java sync/repositories/delegates/DeferredRepositorySessionBeginDelegate.java sync/repositories/delegates/DeferredRepositorySessionFetchRecordsDelegate.java sync/repositories/delegates/DeferredRepositorySessionFinishDelegate.java sync/repositories/delegates/DeferredRepositorySessionStoreDelegate.java sync/repositories/delegates/RepositorySessionBeginDelegate.java sync/repositories/delegates/RepositorySessionCleanDelegate.java sync/repositories/delegates/RepositorySessionCreationDelegate.java sync/repositories/delegates/RepositorySessionFetchRecordsDelegate.java sync/repositories/delegates/RepositorySessionFinishDelegate.java sync/repositories/delegates/RepositorySessionGuidsSinceDelegate.java sync/repositories/delegates/RepositorySessionStoreDelegate.java sync/repositories/delegates/RepositorySessionWipeDelegate.java sync/repositories/domain/BookmarkRecord.java sync/repositories/domain/BookmarkRecordFactory.java sync/repositories/domain/ClientRecord.java sync/repositories/domain/ClientRecordFactory.java sync/repositories/domain/FormHistoryRecord.java sync/repositories/domain/HistoryRecord.java sync/repositories/domain/HistoryRecordFactory.java sync/repositories/domain/PasswordRecord.java sync/repositories/domain/Record.java sync/repositories/domain/TabsRecord.java sync/repositories/domain/VersionConstants.java sync/repositories/FetchFailedException.java sync/repositories/HashSetStoreTracker.java sync/repositories/HistoryRepository.java sync/repositories/IdentityRecordFactory.java sync/repositories/InactiveSessionException.java sync/repositories/InvalidBookmarkTypeException.java sync/repositories/InvalidRequestException.java sync/repositories/InvalidSessionTransitionException.java sync/repositories/MultipleRecordsForGuidException.java sync/repositories/NoContentProviderException.java sync/repositories/NoGuidForIdException.java sync/repositories/NoStoreDelegateException.java sync/repositories/NullCursorException.java sync/repositories/ParentNotFoundException.java sync/repositories/ProfileDatabaseException.java sync/repositories/RecordFactory.java sync/repositories/RecordFilter.java sync/repositories/Repository.java sync/repositories/RepositorySession.java sync/repositories/RepositorySessionBundle.java sync/repositories/Server11Repository.java sync/repositories/Server11RepositorySession.java sync/repositories/StoreFailedException.java sync/repositories/StoreTracker.java sync/repositories/StoreTrackingRepositorySession.java sync/Server11PreviousPostFailedException.java sync/Server11RecordPostFailedException.java sync/setup/activities/AccountActivity.java sync/setup/activities/ActivityUtils.java sync/setup/activities/SetupFailureActivity.java sync/setup/activities/SetupSuccessActivity.java sync/setup/activities/SetupSyncActivity.java sync/setup/Constants.java sync/setup/InvalidSyncKeyException.java sync/setup/SyncAccounts.java sync/setup/SyncAuthenticatorService.java sync/stage/AbstractNonRepositorySyncStage.java sync/stage/AndroidBrowserBookmarksServerSyncStage.java sync/stage/AndroidBrowserHistoryServerSyncStage.java sync/stage/CheckPreconditionsStage.java sync/stage/CompletedStage.java sync/stage/EnsureClusterURLStage.java sync/stage/EnsureCrypto5KeysStage.java sync/stage/FennecTabsServerSyncStage.java sync/stage/FetchInfoCollectionsStage.java sync/stage/FetchMetaGlobalStage.java sync/stage/FormHistoryServerSyncStage.java sync/stage/GlobalSyncStage.java sync/stage/NoSuchStageException.java sync/stage/NoSyncIDException.java sync/stage/PasswordsServerSyncStage.java sync/stage/ServerSyncStage.java sync/stage/SyncClientsEngineStage.java sync/stage/UploadMetaGlobalStage.java sync/StubActivity.java sync/syncadapter/SyncAdapter.java sync/syncadapter/SyncService.java sync/SyncConfiguration.java sync/SyncConfigurationException.java sync/SyncException.java sync/synchronizer/ConcurrentRecordConsumer.java sync/synchronizer/RecordConsumer.java sync/synchronizer/RecordsChannel.java sync/synchronizer/RecordsChannelDelegate.java sync/synchronizer/RecordsConsumerDelegate.java sync/synchronizer/SerialRecordConsumer.java sync/synchronizer/ServerLocalSynchronizer.java sync/synchronizer/ServerLocalSynchronizerSession.java sync/synchronizer/SessionNotBegunException.java sync/synchronizer/Synchronizer.java sync/synchronizer/SynchronizerDelegate.java sync/synchronizer/SynchronizerSession.java sync/synchronizer/SynchronizerSessionDelegate.java sync/synchronizer/UnbundleError.java sync/synchronizer/UnexpectedSessionException.java sync/SynchronizerConfiguration.java sync/ThreadPool.java sync/UnexpectedJSONException.java sync/UnknownSynchronizerConfigurationVersionException.java sync/Utils.java
 SYNC_PP_JAVA_FILES := sync/GlobalConstants.java
 SYNC_THIRDPARTY_JAVA_FILES := httpclientandroidlib/androidextra/HttpClientAndroidLog.java httpclientandroidlib/annotation/GuardedBy.java httpclientandroidlib/annotation/Immutable.java httpclientandroidlib/annotation/NotThreadSafe.java httpclientandroidlib/annotation/ThreadSafe.java httpclientandroidlib/auth/AUTH.java httpclientandroidlib/auth/AuthenticationException.java httpclientandroidlib/auth/AuthScheme.java httpclientandroidlib/auth/AuthSchemeFactory.java httpclientandroidlib/auth/AuthSchemeRegistry.java httpclientandroidlib/auth/AuthScope.java httpclientandroidlib/auth/AuthState.java httpclientandroidlib/auth/BasicUserPrincipal.java httpclientandroidlib/auth/ContextAwareAuthScheme.java httpclientandroidlib/auth/Credentials.java httpclientandroidlib/auth/InvalidCredentialsException.java httpclientandroidlib/auth/MalformedChallengeException.java httpclientandroidlib/auth/NTCredentials.java httpclientandroidlib/auth/NTUserPrincipal.java httpclientandroidlib/auth/params/AuthParamBean.java httpclientandroidlib/auth/params/AuthParams.java httpclientandroidlib/auth/params/AuthPNames.java httpclientandroidlib/auth/UsernamePasswordCredentials.java httpclientandroidlib/client/AuthCache.java httpclientandroidlib/client/AuthenticationHandler.java httpclientandroidlib/client/CircularRedirectException.java httpclientandroidlib/client/ClientProtocolException.java httpclientandroidlib/client/CookieStore.java httpclientandroidlib/client/CredentialsProvider.java httpclientandroidlib/client/entity/DecompressingEntity.java httpclientandroidlib/client/entity/DeflateDecompressingEntity.java httpclientandroidlib/client/entity/GzipDecompressingEntity.java httpclientandroidlib/client/entity/UrlEncodedFormEntity.java httpclientandroidlib/client/HttpClient.java httpclientandroidlib/client/HttpRequestRetryHandler.java httpclientandroidlib/client/HttpResponseException.java httpclientandroidlib/client/methods/AbortableHttpRequest.java httpclientandroidlib/client/methods/HttpDelete.java httpclientandroidlib/client/methods/HttpEntityEnclosingRequestBase.java httpclientandroidlib/client/methods/HttpGet.java httpclientandroidlib/client/methods/HttpHead.java httpclientandroidlib/client/methods/HttpOptions.java httpclientandroidlib/client/methods/HttpPost.java httpclientandroidlib/client/methods/HttpPut.java httpclientandroidlib/client/methods/HttpRequestBase.java httpclientandroidlib/client/methods/HttpTrace.java httpclientandroidlib/client/methods/HttpUriRequest.java httpclientandroidlib/client/NonRepeatableRequestException.java httpclientandroidlib/client/params/AllClientPNames.java httpclientandroidlib/client/params/AuthPolicy.java httpclientandroidlib/client/params/ClientParamBean.java httpclientandroidlib/client/params/ClientPNames.java httpclientandroidlib/client/params/CookiePolicy.java httpclientandroidlib/client/params/HttpClientParams.java httpclientandroidlib/client/protocol/ClientContext.java httpclientandroidlib/client/protocol/ClientContextConfigurer.java httpclientandroidlib/client/protocol/RequestAcceptEncoding.java httpclientandroidlib/client/protocol/RequestAddCookies.java httpclientandroidlib/client/protocol/RequestAuthCache.java httpclientandroidlib/client/protocol/RequestClientConnControl.java httpclientandroidlib/client/protocol/RequestDefaultHeaders.java httpclientandroidlib/client/protocol/RequestProxyAuthentication.java httpclientandroidlib/client/protocol/RequestTargetAuthentication.java httpclientandroidlib/client/protocol/ResponseAuthCache.java httpclientandroidlib/client/protocol/ResponseContentEncoding.java httpclientandroidlib/client/protocol/ResponseProcessCookies.java httpclientandroidlib/client/RedirectException.java httpclientandroidlib/client/RedirectHandler.java httpclientandroidlib/client/RedirectStrategy.java httpclientandroidlib/client/RequestDirector.java httpclientandroidlib/client/ResponseHandler.java httpclientandroidlib/client/UserTokenHandler.java httpclientandroidlib/client/utils/CloneUtils.java httpclientandroidlib/client/utils/Idn.java httpclientandroidlib/client/utils/JdkIdn.java httpclientandroidlib/client/utils/Punycode.java httpclientandroidlib/client/utils/Rfc3492Idn.java httpclientandroidlib/client/utils/URIUtils.java httpclientandroidlib/client/utils/URLEncodedUtils.java httpclientandroidlib/conn/BasicEofSensorWatcher.java httpclientandroidlib/conn/BasicManagedEntity.java httpclientandroidlib/conn/ClientConnectionManager.java httpclientandroidlib/conn/ClientConnectionManagerFactory.java httpclientandroidlib/conn/ClientConnectionOperator.java httpclientandroidlib/conn/ClientConnectionRequest.java httpclientandroidlib/conn/ConnectionKeepAliveStrategy.java httpclientandroidlib/conn/ConnectionPoolTimeoutException.java httpclientandroidlib/conn/ConnectionReleaseTrigger.java httpclientandroidlib/conn/ConnectTimeoutException.java httpclientandroidlib/conn/EofSensorInputStream.java httpclientandroidlib/conn/EofSensorWatcher.java httpclientandroidlib/conn/HttpHostConnectException.java httpclientandroidlib/conn/HttpRoutedConnection.java httpclientandroidlib/conn/ManagedClientConnection.java httpclientandroidlib/conn/MultihomePlainSocketFactory.java httpclientandroidlib/conn/OperatedClientConnection.java httpclientandroidlib/conn/params/ConnConnectionParamBean.java httpclientandroidlib/conn/params/ConnConnectionPNames.java httpclientandroidlib/conn/params/ConnManagerParamBean.java httpclientandroidlib/conn/params/ConnManagerParams.java httpclientandroidlib/conn/params/ConnManagerPNames.java httpclientandroidlib/conn/params/ConnPerRoute.java httpclientandroidlib/conn/params/ConnPerRouteBean.java httpclientandroidlib/conn/params/ConnRouteParamBean.java httpclientandroidlib/conn/params/ConnRouteParams.java httpclientandroidlib/conn/params/ConnRoutePNames.java httpclientandroidlib/conn/routing/BasicRouteDirector.java httpclientandroidlib/conn/routing/HttpRoute.java httpclientandroidlib/conn/routing/HttpRouteDirector.java httpclientandroidlib/conn/routing/HttpRoutePlanner.java httpclientandroidlib/conn/routing/RouteInfo.java httpclientandroidlib/conn/routing/RouteTracker.java httpclientandroidlib/conn/scheme/HostNameResolver.java httpclientandroidlib/conn/scheme/LayeredSchemeSocketFactory.java httpclientandroidlib/conn/scheme/LayeredSchemeSocketFactoryAdaptor.java httpclientandroidlib/conn/scheme/LayeredSocketFactory.java httpclientandroidlib/conn/scheme/LayeredSocketFactoryAdaptor.java httpclientandroidlib/conn/scheme/PlainSocketFactory.java httpclientandroidlib/conn/scheme/Scheme.java httpclientandroidlib/conn/scheme/SchemeRegistry.java httpclientandroidlib/conn/scheme/SchemeSocketFactory.java httpclientandroidlib/conn/scheme/SchemeSocketFactoryAdaptor.java httpclientandroidlib/conn/scheme/SocketFactory.java httpclientandroidlib/conn/scheme/SocketFactoryAdaptor.java httpclientandroidlib/conn/ssl/AbstractVerifier.java httpclientandroidlib/conn/ssl/AllowAllHostnameVerifier.java httpclientandroidlib/conn/ssl/BrowserCompatHostnameVerifier.java httpclientandroidlib/conn/ssl/SSLSocketFactory.java httpclientandroidlib/conn/ssl/StrictHostnameVerifier.java httpclientandroidlib/conn/ssl/TrustManagerDecorator.java httpclientandroidlib/conn/ssl/TrustSelfSignedStrategy.java httpclientandroidlib/conn/ssl/TrustStrategy.java httpclientandroidlib/conn/ssl/X509HostnameVerifier.java httpclientandroidlib/conn/util/InetAddressUtils.java httpclientandroidlib/ConnectionClosedException.java httpclientandroidlib/ConnectionReuseStrategy.java httpclientandroidlib/cookie/ClientCookie.java httpclientandroidlib/cookie/Cookie.java httpclientandroidlib/cookie/CookieAttributeHandler.java httpclientandroidlib/cookie/CookieIdentityComparator.java httpclientandroidlib/cookie/CookieOrigin.java httpclientandroidlib/cookie/CookiePathComparator.java httpclientandroidlib/cookie/CookieRestrictionViolationException.java httpclientandroidlib/cookie/CookieSpec.java httpclientandroidlib/cookie/CookieSpecFactory.java httpclientandroidlib/cookie/CookieSpecRegistry.java httpclientandroidlib/cookie/MalformedCookieException.java httpclientandroidlib/cookie/params/CookieSpecParamBean.java httpclientandroidlib/cookie/params/CookieSpecPNames.java httpclientandroidlib/cookie/SetCookie.java httpclientandroidlib/cookie/SetCookie2.java httpclientandroidlib/cookie/SM.java httpclientandroidlib/entity/AbstractHttpEntity.java httpclientandroidlib/entity/BasicHttpEntity.java httpclientandroidlib/entity/BufferedHttpEntity.java httpclientandroidlib/entity/ByteArrayEntity.java httpclientandroidlib/entity/ContentLengthStrategy.java httpclientandroidlib/entity/ContentProducer.java httpclientandroidlib/entity/EntityTemplate.java httpclientandroidlib/entity/FileEntity.java httpclientandroidlib/entity/HttpEntityWrapper.java httpclientandroidlib/entity/InputStreamEntity.java httpclientandroidlib/entity/SerializableEntity.java httpclientandroidlib/entity/StringEntity.java httpclientandroidlib/FormattedHeader.java httpclientandroidlib/Header.java httpclientandroidlib/HeaderElement.java httpclientandroidlib/HeaderElementIterator.java httpclientandroidlib/HeaderIterator.java httpclientandroidlib/HttpClientConnection.java httpclientandroidlib/HttpConnection.java httpclientandroidlib/HttpConnectionMetrics.java httpclientandroidlib/HttpEntity.java httpclientandroidlib/HttpEntityEnclosingRequest.java httpclientandroidlib/HttpException.java httpclientandroidlib/HttpHeaders.java httpclientandroidlib/HttpHost.java httpclientandroidlib/HttpInetConnection.java httpclientandroidlib/HttpMessage.java httpclientandroidlib/HttpRequest.java httpclientandroidlib/HttpRequestFactory.java httpclientandroidlib/HttpRequestInterceptor.java httpclientandroidlib/HttpResponse.java httpclientandroidlib/HttpResponseFactory.java httpclientandroidlib/HttpResponseInterceptor.java httpclientandroidlib/HttpServerConnection.java httpclientandroidlib/HttpStatus.java httpclientandroidlib/HttpVersion.java httpclientandroidlib/impl/AbstractHttpClientConnection.java httpclientandroidlib/impl/AbstractHttpServerConnection.java httpclientandroidlib/impl/auth/AuthSchemeBase.java httpclientandroidlib/impl/auth/BasicScheme.java httpclientandroidlib/impl/auth/BasicSchemeFactory.java httpclientandroidlib/impl/auth/DigestScheme.java httpclientandroidlib/impl/auth/DigestSchemeFactory.java httpclientandroidlib/impl/auth/NTLMEngine.java httpclientandroidlib/impl/auth/NTLMEngineException.java httpclientandroidlib/impl/auth/NTLMEngineImpl.java httpclientandroidlib/impl/auth/NTLMScheme.java httpclientandroidlib/impl/auth/NTLMSchemeFactory.java httpclientandroidlib/impl/auth/RFC2617Scheme.java httpclientandroidlib/impl/auth/SpnegoTokenGenerator.java httpclientandroidlib/impl/auth/UnsupportedDigestAlgorithmException.java httpclientandroidlib/impl/client/AbstractAuthenticationHandler.java httpclientandroidlib/impl/client/AbstractHttpClient.java httpclientandroidlib/impl/client/BasicAuthCache.java httpclientandroidlib/impl/client/BasicCookieStore.java httpclientandroidlib/impl/client/BasicCredentialsProvider.java httpclientandroidlib/impl/client/BasicResponseHandler.java httpclientandroidlib/impl/client/ClientParamsStack.java httpclientandroidlib/impl/client/ContentEncodingHttpClient.java httpclientandroidlib/impl/client/DefaultConnectionKeepAliveStrategy.java httpclientandroidlib/impl/client/DefaultHttpClient.java httpclientandroidlib/impl/client/DefaultHttpRequestRetryHandler.java httpclientandroidlib/impl/client/DefaultProxyAuthenticationHandler.java httpclientandroidlib/impl/client/DefaultRedirectHandler.java httpclientandroidlib/impl/client/DefaultRedirectStrategy.java httpclientandroidlib/impl/client/DefaultRedirectStrategyAdaptor.java httpclientandroidlib/impl/client/DefaultRequestDirector.java httpclientandroidlib/impl/client/DefaultTargetAuthenticationHandler.java httpclientandroidlib/impl/client/DefaultUserTokenHandler.java httpclientandroidlib/impl/client/EntityEnclosingRequestWrapper.java httpclientandroidlib/impl/client/RedirectLocations.java httpclientandroidlib/impl/client/RequestWrapper.java httpclientandroidlib/impl/client/RoutedRequest.java httpclientandroidlib/impl/client/TunnelRefusedException.java httpclientandroidlib/impl/conn/AbstractClientConnAdapter.java httpclientandroidlib/impl/conn/AbstractPooledConnAdapter.java httpclientandroidlib/impl/conn/AbstractPoolEntry.java httpclientandroidlib/impl/conn/ConnectionShutdownException.java httpclientandroidlib/impl/conn/DefaultClientConnection.java httpclientandroidlib/impl/conn/DefaultClientConnectionOperator.java httpclientandroidlib/impl/conn/DefaultHttpRoutePlanner.java httpclientandroidlib/impl/conn/DefaultResponseParser.java httpclientandroidlib/impl/conn/HttpInetSocketAddress.java httpclientandroidlib/impl/conn/IdleConnectionHandler.java httpclientandroidlib/impl/conn/LoggingSessionInputBuffer.java httpclientandroidlib/impl/conn/LoggingSessionOutputBuffer.java httpclientandroidlib/impl/conn/ProxySelectorRoutePlanner.java httpclientandroidlib/impl/conn/SchemeRegistryFactory.java httpclientandroidlib/impl/conn/SingleClientConnManager.java httpclientandroidlib/impl/conn/tsccm/AbstractConnPool.java httpclientandroidlib/impl/conn/tsccm/BasicPooledConnAdapter.java httpclientandroidlib/impl/conn/tsccm/BasicPoolEntry.java httpclientandroidlib/impl/conn/tsccm/BasicPoolEntryRef.java httpclientandroidlib/impl/conn/tsccm/ConnPoolByRoute.java httpclientandroidlib/impl/conn/tsccm/PoolEntryRequest.java httpclientandroidlib/impl/conn/tsccm/RefQueueHandler.java httpclientandroidlib/impl/conn/tsccm/RefQueueWorker.java httpclientandroidlib/impl/conn/tsccm/RouteSpecificPool.java httpclientandroidlib/impl/conn/tsccm/ThreadSafeClientConnManager.java httpclientandroidlib/impl/conn/tsccm/WaitingThread.java httpclientandroidlib/impl/conn/tsccm/WaitingThreadAborter.java httpclientandroidlib/impl/conn/Wire.java httpclientandroidlib/impl/cookie/AbstractCookieAttributeHandler.java httpclientandroidlib/impl/cookie/AbstractCookieSpec.java httpclientandroidlib/impl/cookie/BasicClientCookie.java httpclientandroidlib/impl/cookie/BasicClientCookie2.java httpclientandroidlib/impl/cookie/BasicCommentHandler.java httpclientandroidlib/impl/cookie/BasicDomainHandler.java httpclientandroidlib/impl/cookie/BasicExpiresHandler.java httpclientandroidlib/impl/cookie/BasicMaxAgeHandler.java httpclientandroidlib/impl/cookie/BasicPathHandler.java httpclientandroidlib/impl/cookie/BasicSecureHandler.java httpclientandroidlib/impl/cookie/BestMatchSpec.java httpclientandroidlib/impl/cookie/BestMatchSpecFactory.java httpclientandroidlib/impl/cookie/BrowserCompatSpec.java httpclientandroidlib/impl/cookie/BrowserCompatSpecFactory.java httpclientandroidlib/impl/cookie/CookieSpecBase.java httpclientandroidlib/impl/cookie/DateParseException.java httpclientandroidlib/impl/cookie/DateUtils.java httpclientandroidlib/impl/cookie/IgnoreSpec.java httpclientandroidlib/impl/cookie/IgnoreSpecFactory.java httpclientandroidlib/impl/cookie/NetscapeDomainHandler.java httpclientandroidlib/impl/cookie/NetscapeDraftHeaderParser.java httpclientandroidlib/impl/cookie/NetscapeDraftSpec.java httpclientandroidlib/impl/cookie/NetscapeDraftSpecFactory.java httpclientandroidlib/impl/cookie/PublicSuffixFilter.java httpclientandroidlib/impl/cookie/PublicSuffixListParser.java httpclientandroidlib/impl/cookie/RFC2109DomainHandler.java httpclientandroidlib/impl/cookie/RFC2109Spec.java httpclientandroidlib/impl/cookie/RFC2109SpecFactory.java httpclientandroidlib/impl/cookie/RFC2109VersionHandler.java httpclientandroidlib/impl/cookie/RFC2965CommentUrlAttributeHandler.java httpclientandroidlib/impl/cookie/RFC2965DiscardAttributeHandler.java httpclientandroidlib/impl/cookie/RFC2965DomainAttributeHandler.java httpclientandroidlib/impl/cookie/RFC2965PortAttributeHandler.java httpclientandroidlib/impl/cookie/RFC2965Spec.java httpclientandroidlib/impl/cookie/RFC2965SpecFactory.java httpclientandroidlib/impl/cookie/RFC2965VersionAttributeHandler.java httpclientandroidlib/impl/DefaultConnectionReuseStrategy.java httpclientandroidlib/impl/DefaultHttpClientConnection.java httpclientandroidlib/impl/DefaultHttpRequestFactory.java httpclientandroidlib/impl/DefaultHttpResponseFactory.java httpclientandroidlib/impl/DefaultHttpServerConnection.java httpclientandroidlib/impl/EnglishReasonPhraseCatalog.java httpclientandroidlib/impl/entity/EntityDeserializer.java httpclientandroidlib/impl/entity/EntitySerializer.java httpclientandroidlib/impl/entity/LaxContentLengthStrategy.java httpclientandroidlib/impl/entity/StrictContentLengthStrategy.java httpclientandroidlib/impl/HttpConnectionMetricsImpl.java httpclientandroidlib/impl/io/AbstractMessageParser.java httpclientandroidlib/impl/io/AbstractMessageWriter.java httpclientandroidlib/impl/io/AbstractSessionInputBuffer.java httpclientandroidlib/impl/io/AbstractSessionOutputBuffer.java httpclientandroidlib/impl/io/ChunkedInputStream.java httpclientandroidlib/impl/io/ChunkedOutputStream.java httpclientandroidlib/impl/io/ContentLengthInputStream.java httpclientandroidlib/impl/io/ContentLengthOutputStream.java httpclientandroidlib/impl/io/HttpRequestParser.java httpclientandroidlib/impl/io/HttpRequestWriter.java httpclientandroidlib/impl/io/HttpResponseParser.java httpclientandroidlib/impl/io/HttpResponseWriter.java httpclientandroidlib/impl/io/HttpTransportMetricsImpl.java httpclientandroidlib/impl/io/IdentityInputStream.java httpclientandroidlib/impl/io/IdentityOutputStream.java httpclientandroidlib/impl/io/SocketInputBuffer.java httpclientandroidlib/impl/io/SocketOutputBuffer.java httpclientandroidlib/impl/NoConnectionReuseStrategy.java httpclientandroidlib/impl/SocketHttpClientConnection.java httpclientandroidlib/impl/SocketHttpServerConnection.java httpclientandroidlib/io/BufferInfo.java httpclientandroidlib/io/EofSensor.java httpclientandroidlib/io/HttpMessageParser.java httpclientandroidlib/io/HttpMessageWriter.java httpclientandroidlib/io/HttpTransportMetrics.java httpclientandroidlib/io/SessionInputBuffer.java httpclientandroidlib/io/SessionOutputBuffer.java httpclientandroidlib/MalformedChunkCodingException.java httpclientandroidlib/message/AbstractHttpMessage.java httpclientandroidlib/message/BasicHeader.java httpclientandroidlib/message/BasicHeaderElement.java httpclientandroidlib/message/BasicHeaderElementIterator.java httpclientandroidlib/message/BasicHeaderIterator.java httpclientandroidlib/message/BasicHeaderValueFormatter.java httpclientandroidlib/message/BasicHeaderValueParser.java httpclientandroidlib/message/BasicHttpEntityEnclosingRequest.java httpclientandroidlib/message/BasicHttpRequest.java httpclientandroidlib/message/BasicHttpResponse.java httpclientandroidlib/message/BasicLineFormatter.java httpclientandroidlib/message/BasicLineParser.java httpclientandroidlib/message/BasicListHeaderIterator.java httpclientandroidlib/message/BasicNameValuePair.java httpclientandroidlib/message/BasicRequestLine.java httpclientandroidlib/message/BasicStatusLine.java httpclientandroidlib/message/BasicTokenIterator.java httpclientandroidlib/message/BufferedHeader.java httpclientandroidlib/message/HeaderGroup.java httpclientandroidlib/message/HeaderValueFormatter.java httpclientandroidlib/message/HeaderValueParser.java httpclientandroidlib/message/LineFormatter.java httpclientandroidlib/message/LineParser.java httpclientandroidlib/message/ParserCursor.java httpclientandroidlib/MethodNotSupportedException.java httpclientandroidlib/NameValuePair.java httpclientandroidlib/NoHttpResponseException.java httpclientandroidlib/params/AbstractHttpParams.java httpclientandroidlib/params/BasicHttpParams.java httpclientandroidlib/params/CoreConnectionPNames.java httpclientandroidlib/params/CoreProtocolPNames.java httpclientandroidlib/params/DefaultedHttpParams.java httpclientandroidlib/params/HttpAbstractParamBean.java httpclientandroidlib/params/HttpConnectionParamBean.java httpclientandroidlib/params/HttpConnectionParams.java httpclientandroidlib/params/HttpParams.java httpclientandroidlib/params/HttpProtocolParamBean.java httpclientandroidlib/params/HttpProtocolParams.java httpclientandroidlib/params/SyncBasicHttpParams.java httpclientandroidlib/ParseException.java httpclientandroidlib/protocol/BasicHttpContext.java httpclientandroidlib/protocol/BasicHttpProcessor.java httpclientandroidlib/protocol/DefaultedHttpContext.java httpclientandroidlib/protocol/ExecutionContext.java httpclientandroidlib/protocol/HTTP.java httpclientandroidlib/protocol/HttpContext.java httpclientandroidlib/protocol/HttpDateGenerator.java httpclientandroidlib/protocol/HttpExpectationVerifier.java httpclientandroidlib/protocol/HttpProcessor.java httpclientandroidlib/protocol/HttpRequestExecutor.java httpclientandroidlib/protocol/HttpRequestHandler.java httpclientandroidlib/protocol/HttpRequestHandlerRegistry.java httpclientandroidlib/protocol/HttpRequestHandlerResolver.java httpclientandroidlib/protocol/HttpRequestInterceptorList.java httpclientandroidlib/protocol/HttpResponseInterceptorList.java httpclientandroidlib/protocol/HttpService.java httpclientandroidlib/protocol/ImmutableHttpProcessor.java httpclientandroidlib/protocol/RequestConnControl.java httpclientandroidlib/protocol/RequestContent.java httpclientandroidlib/protocol/RequestDate.java httpclientandroidlib/protocol/RequestExpectContinue.java httpclientandroidlib/protocol/RequestTargetHost.java httpclientandroidlib/protocol/RequestUserAgent.java httpclientandroidlib/protocol/ResponseConnControl.java httpclientandroidlib/protocol/ResponseContent.java httpclientandroidlib/protocol/ResponseDate.java httpclientandroidlib/protocol/ResponseServer.java httpclientandroidlib/protocol/SyncBasicHttpContext.java httpclientandroidlib/protocol/UriPatternMatcher.java httpclientandroidlib/ProtocolException.java httpclientandroidlib/ProtocolVersion.java httpclientandroidlib/ReasonPhraseCatalog.java httpclientandroidlib/RequestLine.java httpclientandroidlib/StatusLine.java httpclientandroidlib/TokenIterator.java httpclientandroidlib/TruncatedChunkException.java httpclientandroidlib/UnsupportedHttpVersionException.java httpclientandroidlib/util/ByteArrayBuffer.java httpclientandroidlib/util/CharArrayBuffer.java httpclientandroidlib/util/EncodingUtils.java httpclientandroidlib/util/EntityUtils.java httpclientandroidlib/util/ExceptionUtils.java httpclientandroidlib/util/LangUtils.java httpclientandroidlib/util/VersionInfo.java json-simple/ItemList.java json-simple/JSONArray.java json-simple/JSONAware.java json-simple/JSONObject.java json-simple/JSONStreamAware.java json-simple/JSONValue.java json-simple/parser/ContainerFactory.java json-simple/parser/ContentHandler.java json-simple/parser/JSONParser.java json-simple/parser/ParseException.java json-simple/parser/Yylex.java json-simple/parser/Yytoken.java apache/commons/codec/binary/Base32.java apache/commons/codec/binary/Base32InputStream.java apache/commons/codec/binary/Base32OutputStream.java apache/commons/codec/binary/Base64.java apache/commons/codec/binary/Base64InputStream.java apache/commons/codec/binary/Base64OutputStream.java apache/commons/codec/binary/BaseNCodec.java apache/commons/codec/binary/BaseNCodecInputStream.java apache/commons/codec/binary/BaseNCodecOutputStream.java apache/commons/codec/binary/BinaryCodec.java apache/commons/codec/binary/Hex.java apache/commons/codec/binary/StringUtils.java apache/commons/codec/BinaryDecoder.java apache/commons/codec/BinaryEncoder.java apache/commons/codec/CharEncoding.java apache/commons/codec/Decoder.java apache/commons/codec/DecoderException.java apache/commons/codec/digest/DigestUtils.java apache/commons/codec/Encoder.java apache/commons/codec/EncoderException.java apache/commons/codec/language/AbstractCaverphone.java apache/commons/codec/language/Caverphone.java apache/commons/codec/language/Caverphone1.java apache/commons/codec/language/Caverphone2.java apache/commons/codec/language/ColognePhonetic.java apache/commons/codec/language/DoubleMetaphone.java apache/commons/codec/language/Metaphone.java apache/commons/codec/language/RefinedSoundex.java apache/commons/codec/language/Soundex.java apache/commons/codec/language/SoundexUtils.java apache/commons/codec/net/BCodec.java apache/commons/codec/net/QCodec.java apache/commons/codec/net/QuotedPrintableCodec.java apache/commons/codec/net/RFC1522Codec.java apache/commons/codec/net/URLCodec.java apache/commons/codec/net/Utils.java apache/commons/codec/StringDecoder.java apache/commons/codec/StringEncoder.java apache/commons/codec/StringEncoderComparator.java
 SYNC_RES_DRAWABLE := mobile/android/base/resources/drawable/pin_background.xml mobile/android/base/resources/drawable/sync_ic_launcher.png
 SYNC_RES_DRAWABLE_LDPI := mobile/android/base/resources/drawable-ldpi/sync_ic_launcher.png
 SYNC_RES_DRAWABLE_MDPI := mobile/android/base/resources/drawable-mdpi/sync_ic_launcher.png
 SYNC_RES_DRAWABLE_HDPI := mobile/android/base/resources/drawable-hdpi/sync_ic_launcher.png
 SYNC_RES_LAYOUT := res/layout/sync_account.xml res/layout/sync_setup.xml res/layout/sync_setup_failure.xml res/layout/sync_setup_jpake_waiting.xml res/layout/sync_setup_nointernet.xml res/layout/sync_setup_pair.xml res/layout/sync_setup_success.xml res/layout/sync_stub.xml
 SYNC_RES_VALUES := res/values/sync_styles.xml
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/sync/Server11PreviousPostFailedException.java
@@ -0,0 +1,12 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy 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;
+}
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/sync/Server11RecordPostFailedException.java
@@ -0,0 +1,12 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy 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;
+}
--- a/mobile/android/base/sync/middleware/Crypto5MiddlewareRepositorySession.java
+++ b/mobile/android/base/sync/middleware/Crypto5MiddlewareRepositorySession.java
@@ -170,18 +170,18 @@ public class Crypto5MiddlewareRepository
     if (delegate == null) {
       throw new NoStoreDelegateException();
     }
     CryptoRecord rec = record.getEnvelope();
     rec.keyBundle = this.keyBundle;
     try {
       rec.encrypt();
     } catch (UnsupportedEncodingException e) {
-      delegate.onRecordStoreFailed(e);
+      delegate.onRecordStoreFailed(e, record.guid);
       return;
     } catch (CryptoException e) {
-      delegate.onRecordStoreFailed(e);
+      delegate.onRecordStoreFailed(e, record.guid);
       return;
     }
     // Allow the inner session to do delegate handling.
     inner.store(rec);
   }
 }
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/sync/repositories/FetchFailedException.java
@@ -0,0 +1,11 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy 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;
+}
--- a/mobile/android/base/sync/repositories/Server11RepositorySession.java
+++ b/mobile/android/base/sync/repositories/Server11RepositorySession.java
@@ -16,23 +16,26 @@ import java.util.Set;
 import java.util.concurrent.atomic.AtomicLong;
 
 import org.json.simple.JSONArray;
 import org.mozilla.gecko.sync.CryptoRecord;
 import org.mozilla.gecko.sync.DelayedWorkTracker;
 import org.mozilla.gecko.sync.ExtendedJSONObject;
 import org.mozilla.gecko.sync.HTTPFailureException;
 import org.mozilla.gecko.sync.Logger;
+import org.mozilla.gecko.sync.Server11PreviousPostFailedException;
+import org.mozilla.gecko.sync.Server11RecordPostFailedException;
 import org.mozilla.gecko.sync.UnexpectedJSONException;
 import org.mozilla.gecko.sync.crypto.KeyBundle;
 import org.mozilla.gecko.sync.net.SyncStorageCollectionRequest;
 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.net.WBOCollectionRequestDelegate;
+import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionBeginDelegate;
 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 ch.boye.httpclientandroidlib.entity.ContentProducer;
 import ch.boye.httpclientandroidlib.entity.EntityTemplate;
@@ -306,17 +309,35 @@ public class Server11RepositorySession e
     if (!isActive()) {
       delegate.onWipeFailed(new InactiveSessionException(null));
       return;
     }
     // TODO: implement wipe.
   }
 
   protected Object recordsBufferMonitor = new Object();
+
+  /**
+   * Data of outbound records.
+   * <p>
+   * We buffer the data (rather than the <code>Record</code>) so that we can
+   * flush the buffer based on outgoing transmission size.
+   * <p>
+   * Access should be synchronized on <code>recordsBufferMonitor</code>.
+   */
   protected ArrayList<byte[]> recordsBuffer = new ArrayList<byte[]>();
+
+  /**
+   * GUIDs of outbound records.
+   * <p>
+   * Used to fail entire outgoing uploads.
+   * <p>
+   * Access should be synchronized on <code>recordsBufferMonitor</code>.
+   */
+  protected ArrayList<String> recordGuidsBuffer = new ArrayList<String>();
   protected int byteCount = PER_BATCH_OVERHEAD;
 
   @Override
   public void store(Record record) throws NoStoreDelegateException {
     if (delegate == null) {
       throw new NoStoreDelegateException();
     }
     this.enqueue(record);
@@ -335,29 +356,32 @@ public class Server11RepositorySession e
     synchronized (recordsBufferMonitor) {
       if ((delta + byteCount     > UPLOAD_BYTE_THRESHOLD) ||
           (recordsBuffer.size() >= UPLOAD_ITEM_THRESHOLD)) {
 
         // POST the existing contents, then enqueue.
         flush();
       }
       recordsBuffer.add(json);
+      recordGuidsBuffer.add(record.guid);
       byteCount += PER_RECORD_OVERHEAD + delta;
     }
   }
 
   // Asynchronously upload records.
   // Must be locked!
   protected void flush() {
     if (recordsBuffer.size() > 0) {
       final ArrayList<byte[]> outgoing = recordsBuffer;
+      final ArrayList<String> outgoingGuids = recordGuidsBuffer;
       RepositorySessionStoreDelegate uploadDelegate = this.delegate;
-      storeWorkQueue.execute(new RecordUploadRunnable(uploadDelegate, outgoing, byteCount));
+      storeWorkQueue.execute(new RecordUploadRunnable(uploadDelegate, outgoing, outgoingGuids, byteCount));
 
       recordsBuffer = new ArrayList<byte[]>();
+      recordGuidsBuffer = new ArrayList<String>();
       byteCount = PER_BATCH_OVERHEAD;
     }
   }
 
   @Override
   public void storeDone() {
     Logger.debug(LOG_TAG, "storeDone().");
     synchronized (recordsBufferMonitor) {
@@ -373,35 +397,52 @@ public class Server11RepositorySession e
           }
         }
       };
       storeWorkQueue.execute(r);
     }
   }
 
   /**
+   * <code>true</code> if a record upload has failed this session.
+   * <p>
+   * This is only set in begin and possibly by <code>RecordUploadRunnable</code>.
+   * Since those are executed serially, we can use an unsynchronized
+   * volatile boolean here.
+   */
+  protected volatile boolean recordUploadFailed;
+
+  public void begin(RepositorySessionBeginDelegate delegate) throws InvalidSessionTransitionException {
+    recordUploadFailed = false;
+    super.begin(delegate);
+  }
+
+  /**
    * Make an HTTP request, and convert HTTP request delegate callbacks into
    * store callbacks within the context of this RepositorySession.
    *
    * @author rnewman
    *
    */
   protected class RecordUploadRunnable implements Runnable, SyncStorageRequestDelegate {
 
     public final String LOG_TAG = "RecordUploadRunnable";
     private ArrayList<byte[]> outgoing;
+    private ArrayList<String> outgoingGuids;
     private long byteCount;
 
     public RecordUploadRunnable(RepositorySessionStoreDelegate storeDelegate,
                                 ArrayList<byte[]> outgoing,
+                                ArrayList<String> outgoingGuids,
                                 long byteCount) {
       Logger.info(LOG_TAG, "Preparing record upload for " +
                   outgoing.size() + " records (" +
                   byteCount + " bytes).");
-      this.outgoing  = outgoing;
+      this.outgoing = outgoing;
+      this.outgoingGuids = outgoingGuids;
       this.byteCount = byteCount;
     }
 
     @Override
     public String credentials() {
       return serverRepository.credentialsSource.credentials();
     }
 
@@ -414,17 +455,17 @@ public class Server11RepositorySession e
     public void handleRequestSuccess(SyncStorageResponse response) {
       Logger.info(LOG_TAG, "POST of " + outgoing.size() + " records done.");
 
       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);
-        // TODO
+        this.handleRequestError(e);
         return;
       }
 
       // Be defensive when logging timestamp.
       if (body.containsKey("modified")) {
         Long modified = body.getTimestamp("modified");
         if (modified != null) {
           Logger.debug(LOG_TAG, "POST request success. Modified timestamp: " + modified.longValue());
@@ -432,50 +473,69 @@ public class Server11RepositorySession e
           Logger.warn(LOG_TAG, "POST success body contains malformed 'modified': " + body.toJSONString());
         }
       } else {
         Logger.warn(LOG_TAG, "POST success body does not contain key 'modified': " + body.toJSONString());
       }
 
       try {
         JSONArray          success = body.getArray("success");
-        ExtendedJSONObject failed  = body.getObject("failed");
         if ((success != null) &&
             (success.size() > 0)) {
           Logger.debug(LOG_TAG, "Successful records: " + success.toString());
-          // TODO: how do we notify without the whole record?
+          for (Object o : success) {
+            try {
+              delegate.onRecordStoreSucceeded((String) o);
+            } catch (ClassCastException e) {
+              Logger.error(LOG_TAG, "Got exception parsing POST success guid.", e);
+              // Not much to be done.
+            }
+          }
 
           long normalizedTimestamp = getNormalizedTimestamp(response);
           Logger.debug(LOG_TAG, "Passing back upload X-Weave-Timestamp: " + normalizedTimestamp);
           bumpUploadTimestamp(normalizedTimestamp);
         }
+        success = null; // Want to GC this ASAP.
+
+        ExtendedJSONObject failed  = body.getObject("failed");
         if ((failed != null) &&
             (failed.object.size() > 0)) {
           Logger.debug(LOG_TAG, "Failed records: " + failed.object.toString());
-          // TODO: notify.
+          Exception ex = new Server11RecordPostFailedException();
+          for (String guid : failed.keySet()) {
+            delegate.onRecordStoreFailed(ex, guid);
+          }
         }
+        failed = null; // Want to GC this ASAP.
       } catch (UnexpectedJSONException e) {
         Logger.error(LOG_TAG, "Got exception processing success/failed in POST success body.", e);
         // TODO
         return;
       }
       Logger.info(LOG_TAG, "POST of " + outgoing.size() + " records handled.");
     }
 
     @Override
     public void handleRequestFailure(SyncStorageResponse response) {
-      // TODO: ensure that delegate methods don't get called more than once.
       // TODO: call session.interpretHTTPFailure.
       this.handleRequestError(new HTTPFailureException(response));
     }
 
     @Override
     public void handleRequestError(final Exception ex) {
       Logger.warn(LOG_TAG, "Got request error: " + ex, ex);
-      delegate.onRecordStoreFailed(ex);
+
+      recordUploadFailed = true;
+      ArrayList<String> failedOutgoingGuids = outgoingGuids;
+      outgoingGuids = null; // Want to GC this ASAP.
+      for (String guid : failedOutgoingGuids) {
+        delegate.onRecordStoreFailed(ex, guid);
+      }
+      return;
     }
 
     public class ByteArraysContentProducer implements ContentProducer {
 
       ArrayList<byte[]> outgoing;
       public ByteArraysContentProducer(ArrayList<byte[]> arrays) {
         outgoing = arrays;
       }
@@ -515,16 +575,25 @@ public class Server11RepositorySession e
 
     public ByteArraysEntity getBodyEntity() {
       ByteArraysEntity body = new ByteArraysEntity(outgoing, byteCount);
       return body;
     }
 
     @Override
     public void run() {
+      if (recordUploadFailed) {
+        Logger.info(LOG_TAG, "Previous record upload failed.  Failing all records and not retrying.");
+        Exception ex = new Server11PreviousPostFailedException();
+        for (String guid : outgoingGuids) {
+          delegate.onRecordStoreFailed(ex, guid);
+        }
+        return;
+      }
+
       if (outgoing == null ||
           outgoing.size() == 0) {
         Logger.debug(LOG_TAG, "No items: RecordUploadRunnable returning immediately.");
         return;
       }
 
       URI u = serverRepository.collectionURI();
       SyncStorageRequest request = new SyncStorageRequest(u);
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/sync/repositories/StoreFailedException.java
@@ -0,0 +1,11 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy 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;
+}
--- a/mobile/android/base/sync/repositories/android/AndroidBrowserBookmarksRepositorySession.java
+++ b/mobile/android/base/sync/repositories/android/AndroidBrowserBookmarksRepositorySession.java
@@ -583,29 +583,29 @@ public class AndroidBrowserBookmarksRepo
   @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 + "."));
+        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);
+      delegate.onRecordStoreFailed(e, record.guid);
       return false;
     }
     trackRecord(toStore);
-    delegate.onRecordStoreSucceeded(toStore);
+    delegate.onRecordStoreSucceeded(record.guid);
     return true;
   }
 
   /**
    * Implement method of BookmarksInsertionManager.BookmarkInserter.
    */
   @Override
   public void bulkInsertNonFolders(Collection<BookmarkRecord> records) {
@@ -618,34 +618,36 @@ public class AndroidBrowserBookmarksRepo
     }
 
     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 + "."));
+          delegate.onRecordStoreFailed(new RuntimeException("Possibly failed to bulkInsert non-folder with guid " + failed.guid + "."), failed.guid);
         }
         return;
       }
     } catch (NullCursorException e) {
-      delegate.onRecordStoreFailed(e); // TODO: include which records failed.
+      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);
+      delegate.onRecordStoreSucceeded(succeeded.guid);
     }
   }
 
   @Override
   public void finish(RepositorySessionFinishDelegate delegate) throws InactiveSessionException {
     // Allow these to be GCed.
     deletionManager = null;
     insertionManager = null;
--- a/mobile/android/base/sync/repositories/android/AndroidBrowserHistoryRepositorySession.java
+++ b/mobile/android/base/sync/repositories/android/AndroidBrowserHistoryRepositorySession.java
@@ -209,17 +209,17 @@ public class AndroidBrowserHistoryReposi
     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 + "."));
+        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.
@@ -229,17 +229,17 @@ public class AndroidBrowserHistoryReposi
         throw new NullCursorException(e);
       } catch (ParentNotFoundException e) {
         // Should not happen.
         throw new NullCursorException(e);
       } catch (NullCursorException e) {
         throw e;
       }
       trackRecord(succeeded);
-      delegate.onRecordStoreSucceeded(succeeded); // At this point, we are really inserted.
+      delegate.onRecordStoreSucceeded(succeeded.guid); // At this point, we are really inserted.
     }
   }
 
   @Override
   public void storeDone() {
     storeWorkQueue.execute(new Runnable() {
       @Override
       public void run() {
--- a/mobile/android/base/sync/repositories/android/AndroidBrowserRepositorySession.java
+++ b/mobile/android/base/sync/repositories/android/AndroidBrowserRepositorySession.java
@@ -379,17 +379,17 @@ public abstract class AndroidBrowserRepo
     // 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));
+          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.
@@ -498,34 +498,34 @@ public abstract class AndroidBrowserRepo
                        (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);
+          delegate.onRecordStoreSucceeded(replaced.guid);
           return;
 
         } catch (MultipleRecordsForGuidException e) {
           Logger.error(LOG_TAG, "Multiple records returned for given guid: " + record.guid);
-          delegate.onRecordStoreFailed(e);
+          delegate.onRecordStoreFailed(e, record.guid);
           return;
         } catch (NoGuidForIdException e) {
           Logger.error(LOG_TAG, "Store failed for " + record.guid, e);
-          delegate.onRecordStoreFailed(e);
+          delegate.onRecordStoreFailed(e, record.guid);
           return;
         } catch (NullCursorException e) {
           Logger.error(LOG_TAG, "Store failed for " + record.guid, e);
-          delegate.onRecordStoreFailed(e);
+          delegate.onRecordStoreFailed(e, record.guid);
           return;
         } catch (Exception e) {
           Logger.error(LOG_TAG, "Store failed for " + record.guid, e);
-          delegate.onRecordStoreFailed(e);
+          delegate.onRecordStoreFailed(e, record.guid);
           return;
         }
       }
     };
     storeWorkQueue.execute(command);
   }
 
   /**
@@ -534,30 +534,30 @@ public abstract class AndroidBrowserRepo
    *
    * @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);
+    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);
+    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.
--- a/mobile/android/base/sync/repositories/android/BookmarksDeletionManager.java
+++ b/mobile/android/base/sync/repositories/android/BookmarksDeletionManager.java
@@ -7,17 +7,16 @@ package org.mozilla.gecko.sync.repositor
 import java.util.HashSet;
 import java.util.Map;
 import java.util.Set;
 
 import org.mozilla.gecko.db.BrowserContract;
 import org.mozilla.gecko.sync.Logger;
 import org.mozilla.gecko.sync.repositories.NullCursorException;
 import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionStoreDelegate;
-import org.mozilla.gecko.sync.repositories.domain.BookmarkRecord;
 
 /**
  * Queue up deletions. Process them at the end.
  *
  * Algorithm:
  *
  * * Collect GUIDs as we go. For convenience we partition these into
  *   folders and non-folders.
@@ -217,21 +216,18 @@ public class BookmarksDeletionManager {
   }
 
   private void invokeCallbacks(RepositorySessionStoreDelegate delegate,
                                String[] nonFolderGUIDs) {
     if (delegate == null) {
       return;
     }
     Logger.trace(LOG_TAG, "Invoking store callback for " + nonFolderGUIDs.length + " GUIDs.");
-    final long now = System.currentTimeMillis();
-    BookmarkRecord r = new BookmarkRecord(null, "bookmarks", now, true);
     for (String guid : nonFolderGUIDs) {
-      r.guid = guid;
-      delegate.onRecordStoreSucceeded(r);
+      delegate.onRecordStoreSucceeded(guid);
     }
   }
 
   /**
    * Clear state in case of redundancy (e.g., wipe).
    */
   public void clear() {
     nonFolders.clear();
--- a/mobile/android/base/sync/repositories/android/FennecTabsRepository.java
+++ b/mobile/android/base/sync/repositories/android/FennecTabsRepository.java
@@ -135,36 +135,36 @@ public class FennecTabsRepository extend
       }
       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));
+            delegate.onRecordStoreFailed(new InactiveSessionException(null), record.guid);
             return;
           }
           if (tabsRecord.guid == null) {
-            delegate.onRecordStoreFailed(new RuntimeException("Can't store record with null GUID."));
+            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(BrowserContract.Clients.CONTENT_URI,
                                        CLIENT_GUID_IS,
                                        selectionArgs);
-                delegate.onRecordStoreSucceeded(record);
+                delegate.onRecordStoreSucceeded(record.guid);
               } catch (Exception e) {
-                delegate.onRecordStoreFailed(e);
+                delegate.onRecordStoreFailed(e, record.guid);
               }
               return;
             }
 
             // If it exists, update the client record; otherwise insert.
             final ContentValues clientsCV = tabsRecord.getClientsContentValues();
 
             Logger.debug(LOG_TAG, "Updating clients provider.");
@@ -179,20 +179,20 @@ public class FennecTabsRepository extend
             // Now insert tabs.
             final ContentValues[] tabsArray = tabsRecord.getTabsContentValues();
             Logger.debug(LOG_TAG, "Inserting " + tabsArray.length + " tabs for client " + tabsRecord.guid);
 
             tabsProvider.delete(BrowserContract.Tabs.CONTENT_URI, TABS_CLIENT_GUID_IS, selectionArgs);
             final int inserted = tabsProvider.bulkInsert(BrowserContract.Tabs.CONTENT_URI, tabsArray);
             Logger.trace(LOG_TAG, "Inserted: " + inserted);
 
-            delegate.onRecordStoreSucceeded(tabsRecord);
+            delegate.onRecordStoreSucceeded(record.guid);
           } catch (Exception e) {
             Logger.warn(LOG_TAG, "Error storing tabs.", e);
-            delegate.onRecordStoreFailed(e);
+            delegate.onRecordStoreFailed(e, record.guid);
           }
         }
       };
 
       storeWorkQueue.execute(command);
     }
 
     @Override
--- a/mobile/android/base/sync/repositories/android/FormHistoryRepositorySession.java
+++ b/mobile/android/base/sync/repositories/android/FormHistoryRepositorySession.java
@@ -445,17 +445,17 @@ public class FormHistoryRepositorySessio
 
   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);
+          delegate.onRecordStoreFailed(e, record.guid);
           return;
         }
       }
       // Store the ContentValues, rather than the record.
       recordsBuffer.add(contentValuesForRegularRecord(record));
     }
   }
 
@@ -486,17 +486,18 @@ public class FormHistoryRepositorySessio
       public void run() {
         Logger.debug(LOG_TAG, "Checking for residual form history items to insert.");
         try {
           synchronized (recordsBufferMonitor) {
             flushInsertQueue();
           }
           storeDone(now());
         } catch (Exception e) {
-          delegate.onRecordStoreFailed(e);
+          // XXX TODO
+          delegate.onRecordStoreFailed(e, null);
         }
       }
     };
     storeWorkQueue.execute(command);
   }
 
   /**
    * Called when a regular record with locally unknown GUID has been fetched
@@ -557,17 +558,17 @@ public class FormHistoryRepositorySessio
     }
     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));
+          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
@@ -600,26 +601,26 @@ public class FormHistoryRepositorySessio
               return;
             }
 
             boolean locallyModified = existingRecord.lastModified > lastLocalRetrieval;
             if (!locallyModified) {
               Logger.trace(LOG_TAG, "Remote modified, local not. Deleting.");
               deleteExistingRecord(existingRecord);
               trackRecord(record);
-              delegate.onRecordStoreSucceeded(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);
+              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.
             }
@@ -633,59 +634,59 @@ public class FormHistoryRepositorySessio
             existingRecord = findExistingRecordByPayload(record);
           }
 
           if (existingRecord == null) {
             // The record is new.
             Logger.trace(LOG_TAG, "No match. Inserting.");
             insertNewRegularRecord(record);
             trackRecord(record);
-            delegate.onRecordStoreSucceeded(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);
+            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);
+            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);
+            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);
+          delegate.onRecordStoreFailed(e, record.guid);
           return;
         }
       }
     };
 
     storeWorkQueue.execute(command);
   }
 
--- a/mobile/android/base/sync/repositories/android/PasswordsRepositorySession.java
+++ b/mobile/android/base/sync/repositories/android/PasswordsRepositorySession.java
@@ -252,35 +252,35 @@ public class PasswordsRepositorySession 
 
     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));
+          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."));
+          delegate.onRecordStoreFailed(new RuntimeException("Can't store record with null GUID."), record.guid);
           return;
         }
 
         PasswordRecord existingRecord;
         try {
           existingRecord = retrieveByGUID(guid);
         } catch (NullCursorException e) {
           // Indicates a serious problem.
-          delegate.onRecordStoreFailed(e);
+          delegate.onRecordStoreFailed(e, record.guid);
           return;
         } catch (RemoteException e) {
-          delegate.onRecordStoreFailed(e);
+          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.
@@ -325,33 +325,41 @@ public class PasswordsRepositorySession 
           }
           return;
         }
         // End deletion logic.
 
         // Now we're processing a non-deleted incoming record.
         if (existingRecord == null) {
           trace("Looking up match for record " + remoteRecord.guid);
-          existingRecord = findExistingRecord(remoteRecord);
+          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);
+            delegate.onRecordStoreFailed(e, record.guid);
             return;
           }
           trackRecord(inserted);
-          delegate.onRecordStoreSucceeded(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 + " dupes to " + existingRecord);
         Record toStore = reconcileRecords(remoteRecord, existingRecord, lastRemoteRetrieval, lastLocalRetrieval);
 
@@ -364,25 +372,25 @@ public class PasswordsRepositorySession 
         Logger.debug(LOG_TAG, "Replacing " + existingRecord.guid + " with record " + toStore.guid);
         Logger.debug(LOG_TAG, "existing: " + existingRecord);
         Logger.debug(LOG_TAG, "toStore: " + toStore);
         Record replaced = null;
         try {
           replaced = replace(existingRecord, toStore);
         } catch (RemoteException e) {
           Logger.debug(LOG_TAG, "Record replace caused a RemoteException.");
-          delegate.onRecordStoreFailed(e);
+          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(replaced);
+        delegate.onRecordStoreSucceeded(record.guid);
         return;
       }
     };
     storeWorkQueue.execute(storeRunnable);
   }
 
   @Override
   public void wipe(final RepositorySessionWipeDelegate delegate) {
@@ -576,17 +584,17 @@ public class PasswordsRepositorySession 
 
   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) {
+  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,
@@ -605,40 +613,34 @@ public class PasswordsRepositorySession 
         // so we run a more general query and then filter
         // the returned records for a matching username.
         Logger.trace(LOG_TAG, "Checking incoming [" + record.encryptedUsername + "] to [" + foundRecord.encryptedUsername + "]");
         if (record.encryptedUsername.equals(foundRecord.encryptedUsername)) {
           Logger.trace(LOG_TAG, "Found matching record: " + foundRecord);
           return foundRecord;
         }
       }
-    } catch (RemoteException e) {
-      Logger.error(LOG_TAG, "Remote exception in findExistingRecord.");
-      delegate.onRecordStoreFailed(e);
-    } catch (NullCursorException e) {
-      Logger.error(LOG_TAG, "Null cursor in findExistingRecord.");
-      delegate.onRecordStoreFailed(e);
     } 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);
+      delegate.onRecordStoreFailed(e, record.guid);
       return;
     }
-    delegate.onRecordStoreSucceeded(record);
+    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.
--- a/mobile/android/base/sync/repositories/delegates/DeferredRepositorySessionStoreDelegate.java
+++ b/mobile/android/base/sync/repositories/delegates/DeferredRepositorySessionStoreDelegate.java
@@ -1,45 +1,43 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy 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 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 Record record) {
+  public void onRecordStoreSucceeded(final String guid) {
     executor.execute(new Runnable() {
       @Override
       public void run() {
-        inner.onRecordStoreSucceeded(record);
+        inner.onRecordStoreSucceeded(guid);
       }
     });
   }
 
   @Override
-  public void onRecordStoreFailed(final Exception ex) {
+  public void onRecordStoreFailed(final Exception ex, final String guid) {
     executor.execute(new Runnable() {
       @Override
       public void run() {
-        inner.onRecordStoreFailed(ex);
+        inner.onRecordStoreFailed(ex, guid);
       }
     });
   }
 
   @Override
   public RepositorySessionStoreDelegate deferredStoreDelegate(ExecutorService newExecutor) {
     if (newExecutor == executor) {
       return this;
--- a/mobile/android/base/sync/repositories/delegates/RepositorySessionStoreDelegate.java
+++ b/mobile/android/base/sync/repositories/delegates/RepositorySessionStoreDelegate.java
@@ -1,26 +1,23 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy 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;
-
 /**
  * These methods *must* be invoked asynchronously. Use deferredStoreDelegate if you
  * need help doing this.
  *
  * @author rnewman
  *
  */
 public interface RepositorySessionStoreDelegate {
-  public void onRecordStoreFailed(Exception ex);
+  public void onRecordStoreFailed(Exception ex, String recordGuid);
 
-  // Optionally called with an equivalent (but not necessarily identical) record
-  // when a store has succeeded.
-  public void onRecordStoreSucceeded(Record record);
+  // Called with a GUID when store has succeeded.
+  public void onRecordStoreSucceeded(String guid);
   public void onStoreCompleted(long storeEnd);
   public RepositorySessionStoreDelegate deferredStoreDelegate(ExecutorService executor);
 }
--- a/mobile/android/base/sync/stage/ServerSyncStage.java
+++ b/mobile/android/base/sync/stage/ServerSyncStage.java
@@ -32,16 +32,17 @@ import org.mozilla.gecko.sync.repositori
 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 android.content.Context;
 
 /**
  * Fetch from a server collection into a local repository, encrypting
  * and decrypting along the way.
@@ -134,17 +135,17 @@ public abstract class ServerSyncStage im
 
   protected void persistConfig(SynchronizerConfiguration synchronizerConfiguration) {
     synchronizerConfiguration.persist(session.config.getBranch(bundlePrefix()));
   }
 
   public Synchronizer getConfiguredSynchronizer(GlobalSession session) throws NoCollectionKeysSetException, URISyntaxException, NonObjectJSONException, IOException, ParseException {
     Repository remote = wrappedServerRepo();
 
-    Synchronizer synchronizer = new Synchronizer();
+    Synchronizer synchronizer = new ServerLocalSynchronizer();
     synchronizer.repositoryA = remote;
     synchronizer.repositoryB = this.getLocalRepository();
     synchronizer.load(getConfig());
 
     return synchronizer;
   }
 
   /**
@@ -517,16 +518,9 @@ public abstract class ServerSyncStage im
 
     // This failure could be due to a 503 or a 401 and it could have headers.
     if (lastException instanceof HTTPFailureException) {
       session.handleHTTPError(((HTTPFailureException)lastException).response, reason);
     } else {
       session.abort(lastException, reason);
     }
   }
-
-  @Override
-  public void onSynchronizeAborted(Synchronizer synchronize) {
-    Logger.info(LOG_TAG, "onSynchronizeAborted.");
-
-    session.abort(null, "Synchronization was aborted.");
-  }
 }
--- a/mobile/android/base/sync/synchronizer/ConcurrentRecordConsumer.java
+++ b/mobile/android/base/sync/synchronizer/ConcurrentRecordConsumer.java
@@ -79,33 +79,34 @@ class ConcurrentRecordConsumer extends R
 
   private void consumerIsDone() {
     info("Consumer is done. Processed " + counter + ((counter == 1) ? " record." : " records."));
     delegate.consumerIsDone(!allRecordsQueued);
   }
 
   @Override
   public void run() {
+    Record record;
+
     while (true) {
-      synchronized (monitor) {
-        trace("run() took monitor.");
-        if (stopImmediately) {
-          debug("Stopping immediately. Clearing queue.");
-          delegate.getQueue().clear();
-          debug("Notifying consumer.");
-          consumerIsDone();
-          return;
+      // The queue is concurrent-safe.
+      while ((record = delegate.getQueue().poll()) != null) {
+        synchronized (monitor) {
+          trace("run() took monitor.");
+          if (stopImmediately) {
+            debug("Stopping immediately. Clearing queue.");
+            delegate.getQueue().clear();
+            debug("Notifying consumer.");
+            consumerIsDone();
+            return;
+          }
+          debug("run() dropped monitor.");
         }
-        debug("run() dropped monitor.");
-      }
-      // The queue is concurrent-safe.
-      while (!delegate.getQueue().isEmpty()) {
-        trace("Grabbing record...");
-        Record record = delegate.getQueue().remove();
-        trace("Storing record... " + delegate);
+
+        trace("Storing record with guid " + record.guid + ".");
         try {
           delegate.store(record);
         } catch (Exception e) {
           // TODO: Bug 709371: track records that failed to apply.
           Log.e(LOG_TAG, "Caught error in store.", e);
         }
         trace("Done with record.");
       }
--- a/mobile/android/base/sync/synchronizer/RecordsChannel.java
+++ b/mobile/android/base/sync/synchronizer/RecordsChannel.java
@@ -1,16 +1,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy 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.sync.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;
@@ -54,29 +55,32 @@ import org.mozilla.gecko.sync.repositori
  *   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
  *
  */
-class RecordsChannel implements
+public class RecordsChannel implements
   RepositorySessionFetchRecordsDelegate,
   RepositorySessionStoreDelegate,
   RecordsConsumerDelegate,
   RepositorySessionBeginDelegate {
 
   private static final String LOG_TAG = "RecordsChannel";
   public RepositorySession source;
   public RepositorySession sink;
   private RecordsChannelDelegate delegate;
   private long timestamp;
   private long fetchEnd = -1;
 
+  private final AtomicInteger numFetchFailed = new AtomicInteger();
+  private final AtomicInteger numStoreFailed = new AtomicInteger();
+
   public RecordsChannel(RepositorySession source, RepositorySession sink, RecordsChannelDelegate delegate) {
     this.source    = source;
     this.sink      = sink;
     this.delegate  = delegate;
     this.timestamp = source.lastSyncTimestamp;
   }
 
   /*
@@ -97,46 +101,46 @@ class RecordsChannel implements
     return toProcess;
   }
 
   protected boolean isReady() {
     return source.isActive() && sink.isActive();
   }
 
   /**
-   * Attempt to abort an outstanding fetch. Finish both sessions, and
-   * halt the consumer if it exists.
+   * Get the number of fetch failures recorded so far.
+   * @return number of fetch failures.
    */
-  public void abort() {
-    if (source.isActive()) {
-      source.abort();
-    }
-    if (sink.isActive()) {
-      sink.abort();
-    }
+  public int getFetchFailureCount() {
+    return numFetchFailed.get();
+  }
 
-    toProcess.clear();
-    if (consumer == null) {
-      return;
-    }
-    consumer.halt();
+  /**
+   * 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;
     }
     sink.setStoreDelegate(this);
+    numFetchFailed.set(0);
+    numStoreFailed.set(0);
     // Start a consumer thread.
     this.consumer = new ConcurrentRecordConsumer(this);
     ThreadPool.run(this.consumer);
     waitingForQueueDone = true;
     source.fetchSince(timestamp, this);
   }
 
   /**
@@ -149,24 +153,24 @@ class RecordsChannel implements
   }
 
   @Override
   public void store(Record record) {
     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);
-      this.abort();
+      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) {
     this.toProcess.add(record);
     this.consumer.doNotify();
@@ -185,24 +189,27 @@ class RecordsChannel implements
   public void onFetchCompleted(final long fetchEnd) {
     Logger.info(LOG_TAG, "onFetchCompleted. Stopping consumer once stores are done.");
     Logger.info(LOG_TAG, "Fetch timestamp is " + fetchEnd);
     this.fetchEnd = fetchEnd;
     this.consumer.queueFilled();
   }
 
   @Override
-  public void onRecordStoreFailed(Exception ex) {
+  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);
+    delegate.onFlowStoreFailed(this, ex, recordGuid);
     // TODO: abort?
   }
 
   @Override
-  public void onRecordStoreSucceeded(Record record) {
+  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) {
@@ -227,16 +234,17 @@ class RecordsChannel implements
   @Override
   public void onBeginSucceeded(RepositorySession session) {
     if (session == source) {
       Logger.info(LOG_TAG, "Source session began. Beginning sink session.");
       try {
         sink.begin(this);
       } catch (InvalidSessionTransitionException e) {
         onBeginFailed(e);
+        return;
       }
     }
     if (session == sink) {
       Logger.info(LOG_TAG, "Sink session began. Beginning flow.");
       this.flow();
       return;
     }
 
--- a/mobile/android/base/sync/synchronizer/RecordsChannelDelegate.java
+++ b/mobile/android/base/sync/synchronizer/RecordsChannelDelegate.java
@@ -3,11 +3,11 @@
  * 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);
+  public void onFlowStoreFailed(RecordsChannel recordsChannel, Exception ex, String recordGuid);
   public void onFlowFinishFailed(RecordsChannel recordsChannel, Exception ex);
 }
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/sync/synchronizer/ServerLocalSynchronizer.java
@@ -0,0 +1,17 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy 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 {
+  public SynchronizerSession getSynchronizerSession() {
+    return new ServerLocalSynchronizerSession(this, this);
+  }
+}
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/sync/synchronizer/ServerLocalSynchronizerSession.java
@@ -0,0 +1,76 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy 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.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);
+  }
+
+  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);
+  }
+
+  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);
+  }
+}
--- a/mobile/android/base/sync/synchronizer/Synchronizer.java
+++ b/mobile/android/base/sync/synchronizer/Synchronizer.java
@@ -22,95 +22,66 @@ import android.util.Log;
  * 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 {
+public class Synchronizer implements SynchronizerSessionDelegate {
+  public static final String LOG_TAG = "SyncDelSDelegate";
+
   protected String configSyncID; // Used to pass syncID from load() back into save().
 
-  /**
-   * I translate the fine-grained feedback of a SynchronizerSessionDelegate into
-   * the coarse-grained feedback of a SynchronizerDelegate.
-   */
-  public class SynchronizerDelegateSessionDelegate implements
-      SynchronizerSessionDelegate {
-
-    private static final String LOG_TAG = "SyncDelSDelegate";
-    private SynchronizerDelegate synchronizerDelegate;
-    private SynchronizerSession  session;
+  protected SynchronizerDelegate synchronizerDelegate;
 
-    public SynchronizerDelegateSessionDelegate(SynchronizerDelegate delegate) {
-      this.synchronizerDelegate = delegate;
-    }
-
-    @Override
-    public void onInitialized(SynchronizerSession session) {
-      this.session = session;
-      session.synchronize();
-    }
-
-    @Override
-    public void onSynchronized(SynchronizerSession synchronizerSession) {
-      Log.d(LOG_TAG, "Got onSynchronized.");
-      Log.d(LOG_TAG, "Notifying SynchronizerDelegate.");
-      this.synchronizerDelegate.onSynchronized(synchronizerSession.getSynchronizer());
-    }
+  @Override
+  public void onInitialized(SynchronizerSession session) {
+    session.synchronize();
+  }
 
-    @Override
-    public void onSynchronizeSkipped(SynchronizerSession synchronizerSession) {
-      Log.d(LOG_TAG, "Got onSynchronizeSkipped.");
-      Log.d(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);
-    }
-
-    @Override
-    public void onSynchronizeAborted(SynchronizerSession synchronizerSession) {
-      this.synchronizerDelegate.onSynchronizeAborted(session.getSynchronizer());
-    }
+  @Override
+  public void onSynchronized(SynchronizerSession synchronizerSession) {
+    Log.d(LOG_TAG, "Got onSynchronized.");
+    Log.d(LOG_TAG, "Notifying SynchronizerDelegate.");
+    this.synchronizerDelegate.onSynchronized(synchronizerSession.getSynchronizer());
+  }
 
-    @Override
-    public void onFetchError(Exception e) {
-      session.abort();
-      synchronizerDelegate.onSynchronizeFailed(session.getSynchronizer(), e, "Got fetch error.");
-    }
+  @Override
+  public void onSynchronizeSkipped(SynchronizerSession synchronizerSession) {
+    Log.d(LOG_TAG, "Got onSynchronizeSkipped.");
+    Log.d(LOG_TAG, "Notifying SynchronizerDelegate as if on success.");
+    this.synchronizerDelegate.onSynchronized(synchronizerSession.getSynchronizer());
+  }
 
-    @Override
-    public void onStoreError(Exception e) {
-      session.abort();
-      synchronizerDelegate.onSynchronizeFailed(session.getSynchronizer(), e, "Got store error.");
-    }
-
-    @Override
-    public void onSessionError(Exception e) {
-      session.abort();
-      synchronizerDelegate.onSynchronizeFailed(session.getSynchronizer(), e, "Got session error.");
-    }
+  @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>
+   */
+  public SynchronizerSession getSynchronizerSession() {
+    return new SynchronizerSession(this, this);
+  }
+
+  /**
    * Start synchronizing, calling delegate's callback methods.
    */
   public void synchronize(Context context, SynchronizerDelegate delegate) {
-    SynchronizerDelegateSessionDelegate sessionDelegate = new SynchronizerDelegateSessionDelegate(delegate);
-    SynchronizerSession session = new SynchronizerSession(this, sessionDelegate);
+    this.synchronizerDelegate = delegate;
+    SynchronizerSession session = getSynchronizerSession();
     session.init(context, bundleA, bundleB);
   }
 
   public SynchronizerConfiguration save() {
     return new SynchronizerConfiguration(configSyncID, bundleA, bundleB);
   }
 
   /**
--- a/mobile/android/base/sync/synchronizer/SynchronizerDelegate.java
+++ b/mobile/android/base/sync/synchronizer/SynchronizerDelegate.java
@@ -2,10 +2,9 @@
  * License, v. 2.0. If a copy 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);
-  public void onSynchronizeAborted(Synchronizer synchronize);
 }
--- a/mobile/android/base/sync/synchronizer/SynchronizerSession.java
+++ b/mobile/android/base/sync/synchronizer/SynchronizerSession.java
@@ -46,19 +46,19 @@ import android.content.Context;
  * `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";
-  private Synchronizer synchronizer;
-  private SynchronizerSessionDelegate delegate;
-  private Context context;
+  protected Synchronizer synchronizer;
+  protected SynchronizerSessionDelegate delegate;
+  protected Context context;
 
   /*
    * Computed during init.
    */
   private RepositorySession sessionA;
   private RepositorySession sessionB;
   private RepositorySessionBundle bundleA;
   private RepositorySessionBundle bundleB;
@@ -98,30 +98,16 @@ implements RecordsChannelDelegate,
     this.getSynchronizer().repositoryA.createSession(this, context);
   }
 
   // These are accessed by `abort` and `synchronize`, both of which are synchronized.
   // Guarded by `this`.
   protected RecordsChannel channelAToB;
   protected RecordsChannel channelBToA;
 
-  public synchronized void abort() {
-    // Guaranteed to have been begun by the time we get to run.
-    if (channelAToB != null) {
-      channelAToB.abort();
-    }
-
-    // Not guaranteed. It's possible for the second flow to begin after we've aborted.
-    // TODO: stop this from happening!
-    if (channelBToA != null) {
-      channelBToA.abort();
-    }
-    this.delegate.onSynchronizeAborted(this);
-  }
-
   /**
    * Please don't call this until you've been notified with onInitialized.
    */
   public synchronized void synchronize() {
     // First thing: decide whether we should.
     if (!sessionA.dataAvailable() &&
         !sessionB.dataAvailable()) {
       Logger.info(LOG_TAG, "Neither session reports data available. Short-circuiting sync.");
@@ -137,109 +123,120 @@ implements RecordsChannelDelegate,
 
     // 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() {
       public void onFlowCompleted(RecordsChannel recordsChannel, long fetchEnd, long storeEnd) {
-        Logger.info(LOG_TAG, "First RecordsChannel onFlowCompleted. Fetch end is " + fetchEnd +
-             ". Store end is " + storeEnd + ". Starting next.");
-        pendingATimestamp = fetchEnd;
-        storeEndBTimestamp = storeEnd;
-        flowAToBCompleted = true;
-        channelBToA.flow();
+        session.onFirstFlowCompleted(recordsChannel, fetchEnd, storeEnd);
       }
 
       @Override
       public void onFlowBeginFailed(RecordsChannel recordsChannel, Exception ex) {
-        Logger.warn(LOG_TAG, "First RecordsChannel onFlowBeginFailed. Reporting session error.", ex);
-        session.delegate.onSessionError(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) {
-        // TODO: clean up, tear down, abort.
-        Logger.warn(LOG_TAG, "First RecordsChannel onFlowFetchFailed. Reporting fetch error.", ex);
-        session.delegate.onFetchError(ex);
+        Logger.warn(LOG_TAG, "First RecordsChannel onFlowFetchFailed. Logging remote fetch error.", ex);
       }
 
       @Override
-      public void onFlowStoreFailed(RecordsChannel recordsChannel, Exception ex) {
-        // TODO: clean up, tear down, abort.
-        Logger.warn(LOG_TAG, "First RecordsChannel onFlowStoreFailed. Reporting store error.", ex);
-        session.delegate.onStoreError(ex);
+      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. Reporting session error.", ex);
-        session.delegate.onSessionError(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.info(LOG_TAG, "Starting A to B flow. Channel is " + channelAToB);
     try {
       channelAToB.beginAndFlow();
     } catch (InvalidSessionTransitionException e) {
       onFlowBeginFailed(channelAToB, e);
     }
   }
 
-  @Override
-  public void onFlowCompleted(RecordsChannel channel, long fetchEnd, long storeEnd) {
-    Logger.info(LOG_TAG, "Second RecordsChannel onFlowCompleted. Fetch end is " + fetchEnd +
-         ". Store end is " + storeEnd + ". Finishing.");
+  /**
+   * 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.info(LOG_TAG, "First RecordsChannel onFlowCompleted.");
+    Logger.info(LOG_TAG, "Fetch end is " + fetchEnd + ". Store end is " + storeEnd + ". Starting next.");
+    pendingATimestamp = fetchEnd;
+    storeEndBTimestamp = storeEnd;
+    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.info(LOG_TAG, "Second RecordsChannel onFlowCompleted.");
+    Logger.info(LOG_TAG, "Fetch end is " + fetchEnd + ". Store end is " + storeEnd + ". Finishing.");
 
     pendingBTimestamp = fetchEnd;
     storeEndATimestamp = storeEnd;
     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. Reporting session error.", ex);
-    this.delegate.onSessionError(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) {
-    // TODO: clean up, tear down, abort.
-    Logger.warn(LOG_TAG, "Second RecordsChannel onFlowFetchFailed. Reporting fetch error.", ex);
-    this.delegate.onFetchError(ex);
+    Logger.warn(LOG_TAG, "Second RecordsChannel onFlowFetchFailed. Logging local fetch error.", ex);
   }
 
-  /**
-   * We ignore possible store errors, since failure to store a record is not
-   * necessarily a cause to abort. It might mean that the record should be
-   * tracked for re-downloading, or skipped, or we might abort.
-   *
-   * TODO: Bug 709371.
-   */
   @Override
-  public void onFlowStoreFailed(RecordsChannel recordsChannel, Exception ex) {
-    // TODO: clean up, tear down, abort.
-    Logger.warn(LOG_TAG, "Second RecordsChannel onFlowStoreFailed. Ignoring store error.", ex);
+  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. Reporting session error.", ex);
-    this.delegate.onSessionError(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.
@@ -256,67 +253,66 @@ implements RecordsChannelDelegate,
         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.onSessionError(ex);
+    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.onSessionError(new UnexpectedSessionException(session));
+      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.onSessionError(new UnbundleError(e, sessionA));
+        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) {
-        // TODO: abort
-        this.delegate.onSessionError(new UnbundleError(e, sessionB));
+        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.onSessionError(new UnexpectedSessionException(session));
+    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.
@@ -349,37 +345,40 @@ implements RecordsChannelDelegate,
 
     if (session == sessionA) {
       if (flowAToBCompleted) {
         Logger.info(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.onSessionError(new UnexpectedSessionException(sessionA));
+        this.delegate.onSynchronizeFailed(this, new UnexpectedSessionException(sessionA), "Failed to finish first session.");
+        return;
       }
       if (this.sessionB != null) {
         Logger.info(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.info(LOG_TAG, "onFinishSucceeded: bumping session B's timestamp to " + pendingBTimestamp + " or " + storeEndBTimestamp);
         bundle.bumpTimestamp(Math.max(pendingBTimestamp, storeEndBTimestamp));
         this.synchronizer.bundleB = bundle;
         Logger.info(LOG_TAG, "Notifying delegate.onSynchronized.");
         this.delegate.onSynchronized(this);
       } else {
         // Should not happen!
-        this.delegate.onSessionError(new UnexpectedSessionException(sessionB));
+        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.
     }
--- a/mobile/android/base/sync/synchronizer/SynchronizerSessionDelegate.java
+++ b/mobile/android/base/sync/synchronizer/SynchronizerSessionDelegate.java
@@ -4,17 +4,10 @@
 
 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 onSynchronizeAborted(SynchronizerSession synchronizerSession);
   public void onSynchronizeSkipped(SynchronizerSession synchronizerSession);
-
-  // TODO: return value?
-  public void onFetchError(Exception e);
-  public void onStoreError(Exception e);
-  public void onSessionError(Exception e);
-
 }
--- a/mobile/android/sync/java-sources.mn
+++ b/mobile/android/sync/java-sources.mn
@@ -131,16 +131,17 @@ sync/repositories/domain/ClientRecord.ja
 sync/repositories/domain/ClientRecordFactory.java
 sync/repositories/domain/FormHistoryRecord.java
 sync/repositories/domain/HistoryRecord.java
 sync/repositories/domain/HistoryRecordFactory.java
 sync/repositories/domain/PasswordRecord.java
 sync/repositories/domain/Record.java
 sync/repositories/domain/TabsRecord.java
 sync/repositories/domain/VersionConstants.java
+sync/repositories/FetchFailedException.java
 sync/repositories/HashSetStoreTracker.java
 sync/repositories/HistoryRepository.java
 sync/repositories/IdentityRecordFactory.java
 sync/repositories/InactiveSessionException.java
 sync/repositories/InvalidBookmarkTypeException.java
 sync/repositories/InvalidRequestException.java
 sync/repositories/InvalidSessionTransitionException.java
 sync/repositories/MultipleRecordsForGuidException.java
@@ -152,18 +153,21 @@ sync/repositories/ParentNotFoundExceptio
 sync/repositories/ProfileDatabaseException.java
 sync/repositories/RecordFactory.java
 sync/repositories/RecordFilter.java
 sync/repositories/Repository.java
 sync/repositories/RepositorySession.java
 sync/repositories/RepositorySessionBundle.java
 sync/repositories/Server11Repository.java
 sync/repositories/Server11RepositorySession.java
+sync/repositories/StoreFailedException.java
 sync/repositories/StoreTracker.java
 sync/repositories/StoreTrackingRepositorySession.java
+sync/Server11PreviousPostFailedException.java
+sync/Server11RecordPostFailedException.java
 sync/setup/activities/AccountActivity.java
 sync/setup/activities/ActivityUtils.java
 sync/setup/activities/SetupFailureActivity.java
 sync/setup/activities/SetupSuccessActivity.java
 sync/setup/activities/SetupSyncActivity.java
 sync/setup/Constants.java
 sync/setup/InvalidSyncKeyException.java
 sync/setup/SyncAccounts.java
@@ -193,16 +197,18 @@ sync/SyncConfiguration.java
 sync/SyncConfigurationException.java
 sync/SyncException.java
 sync/synchronizer/ConcurrentRecordConsumer.java
 sync/synchronizer/RecordConsumer.java
 sync/synchronizer/RecordsChannel.java
 sync/synchronizer/RecordsChannelDelegate.java
 sync/synchronizer/RecordsConsumerDelegate.java
 sync/synchronizer/SerialRecordConsumer.java
+sync/synchronizer/ServerLocalSynchronizer.java
+sync/synchronizer/ServerLocalSynchronizerSession.java
 sync/synchronizer/SessionNotBegunException.java
 sync/synchronizer/Synchronizer.java
 sync/synchronizer/SynchronizerDelegate.java
 sync/synchronizer/SynchronizerSession.java
 sync/synchronizer/SynchronizerSessionDelegate.java
 sync/synchronizer/UnbundleError.java
 sync/synchronizer/UnexpectedSessionException.java
 sync/SynchronizerConfiguration.java