Bug 1408585 - Remove RepositorySession createSession delegates r=rnewman
authorGrigory Kruglov <gkruglov@mozilla.com>
Tue, 14 Nov 2017 14:37:05 -0500
changeset 443547 526f6a68b77d340db9ec41f828ce1eeeb91aa5fb
parent 443546 421ecf5915434aac48675ecc5ab8b6ca6c089a15
child 443548 00dd7dfadff3816d02cd6d1bcce8836809d5e22f
push id8527
push userCallek@gmail.com
push dateThu, 11 Jan 2018 21:05:50 +0000
treeherdermozilla-beta@95342d212a7a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersrnewman
bugs1408585
milestone59.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 1408585 - Remove RepositorySession createSession delegates r=rnewman MozReview-Commit-ID: KezYHeSWDiL
mobile/android/base/android-services.mozbuild
mobile/android/services/src/androidTest/java/org/mozilla/gecko/background/db/TestAndroidBrowserBookmarksRepository.java
mobile/android/services/src/androidTest/java/org/mozilla/gecko/background/db/TestAndroidBrowserHistoryRepository.java
mobile/android/services/src/androidTest/java/org/mozilla/gecko/background/db/TestBookmarks.java
mobile/android/services/src/androidTest/java/org/mozilla/gecko/background/db/TestFennecTabsRepositorySession.java
mobile/android/services/src/androidTest/java/org/mozilla/gecko/background/db/TestFormHistoryRepositorySession.java
mobile/android/services/src/androidTest/java/org/mozilla/gecko/background/db/TestPasswordsRepository.java
mobile/android/services/src/androidTest/java/org/mozilla/gecko/background/db/ThreadedRepositoryTestCase.java
mobile/android/services/src/androidTest/java/org/mozilla/gecko/background/sync/TestStoreTracking.java
mobile/android/services/src/androidTest/java/org/mozilla/gecko/background/sync/helpers/DefaultSessionCreationDelegate.java
mobile/android/services/src/androidTest/java/org/mozilla/gecko/background/sync/helpers/SessionTestHelper.java
mobile/android/services/src/androidTest/java/org/mozilla/gecko/background/sync/helpers/SimpleSuccessCreationDelegate.java
mobile/android/services/src/androidTest/java/org/mozilla/gecko/background/testhelpers/WBORepository.java
mobile/android/services/src/main/java/org/mozilla/gecko/sync/SessionCreateException.java
mobile/android/services/src/main/java/org/mozilla/gecko/sync/middleware/BufferingMiddlewareRepository.java
mobile/android/services/src/main/java/org/mozilla/gecko/sync/middleware/BufferingMiddlewareRepositorySession.java
mobile/android/services/src/main/java/org/mozilla/gecko/sync/middleware/Crypto5MiddlewareRepository.java
mobile/android/services/src/main/java/org/mozilla/gecko/sync/middleware/MiddlewareRepository.java
mobile/android/services/src/main/java/org/mozilla/gecko/sync/middleware/MiddlewareRepositorySession.java
mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/BookmarksRepository.java
mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/HistoryRepository.java
mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/Repository.java
mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/RepositorySession.java
mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/Server15Repository.java
mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/BookmarksRepository.java
mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/BookmarksValidationRepository.java
mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/FennecTabsRepository.java
mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/FormHistoryRepositorySession.java
mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/HistoryRepository.java
mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/PasswordsRepositorySession.java
mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/ThreadedRepository.java
mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/delegates/DeferrableRepositorySessionCreationDelegate.java
mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/delegates/RepositorySessionCreationDelegate.java
mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/ServerSyncStage.java
mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/ValidateBookmarksSyncStage.java
mobile/android/services/src/main/java/org/mozilla/gecko/sync/synchronizer/Synchronizer.java
mobile/android/services/src/main/java/org/mozilla/gecko/sync/synchronizer/SynchronizerSession.java
mobile/android/services/src/main/java/org/mozilla/gecko/sync/synchronizer/SynchronizerSessionDelegate.java
mobile/android/services/src/test/java/org/mozilla/android/sync/test/SynchronizerHelpers.java
mobile/android/services/src/test/java/org/mozilla/android/sync/test/TestRecordsChannel.java
mobile/android/services/src/test/java/org/mozilla/android/sync/test/TestSynchronizer.java
mobile/android/services/src/test/java/org/mozilla/android/sync/test/TestSynchronizerSession.java
mobile/android/services/src/test/java/org/mozilla/android/sync/test/helpers/ExpectSuccessRepositorySessionCreationDelegate.java
mobile/android/services/src/test/java/org/mozilla/gecko/background/testhelpers/WBORepository.java
mobile/android/services/src/test/java/org/mozilla/gecko/sync/middleware/test/TestCrypto5MiddlewareRepositorySession.java
--- a/mobile/android/base/android-services.mozbuild
+++ b/mobile/android/base/android-services.mozbuild
@@ -902,17 +902,16 @@ sync_java_files = [TOPSRCDIR + '/mobile/
     'sync/MetaGlobal.java',
     'sync/MetaGlobalException.java',
     'sync/MetaGlobalMissingEnginesException.java',
     'sync/MetaGlobalNotSetException.java',
     'sync/middleware/BufferingMiddlewareRepository.java',
     'sync/middleware/BufferingMiddlewareRepositorySession.java',
     'sync/middleware/Crypto5MiddlewareRepository.java',
     'sync/middleware/Crypto5MiddlewareRepositorySession.java',
-    'sync/middleware/MiddlewareRepository.java',
     'sync/middleware/MiddlewareRepositorySession.java',
     'sync/middleware/storage/BufferStorage.java',
     'sync/middleware/storage/MemoryBufferStorage.java',
     'sync/net/AbstractBearerTokenAuthHeaderProvider.java',
     'sync/net/AuthHeaderProvider.java',
     'sync/net/BaseResource.java',
     'sync/net/BaseResourceDelegate.java',
     'sync/net/BasicAuthHeaderProvider.java',
@@ -964,27 +963,23 @@ sync_java_files = [TOPSRCDIR + '/mobile/
     'sync/repositories/android/FormHistoryRepositorySession.java',
     'sync/repositories/android/HistoryDataAccessor.java',
     'sync/repositories/android/HistoryRepository.java',
     'sync/repositories/android/HistoryRepositorySession.java',
     'sync/repositories/android/HistorySessionHelper.java',
     'sync/repositories/android/PasswordsRepositorySession.java',
     'sync/repositories/android/RepoUtils.java',
     'sync/repositories/android/SessionHelper.java',
-    'sync/repositories/android/ThreadedRepository.java',
     'sync/repositories/android/VisitsHelper.java',
     'sync/repositories/BookmarkNeedsReparentingException.java',
-    'sync/repositories/BookmarksRepository.java',
     'sync/repositories/ConfigurableServer15Repository.java',
-    'sync/repositories/delegates/DeferrableRepositorySessionCreationDelegate.java',
     'sync/repositories/delegates/DeferredRepositorySessionFetchRecordsDelegate.java',
     'sync/repositories/delegates/DeferredRepositorySessionFinishDelegate.java',
     'sync/repositories/delegates/DeferredRepositorySessionStoreDelegate.java',
     'sync/repositories/delegates/RepositorySessionCleanDelegate.java',
-    'sync/repositories/delegates/RepositorySessionCreationDelegate.java',
     'sync/repositories/delegates/RepositorySessionFetchRecordsDelegate.java',
     'sync/repositories/delegates/RepositorySessionFinishDelegate.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',
@@ -998,17 +993,16 @@ sync_java_files = [TOPSRCDIR + '/mobile/
     'sync/repositories/domain/TabsRecord.java',
     'sync/repositories/domain/TabsRecordFactory.java',
     'sync/repositories/domain/VersionConstants.java',
     'sync/repositories/downloaders/BatchingDownloader.java',
     'sync/repositories/downloaders/BatchingDownloaderController.java',
     'sync/repositories/downloaders/BatchingDownloaderDelegate.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',
@@ -1036,16 +1030,17 @@ sync_java_files = [TOPSRCDIR + '/mobile/
     'sync/repositories/uploaders/Payload.java',
     'sync/repositories/uploaders/PayloadDispatcher.java',
     'sync/repositories/uploaders/PayloadUploadDelegate.java',
     'sync/repositories/uploaders/RecordUploadRunnable.java',
     'sync/repositories/uploaders/UploaderMeta.java',
     'sync/repositories/VersioningDelegateHelper.java',
     'sync/Server15PreviousPostFailedException.java',
     'sync/Server15RecordPostFailedException.java',
+    'sync/SessionCreateException.java',
     'sync/setup/activities/ActivityUtils.java',
     'sync/setup/activities/WebURLFinder.java',
     'sync/setup/Constants.java',
     'sync/setup/InvalidSyncKeyException.java',
     'sync/SharedPreferencesClientsDataDelegate.java',
     'sync/stage/AbstractNonRepositorySyncStage.java',
     'sync/stage/AbstractSessionManagingSyncStage.java',
     'sync/stage/BookmarksServerSyncStage.java',
--- a/mobile/android/services/src/androidTest/java/org/mozilla/gecko/background/db/TestAndroidBrowserBookmarksRepository.java
+++ b/mobile/android/services/src/androidTest/java/org/mozilla/gecko/background/db/TestAndroidBrowserBookmarksRepository.java
@@ -8,54 +8,50 @@ import java.util.ArrayList;
 import org.json.simple.JSONArray;
 import org.mozilla.gecko.background.sync.helpers.BookmarkHelpers;
 import org.mozilla.gecko.background.sync.helpers.ExpectFetchDelegate;
 import org.mozilla.gecko.background.sync.helpers.ExpectFetchSinceDelegate;
 import org.mozilla.gecko.background.sync.helpers.ExpectFinishDelegate;
 import org.mozilla.gecko.background.sync.helpers.ExpectInvalidTypeStoreDelegate;
 import org.mozilla.gecko.db.BrowserContract;
 import org.mozilla.gecko.sync.Utils;
-import org.mozilla.gecko.sync.repositories.InactiveSessionException;
 import org.mozilla.gecko.sync.repositories.NullCursorException;
+import org.mozilla.gecko.sync.repositories.Repository;
 import org.mozilla.gecko.sync.repositories.RepositorySession;
 import org.mozilla.gecko.sync.repositories.android.BookmarksDataAccessor;
 import org.mozilla.gecko.sync.repositories.android.BookmarksRepository;
 import org.mozilla.gecko.sync.repositories.android.BookmarksRepositorySession;
-import org.mozilla.gecko.sync.repositories.android.ThreadedRepository;
 import org.mozilla.gecko.sync.repositories.android.DataAccessor;
 import org.mozilla.gecko.sync.repositories.android.BrowserContractHelpers;
 import org.mozilla.gecko.sync.repositories.android.RepoUtils;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionCreationDelegate;
 import org.mozilla.gecko.sync.repositories.domain.BookmarkRecord;
 import org.mozilla.gecko.sync.repositories.domain.Record;
 
 import android.content.ContentValues;
 import android.content.Context;
 import android.database.Cursor;
 
 public class TestAndroidBrowserBookmarksRepository extends ThreadedRepositoryTestCase {
 
   @Override
-  protected ThreadedRepository getRepository() {
+  protected Repository getRepository() {
 
     /**
      * Override this chain in order to avoid our test code having to create two
      * sessions all the time.
      */
     return new BookmarksRepository() {
       @Override
-      protected void sessionCreator(RepositorySessionCreationDelegate delegate, Context context) {
-        BookmarksRepositorySession session;
-        session = new BookmarksRepositorySession(this, context) {
+      public RepositorySession createSession(Context context) {
+        return new BookmarksRepositorySession(this, context) {
           @Override
           protected synchronized void trackGUID(String guid) {
             System.out.println("Ignoring trackGUID call: this is a test!");
           }
         };
-        delegate.deferredCreationDelegate().onSessionCreated(session);
       }
     };
   }
 
   @Override
   protected DataAccessor getDataAccessor() {
     return new BookmarksDataAccessor(getApplicationContext());
   }
@@ -82,17 +78,17 @@ public class TestAndroidBrowserBookmarks
   // NOTE NOTE NOTE
   // Must store folder before records if we we are checking that the
   // records returned are the same as those sent in. If the folder isn't stored
   // first, the returned records won't be identical to those stored because we
   // aren't able to find the parent name/guid when we do a fetch. If you don't want
   // to store a folder first, store your record in "mobile" or one of the folders
   // that always exists.
 
-  public void testFetchOneWithChildren() {
+  public void testFetchOneWithChildren() throws Exception {
     BookmarkRecord folder = BookmarkHelpers.createFolder1();
     BookmarkRecord bookmark1 = BookmarkHelpers.createBookmark1();
     BookmarkRecord bookmark2 = BookmarkHelpers.createBookmark2();
 
     RepositorySession session = createAndBeginSession();
 
     Record[] records = new Record[] { folder, bookmark1, bookmark2 };
     performWait(storeManyRunnable(session, records));
@@ -154,17 +150,17 @@ public class TestAndroidBrowserBookmarks
   }
 
   @Override
   public void testStore() {
     basicStoreTest(BookmarkHelpers.createBookmark1());
   }
 
 
-  public void testStoreFolder() {
+  public void testStoreFolder() throws Exception {
     basicStoreTest(BookmarkHelpers.createFolder1());
   }
 
   /**
    * TODO: 2011-12-24, tests disabled because we no longer fail
    * a store call if we get an unknown record type.
    */
   /*
@@ -185,78 +181,78 @@ public class TestAndroidBrowserBookmarks
     basicStoreFailTest(BookmarkHelpers.createLivemark());
   }
 
   public void testStoreSeparator() {
     basicStoreFailTest(BookmarkHelpers.createSeparator());
   }
    */
 
-  protected void basicStoreFailTest(Record record) {
+  protected void basicStoreFailTest(Record record) throws Exception {
     final RepositorySession session = createAndBeginSession();
     performWait(storeRunnable(session, record, new ExpectInvalidTypeStoreDelegate()));
     dispose(session);
   }
 
   /*
    * Re-parenting tests
    */
   // Insert two records missing parent, then insert their parent.
   // Make sure they end up with the correct parent on fetch.
-  public void testBasicReparenting() throws InactiveSessionException {
+  public void testBasicReparenting() throws Exception {
     Record[] expected = new Record[] {
         BookmarkHelpers.createBookmark1(),
         BookmarkHelpers.createBookmark2(),
         BookmarkHelpers.createFolder1()
     };
     doMultipleFolderReparentingTest(expected);
   }
 
   // Insert 3 folders and 4 bookmarks in different orders
   // and make sure they come out parented correctly
-  public void testMultipleFolderReparenting1() throws InactiveSessionException {
+  public void testMultipleFolderReparenting1() throws Exception {
     Record[] expected = new Record[] {
         BookmarkHelpers.createBookmark1(),
         BookmarkHelpers.createBookmark2(),
         BookmarkHelpers.createBookmark3(),
         BookmarkHelpers.createFolder1(),
         BookmarkHelpers.createBookmark4(),
         BookmarkHelpers.createFolder3(),
         BookmarkHelpers.createFolder2(),
     };
     doMultipleFolderReparentingTest(expected);
   }
 
-  public void testMultipleFolderReparenting2() throws InactiveSessionException {
+  public void testMultipleFolderReparenting2() throws Exception {
     Record[] expected = new Record[] {
         BookmarkHelpers.createBookmark1(),
         BookmarkHelpers.createBookmark2(),
         BookmarkHelpers.createBookmark3(),
         BookmarkHelpers.createFolder1(),
         BookmarkHelpers.createBookmark4(),
         BookmarkHelpers.createFolder3(),
         BookmarkHelpers.createFolder2(),
     };
     doMultipleFolderReparentingTest(expected);
   }
 
-  public void testMultipleFolderReparenting3() throws InactiveSessionException {
+  public void testMultipleFolderReparenting3() throws Exception {
     Record[] expected = new Record[] {
         BookmarkHelpers.createBookmark1(),
         BookmarkHelpers.createBookmark2(),
         BookmarkHelpers.createBookmark3(),
         BookmarkHelpers.createFolder1(),
         BookmarkHelpers.createBookmark4(),
         BookmarkHelpers.createFolder3(),
         BookmarkHelpers.createFolder2(),
     };
     doMultipleFolderReparentingTest(expected);
   }
 
-  private void doMultipleFolderReparentingTest(Record[] expected) throws InactiveSessionException {
+  private void doMultipleFolderReparentingTest(Record[] expected) throws Exception {
     final RepositorySession session = createAndBeginSession();
     doStore(session, expected);
     ExpectFetchDelegate delegate = preparedExpectFetchDelegate(expected);
     performWait(fetchAllRunnable(session, delegate));
     performWait(finishRunnable(session, new ExpectFinishDelegate()));
   }
 
   /*
@@ -272,17 +268,17 @@ public class TestAndroidBrowserBookmarks
   /*
    * More complicated situation in which we insert a folder
    * followed by a couple of its children. We then insert
    * the folder again but with a different guid. Children
    * must still get correct parent when they are fetched.
    * Store a record after with the new guid as the parent
    * and make sure it works as well.
    */
-  public void testStoreIdenticalFoldersWithChildren() {
+  public void testStoreIdenticalFoldersWithChildren() throws Exception {
     final RepositorySession session = createAndBeginSession();
     Record record0 = BookmarkHelpers.createFolder1();
 
     // Get timestamp so that the conflicting folder that we store below is newer.
     // Children won't come back on this fetch since they haven't been stored, so remove them
     // before our delegate throws a failure.
     BookmarkRecord rec0 = (BookmarkRecord) record0;
     rec0.children = new JSONArray();
@@ -363,17 +359,17 @@ public class TestAndroidBrowserBookmarks
     cleanMultipleRecords(
         BookmarkHelpers.createBookmarkInMobileFolder1(),
         BookmarkHelpers.createBookmarkInMobileFolder2(),
         BookmarkHelpers.createBookmark1(),
         BookmarkHelpers.createBookmark2(),
         BookmarkHelpers.createFolder1());
   }
 
-  public void testBasicPositioning() {
+  public void testBasicPositioning() throws Exception {
     final RepositorySession session = createAndBeginSession();
     Record[] expected = new Record[] {
         BookmarkHelpers.createBookmark1(),
         BookmarkHelpers.createFolder1(),
         BookmarkHelpers.createBookmark2()
     };
     System.out.println("TEST: Inserting " + expected[0].guid + ", "
         + expected[1].guid + ", "
@@ -399,17 +395,17 @@ public class TestAndroidBrowserBookmarks
         System.out.println("TEST: found " + rec.guid);
       }
     }
     assertTrue(foundFolder);
     assertEquals(2, found);
     dispose(session);
   }
 
-  public void testSqlInjectPurgeDeleteAndUpdateByGuid() {
+  public void testSqlInjectPurgeDeleteAndUpdateByGuid() throws Exception {
     // Some setup.
     RepositorySession session = createAndBeginSession();
     DataAccessor db = getDataAccessor();
 
     ContentValues cv = new ContentValues();
     cv.put(BrowserContract.SyncColumns.IS_DELETED, 1);
 
     // Create and insert 2 bookmarks, 2nd one is evil (attempts injection).
@@ -480,17 +476,17 @@ public class TestAndroidBrowserBookmarks
 
   protected Cursor getAllBookmarks() {
     Context context = getApplicationContext();
     Cursor cur = context.getContentResolver().query(BrowserContractHelpers.BOOKMARKS_CONTENT_URI,
         BrowserContractHelpers.BookmarkColumns, null, null, null);
     return cur;
   }
 
-  public void testSqlInjectFetch() {
+  public void testSqlInjectFetch() throws Exception {
     // Some setup.
     RepositorySession session = createAndBeginSession();
     DataAccessor db = getDataAccessor();
 
     // Create and insert 4 bookmarks, last one is evil (attempts injection).
     BookmarkRecord bmk1 = BookmarkHelpers.createBookmark1();
     BookmarkRecord bmk2 = BookmarkHelpers.createBookmark2();
     BookmarkRecord bmk3 = BookmarkHelpers.createBookmark3();
@@ -528,17 +524,17 @@ public class TestAndroidBrowserBookmarks
         cur.moveToNext();
       }
     } finally {
       cur.close();
     }
     dispose(session);
   }
 
-  public void testSqlInjectDelete() {
+  public void testSqlInjectDelete() throws Exception {
     // Some setup.
     RepositorySession session = createAndBeginSession();
     DataAccessor db = getDataAccessor();
 
     // Create and insert 2 bookmarks, 2nd one is evil (attempts injection).
     BookmarkRecord bmk1 = BookmarkHelpers.createBookmark1();
     BookmarkRecord bmk2 = BookmarkHelpers.createBookmark2();
     bmk2.guid = "' or '1'='1";
@@ -573,17 +569,17 @@ public class TestAndroidBrowserBookmarks
     }
     dispose(session);
   }
 
   /**
    * Verify that data accessor's bulkInsert actually inserts.
    * @throws NullCursorException
    */
-  public void testBulkInsert() throws NullCursorException {
+  public void testBulkInsert() throws Exception {
     RepositorySession session = createAndBeginSession();
     DataAccessor db = getDataAccessor();
 
     // Have to set androidID of parent manually.
     Cursor cur = db.fetch(new String[] { "mobile" } );
     assertEquals(1, cur.getCount());
     cur.moveToFirst();
     int mobileAndroidID = RepoUtils.getIntFromCursor(cur, BrowserContract.Bookmarks._ID);
--- a/mobile/android/services/src/androidTest/java/org/mozilla/gecko/background/db/TestAndroidBrowserHistoryRepository.java
+++ b/mobile/android/services/src/androidTest/java/org/mozilla/gecko/background/db/TestAndroidBrowserHistoryRepository.java
@@ -6,53 +6,50 @@ package org.mozilla.gecko.background.db;
 import java.util.ArrayList;
 
 import org.json.simple.JSONObject;
 import org.mozilla.gecko.background.sync.helpers.ExpectFetchDelegate;
 import org.mozilla.gecko.background.sync.helpers.HistoryHelpers;
 import org.mozilla.gecko.db.BrowserContract;
 import org.mozilla.gecko.sync.Utils;
 import org.mozilla.gecko.sync.repositories.NullCursorException;
+import org.mozilla.gecko.sync.repositories.Repository;
 import org.mozilla.gecko.sync.repositories.RepositorySession;
 import org.mozilla.gecko.sync.repositories.android.HistoryDataAccessor;
 import org.mozilla.gecko.sync.repositories.android.HistoryRepository;
 import org.mozilla.gecko.sync.repositories.android.HistoryRepositorySession;
-import org.mozilla.gecko.sync.repositories.android.ThreadedRepository;
 import org.mozilla.gecko.sync.repositories.android.DataAccessor;
 import org.mozilla.gecko.sync.repositories.android.BrowserContractHelpers;
 import org.mozilla.gecko.sync.repositories.android.RepoUtils;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionCreationDelegate;
 import org.mozilla.gecko.sync.repositories.domain.HistoryRecord;
 import org.mozilla.gecko.sync.repositories.domain.Record;
 
 import android.content.ContentValues;
 import android.content.Context;
 import android.database.Cursor;
 import android.net.Uri;
 
 public class TestAndroidBrowserHistoryRepository extends ThreadedRepositoryTestCase {
 
   @Override
-  protected ThreadedRepository getRepository() {
+  protected Repository getRepository() {
 
     /**
      * Override this chain in order to avoid our test code having to create two
      * sessions all the time.
      */
     return new HistoryRepository() {
       @Override
-      protected void sessionCreator(RepositorySessionCreationDelegate delegate, Context context) {
-        HistoryRepositorySession session;
-        session = new HistoryRepositorySession(this, context) {
+      public RepositorySession createSession(Context context) {
+        return new HistoryRepositorySession(this, context) {
           @Override
           protected synchronized void trackGUID(String guid) {
             System.out.println("Ignoring trackGUID call: this is a test!");
           }
         };
-        delegate.onSessionCreated(session);
       }
     };
   }
 
   @Override
   protected DataAccessor getDataAccessor() {
     return new HistoryDataAccessor(getApplicationContext());
   }
@@ -197,17 +194,17 @@ public class TestAndroidBrowserHistoryRe
     assertFalse(record1.equalPayloads(record2));
   }
 
   /*
    * Tests for adding some visits to a history record
    * and doing a fetch.
    */
   @SuppressWarnings("unchecked")
-  public void testAddOneVisit() {
+  public void testAddOneVisit() throws Exception {
     final RepositorySession session = createAndBeginSession();
 
     HistoryRecord record0 = HistoryHelpers.createHistory3();
     performWait(storeRunnable(session, record0));
 
     // Add one visit to the count and put in a new
     // last visited date.
     ContentValues cv = new ContentValues();
@@ -224,17 +221,17 @@ public class TestAndroidBrowserHistoryRe
     expectedVisit.put("type", 1L);
     record0.visits.add(expectedVisit);
 
     performWait(fetchRunnable(session, new String[] { record0.guid }, new ExpectFetchDelegate(new Record[] { record0 })));
     closeDataAccessor(dataAccessor);
   }
 
   @SuppressWarnings("unchecked")
-  public void testAddMultipleVisits() {
+  public void testAddMultipleVisits() throws Exception {
     final RepositorySession session = createAndBeginSession();
 
     HistoryRecord record0 = HistoryHelpers.createHistory4();
     performWait(storeRunnable(session, record0));
 
     // Add three visits to the count and put in a new
     // last visited date.
     ContentValues cv = new ContentValues();
@@ -265,17 +262,17 @@ public class TestAndroidBrowserHistoryRe
     ExpectFetchDelegate delegate = new ExpectFetchDelegate(new Record[] { record0 });
     performWait(fetchRunnable(session, new String[] { record0.guid }, delegate));
 
     Record fetched = delegate.records.get(0);
     assertTrue(record0.equalPayloads(fetched));
     closeDataAccessor(dataAccessor);
   }
 
-  public void testInvalidHistoryItemIsSkipped() throws NullCursorException {
+  public void testInvalidHistoryItemIsSkipped() throws Exception {
     final HistoryRepositorySession session = (HistoryRepositorySession) createAndBeginSession();
     final DataAccessor dbHelper = new HistoryDataAccessor(getApplicationContext());
 
     final long now = System.currentTimeMillis();
     final HistoryRecord emptyURL = new HistoryRecord(Utils.generateGuid(), "history", now, false);
     final HistoryRecord noVisits = new HistoryRecord(Utils.generateGuid(), "history", now, false);
     final HistoryRecord aboutURL = new HistoryRecord(Utils.generateGuid(), "history", now, false);
 
@@ -307,17 +304,17 @@ public class TestAndroidBrowserHistoryRe
     all.close();
 
     // But aren't returned by fetching.
     performWait(fetchAllRunnable(session, new Record[] {}));
 
     session.abort();
   }
 
-  public void testSqlInjectPurgeDelete() {
+  public void testSqlInjectPurgeDelete() throws Exception {
     // Some setup.
     RepositorySession session = createAndBeginSession();
     final DataAccessor db = getDataAccessor();
 
     try {
       ContentValues cv = new ContentValues();
       cv.put(BrowserContract.SyncColumns.IS_DELETED, 1);
 
@@ -392,17 +389,17 @@ public class TestAndroidBrowserHistoryRe
 
   protected Cursor getAllHistory() {
     Context context = getApplicationContext();
     Cursor cur = context.getContentResolver().query(BrowserContractHelpers.HISTORY_CONTENT_URI,
         BrowserContractHelpers.HistoryColumns, null, null, null);
     return cur;
   }
 
-  public void testDataAccessorBulkInsert() throws NullCursorException {
+  public void testDataAccessorBulkInsert() throws Exception {
     final HistoryRepositorySession session = (HistoryRepositorySession) createAndBeginSession();
     final HistoryDataAccessor db = new HistoryDataAccessor(getApplicationContext());
 
     ArrayList<HistoryRecord> records = new ArrayList<HistoryRecord>();
     records.add(HistoryHelpers.createHistory1());
     records.add(HistoryHelpers.createHistory2());
     records.add(HistoryHelpers.createHistory3());
     db.bulkInsert(records);
--- a/mobile/android/services/src/androidTest/java/org/mozilla/gecko/background/db/TestBookmarks.java
+++ b/mobile/android/services/src/androidTest/java/org/mozilla/gecko/background/db/TestBookmarks.java
@@ -9,17 +9,16 @@ import java.util.Collections;
 import java.util.HashSet;
 import java.util.Iterator;
 
 import org.json.simple.JSONArray;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.background.common.log.Logger;
 import org.mozilla.gecko.background.helpers.AndroidSyncTestCase;
 import org.mozilla.gecko.background.sync.helpers.BookmarkHelpers;
-import org.mozilla.gecko.background.sync.helpers.SimpleSuccessCreationDelegate;
 import org.mozilla.gecko.background.sync.helpers.SimpleSuccessFetchDelegate;
 import org.mozilla.gecko.background.sync.helpers.SimpleSuccessFinishDelegate;
 import org.mozilla.gecko.background.sync.helpers.SimpleSuccessStoreDelegate;
 import org.mozilla.gecko.db.BrowserContract;
 import org.mozilla.gecko.db.BrowserContract.Bookmarks;
 import org.mozilla.gecko.sync.SyncException;
 import org.mozilla.gecko.sync.Utils;
 import org.mozilla.gecko.sync.repositories.InactiveSessionException;
@@ -43,17 +42,17 @@ import android.net.Uri;
 
 public class TestBookmarks extends AndroidSyncTestCase {
 
   protected static final String LOG_TAG = "BookmarksTest";
 
   /**
    * Trivial test that pinned items will be skipped if present in the DB.
    */
-  public void testPinnedItemsAreNotRetrieved() {
+  public void testPinnedItemsAreNotRetrieved() throws SyncException {
     final BookmarksRepository repo = new BookmarksRepository();
 
     // Ensure that they exist.
     setUpFennecPinnedItemsRecord();
 
     // They're there in the DB…
     final ArrayList<String> roots = fetchChildrenDirect(Bookmarks.FIXED_ROOT_ID);
     Logger.info(LOG_TAG, "Roots: " + roots);
@@ -64,17 +63,17 @@ public class TestBookmarks extends Andro
     assertTrue(pinned.contains("dapinneditem"));
 
     // … but not when we fetch.
     final ArrayList<String> guids = fetchGUIDs(repo);
     assertFalse(guids.contains(Bookmarks.PINNED_FOLDER_GUID));
     assertFalse(guids.contains("dapinneditem"));
   }
 
-  public void testRetrieveFolderHasAccurateChildren() {
+  public void testRetrieveFolderHasAccurateChildren() throws SyncException {
     BookmarksRepository repo = new BookmarksRepository();
 
     final long now = System.currentTimeMillis();
 
     final String folderGUID = "eaaaaaaaafff";
     BookmarkRecord folder    = new BookmarkRecord(folderGUID,     "bookmarks", now - 5, false);
     BookmarkRecord bookmarkA = new BookmarkRecord("daaaaaaaaaaa", "bookmarks", now - 1, false);
     BookmarkRecord bookmarkB = new BookmarkRecord("baaaaaaaabbb", "bookmarks", now - 3, false);
@@ -174,17 +173,17 @@ public class TestBookmarks extends Andro
     // Ensure that records are ordered even if we re-process the folder.
     wipe();
     storeRecordsInSession(repo, parentLast, null);
     folder.lastModified++;
     storeRecordsInSession(repo, folderOnly, null);
     assertChildrenAreOrdered(repo, folderGUID, children);
   }
 
-  public void testMergeFoldersPreservesSaneOrder() {
+  public void testMergeFoldersPreservesSaneOrder() throws SyncException {
     BookmarksRepository repo = new BookmarksRepository();
 
     final long now = System.currentTimeMillis();
     final String folderGUID = "mobile";
 
     wipe();
     final long mobile = setUpFennecMobileRecord();
 
@@ -266,17 +265,17 @@ public class TestBookmarks extends Andro
     });
   }
 
   /**
    * Apply a folder record whose children array is already accurately
    * stored in the database. Verify that the parent folder is not flagged
    * for reupload (i.e., that its modified time is *ahem* unmodified).
    */
-  public void testNoReorderingMeansNoReupload() {
+  public void testNoReorderingMeansNoReupload() throws SyncException {
     BookmarksRepository repo = new BookmarksRepository();
 
     final long now = System.currentTimeMillis();
 
     final String folderGUID = "eaaaaaaaafff";
     BookmarkRecord folder    = new BookmarkRecord(folderGUID,     "bookmarks", now -5, false);
     BookmarkRecord bookmarkA = new BookmarkRecord("daaaaaaaaaaa", "bookmarks", now -1, false);
     BookmarkRecord bookmarkB = new BookmarkRecord("baaaaaaaabbb", "bookmarks", now -3, false);
@@ -350,17 +349,17 @@ public class TestBookmarks extends Andro
     assertFalse(tracked.contains(folderGUID));
   }
 
   /**
    * Exercise the deletion of folders when their children have not been
    * marked as deleted. In a database with constraints, this would fail
    * if we simply deleted the records, so we move them first.
    */
-  public void testFolderDeletionOrphansChildren() {
+  public void testFolderDeletionOrphansChildren() throws SyncException {
     BookmarksRepository repo = new BookmarksRepository();
 
     long now = System.currentTimeMillis();
 
     // Add a folder and four children.
     final String folderGUID = "eaaaaaaaafff";
     BookmarkRecord folder    = new BookmarkRecord(folderGUID,     "bookmarks", now -5, false);
     BookmarkRecord bookmarkA = new BookmarkRecord("daaaaaaaaaaa", "bookmarks", now -1, false);
@@ -462,17 +461,17 @@ public class TestBookmarks extends Andro
     assertChildrenAreDirect(toolbarID, new String[] {});
   }
 
   /**
    * A test where we expect to replace a local folder with a new folder (with a
    * new GUID), whilst adding children to it. Verifies that replace and insert
    * co-operate.
    */
-  public void testInsertAndReplaceGuid() {
+  public void testInsertAndReplaceGuid() throws SyncException {
     BookmarksRepository repo = new BookmarksRepository();
     wipe();
 
     BookmarkRecord folder1 = BookmarkHelpers.createFolder1();
     BookmarkRecord folder2 = BookmarkHelpers.createFolder2(); // child of folder1
     BookmarkRecord folder3 = BookmarkHelpers.createFolder3(); // child of folder2
     BookmarkRecord bmk1 = BookmarkHelpers.createBookmark1();  // child of folder1
     BookmarkRecord bmk2 = BookmarkHelpers.createBookmark2();  // child of folder1
@@ -512,17 +511,17 @@ public class TestBookmarks extends Andro
     assertEquals(bmk4.title, fetchGUID(repo, bmk4.guid).title);
   }
 
   /**
    * A test where we expect to replace a local folder with a new folder (with a
    * new title but the same GUID), whilst adding children to it. Verifies that
    * replace and insert co-operate.
    */
-  public void testInsertAndReplaceTitle() {
+  public void testInsertAndReplaceTitle() throws SyncException {
     BookmarksRepository repo = new BookmarksRepository();
     wipe();
 
     BookmarkRecord folder1 = BookmarkHelpers.createFolder1();
     BookmarkRecord folder2 = BookmarkHelpers.createFolder2(); // child of folder1
     BookmarkRecord folder3 = BookmarkHelpers.createFolder3(); // child of folder2
     BookmarkRecord bmk1 = BookmarkHelpers.createBookmark1();  // child of folder1
     BookmarkRecord bmk2 = BookmarkHelpers.createBookmark2();  // child of folder1
@@ -558,40 +557,16 @@ public class TestBookmarks extends Andro
     assertChildrenAreUnordered(repo, folder2.guid, new Record[] { bmk3, folder3 });
     assertChildrenAreUnordered(repo, folder3.guid, new Record[] { bmk4 });
 
     assertEquals(folder1.title, fetchGUID(repo, folder1.guid).title);
     assertEquals(bmk2.title, fetchGUID(repo, bmk2.guid).title);
   }
 
   /**
-   * Create and begin a new session, handing control to the delegate when started.
-   * Returns when the delegate has notified.
-   */
-  private void inBegunSession(final BookmarksRepository repo) {
-    Runnable go = new Runnable() {
-      @Override
-      public void run() {
-        repo.createSession(new SimpleSuccessCreationDelegate() {
-          @Override
-          public void onSessionCreated(final RepositorySession session) {
-            try {
-              session.begin();
-              performNotify();
-            } catch (SyncException e) {
-              performNotify("Begin failed", e);
-            }
-          }
-        }, getApplicationContext());
-      }
-    };
-    performWait(go);
-  }
-
-  /**
    * Finish the provided session, notifying on success.
    *
    * @param session
    */
   public void finishAndNotify(final RepositorySession session) {
     try {
       session.finish(new SimpleSuccessFinishDelegate() {
         @Override
@@ -611,147 +586,129 @@ public class TestBookmarks extends Andro
    *
    * Optionally populates a provided Collection with tracked items.
    * @param repo
    * @param records
    * @param tracked
    */
   private void storeRecordsInSession(BookmarksRepository repo,
                                     final BookmarkRecord[] records,
-                                    final Collection<String> tracked) {
-    repo.createSession(new SimpleSuccessCreationDelegate() {
-      @Override
-      public void onSessionCreated(final RepositorySession session) {
-        try {
-          session.begin();
-        } catch (SyncException e) {
-          performNotify("Begin failed", e);
-        }
+                                    final Collection<String> tracked) throws SyncException {
+    final RepositorySession session = repo.createSession(getApplicationContext());
+    session.begin();
 
-        RepositorySessionStoreDelegate storeDelegate = new SimpleSuccessStoreDelegate() {
-          @Override
-          public void onStoreCompleted() {
-            // Pass back whatever we tracked.
-            if (tracked != null) {
-              Iterator<String> iter = session.getTrackedRecordIDs();
-              while (iter.hasNext()) {
-                tracked.add(iter.next());
-              }
-            }
-            finishAndNotify(session);
-          }
-
-          @Override
-          public void onRecordStoreSucceeded(String guid) {
-          }
-
-          @Override
-          public void onRecordStoreReconciled(String guid, String oldGuid, Integer newVersion) {
-          }
-
-          @Override
-          public void onStoreFailed(Exception e) {
-
-          }
-        };
-        session.setStoreDelegate(storeDelegate);
-        for (BookmarkRecord record : records) {
-          try {
-            session.store(record);
-          } catch (NoStoreDelegateException e) {
-            // Never happens.
+    RepositorySessionStoreDelegate storeDelegate = new SimpleSuccessStoreDelegate() {
+      @Override
+      public void onStoreCompleted() {
+        // Pass back whatever we tracked.
+        if (tracked != null) {
+          Iterator<String> iter = session.getTrackedRecordIDs();
+          while (iter.hasNext()) {
+            tracked.add(iter.next());
           }
         }
-        session.storeDone();
+        finishAndNotify(session);
+      }
+
+      @Override
+      public void onRecordStoreSucceeded(String guid) {
+      }
+
+      @Override
+      public void onRecordStoreReconciled(String guid, String oldGuid, Integer newVersion) {
       }
-    }, getApplicationContext());
+
+      @Override
+      public void onStoreFailed(Exception e) {
+
+      }
+    };
+    session.setStoreDelegate(storeDelegate);
+    for (BookmarkRecord record : records) {
+      try {
+        session.store(record);
+      } catch (NoStoreDelegateException e) {
+        // Never happens.
+      }
+    }
+    session.storeDone();
   }
 
-  public ArrayList<String> fetchGUIDs(BookmarksRepository repo) {
-    final ArrayList<String> fetchedGUIDs = new ArrayList<String>();
+  private ArrayList<String> fetchGUIDs(BookmarksRepository repo) throws SyncException {
+    final ArrayList<String> fetchedGUIDs = new ArrayList<>();
 
-    repo.createSession(new SimpleSuccessCreationDelegate() {
+    final RepositorySession session = repo.createSession(getApplicationContext());
+    session.begin();
+
+    RepositorySessionFetchRecordsDelegate fetchDelegate = new SimpleSuccessFetchDelegate() {
       @Override
-      public void onSessionCreated(final RepositorySession session) {
-        try {
-          session.begin();
-        } catch (SyncException e) {
-          performNotify("Begin failed", e);
-        }
+      public void onFetchedRecord(Record record) {
+        fetchedGUIDs.add(record.guid);
+      }
 
-        RepositorySessionFetchRecordsDelegate fetchDelegate = new SimpleSuccessFetchDelegate() {
-          @Override
-          public void onFetchedRecord(Record record) {
-            fetchedGUIDs.add(record.guid);
-          }
+      @Override
+      public void onFetchCompleted() {
+        finishAndNotify(session);
+      }
 
-          @Override
-          public void onFetchCompleted() {
-            finishAndNotify(session);
-          }
+      @Override
+      public void onBatchCompleted() {
 
-          @Override
-          public void onBatchCompleted() {
-
-          }
-        };
-        session.fetchModified(fetchDelegate);
       }
-    }, getApplicationContext());
+    };
+    session.fetchModified(fetchDelegate);
 
     return fetchedGUIDs;
   }
 
   private BookmarkRecord fetchGUID(BookmarksRepository repo,
-                                  final String guid) {
+                                  final String guid) throws SyncException {
     Logger.info(LOG_TAG, "Fetching for " + guid);
     final ArrayList<Record> fetchedRecords = new ArrayList<>();
 
-    repo.createSession(new SimpleSuccessCreationDelegate() {
+    final RepositorySession session = repo.createSession(getApplicationContext());
+    session.begin();
+
+    final RepositorySessionFetchRecordsDelegate fetchDelegate = new SimpleSuccessFetchDelegate() {
       @Override
-      public void onSessionCreated(final RepositorySession session) {
-        try {
-          session.begin();
-        } catch (SyncException e) {
-          performNotify("Begin failed", e);
-        }
+      public void onFetchedRecord(Record record) {
+        fetchedRecords.add(record);
+      }
 
-        RepositorySessionFetchRecordsDelegate fetchDelegate = new SimpleSuccessFetchDelegate() {
-          @Override
-          public void onFetchedRecord(Record record) {
-            fetchedRecords.add(record);
-          }
+      @Override
+      public void onFetchCompleted() {
+        finishAndNotify(session);
+      }
 
-          @Override
-          public void onFetchCompleted() {
-            finishAndNotify(session);
-          }
+      @Override
+      public void onBatchCompleted() {
+
+      }
+    };
 
-          @Override
-          public void onBatchCompleted() {
-
-          }
-        };
+    performWait(new Runnable() {
+      @Override
+      public void run() {
         try {
-          session.fetch(new String[] { guid }, fetchDelegate);
+          session.fetch(new String[]{guid}, fetchDelegate);
         } catch (InactiveSessionException e) {
-          performNotify("Session is inactive.", e);
+          performNotify(e);
         }
       }
-    }, getApplicationContext());
+    });
 
     assertEquals(1, fetchedRecords.size());
     Record fetchedRecord = fetchedRecords.get(0);
 
     Logger.info(LOG_TAG, "Fetched " + fetchedRecord);
     return (BookmarkRecord) fetchedRecord;
   }
 
   public JSONArray fetchChildrenForGUID(BookmarksRepository repo,
-      final String guid) {
+      final String guid) throws SyncException {
     return fetchGUID(repo, guid).children;
   }
 
   @SuppressWarnings("unchecked")
   protected static JSONArray childrenFromRecords(BookmarkRecord... records) {
     JSONArray children = new JSONArray();
     for (BookmarkRecord record : records) {
       children.add(record.guid);
@@ -922,29 +879,29 @@ public class TestBookmarks extends Andro
     return dataAccessor;
   }
 
   protected void wipe() {
     Logger.debug(getName(), "Wiping.");
     getDataAccessor().wipe();
   }
 
-  protected void assertChildrenAreOrdered(BookmarksRepository repo, String guid, Record[] expected) {
+  protected void assertChildrenAreOrdered(BookmarksRepository repo, String guid, Record[] expected) throws SyncException {
     Logger.debug(getName(), "Fetching children...");
     JSONArray folderChildren = fetchChildrenForGUID(repo, guid);
 
     assertTrue(folderChildren != null);
     Logger.debug(getName(), "Children are " + folderChildren.toJSONString());
     assertEquals(expected.length, folderChildren.size());
     for (int i = 0; i < expected.length; ++i) {
       assertEquals(expected[i].guid, ((String) folderChildren.get(i)));
     }
   }
 
-  protected void assertChildrenAreUnordered(BookmarksRepository repo, String guid, Record[] expected) {
+  protected void assertChildrenAreUnordered(BookmarksRepository repo, String guid, Record[] expected) throws SyncException {
     Logger.debug(getName(), "Fetching children...");
     JSONArray folderChildren = fetchChildrenForGUID(repo, guid);
 
     assertTrue(folderChildren != null);
     Logger.debug(getName(), "Children are " + folderChildren.toJSONString());
     assertEquals(expected.length, folderChildren.size());
     for (Record record : expected) {
       folderChildren.contains(record.guid);
--- a/mobile/android/services/src/androidTest/java/org/mozilla/gecko/background/db/TestFennecTabsRepositorySession.java
+++ b/mobile/android/services/src/androidTest/java/org/mozilla/gecko/background/db/TestFennecTabsRepositorySession.java
@@ -5,23 +5,23 @@ package org.mozilla.gecko.background.db;
 
 import org.json.simple.JSONArray;
 import org.mozilla.gecko.background.helpers.AndroidSyncTestCase;
 import org.mozilla.gecko.background.sync.helpers.ExpectFetchDelegate;
 import org.mozilla.gecko.background.sync.helpers.SessionTestHelper;
 import org.mozilla.gecko.background.testhelpers.MockClientsDataDelegate;
 import org.mozilla.gecko.db.BrowserContract;
 import org.mozilla.gecko.db.BrowserContract.Clients;
+import org.mozilla.gecko.sync.SessionCreateException;
 import org.mozilla.gecko.sync.repositories.NoContentProviderException;
 import org.mozilla.gecko.sync.repositories.RepositorySession;
 import org.mozilla.gecko.sync.repositories.android.BrowserContractHelpers;
 import org.mozilla.gecko.sync.repositories.android.ClientsDatabaseAccessor;
 import org.mozilla.gecko.sync.repositories.android.FennecTabsRepository;
 import org.mozilla.gecko.sync.repositories.android.FennecTabsRepository.FennecTabsRepositorySession;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionCreationDelegate;
 import org.mozilla.gecko.sync.repositories.domain.ClientRecord;
 import org.mozilla.gecko.sync.repositories.domain.Record;
 import org.mozilla.gecko.sync.repositories.domain.TabsRecord;
 
 import android.content.ContentProviderClient;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.database.Cursor;
@@ -101,37 +101,35 @@ public class TestFennecTabsRepositorySes
 
   protected FennecTabsRepository getRepository() {
     /**
      * Override this chain in order to avoid our test code having to create two
      * sessions all the time.
      */
     return new FennecTabsRepository(clientsDataDelegate) {
       @Override
-      public void createSession(RepositorySessionCreationDelegate delegate,
-                                Context context) {
+      public RepositorySession createSession(Context context) throws SessionCreateException {
         try {
-          final FennecTabsRepositorySession session = new FennecTabsRepositorySession(this, context) {
+          return new FennecTabsRepositorySession(this, context) {
             @Override
             protected synchronized void trackGUID(String guid) {
             }
 
             @Override
             protected String localClientSelection() {
               return TEST_TABS_CLIENT_GUID_IS_LOCAL_SELECTION;
             }
 
             @Override
             protected String[] localClientSelectionArgs() {
               return TEST_TABS_CLIENT_GUID_IS_LOCAL_SELECTION_ARGS;
             }
           };
-          delegate.onSessionCreated(session);
         } catch (Exception e) {
-          delegate.onSessionCreateFailed(e);
+          throw new SessionCreateException(e);
         }
       }
     };
   }
 
   protected FennecTabsRepositorySession createSession() {
     return (FennecTabsRepositorySession) SessionTestHelper.createSession(
         getApplicationContext(),
@@ -207,26 +205,26 @@ public class TestFennecTabsRepositorySes
       assertEquals(cursor.getCount(), tabsRecord.tabs.size());
 
       return tabsRecord;
     } finally {
       cursor.close();
     }
   }
 
-  public void testFetchAll() throws NoContentProviderException, RemoteException {
+  public void testFetchAll() throws Exception {
     final TabsRecord tabsRecord = insertTestTabsAndExtractTabsRecord();
 
     final FennecTabsRepositorySession session = createAndBeginSession();
     performWait(fetchAllRunnable(session, new Record[] { tabsRecord }));
 
     session.abort();
   }
 
-  public void testFetchSince() throws NoContentProviderException, RemoteException {
+  public void testFetchSince() throws Exception {
     final TabsRecord tabsRecord = insertTestTabsAndExtractTabsRecord();
 
     final FennecTabsRepositorySession session = createAndBeginSession();
 
     // Not all tabs are modified after this, but the record should contain them all.
     performWait(fetchSinceRunnable(session, 1000, new Record[] { tabsRecord }));
 
     // No tabs are modified after this, but our client name has changed in the interim.
@@ -243,17 +241,17 @@ public class TestFennecTabsRepositorySes
     clientsDataDelegate.setClientName("new client name", System.currentTimeMillis());
     performWait(fetchSinceRunnable(session, now, new Record[] { tabsRecord }));
 
     session.abort();
   }
 
   // Verify that storing a tabs record writes a clients record with the correct
   // device type to the Fennec clients provider.
-  public void testStore() throws NoContentProviderException, RemoteException {
+  public void testStore() throws Exception {
     // Get a valid tabsRecord to write.
     final TabsRecord tabsRecord = insertTestTabsAndExtractTabsRecord();
     deleteAllTestTabs(tabsClient);
     deleteTestClient(clientsClient);
 
     final ContentResolver cr = getApplicationContext().getContentResolver();
     final ContentProviderClient clientsClient = cr.acquireContentProviderClient(BrowserContractHelpers.CLIENTS_CONTENT_URI);
 
--- a/mobile/android/services/src/androidTest/java/org/mozilla/gecko/background/db/TestFormHistoryRepositorySession.java
+++ b/mobile/android/services/src/androidTest/java/org/mozilla/gecko/background/db/TestFormHistoryRepositorySession.java
@@ -8,23 +8,23 @@ import java.util.concurrent.ExecutorServ
 import org.mozilla.gecko.background.helpers.AndroidSyncTestCase;
 import org.mozilla.gecko.background.sync.helpers.ExpectFetchDelegate;
 import org.mozilla.gecko.background.sync.helpers.ExpectFetchSinceDelegate;
 import org.mozilla.gecko.background.sync.helpers.ExpectNoStoreDelegate;
 import org.mozilla.gecko.background.sync.helpers.ExpectStoredDelegate;
 import org.mozilla.gecko.background.sync.helpers.SessionTestHelper;
 import org.mozilla.gecko.background.testhelpers.WaitHelper;
 import org.mozilla.gecko.db.BrowserContract;
+import org.mozilla.gecko.sync.SessionCreateException;
 import org.mozilla.gecko.sync.repositories.InactiveSessionException;
 import org.mozilla.gecko.sync.repositories.NoContentProviderException;
 import org.mozilla.gecko.sync.repositories.NoStoreDelegateException;
 import org.mozilla.gecko.sync.repositories.RepositorySession;
 import org.mozilla.gecko.sync.repositories.android.BrowserContractHelpers;
 import org.mozilla.gecko.sync.repositories.android.FormHistoryRepositorySession;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionCreationDelegate;
 import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionStoreDelegate;
 import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionWipeDelegate;
 import org.mozilla.gecko.sync.repositories.domain.FormHistoryRecord;
 import org.mozilla.gecko.sync.repositories.domain.Record;
 
 import android.content.ContentProviderClient;
 import android.content.ContentValues;
 import android.content.Context;
@@ -64,27 +64,25 @@ public class TestFormHistoryRepositorySe
 
   protected FormHistoryRepositorySession.FormHistoryRepository getRepository() {
     /**
      * Override this chain in order to avoid our test code having to create two
      * sessions all the time.
      */
     return new FormHistoryRepositorySession.FormHistoryRepository() {
       @Override
-      public void createSession(RepositorySessionCreationDelegate delegate,
-                                Context context) {
+      public RepositorySession createSession(Context context) throws SessionCreateException {
         try {
-          final FormHistoryRepositorySession session = new FormHistoryRepositorySession(this, context) {
+          return new FormHistoryRepositorySession(this, context) {
             @Override
             protected synchronized void trackGUID(String guid) {
             }
           };
-          delegate.onSessionCreated(session);
         } catch (Exception e) {
-          delegate.onSessionCreateFailed(e);
+          throw new SessionCreateException(e);
         }
       }
     };
   }
 
 
   protected FormHistoryRepositorySession createSession() {
     return (FormHistoryRepositorySession) SessionTestHelper.createSession(
@@ -93,17 +91,17 @@ public class TestFormHistoryRepositorySe
   }
 
   protected FormHistoryRepositorySession createAndBeginSession() {
     return (FormHistoryRepositorySession) SessionTestHelper.createAndBeginSession(
         getApplicationContext(),
         getRepository());
   }
 
-  public void testAcquire() throws NoContentProviderException {
+  public void testAcquire() throws Exception {
     final FormHistoryRepositorySession session = createAndBeginSession();
     assertNotNull(session.getFormsProvider());
     session.abort();
   }
 
   protected int numRecords(FormHistoryRepositorySession session, Uri uri) throws RemoteException {
     Cursor cur = null;
     try {
@@ -181,17 +179,17 @@ public class TestFormHistoryRepositorySe
     cv4.put(BrowserContract.FormHistory.GUID, deleted2.guid);
     // cv4.put(BrowserContract.DeletedFormHistory.TIME_DELETED, record4.lastModified); // Set by CP.
 
     int deletedInserted = session.getFormsProvider().bulkInsert(deletedUri, new ContentValues[] { cv4 });
     assertEquals(1, deletedInserted);
     after4 = System.currentTimeMillis();
   }
 
-  public void testWipe() throws NoContentProviderException, RemoteException {
+  public void testWipe() throws Exception {
     final FormHistoryRepositorySession session = createAndBeginSession();
 
     insertTwoRecords(session);
     assertTrue(numRecords(session, BrowserContractHelpers.FORM_HISTORY_CONTENT_URI) > 0);
     assertTrue(numRecords(session, BrowserContractHelpers.DELETED_FORM_HISTORY_CONTENT_URI) > 0);
 
     performWait(WaitHelper.onThreadRunnable(new Runnable() {
       @Override
@@ -243,27 +241,27 @@ public class TestFormHistoryRepositorySe
           session.fetch(guids, new ExpectFetchDelegate(expectedRecords));
         } catch (InactiveSessionException e) {
           performNotify(e);
         }
       }
     };
   }
 
-  public void testFetchAll() throws NoContentProviderException, RemoteException {
+  public void testFetchAll() throws Exception {
     final FormHistoryRepositorySession session = createAndBeginSession();
 
     insertTwoRecords(session);
 
     performWait(fetchAllRunnable(session, new Record[] { regular1, deleted1 }));
 
     session.abort();
   }
 
-  public void testFetchByGuid() throws NoContentProviderException, RemoteException {
+  public void testFetchByGuid() throws Exception {
     final FormHistoryRepositorySession session = createAndBeginSession();
 
     insertTwoRecords(session);
 
     performWait(fetchRunnable(session,
         new String[] { regular1.guid, deleted1.guid },
         new Record[] { regular1, deleted1 }));
     performWait(fetchRunnable(session,
@@ -274,17 +272,17 @@ public class TestFormHistoryRepositorySe
         new Record[] { deleted1 }));
     performWait(fetchRunnable(session,
         new String[] { "FIRST_NON_EXISTENT_GUID", "SECOND_NON_EXISTENT_GUID?" },
         new Record[] { }));
 
     session.abort();
   }
 
-  public void testFetchSince() throws NoContentProviderException, RemoteException {
+  public void testFetchSince() throws Exception {
     final FormHistoryRepositorySession session = createAndBeginSession();
 
     insertFourRecords(session);
 
     performWait(fetchSinceRunnable(session,
         after0, new String[] { regular1.guid, deleted1.guid, regular2.guid, deleted2.guid }));
     performWait(fetchSinceRunnable(session,
         after1, new String[] { deleted1.guid, regular2.guid, deleted2.guid }));
@@ -308,17 +306,17 @@ public class TestFormHistoryRepositorySe
           session.storeDone();
         } catch (NoStoreDelegateException e) {
           performNotify("NoStoreDelegateException should not occur.", e);
         }
       }
     };
   }
 
-  public void testStoreRemoteNew() throws NoContentProviderException, RemoteException {
+  public void testStoreRemoteNew() throws Exception {
     final FormHistoryRepositorySession session = createAndBeginSession();
 
     insertTwoRecords(session);
 
     FormHistoryRecord rec;
 
     // remote regular, local missing => should store.
     rec = new FormHistoryRecord("new1", "forms", System.currentTimeMillis(), false);
@@ -330,17 +328,17 @@ public class TestFormHistoryRepositorySe
     // remote deleted, local missing => should delete, but at the moment we ignore.
     rec = new FormHistoryRecord("new2", "forms", System.currentTimeMillis(), true);
     performWait(storeRunnable(session, rec, new ExpectNoStoreDelegate()));
     performWait(fetchRunnable(session, new String[] { rec.guid }, new Record[] { }));
 
     session.abort();
   }
 
-  public void testStoreRemoteNewer() throws NoContentProviderException, RemoteException {
+  public void testStoreRemoteNewer() throws Exception {
     final FormHistoryRepositorySession session = createAndBeginSession();
 
     insertFourRecords(session);
     long newTimestamp = System.currentTimeMillis();
 
     FormHistoryRecord rec;
 
     // remote regular, local regular, remote newer => should update.
@@ -365,17 +363,17 @@ public class TestFormHistoryRepositorySe
     // remote deleted, local deleted, remote newer => should delete everything.
     rec = new FormHistoryRecord(deleted2.guid, deleted2.collection, newTimestamp, true);
     performWait(storeRunnable(session, rec, new ExpectNoStoreDelegate()));
     performWait(fetchRunnable(session, new String[] { deleted2.guid }, new Record[] { }));
 
     session.abort();
   }
 
-  public void testStoreRemoteOlder() throws NoContentProviderException, RemoteException {
+  public void testStoreRemoteOlder() throws Exception {
     final FormHistoryRepositorySession session = createAndBeginSession();
 
     long oldTimestamp = System.currentTimeMillis() - 100;
     insertFourRecords(session);
 
     FormHistoryRecord rec;
 
     // remote regular, local regular, remote older => should ignore.
@@ -396,17 +394,17 @@ public class TestFormHistoryRepositorySe
 
     // remote deleted, local deleted, remote older => should ignore.
     rec = new FormHistoryRecord(deleted2.guid, deleted2.collection, oldTimestamp, true);
     performWait(storeRunnable(session, rec, new ExpectNoStoreDelegate()));
 
     session.abort();
   }
 
-  public void testStoreDifferentGuid() throws NoContentProviderException, RemoteException {
+  public void testStoreDifferentGuid() throws Exception {
     final FormHistoryRepositorySession session = createAndBeginSession();
 
     insertTwoRecords(session);
 
     FormHistoryRecord rec = (FormHistoryRecord) regular1.copyWithIDs("distinct", 999);
     performWait(storeRunnable(session, rec, new ExpectStoredDelegate(rec.guid)));
     // Existing record should take remote record's GUID.
     performWait(fetchAllRunnable(session, new Record[] { rec, deleted1 }));
--- a/mobile/android/services/src/androidTest/java/org/mozilla/gecko/background/db/TestPasswordsRepository.java
+++ b/mobile/android/services/src/androidTest/java/org/mozilla/gecko/background/db/TestPasswordsRepository.java
@@ -18,50 +18,48 @@ import org.mozilla.gecko.db.BrowserContr
 import org.mozilla.gecko.sync.Utils;
 import org.mozilla.gecko.sync.repositories.InactiveSessionException;
 import org.mozilla.gecko.sync.repositories.NoStoreDelegateException;
 import org.mozilla.gecko.sync.repositories.Repository;
 import org.mozilla.gecko.sync.repositories.RepositorySession;
 import org.mozilla.gecko.sync.repositories.android.BrowserContractHelpers;
 import org.mozilla.gecko.sync.repositories.android.PasswordsRepositorySession;
 import org.mozilla.gecko.sync.repositories.android.RepoUtils;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionCreationDelegate;
 import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionStoreDelegate;
 import org.mozilla.gecko.sync.repositories.domain.PasswordRecord;
 import org.mozilla.gecko.sync.repositories.domain.Record;
 
 import android.content.ContentProviderClient;
 import android.content.ContentValues;
 import android.content.Context;
 import android.database.Cursor;
-import android.os.RemoteException;
 
 public class TestPasswordsRepository extends AndroidSyncTestCase {
   private final String NEW_PASSWORD1 = "password";
   private final String NEW_PASSWORD2 = "drowssap";
 
   @Override
   public void setUp() {
     wipe();
     assertTrue(WaitHelper.getTestWaiter().isIdle());
   }
 
-  public void testFetchAll() {
+  public void testFetchAll() throws Exception {
     RepositorySession session = createAndBeginSession();
     Record[] expected = new Record[] { PasswordHelpers.createPassword1(),
                                        PasswordHelpers.createPassword2() };
 
     performWait(storeRunnable(session, expected[0]));
     performWait(storeRunnable(session, expected[1]));
 
     performWait(fetchAllRunnable(session, expected));
     dispose(session);
   }
 
-  public void testFetchSinceOneRecord() {
+  public void testFetchSinceOneRecord() throws Exception {
     RepositorySession session = createAndBeginSession();
 
     // Passwords fetchModified checks timePasswordChanged, not insertion time.
     PasswordRecord record1 = PasswordHelpers.createPassword1();
     long timeModified1 = updatePassword(NEW_PASSWORD1, record1);
     performWait(storeRunnable(session, record1));
 
     PasswordRecord record2 = PasswordHelpers.createPassword2();
@@ -72,73 +70,73 @@ public class TestPasswordsRepository ext
     performWait(fetchSinceRunnable(session, timeModified2 - 10, expectedOne));
 
     String[] expectedBoth = new String[] { record1.guid, record2.guid };
     performWait(fetchSinceRunnable(session, timeModified1 - 10, expectedBoth));
 
     dispose(session);
   }
 
-  public void testFetchSinceReturnNoRecords() {
+  public void testFetchSinceReturnNoRecords() throws Exception {
    RepositorySession session = createAndBeginSession();
 
     performWait(storeRunnable(session, PasswordHelpers.createPassword2()));
 
     long timestamp = System.currentTimeMillis();
 
     performWait(fetchSinceRunnable(session, timestamp + 2000, new String[] {}));
     dispose(session);
   }
 
-  public void testFetchOneRecordByGuid() {
+  public void testFetchOneRecordByGuid() throws Exception {
     RepositorySession session = createAndBeginSession();
     Record record = PasswordHelpers.createPassword1();
     performWait(storeRunnable(session, record));
     performWait(storeRunnable(session, PasswordHelpers.createPassword2()));
 
     String[] guids = new String[] { record.guid };
     Record[] expected = new Record[] { record };
     performWait(fetchRunnable(session, guids, expected));
     dispose(session);
   }
 
-  public void testFetchMultipleRecordsByGuids() {
+  public void testFetchMultipleRecordsByGuids() throws Exception {
     RepositorySession session = createAndBeginSession();
     PasswordRecord record1 = PasswordHelpers.createPassword1();
     PasswordRecord record2 = PasswordHelpers.createPassword2();
     PasswordRecord record3 = PasswordHelpers.createPassword3();
 
     performWait(storeRunnable(session, record1));
     performWait(storeRunnable(session, record2));
     performWait(storeRunnable(session, record3));
 
     String[] guids = new String[] { record1.guid, record2.guid };
     Record[] expected = new Record[] { record1, record2 };
     performWait(fetchRunnable(session, guids, expected));
     dispose(session);
   }
 
-  public void testFetchNoRecordByGuid() {
+  public void testFetchNoRecordByGuid() throws Exception {
     RepositorySession session = createAndBeginSession();
     Record record = PasswordHelpers.createPassword1();
 
     performWait(storeRunnable(session, record));
     performWait(fetchRunnable(session,
                               new String[] { Utils.generateGuid() },
                               new Record[] {}));
     dispose(session);
   }
 
-  public void testStore() {
+  public void testStore() throws Exception {
     final RepositorySession session = createAndBeginSession();
     performWait(storeRunnable(session, PasswordHelpers.createPassword1()));
     dispose(session);
   }
 
-  public void testRemoteNewerTimeStamp() {
+  public void testRemoteNewerTimeStamp() throws Exception {
     final RepositorySession session = createAndBeginSession();
 
     // Store updated local record.
     PasswordRecord local = PasswordHelpers.createPassword1();
     updatePassword(NEW_PASSWORD1, local, System.currentTimeMillis() - 1000);
     performWait(storeRunnable(session, local));
 
     // Sync a remote record version that is newer.
@@ -178,17 +176,17 @@ public class TestPasswordsRepository ext
     updatePassword(NEW_PASSWORD2, remote3);
     performWait(storeRunnable(session, remote3));
 
     // Make a fetch, expecting the local record to be deleted.
     performWait(fetchRunnable(session, new String[] { remote3.guid }, new Record[] {}));
     dispose(session);
   }
 
-  public void testLocalNewerTimeStamp() {
+  public void testLocalNewerTimeStamp() throws Exception {
     final RepositorySession session = createAndBeginSession();
     // Remote record updated before local record.
     PasswordRecord remote = PasswordHelpers.createPassword1();
     updatePassword(NEW_PASSWORD1, remote, System.currentTimeMillis() - 1000);
 
     // Store updated local record.
     PasswordRecord local = PasswordHelpers.createPassword2();
     updatePassword(NEW_PASSWORD2, local);
@@ -237,17 +235,17 @@ public class TestPasswordsRepository ext
     performWait(fetchRunnable(session, new String[] { local3.guid }, new Record[] {}));
     dispose(session);
   }
 
   /*
    * Store two records that are identical except for guid. Expect to find the
    * remote one after reconciling.
    */
-  public void testStoreIdenticalExceptGuid() {
+  public void testStoreIdenticalExceptGuid() throws Exception {
     RepositorySession session = createAndBeginSession();
     PasswordRecord record = PasswordHelpers.createPassword1();
     record.guid = "before1";
     // Store record.
     performWait(storeRunnable(session, record));
 
     // Store same record, but with different guid.
     record.guid = Utils.generateGuid();
@@ -271,17 +269,17 @@ public class TestPasswordsRepository ext
     dispose(session);
   }
 
   /*
    * Store two records that are identical except for guid when they both point
    * to the same site and there are multiple records for that site. Expect to
    * find the remote one after reconciling.
    */
-  public void testStoreIdenticalExceptGuidOnSameSite() {
+  public void testStoreIdenticalExceptGuidOnSameSite() throws Exception {
     RepositorySession session = createAndBeginSession();
     PasswordRecord record1 = PasswordHelpers.createPassword1();
     record1.encryptedUsername = "original";
     record1.guid = "before1";
     PasswordRecord record2 = PasswordHelpers.createPassword1();
     record2.encryptedUsername = "different";
     record1.guid = "before2";
     // Store records.
@@ -299,17 +297,17 @@ public class TestPasswordsRepository ext
 
     record2.guid = Utils.generateGuid();
     performWait(storeRunnable(session, record2));
     performWait(fetchAllRunnable(session, new Record[] { record1, record2 }));
 
     dispose(session);
   }
 
-  public void testRawFetch() throws RemoteException {
+  public void testRawFetch() throws Exception {
     RepositorySession session = createAndBeginSession();
     Record[] expected = new Record[] { PasswordHelpers.createPassword1(),
                                        PasswordHelpers.createPassword2() };
 
     performWait(storeRunnable(session, expected[0]));
     performWait(storeRunnable(session, expected[1]));
 
     ContentProviderClient client = getApplicationContext().getContentResolver().acquireContentProviderClient(BrowserContract.PASSWORDS_AUTHORITY_URI);
@@ -338,25 +336,22 @@ public class TestPasswordsRepository ext
 
   private Repository getRepository() {
     /**
      * Override this chain in order to avoid our test code having to create two
      * sessions all the time. Don't track records, so they filtering doesn't happen.
      */
     return new PasswordsRepositorySession.PasswordsRepository() {
       @Override
-      public void createSession(RepositorySessionCreationDelegate delegate,
-          Context context) {
-        PasswordsRepositorySession session;
-        session = new PasswordsRepositorySession(this, context) {
+      public RepositorySession createSession(Context context) {
+        return new PasswordsRepositorySession(this, context) {
           @Override
           protected synchronized void trackGUID(String guid) {
           }
         };
-        delegate.onSessionCreated(session);
       }
     };
   }
 
   private void wipe() {
     Context context = getApplicationContext();
     context.getContentResolver().delete(BrowserContractHelpers.PASSWORDS_CONTENT_URI, null, null);
     context.getContentResolver().delete(BrowserContractHelpers.DELETED_PASSWORDS_CONTENT_URI, null, null);
--- a/mobile/android/services/src/androidTest/java/org/mozilla/gecko/background/db/ThreadedRepositoryTestCase.java
+++ b/mobile/android/services/src/androidTest/java/org/mozilla/gecko/background/db/ThreadedRepositoryTestCase.java
@@ -5,17 +5,16 @@ package org.mozilla.gecko.background.db;
 
 import java.util.concurrent.ExecutorService;
 
 import org.mozilla.gecko.background.common.log.Logger;
 import org.mozilla.gecko.background.helpers.AndroidSyncTestCase;
 import org.mozilla.gecko.background.sync.helpers.DefaultCleanDelegate;
 import org.mozilla.gecko.background.sync.helpers.DefaultFetchDelegate;
 import org.mozilla.gecko.background.sync.helpers.DefaultFinishDelegate;
-import org.mozilla.gecko.background.sync.helpers.DefaultSessionCreationDelegate;
 import org.mozilla.gecko.background.sync.helpers.DefaultStoreDelegate;
 import org.mozilla.gecko.background.sync.helpers.ExpectFetchDelegate;
 import org.mozilla.gecko.background.sync.helpers.ExpectFetchSinceDelegate;
 import org.mozilla.gecko.background.sync.helpers.ExpectFinishDelegate;
 import org.mozilla.gecko.background.sync.helpers.ExpectFinishFailDelegate;
 import org.mozilla.gecko.background.sync.helpers.ExpectInvalidRequestFetchDelegate;
 import org.mozilla.gecko.background.sync.helpers.ExpectManyStoredDelegate;
 import org.mozilla.gecko.background.sync.helpers.ExpectStoreCompletedDelegate;
@@ -619,18 +618,19 @@ public abstract class ThreadedRepository
 
    /*
     * Tests that don't require specific records based on type of repository.
     * These tests don't need to be overriden in subclasses, they will just work.
     */
    public void testCreateSessionNullContext() {
      Logger.debug(LOG_TAG, "In testCreateSessionNullContext.");
      Repository repo = getRepository();
+
      try {
-       repo.createSession(new DefaultSessionCreationDelegate(), null);
+       repo.createSession(null);
        fail("Should throw.");
      } catch (Exception ex) {
        assertNotNull(ex);
      }
    }
 
    public void testStoreNullRecord() {
      final RepositorySession session = createAndBeginSession();
@@ -670,46 +670,46 @@ public abstract class ThreadedRepository
        dispose(session);
        return;
      } catch (org.mozilla.gecko.sync.SyncException e) {
        e.printStackTrace();
      }
      fail("Should have caught InvalidSessionTransitionException.");
    }
 
-   public void testBeginOnFinishedSession() throws InactiveSessionException {
+   public void testBeginOnFinishedSession() {
      final RepositorySession session = createAndBeginSession();
      performWait(finishRunnable(session, new ExpectFinishDelegate()));
      try {
        session.begin();
      } catch (InvalidSessionTransitionException e) {
        Logger.debug(getName(), "Yay! Got an exception.", e);
        dispose(session);
        return;
      } catch (Exception e) {
        Logger.debug(getName(), "Yay! Got an exception.", e);
        dispose(session);
        return;
      }
      fail("Should have caught InvalidSessionTransitionException.");
    }
 
-   public void testFinishOnFinishedSession() throws InactiveSessionException {
+   public void testFinishOnFinishedSession() {
      final RepositorySession session = createAndBeginSession();
      performWait(finishRunnable(session, new ExpectFinishDelegate()));
      try {
        session.finish(new ExpectFinishFailDelegate());
      } catch (InactiveSessionException e) {
        dispose(session);
        return;
      }
      fail("Should have caught InactiveSessionException.");
    }
 
-   public void testFetchOnInactiveSession() throws InactiveSessionException {
+   public void testFetchOnInactiveSession() {
      final RepositorySession session = createSession();
      try {
        session.fetch(new String[] { Utils.generateGuid() }, new DefaultFetchDelegate());
      } catch (InactiveSessionException e) {
        // Yay.
        dispose(session);
        return;
      };
@@ -725,17 +725,11 @@ public abstract class ThreadedRepository
      } catch (InactiveSessionException e) {
        // Yay.
        dispose(session);
        return;
      };
      fail("Should have caught InactiveSessionException.");
    }
 
-   private static void verifyInactiveException(Exception ex) {
-     if (!(ex instanceof InactiveSessionException)) {
-       fail("Wrong exception type");
-     }
-   }
-
    protected void closeDataAccessor(DataAccessor dataAccessor) {
    }
 }
--- a/mobile/android/services/src/androidTest/java/org/mozilla/gecko/background/sync/TestStoreTracking.java
+++ b/mobile/android/services/src/androidTest/java/org/mozilla/gecko/background/sync/TestStoreTracking.java
@@ -5,30 +5,26 @@ package org.mozilla.gecko.background.syn
 
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicLong;
 
 import junit.framework.AssertionFailedError;
 
 import org.mozilla.gecko.background.common.log.Logger;
 import org.mozilla.gecko.background.helpers.AndroidSyncTestCase;
-import org.mozilla.gecko.background.sync.helpers.SimpleSuccessCreationDelegate;
 import org.mozilla.gecko.background.sync.helpers.SimpleSuccessFetchDelegate;
 import org.mozilla.gecko.background.sync.helpers.SimpleSuccessFinishDelegate;
 import org.mozilla.gecko.background.sync.helpers.SimpleSuccessStoreDelegate;
 import org.mozilla.gecko.background.testhelpers.WBORepository;
 import org.mozilla.gecko.sync.CryptoRecord;
 import org.mozilla.gecko.sync.ExtendedJSONObject;
-import org.mozilla.gecko.sync.SyncException;
 import org.mozilla.gecko.sync.repositories.InactiveSessionException;
-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.RepositorySessionBundle;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionCreationDelegate;
 import org.mozilla.gecko.sync.repositories.domain.BookmarkRecord;
 import org.mozilla.gecko.sync.repositories.domain.Record;
 import org.mozilla.gecko.sync.synchronizer.Synchronizer;
 import org.mozilla.gecko.sync.synchronizer.SynchronizerDelegate;
 
 import android.content.Context;
 
 public class TestStoreTracking extends AndroidSyncTestCase {
@@ -129,129 +125,101 @@ public class TestStoreTracking extends A
       }
 
       @Override
       public void onStoreFailed(Exception e) {
 
       }
     };
 
-    session.setStoreDelegate(storeDelegate);
-    try {
-      Logger.debug(getName(), "Storing...");
-      session.store(record);
-      session.storeDone();
-    } catch (NoStoreDelegateException e) {
-      // Should not happen.
-    }
+    final Runnable doStore = new Runnable() {
+      @Override
+      public void run() {
+        session.setStoreDelegate(storeDelegate);
+        try {
+          Logger.debug(getName(), "Storing...");
+          session.store(record);
+          session.storeDone();
+        } catch (NoStoreDelegateException e) {
+          throw new IllegalStateException(e);
+        }
+      }
+    };
+
+    performWait(doStore);
   }
 
   private void doTestNewSessionRetrieveByTime(final WBORepository repository,
-                                              final String expectedGUID) {
-    final SimpleSuccessCreationDelegate createDelegate = new SimpleSuccessCreationDelegate() {
+                                              final String expectedGUID) throws Exception {
+    final RepositorySession session = repository.createSession(getApplicationContext());
+    Logger.debug(getName(), "Session created.");
+    session.begin();
+
+    // Now we get a result.
+    final Runnable doFetch = new Runnable() {
       @Override
-      public void onSessionCreated(final RepositorySession session) {
-        Logger.debug(getName(), "Session created.");
-        try {
-          session.begin();
-        } catch (SyncException e) {
-          e.printStackTrace();
-          performNotify(e);
-        }
-
-        // Now we get a result.
+      public void run() {
         session.fetchModified(new SimpleSuccessFetchDelegate() {
           @Override
           public void onFetchedRecord(Record record) {
             assertEq(expectedGUID, record.guid);
           }
 
           @Override
           public void onFetchCompleted() {
             try {
               session.finish(new SimpleSuccessFinishDelegate() {
                 @Override
                 public void onFinishSucceeded(RepositorySession session,
                                               RepositorySessionBundle bundle) {
-                  // Hooray!
                   performNotify();
                 }
+
+                @Override
+                public void onFinishFailed(Exception ex) {
+                  performNotify(ex);
+                }
               });
             } catch (InactiveSessionException e) {
               performNotify(e);
             }
           }
 
           @Override
           public void onBatchCompleted() {
           }
         });
       }
     };
-    Runnable create = new Runnable() {
-      @Override
-      public void run() {
-        repository.createSession(createDelegate, getApplicationContext());
-      }
-    };
 
-    performWait(create);
+    performWait(doFetch);
   }
 
   /**
    * Store a record in one session. Verify that fetching by GUID returns
    * the record. Verify that fetching by timestamp fails to return records.
    * Start a new session. Verify that fetching by timestamp returns the
    * stored record.
    *
    * Invokes doTestStoreRetrieveByGUID, doTestNewSessionRetrieveByTime.
    */
-  public void testStoreRetrieveByGUID() {
+  public void testStoreRetrieveByGUID() throws Exception {
     Logger.debug(getName(), "Started.");
     final WBORepository r = new TrackingWBORepository();
     final long now = System.currentTimeMillis();
     final String expectedGUID = "abcdefghijkl";
     final Record record = new BookmarkRecord(expectedGUID, "bookmarks", now , false);
 
-    final RepositorySessionCreationDelegate createDelegate = new SimpleSuccessCreationDelegate() {
-      @Override
-      public void onSessionCreated(RepositorySession session) {
-        Logger.debug(getName(), "Session created: " + session);
-        try {
-          session.begin();
-        } catch (SyncException e) {
-          e.printStackTrace();
-          performNotify(e);
-        }
-
-        doTestStoreRetrieveByGUID(r, session, expectedGUID, record);
-      }
-    };
-
     final Context applicationContext = getApplicationContext();
 
-    // This has to happen on a new thread so that we
-    // can wait for it!
-    Runnable create = onThreadRunnable(new Runnable() {
-      @Override
-      public void run() {
-        r.createSession(createDelegate, applicationContext);
-      }
-    });
+    final RepositorySession session = r.createSession(applicationContext);
+    session.begin();
+    doTestStoreRetrieveByGUID(r, session, expectedGUID, record);
 
-    Runnable retrieve = onThreadRunnable(new Runnable() {
-      @Override
-      public void run() {
-        doTestNewSessionRetrieveByTime(r, expectedGUID);
-        performNotify();
-      }
-    });
-
-    performWait(create);
-    performWait(retrieve);
+    doTestNewSessionRetrieveByTime(r, expectedGUID);
   }
 
   private Runnable onThreadRunnable(final Runnable r) {
     return new Runnable() {
       @Override
       public void run() {
         new Thread(r).start();
       }
@@ -271,19 +239,18 @@ public class TestStoreTracking extends A
       @Override
       public void store(final Record record) throws NoStoreDelegateException {
         Logger.debug(LOG_TAG, "Counter now " + counter.incrementAndGet());
         super.store(record);
       }
     }
 
     @Override
-    public void createSession(RepositorySessionCreationDelegate delegate,
-                              Context context) {
-      delegate.deferredCreationDelegate().onSessionCreated(new CountingWBORepositorySession(this));
+    public RepositorySession createSession(Context context) {
+      return new CountingWBORepositorySession(this);
     }
   }
 
   public class TestRecord extends Record {
     public TestRecord(String guid, String collection, long lastModified,
                       boolean deleted) {
       super(guid, collection, lastModified, deleted);
     }
deleted file mode 100644
--- a/mobile/android/services/src/androidTest/java/org/mozilla/gecko/background/sync/helpers/DefaultSessionCreationDelegate.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
-   http://creativecommons.org/publicdomain/zero/1.0/ */
-
-package org.mozilla.gecko.background.sync.helpers;
-
-import org.mozilla.gecko.sync.repositories.RepositorySession;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionCreationDelegate;
-
-public class DefaultSessionCreationDelegate extends DefaultDelegate implements
-    RepositorySessionCreationDelegate {
-
-  @Override
-  public void onSessionCreateFailed(Exception ex) {
-    performNotify("Session creation failed", ex);
-  }
-
-  @Override
-  public void onSessionCreated(RepositorySession session) {
-    performNotify("Should not have been created.", null);
-  }
-
-  @Override
-  public RepositorySessionCreationDelegate deferredCreationDelegate() {
-    final RepositorySessionCreationDelegate self = this;
-    return new RepositorySessionCreationDelegate() {
-
-      @Override
-      public void onSessionCreated(final RepositorySession session) {
-        new Thread(new Runnable() {
-          @Override
-          public void run() {
-            self.onSessionCreated(session);
-          }
-        }).start();
-      }
-
-      @Override
-      public void onSessionCreateFailed(final Exception ex) {
-        new Thread(new Runnable() {
-          @Override
-          public void run() {
-            self.onSessionCreateFailed(ex);
-          }
-        }).start();
-      }
-
-      @Override
-      public RepositorySessionCreationDelegate deferredCreationDelegate() {
-        return this;
-      }
-    };
-  }
-}
--- a/mobile/android/services/src/androidTest/java/org/mozilla/gecko/background/sync/helpers/SessionTestHelper.java
+++ b/mobile/android/services/src/androidTest/java/org/mozilla/gecko/background/sync/helpers/SessionTestHelper.java
@@ -1,81 +1,44 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.gecko.background.sync.helpers;
 
 import static junit.framework.Assert.assertNotNull;
 
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.background.testhelpers.WaitHelper;
+import org.mozilla.gecko.sync.SessionCreateException;
 import org.mozilla.gecko.sync.SyncException;
-import org.mozilla.gecko.sync.repositories.InvalidSessionTransitionException;
 import org.mozilla.gecko.sync.repositories.Repository;
 import org.mozilla.gecko.sync.repositories.RepositorySession;
 
 import android.content.Context;
 
 public class SessionTestHelper {
-
-  protected static RepositorySession prepareRepositorySession(
+  private static RepositorySession prepareRepositorySession(
       final Context context,
       final boolean begin,
       final Repository repository) {
 
-    final WaitHelper testWaiter = WaitHelper.getTestWaiter();
-
-    final String logTag = "prepareRepositorySession";
-    class CreationDelegate extends DefaultSessionCreationDelegate {
-      private RepositorySession session;
-      synchronized void setSession(RepositorySession session) {
-        this.session = session;
-      }
-      synchronized RepositorySession getSession() {
-        return this.session;
-      }
+    final RepositorySession session;
+    try {
+      session = repository.createSession(context);
+      assertNotNull(session);
+    } catch (SessionCreateException e) {
+      throw new IllegalStateException(e);
+    }
 
-      @Override
-      public void onSessionCreated(final RepositorySession session) {
-        assertNotNull(session);
-        Logger.info(logTag, "Setting session to " + session);
-        setSession(session);
-        if (begin) {
-          Logger.info(logTag, "Calling session.begin on new session.");
-          // The begin callbacks will notify.
-          try {
-            session.begin();
-          } catch (SyncException e) {
-            testWaiter.performNotify(e);
-          }
-          testWaiter.performNotify();
-        } else {
-          Logger.info(logTag, "Notifying after setting new session.");
-          testWaiter.performNotify();
-        }
+    if (begin) {
+      try {
+        session.begin();
+      } catch (SyncException e) {
+        throw new IllegalStateException(e);
       }
     }
 
-    final CreationDelegate delegate = new CreationDelegate();
-    try {
-      Runnable runnable = new Runnable() {
-        @Override
-        public void run() {
-          repository.createSession(delegate, context);
-        }
-      };
-      testWaiter.performWait(runnable);
-    } catch (IllegalArgumentException ex) {
-      Logger.warn(logTag, "Caught IllegalArgumentException.");
-    }
-
-    Logger.info(logTag, "Retrieving new session.");
-    final RepositorySession session = delegate.getSession();
-    assertNotNull(session);
-
     return session;
   }
 
   public static RepositorySession createSession(final Context context, final Repository repository) {
     return prepareRepositorySession(context, false, repository);
   }
 
   public static RepositorySession createAndBeginSession(Context context, Repository repository) {
deleted file mode 100644
--- a/mobile/android/services/src/androidTest/java/org/mozilla/gecko/background/sync/helpers/SimpleSuccessCreationDelegate.java
+++ /dev/null
@@ -1,18 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
-   http://creativecommons.org/publicdomain/zero/1.0/ */
-
-package org.mozilla.gecko.background.sync.helpers;
-
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionCreationDelegate;
-
-public abstract class SimpleSuccessCreationDelegate extends DefaultDelegate implements RepositorySessionCreationDelegate {
-  @Override
-  public void onSessionCreateFailed(Exception ex) {
-    performNotify("Session creation failed", ex);
-  }
-
-  @Override
-  public RepositorySessionCreationDelegate deferredCreationDelegate() {
-    return this;
-  }
-}
--- a/mobile/android/services/src/androidTest/java/org/mozilla/gecko/background/testhelpers/WBORepository.java
+++ b/mobile/android/services/src/androidTest/java/org/mozilla/gecko/background/testhelpers/WBORepository.java
@@ -9,18 +9,18 @@ import java.util.concurrent.ExecutorServ
 import java.util.concurrent.Executors;
 
 import org.mozilla.gecko.background.common.log.Logger;
 import org.mozilla.gecko.sync.SyncException;
 import org.mozilla.gecko.sync.repositories.InactiveSessionException;
 import org.mozilla.gecko.sync.repositories.NoStoreDelegateException;
 import org.mozilla.gecko.sync.repositories.RecordFilter;
 import org.mozilla.gecko.sync.repositories.Repository;
+import org.mozilla.gecko.sync.repositories.RepositorySession;
 import org.mozilla.gecko.sync.repositories.StoreTrackingRepositorySession;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionCreationDelegate;
 import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionFetchRecordsDelegate;
 import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionFinishDelegate;
 import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionWipeDelegate;
 import org.mozilla.gecko.sync.repositories.domain.Record;
 
 import android.content.Context;
 
 public class WBORepository extends Repository {
@@ -207,19 +207,18 @@ public class WBORepository extends Repos
     this(false);
   }
 
   public synchronized boolean shouldTrack() {
     return false;
   }
 
   @Override
-  public void createSession(RepositorySessionCreationDelegate delegate,
-                            Context context) {
-    delegate.deferredCreationDelegate().onSessionCreated(new WBORepositorySession(this));
+  public RepositorySession createSession(Context context) {
+    return new WBORepositorySession(this);
   }
 
   public ConcurrentHashMap<String, Record> cloneWBOs() {
     ConcurrentHashMap<String, Record> out = new ConcurrentHashMap<String, Record>();
     for (Entry<String, Record> entry : wbos.entrySet()) {
       out.put(entry.getKey(), entry.getValue()); // Assume that records are
                                                  // immutable.
     }
new file mode 100644
--- /dev/null
+++ b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/SessionCreateException.java
@@ -0,0 +1,13 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.gecko.sync;
+
+public class SessionCreateException extends SyncException {
+    private static final long serialVersionUID = -3852991560658529574L;
+
+    public SessionCreateException(Exception e) {
+        super(e);
+    }
+}
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/middleware/BufferingMiddlewareRepository.java
+++ b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/middleware/BufferingMiddlewareRepository.java
@@ -1,60 +1,36 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.sync.middleware;
 
 import android.content.Context;
 
+import org.mozilla.gecko.sync.SessionCreateException;
 import org.mozilla.gecko.sync.middleware.storage.BufferStorage;
 import org.mozilla.gecko.sync.repositories.Repository;
 import org.mozilla.gecko.sync.repositories.RepositorySession;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionCreationDelegate;
 
 /**
  * A buffering-enabled middleware which is intended to wrap local repositories. Configurable with
  * a sync deadline, buffer storage implementation and a consistency checker implementation.
  *
  * @author grisha
  */
-public class BufferingMiddlewareRepository extends MiddlewareRepository {
+public class BufferingMiddlewareRepository extends Repository {
     private final long syncDeadline;
     private final Repository inner;
     private final BufferStorage bufferStorage;
 
-    private class BufferingMiddlewareRepositorySessionCreationDelegate extends MiddlewareRepository.SessionCreationDelegate {
-        private final BufferingMiddlewareRepository repository;
-        private final RepositorySessionCreationDelegate outerDelegate;
-
-        private BufferingMiddlewareRepositorySessionCreationDelegate(BufferingMiddlewareRepository repository, RepositorySessionCreationDelegate outerDelegate) {
-            this.repository = repository;
-            this.outerDelegate = outerDelegate;
-        }
-
-        @Override
-        public void onSessionCreateFailed(Exception ex) {
-            this.outerDelegate.onSessionCreateFailed(ex);
-        }
-
-        @Override
-        public void onSessionCreated(RepositorySession session) {
-            outerDelegate.onSessionCreated(new BufferingMiddlewareRepositorySession(
-                    session, this.repository, syncDeadline, bufferStorage
-            ));
-        }
-    }
-
     public BufferingMiddlewareRepository(long syncDeadline, BufferStorage bufferStore, Repository wrappedRepository) {
         this.syncDeadline = syncDeadline;
         this.inner = wrappedRepository;
         this.bufferStorage = bufferStore;
     }
 
     @Override
-    public void createSession(RepositorySessionCreationDelegate delegate, Context context) {
-        this.inner.createSession(
-                new BufferingMiddlewareRepositorySessionCreationDelegate(this, delegate),
-                context
-        );
+    public RepositorySession createSession(Context context) throws SessionCreateException {
+        final RepositorySession innerSession = this.inner.createSession(context);
+        return new BufferingMiddlewareRepositorySession(innerSession, this, syncDeadline, bufferStorage);
     }
 }
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/middleware/BufferingMiddlewareRepositorySession.java
+++ b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/middleware/BufferingMiddlewareRepositorySession.java
@@ -6,16 +6,17 @@ package org.mozilla.gecko.sync.middlewar
 
 import android.os.SystemClock;
 import android.support.annotation.VisibleForTesting;
 
 import org.mozilla.gecko.sync.SyncDeadlineReachedException;
 import org.mozilla.gecko.sync.middleware.storage.BufferStorage;
 import org.mozilla.gecko.sync.repositories.InactiveSessionException;
 import org.mozilla.gecko.sync.repositories.NoStoreDelegateException;
+import org.mozilla.gecko.sync.repositories.Repository;
 import org.mozilla.gecko.sync.repositories.RepositorySession;
 import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionFetchRecordsDelegate;
 import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionStoreDelegate;
 import org.mozilla.gecko.sync.repositories.domain.Record;
 
 import java.util.Collection;
 
 /**
@@ -28,17 +29,17 @@ import java.util.Collection;
  *
  * @author grisha
  */
 /* package-local */ class BufferingMiddlewareRepositorySession extends MiddlewareRepositorySession {
     private final BufferStorage bufferStorage;
     private final long syncDeadlineMillis;
 
     /* package-local */ BufferingMiddlewareRepositorySession(
-            RepositorySession repositorySession, MiddlewareRepository repository,
+            RepositorySession repositorySession, BufferingMiddlewareRepository repository,
             long syncDeadlineMillis, BufferStorage bufferStorage) {
         super(repositorySession, repository);
         this.syncDeadlineMillis = syncDeadlineMillis;
         this.bufferStorage = bufferStorage;
     }
 
     @Override
     public void fetchModified(RepositorySessionFetchRecordsDelegate delegate) {
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/middleware/Crypto5MiddlewareRepository.java
+++ b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/middleware/Crypto5MiddlewareRepository.java
@@ -1,76 +1,47 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.sync.middleware;
 
+import org.mozilla.gecko.sync.SessionCreateException;
 import org.mozilla.gecko.sync.crypto.KeyBundle;
 import org.mozilla.gecko.sync.repositories.IdentityRecordFactory;
 import org.mozilla.gecko.sync.repositories.RecordFactory;
 import org.mozilla.gecko.sync.repositories.Repository;
 import org.mozilla.gecko.sync.repositories.RepositorySession;
 import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionCleanDelegate;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionCreationDelegate;
 
 import android.content.Context;
 
 /**
  * Wrap an existing repository in middleware that encrypts and decrypts records
  * passing through.
  *
  * @author rnewman
  *
  */
-public class Crypto5MiddlewareRepository extends MiddlewareRepository {
-
+public class Crypto5MiddlewareRepository extends Repository {
   public RecordFactory recordFactory = new IdentityRecordFactory();
 
-  public class Crypto5MiddlewareRepositorySessionCreationDelegate extends MiddlewareRepository.SessionCreationDelegate {
-    private final Crypto5MiddlewareRepository repository;
-    private final RepositorySessionCreationDelegate outerDelegate;
-
-    public Crypto5MiddlewareRepositorySessionCreationDelegate(Crypto5MiddlewareRepository repository, RepositorySessionCreationDelegate outerDelegate) {
-      this.repository = repository;
-      this.outerDelegate = outerDelegate;
-    }
-
-    @Override
-    public void onSessionCreateFailed(Exception ex) {
-      this.outerDelegate.onSessionCreateFailed(ex);
-    }
-
-    @Override
-    public void onSessionCreated(RepositorySession session) {
-      // Do some work, then report success with the wrapping session.
-      Crypto5MiddlewareRepositorySession cryptoSession;
-      try {
-        // Synchronous, baby.
-        cryptoSession = new Crypto5MiddlewareRepositorySession(session, this.repository, recordFactory);
-      } catch (Exception ex) {
-        this.outerDelegate.onSessionCreateFailed(ex);
-        return;
-      }
-      this.outerDelegate.onSessionCreated(cryptoSession);
-    }
-  }
-
   public KeyBundle keyBundle;
   private final Repository inner;
 
   public Crypto5MiddlewareRepository(Repository inner, KeyBundle keys) {
     super();
     this.inner = inner;
     this.keyBundle = keys;
   }
+
   @Override
-  public void createSession(RepositorySessionCreationDelegate delegate, Context context) {
-    Crypto5MiddlewareRepositorySessionCreationDelegate delegateWrapper = new Crypto5MiddlewareRepositorySessionCreationDelegate(this, delegate);
-    inner.createSession(delegateWrapper, context);
+  public RepositorySession createSession(Context context) throws SessionCreateException {
+    final RepositorySession innerSession = inner.createSession(context);
+    return new Crypto5MiddlewareRepositorySession(innerSession, this, recordFactory);
   }
 
   @Override
   public void clean(boolean success, RepositorySessionCleanDelegate delegate,
                     Context context) {
     this.inner.clean(success, delegate, context);
   }
 }
deleted file mode 100644
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/middleware/MiddlewareRepository.java
+++ /dev/null
@@ -1,22 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.middleware;
-
-import org.mozilla.gecko.sync.repositories.Repository;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionCreationDelegate;
-
-public abstract class MiddlewareRepository extends Repository {
-
-  public static abstract class SessionCreationDelegate implements
-      RepositorySessionCreationDelegate {
-
-    // We call through to our inner repository, so we don't need our own
-    // deferral scheme.
-    @Override
-    public RepositorySessionCreationDelegate deferredCreationDelegate() {
-      return this;
-    }
-  }
-}
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/middleware/MiddlewareRepositorySession.java
+++ b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/middleware/MiddlewareRepositorySession.java
@@ -4,26 +4,27 @@
 
 package org.mozilla.gecko.sync.middleware;
 
 import java.util.concurrent.ExecutorService;
 
 import org.mozilla.gecko.sync.SyncException;
 import org.mozilla.gecko.sync.repositories.InactiveSessionException;
 import org.mozilla.gecko.sync.repositories.InvalidSessionTransitionException;
+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.delegates.RepositorySessionFinishDelegate;
 import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionWipeDelegate;
 
 public abstract class MiddlewareRepositorySession extends RepositorySession {
   private static final String LOG_TAG = "MiddlewareSession";
   protected final RepositorySession inner;
 
-  public MiddlewareRepositorySession(RepositorySession innerSession, MiddlewareRepository repository) {
+  /* package-private */ MiddlewareRepositorySession(RepositorySession innerSession, Repository repository) {
     super(repository);
     this.inner = innerSession;
   }
 
   @Override
   public void wipe(RepositorySessionWipeDelegate delegate) {
     inner.wipe(delegate);
   }
@@ -32,17 +33,17 @@ public abstract class MiddlewareReposito
   public void begin() throws SyncException {
     inner.begin();
   }
 
   public static final class MiddlewareRepositorySessionFinishDelegate implements RepositorySessionFinishDelegate {
     private final MiddlewareRepositorySession outerSession;
     private final RepositorySessionFinishDelegate next;
 
-    public MiddlewareRepositorySessionFinishDelegate(MiddlewareRepositorySession outerSession, RepositorySessionFinishDelegate next) {
+    /* package-private */ MiddlewareRepositorySessionFinishDelegate(MiddlewareRepositorySession outerSession, RepositorySessionFinishDelegate next) {
       this.outerSession = outerSession;
       this.next = next;
     }
 
     @Override
     public void onFinishFailed(Exception ex) {
       next.onFinishFailed(ex);
     }
deleted file mode 100644
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/BookmarksRepository.java
+++ /dev/null
@@ -1,16 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.repositories;
-
-/**
- * Shared interface for repositories that consume and produce
- * bookmark records.
- *
- * @author rnewman
- *
- */
-public interface BookmarksRepository {
-
-}
deleted file mode 100644
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/HistoryRepository.java
+++ /dev/null
@@ -1,16 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.repositories;
-
-/**
- * Shared interface for repositories that consume and produce
- * history records.
- *
- * @author rnewman
- *
- */
-public interface HistoryRepository {
-
-}
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/Repository.java
+++ b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/Repository.java
@@ -1,18 +1,18 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy 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.SessionCreateException;
 import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionCleanDelegate;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionCreationDelegate;
 
 import android.content.Context;
 
 public abstract class Repository {
-  public abstract void createSession(RepositorySessionCreationDelegate delegate, Context context);
+  public abstract RepositorySession createSession(Context context) throws SessionCreateException;
 
   public void clean(boolean success, RepositorySessionCleanDelegate delegate, Context context) {
     delegate.onCleaned(this);
   }
 }
\ No newline at end of file
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/RepositorySession.java
+++ b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/RepositorySession.java
@@ -18,17 +18,17 @@ import org.mozilla.gecko.sync.repositori
 import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionWipeDelegate;
 import org.mozilla.gecko.sync.repositories.domain.Record;
 
 /**
  * A <code>RepositorySession</code> is created and used thusly:
  *
  *<ul>
  * <li>Construct, with a reference to its parent {@link Repository}, by calling
- *   {@link Repository#createSession(org.mozilla.gecko.sync.repositories.delegates.RepositorySessionCreationDelegate, android.content.Context)}.</li>
+ *   {@link Repository#createSession(android.content.Context)}.</li>
  * <li>Populate with saved information by calling {@link #unbundle(RepositorySessionBundle)}.</li>
  * <li>Begin a sync by calling {@link #begin()}. <code>begin()</code>
  *   is an appropriate place to initialize expensive resources.</li>
  * <li>Perform operations such as {@link #fetchModified(RepositorySessionFetchRecordsDelegate)} and
  *   {@link #store(Record)}.</li>
  * <li>Finish by calling {@link #finish(RepositorySessionFinishDelegate)}, retrieving and storing
  *   the current bundle.</li>
  *</ul>
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/Server15Repository.java
+++ b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/Server15Repository.java
@@ -5,17 +5,16 @@
 package org.mozilla.gecko.sync.repositories;
 
 import java.net.URI;
 import java.net.URISyntaxException;
 
 import org.mozilla.gecko.sync.InfoCollections;
 import org.mozilla.gecko.sync.InfoConfiguration;
 import org.mozilla.gecko.sync.net.AuthHeaderProvider;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionCreationDelegate;
 
 import android.content.Context;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
 
 /**
  * A Server15Repository implements fetching and storing against the Sync 1.5 API.
  * It doesn't do crypto: that's the job of the middleware.
@@ -68,19 +67,18 @@ public class Server15Repository extends 
     this.collectionURI = new URI(storageURL + (storageURL.endsWith("/") ? collection : "/" + collection));
     this.authHeaderProvider = authHeaderProvider;
     this.infoCollections = infoCollections;
     this.infoConfiguration = infoConfiguration;
     this.stateProvider = stateProvider;
   }
 
   @Override
-  public void createSession(RepositorySessionCreationDelegate delegate,
-                            Context context) {
-    delegate.onSessionCreated(new Server15RepositorySession(this));
+  public RepositorySession createSession(Context context) {
+    return new Server15RepositorySession(this);
   }
 
   public URI collectionURI() {
     return this.collectionURI;
   }
 
   /* package-local */ boolean updateNeeded(long lastSyncTimestamp) {
     return infoCollections.updateNeeded(collection, lastSyncTimestamp);
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/BookmarksRepository.java
+++ b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/BookmarksRepository.java
@@ -1,24 +1,36 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.sync.repositories.android;
 
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionCreationDelegate;
+import org.mozilla.gecko.sync.repositories.Repository;
+import org.mozilla.gecko.sync.repositories.RepositorySession;
+import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionCleanDelegate;
 
 import android.content.Context;
 
-public class BookmarksRepository extends ThreadedRepository implements org.mozilla.gecko.sync.repositories.BookmarksRepository {
-
+public class BookmarksRepository extends Repository {
   @Override
-  protected void sessionCreator(RepositorySessionCreationDelegate delegate, Context context) {
-    BookmarksRepositorySession session = new BookmarksRepositorySession(BookmarksRepository.this, context);
-    final RepositorySessionCreationDelegate deferredCreationDelegate = delegate.deferredCreationDelegate();
-    deferredCreationDelegate.onSessionCreated(session);
+  public RepositorySession createSession(Context context) {
+    return new BookmarksRepositorySession(this, context);
   }
 
   @Override
-  protected DataAccessor getDataAccessor(Context context) {
-    return new BookmarksDataAccessor(context);
+  public void clean(boolean success, RepositorySessionCleanDelegate delegate, Context context) {
+    if (!success) {
+      return;
+    }
+
+    final BookmarksDataAccessor dataAccessor = new BookmarksDataAccessor(context);
+
+    try {
+      dataAccessor.purgeDeleted();
+    } catch (Exception e) {
+      delegate.onCleanFailed(this, e);
+      return;
+    }
+
+    delegate.onCleaned(this);
   }
 }
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/BookmarksValidationRepository.java
+++ b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/BookmarksValidationRepository.java
@@ -13,17 +13,16 @@ import org.mozilla.gecko.background.comm
 import org.mozilla.gecko.sync.ExtendedJSONObject;
 import org.mozilla.gecko.sync.SyncException;
 import org.mozilla.gecko.sync.delegates.ClientsDataDelegate;
 import org.mozilla.gecko.sync.repositories.InactiveSessionException;
 import org.mozilla.gecko.sync.repositories.NoStoreDelegateException;
 import org.mozilla.gecko.sync.repositories.Repository;
 import org.mozilla.gecko.sync.repositories.RepositorySession;
 import org.mozilla.gecko.sync.repositories.RepositorySessionBundle;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionCreationDelegate;
 import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionFetchRecordsDelegate;
 import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionFinishDelegate;
 import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionWipeDelegate;
 import org.mozilla.gecko.sync.repositories.domain.BookmarkRecord;
 import org.mozilla.gecko.sync.repositories.domain.Record;
 import org.mozilla.gecko.sync.telemetry.TelemetryStageCollector;
 import org.mozilla.gecko.sync.validation.BookmarkValidationResults;
 import org.mozilla.gecko.sync.validation.BookmarkValidator;
@@ -57,18 +56,18 @@ public class BookmarksValidationReposito
     private final TelemetryStageCollector parentCollector;
 
     public BookmarksValidationRepository(ClientsDataDelegate clientsDataDelegate, TelemetryStageCollector collector) {
         this.clientsDataDelegate = clientsDataDelegate;
         this.parentCollector = collector;
     }
 
     @Override
-    public void createSession(RepositorySessionCreationDelegate delegate, Context context) {
-        delegate.onSessionCreated(new BookmarksValidationRepositorySession(this, context));
+    public RepositorySession createSession(Context context) {
+        return new BookmarksValidationRepositorySession(this, context);
     }
 
     public class BookmarksValidationRepositorySession extends RepositorySession {
 
         private final ConcurrentLinkedQueue<BookmarkRecord> local = new ConcurrentLinkedQueue<>();
         private final ConcurrentLinkedQueue<BookmarkRecord> remote = new ConcurrentLinkedQueue<>();
         private long startTime;
         private final BookmarksRepositorySession wrappedSession;
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/FennecTabsRepository.java
+++ b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/FennecTabsRepository.java
@@ -5,23 +5,23 @@
 package org.mozilla.gecko.sync.repositories.android;
 
 import java.util.ArrayList;
 
 import org.mozilla.gecko.background.common.log.Logger;
 import org.mozilla.gecko.background.db.Tab;
 import org.mozilla.gecko.db.BrowserContract;
 import org.mozilla.gecko.db.BrowserContract.Clients;
+import org.mozilla.gecko.sync.SessionCreateException;
 import org.mozilla.gecko.sync.delegates.ClientsDataDelegate;
 import org.mozilla.gecko.sync.repositories.InactiveSessionException;
 import org.mozilla.gecko.sync.repositories.NoContentProviderException;
 import org.mozilla.gecko.sync.repositories.NoStoreDelegateException;
 import org.mozilla.gecko.sync.repositories.Repository;
 import org.mozilla.gecko.sync.repositories.RepositorySession;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionCreationDelegate;
 import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionFetchRecordsDelegate;
 import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionFinishDelegate;
 import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionWipeDelegate;
 import org.mozilla.gecko.sync.repositories.domain.ClientRecord;
 import org.mozilla.gecko.sync.repositories.domain.Record;
 import org.mozilla.gecko.sync.repositories.domain.TabsRecord;
 
 import android.content.ContentProviderClient;
@@ -304,23 +304,21 @@ public class FennecTabsRepository extend
         delegate.onWipeFailed(e);
         return;
       }
       delegate.onWipeSucceeded();
     }
   }
 
   @Override
-  public void createSession(RepositorySessionCreationDelegate delegate,
-                            Context context) {
+  public RepositorySession createSession(Context context) throws SessionCreateException {
     try {
-      final FennecTabsRepositorySession session = new FennecTabsRepositorySession(this, context);
-      delegate.onSessionCreated(session);
+      return new FennecTabsRepositorySession(this, context);
     } catch (Exception e) {
-      delegate.onSessionCreateFailed(e);
+      throw new SessionCreateException(e);
     }
   }
 
   /**
    * Extract a <code>TabsRecord</code> from a cursor.
    * <p>
    * Caller is responsible for creating and closing cursor. Each row of the
    * cursor should be an individual tab record.
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/FormHistoryRepositorySession.java
+++ b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/FormHistoryRepositorySession.java
@@ -8,24 +8,25 @@ import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 import java.util.concurrent.Callable;
 
 import org.mozilla.gecko.background.common.log.Logger;
 import org.mozilla.gecko.db.BrowserContract;
 import org.mozilla.gecko.db.BrowserContract.DeletedFormHistory;
 import org.mozilla.gecko.db.BrowserContract.FormHistory;
+import org.mozilla.gecko.sync.SessionCreateException;
 import org.mozilla.gecko.sync.repositories.InactiveSessionException;
 import org.mozilla.gecko.sync.repositories.NoContentProviderException;
 import org.mozilla.gecko.sync.repositories.NoStoreDelegateException;
 import org.mozilla.gecko.sync.repositories.NullCursorException;
 import org.mozilla.gecko.sync.repositories.RecordFilter;
 import org.mozilla.gecko.sync.repositories.Repository;
+import org.mozilla.gecko.sync.repositories.RepositorySession;
 import org.mozilla.gecko.sync.repositories.StoreTrackingRepositorySession;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionCreationDelegate;
 import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionFetchRecordsDelegate;
 import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionFinishDelegate;
 import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionWipeDelegate;
 import org.mozilla.gecko.sync.repositories.domain.FormHistoryRecord;
 import org.mozilla.gecko.sync.repositories.domain.Record;
 
 import android.content.ContentProviderClient;
 import android.content.ContentValues;
@@ -44,23 +45,21 @@ public class FormHistoryRepositorySessio
   public static final int INSERT_ITEM_THRESHOLD = 200;
 
   private static final Uri FORM_HISTORY_CONTENT_URI = BrowserContractHelpers.FORM_HISTORY_CONTENT_URI;
   private static final Uri DELETED_FORM_HISTORY_CONTENT_URI = BrowserContractHelpers.DELETED_FORM_HISTORY_CONTENT_URI;
 
   public static class FormHistoryRepository extends Repository {
 
     @Override
-    public void createSession(RepositorySessionCreationDelegate delegate,
-                              Context context) {
+    public RepositorySession createSession(Context context) throws SessionCreateException {
       try {
-        final FormHistoryRepositorySession session = new FormHistoryRepositorySession(this, context);
-        delegate.onSessionCreated(session);
+        return new FormHistoryRepositorySession(this, context);
       } catch (Exception e) {
-        delegate.onSessionCreateFailed(e);
+        throw new SessionCreateException(e);
       }
     }
   }
 
   protected final ContentProviderClient formsProvider;
   protected final RepoUtils.QueryHelper regularHelper;
   protected final RepoUtils.QueryHelper deletedHelper;
 
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/HistoryRepository.java
+++ b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/HistoryRepository.java
@@ -1,24 +1,36 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.sync.repositories.android;
 
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionCreationDelegate;
+import org.mozilla.gecko.sync.repositories.Repository;
+import org.mozilla.gecko.sync.repositories.RepositorySession;
+import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionCleanDelegate;
 
 import android.content.Context;
 
-public class HistoryRepository extends ThreadedRepository implements org.mozilla.gecko.sync.repositories.HistoryRepository {
-
+public class HistoryRepository extends Repository {
   @Override
-  protected void sessionCreator(RepositorySessionCreationDelegate delegate, Context context) {
-    HistoryRepositorySession session = new HistoryRepositorySession(HistoryRepository.this, context);
-    delegate.onSessionCreated(session);
+  public RepositorySession createSession(Context context) {
+    return new HistoryRepositorySession(this, context);
   }
 
   @Override
-  protected DataAccessor getDataAccessor(Context context) {
-    return new HistoryDataAccessor(context);
+  public void clean(boolean success, RepositorySessionCleanDelegate delegate, Context context) {
+    if (!success) {
+      return;
+    }
+
+    final HistoryDataAccessor dataAccessor = new HistoryDataAccessor(context);
+
+    try {
+      dataAccessor.purgeDeleted();
+    } catch (Exception e) {
+      delegate.onCleanFailed(this, e);
+      return;
+    }
+
+    delegate.onCleaned(this);
   }
-
 }
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/PasswordsRepositorySession.java
+++ b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/PasswordsRepositorySession.java
@@ -9,19 +9,19 @@ import org.mozilla.gecko.db.BrowserContr
 import org.mozilla.gecko.db.BrowserContract.DeletedColumns;
 import org.mozilla.gecko.db.BrowserContract.DeletedPasswords;
 import org.mozilla.gecko.db.BrowserContract.Passwords;
 import org.mozilla.gecko.sync.repositories.InactiveSessionException;
 import org.mozilla.gecko.sync.repositories.NoStoreDelegateException;
 import org.mozilla.gecko.sync.repositories.NullCursorException;
 import org.mozilla.gecko.sync.repositories.RecordFilter;
 import org.mozilla.gecko.sync.repositories.Repository;
+import org.mozilla.gecko.sync.repositories.RepositorySession;
 import org.mozilla.gecko.sync.repositories.StoreTrackingRepositorySession;
 import org.mozilla.gecko.sync.repositories.android.RepoUtils.QueryHelper;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionCreationDelegate;
 import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionFetchRecordsDelegate;
 import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionFinishDelegate;
 import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionWipeDelegate;
 import org.mozilla.gecko.sync.repositories.domain.PasswordRecord;
 import org.mozilla.gecko.sync.repositories.domain.Record;
 
 import android.content.ContentProviderClient;
 import android.content.ContentUris;
@@ -31,21 +31,18 @@ import android.database.Cursor;
 import android.net.Uri;
 import android.os.RemoteException;
 
 public class PasswordsRepositorySession extends
     StoreTrackingRepositorySession {
 
   public static class PasswordsRepository extends Repository {
     @Override
-    public void createSession(RepositorySessionCreationDelegate delegate,
-        Context context) {
-      PasswordsRepositorySession session = new PasswordsRepositorySession(PasswordsRepository.this, context);
-      final RepositorySessionCreationDelegate deferredCreationDelegate = delegate.deferredCreationDelegate();
-      deferredCreationDelegate.onSessionCreated(session);
+    public RepositorySession createSession(Context context) {
+      return new PasswordsRepositorySession(this, context);
     }
   }
 
   private static final String LOG_TAG = "PasswordsRepoSession";
   private static final String COLLECTION = "passwords";
 
   private final RepoUtils.QueryHelper passwordsHelper;
   private final RepoUtils.QueryHelper deletedPasswordsHelper;
deleted file mode 100644
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/ThreadedRepository.java
+++ /dev/null
@@ -1,73 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.repositories.android;
-
-import org.mozilla.gecko.sync.repositories.Repository;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionCleanDelegate;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionCreationDelegate;
-
-import android.content.Context;
-
-public abstract class ThreadedRepository extends Repository {
-
-  @Override
-  public void createSession(RepositorySessionCreationDelegate delegate, Context context) {
-    new CreateSessionThread(delegate, context).start();
-  }
-
-  @Override
-  public void clean(boolean success, RepositorySessionCleanDelegate delegate, Context context) {
-    // Only clean deleted records if success
-    if (success) {
-      new CleanThread(delegate, context).start();
-    }
-  }
-
-  class CleanThread extends Thread {
-    private final RepositorySessionCleanDelegate delegate;
-    private final Context context;
-
-    public CleanThread(RepositorySessionCleanDelegate delegate, Context context) {
-      if (context == null) {
-        throw new IllegalArgumentException("context is null");
-      }
-      this.delegate = delegate;
-      this.context = context;
-    }
-
-    @Override
-    public void run() {
-      try {
-        getDataAccessor(context).purgeDeleted();
-      } catch (Exception e) {
-        delegate.onCleanFailed(ThreadedRepository.this, e);
-        return;
-      }
-      delegate.onCleaned(ThreadedRepository.this);
-    }
-  }
-
-  protected abstract DataAccessor getDataAccessor(Context context);
-  protected abstract void sessionCreator(RepositorySessionCreationDelegate delegate, Context context);
-
-  class CreateSessionThread extends Thread {
-    private final RepositorySessionCreationDelegate delegate;
-    private final Context context;
-
-    public CreateSessionThread(RepositorySessionCreationDelegate delegate, Context context) {
-      if (context == null) {
-        throw new IllegalArgumentException("context is null.");
-      }
-      this.delegate = delegate;
-      this.context = context;
-    }
-
-    @Override
-    public void run() {
-      sessionCreator(delegate, context);
-    }
-  }
-
-}
deleted file mode 100644
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/delegates/DeferrableRepositorySessionCreationDelegate.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.repositories.delegates;
-
-import org.mozilla.gecko.sync.ThreadPool;
-import org.mozilla.gecko.sync.repositories.RepositorySession;
-
-public abstract class DeferrableRepositorySessionCreationDelegate implements RepositorySessionCreationDelegate {
-  @Override
-  public RepositorySessionCreationDelegate deferredCreationDelegate() {
-    final RepositorySessionCreationDelegate self = this;
-    return new RepositorySessionCreationDelegate() {
-
-      // TODO: rewrite to use ExecutorService.
-      @Override
-      public void onSessionCreated(final RepositorySession session) {
-        ThreadPool.run(new Runnable() {
-          @Override
-          public void run() {
-            self.onSessionCreated(session);
-          }});
-      }
-
-      @Override
-      public void onSessionCreateFailed(final Exception ex) {
-        ThreadPool.run(new Runnable() {
-          @Override
-          public void run() {
-            self.onSessionCreateFailed(ex);
-          }});
-      }
-
-      @Override
-      public RepositorySessionCreationDelegate deferredCreationDelegate() {
-        return this;
-      }
-    };
-  }
-}
deleted file mode 100644
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/delegates/RepositorySessionCreationDelegate.java
+++ /dev/null
@@ -1,15 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.repositories.delegates;
-
-import org.mozilla.gecko.sync.repositories.RepositorySession;
-
-// Used to provide the sessionCallback and storeCallback
-// mechanism to repository instances.
-public interface RepositorySessionCreationDelegate {
-  public void onSessionCreateFailed(Exception ex);
-  public void onSessionCreated(RepositorySession session);
-  public RepositorySessionCreationDelegate deferredCreationDelegate();
-}
\ No newline at end of file
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/ServerSyncStage.java
+++ b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/ServerSyncStage.java
@@ -22,25 +22,23 @@ import org.mozilla.gecko.sync.crypto.Key
 import org.mozilla.gecko.sync.delegates.WipeServerDelegate;
 import org.mozilla.gecko.sync.middleware.Crypto5MiddlewareRepository;
 import org.mozilla.gecko.sync.net.AuthHeaderProvider;
 import org.mozilla.gecko.sync.net.BaseResource;
 import org.mozilla.gecko.sync.net.SyncStorageRequest;
 import org.mozilla.gecko.sync.net.SyncStorageRequestDelegate;
 import org.mozilla.gecko.sync.net.SyncStorageResponse;
 import org.mozilla.gecko.sync.repositories.InactiveSessionException;
-import org.mozilla.gecko.sync.repositories.InvalidSessionTransitionException;
 import org.mozilla.gecko.sync.repositories.NonPersistentRepositoryStateProvider;
 import org.mozilla.gecko.sync.repositories.RecordFactory;
 import org.mozilla.gecko.sync.repositories.Repository;
 import org.mozilla.gecko.sync.repositories.RepositorySession;
 import org.mozilla.gecko.sync.repositories.RepositorySessionBundle;
 import org.mozilla.gecko.sync.repositories.RepositoryStateProvider;
 import org.mozilla.gecko.sync.repositories.Server15Repository;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionCreationDelegate;
 import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionFinishDelegate;
 import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionWipeDelegate;
 import org.mozilla.gecko.sync.synchronizer.ServerLocalSynchronizer;
 import org.mozilla.gecko.sync.synchronizer.Synchronizer;
 import org.mozilla.gecko.sync.synchronizer.SynchronizerDelegate;
 import org.mozilla.gecko.sync.synchronizer.SynchronizerSession;
 import org.mozilla.gecko.sync.telemetry.TelemetryCollector;
 
@@ -308,93 +306,86 @@ public abstract class ServerSyncStage ex
 
     final WipeWaiter monitor = new WipeWaiter();
     final Context context = session.getContext();
     final Repository r = this.getLocalRepository();
 
     final Runnable doWipe = new Runnable() {
       @Override
       public void run() {
-        r.createSession(new RepositorySessionCreationDelegate() {
+        final RepositorySession localSession;
+        try {
+          localSession = r.createSession(context);
+        } catch (Exception e) {
+          synchronized (monitor) {
+            monitor.notify(e, false);
+          }
+          return;
+        }
+
+        try {
+          localSession.begin();
+        } catch (SyncException e) {
+          Logger.error(LOG_TAG, "Couldn't begin session", e);
+          localSession.abort();
+          synchronized (monitor) {
+            monitor.notify(e, true);
+          }
+          return;
+        }
+
+        localSession.wipe(new RepositorySessionWipeDelegate() {
           @Override
-          public void onSessionCreated(final RepositorySession session) {
+          public void onWipeSucceeded() {
             try {
-              session.begin();
-            } catch (SyncException e) {
-              Logger.error(LOG_TAG, "Couldn't begin session", e);
-              session.abort();
+              localSession.finish(new RepositorySessionFinishDelegate() {
+
+                @Override
+                public void onFinishSucceeded(RepositorySession session,
+                                              RepositorySessionBundle bundle) {
+                  // Hurrah.
+                  synchronized (monitor) {
+                    monitor.notify();
+                  }
+                }
+
+                @Override
+                public void onFinishFailed(Exception ex) {
+                  // Assume that no finish => no wipe.
+                  synchronized (monitor) {
+                    monitor.notify(ex, true);
+                  }
+                }
+
+                @Override
+                public RepositorySessionFinishDelegate deferredFinishDelegate(ExecutorService executor) {
+                  return this;
+                }
+              });
+            } catch (InactiveSessionException e) {
+              // Cannot happen. Call for safety.
               synchronized (monitor) {
                 monitor.notify(e, true);
               }
-              return;
-            }
-
-            session.wipe(new RepositorySessionWipeDelegate() {
-              @Override
-              public void onWipeSucceeded() {
-                try {
-                  session.finish(new RepositorySessionFinishDelegate() {
-
-                    @Override
-                    public void onFinishSucceeded(RepositorySession session,
-                                                  RepositorySessionBundle bundle) {
-                      // Hurrah.
-                      synchronized (monitor) {
-                        monitor.notify();
-                      }
-                    }
-
-                    @Override
-                    public void onFinishFailed(Exception ex) {
-                      // Assume that no finish => no wipe.
-                      synchronized (monitor) {
-                        monitor.notify(ex, true);
-                      }
-                    }
-
-                    @Override
-                    public RepositorySessionFinishDelegate deferredFinishDelegate(ExecutorService executor) {
-                      return this;
-                    }
-                  });
-                } catch (InactiveSessionException e) {
-                  // Cannot happen. Call for safety.
-                  synchronized (monitor) {
-                    monitor.notify(e, true);
-                  }
-                }
-              }
-
-              @Override
-              public void onWipeFailed(Exception ex) {
-                session.abort();
-                synchronized (monitor) {
-                  monitor.notify(ex, true);
-                }
-              }
-
-              @Override
-              public RepositorySessionWipeDelegate deferredWipeDelegate(ExecutorService executor) {
-                return this;
-              }
-            });
-          }
-
-          @Override
-          public void onSessionCreateFailed(Exception ex) {
-            synchronized (monitor) {
-              monitor.notify(ex, false);
             }
           }
 
           @Override
-          public RepositorySessionCreationDelegate deferredCreationDelegate() {
+          public void onWipeFailed(Exception ex) {
+            localSession.abort();
+            synchronized (monitor) {
+              monitor.notify(ex, true);
+            }
+          }
+
+          @Override
+          public RepositorySessionWipeDelegate deferredWipeDelegate(ExecutorService executor) {
             return this;
           }
-        }, context);
+        });
       }
     };
 
     final Thread wiping = new Thread(doWipe);
     synchronized (monitor) {
       wiping.start();
       try {
         monitor.wait();
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/ValidateBookmarksSyncStage.java
+++ b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/ValidateBookmarksSyncStage.java
@@ -3,37 +3,30 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.sync.stage;
 
 
 import android.content.Context;
 import android.database.Cursor;
 import android.os.SystemClock;
-import android.support.annotation.Nullable;
 
 import org.mozilla.gecko.AppConstants;
 import org.mozilla.gecko.db.BrowserContract;
 import org.mozilla.gecko.sync.MetaGlobalException;
-import org.mozilla.gecko.sync.NonObjectJSONException;
-import org.mozilla.gecko.sync.SynchronizerConfiguration;
-import org.mozilla.gecko.sync.middleware.BufferingMiddlewareRepository;
-import org.mozilla.gecko.sync.middleware.MiddlewareRepository;
-import org.mozilla.gecko.sync.middleware.storage.MemoryBufferStorage;
 import org.mozilla.gecko.sync.repositories.ConfigurableServer15Repository;
 import org.mozilla.gecko.sync.repositories.RecordFactory;
 import org.mozilla.gecko.sync.repositories.Repository;
 import org.mozilla.gecko.sync.repositories.android.BookmarksValidationRepository;
 import org.mozilla.gecko.sync.repositories.android.BrowserContractHelpers;
 import org.mozilla.gecko.sync.repositories.domain.BookmarkRecordFactory;
 import org.mozilla.gecko.sync.repositories.domain.VersionConstants;
 import org.mozilla.gecko.sync.telemetry.TelemetryCollector;
 import org.mozilla.gecko.sync.telemetry.TelemetryStageCollector;
 
-import java.io.IOException;
 import java.net.URISyntaxException;
 import java.util.concurrent.TimeUnit;
 
 /**
  * This sync stage runs bookmark validation for including in telemetry. Bookmark validation
  * is fairly costly, so we don't want to run it frequently, and there are a number of
  * checks to make sure it's a good idea to run it.
  *
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/synchronizer/Synchronizer.java
+++ b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/synchronizer/Synchronizer.java
@@ -36,21 +36,16 @@ public class Synchronizer implements Syn
 
   protected SynchronizerSession session = null;
 
   public SynchronizerSession getSynchronizerSession() {
     return session;
   }
 
   @Override
-  public void onInitialized(SynchronizerSession session) {
-    session.synchronize();
-  }
-
-  @Override
   public void onSynchronized(SynchronizerSession synchronizerSession) {
     Logger.debug(LOG_TAG, "Got onSynchronized.");
     Logger.debug(LOG_TAG, "Notifying SynchronizerDelegate.");
     this.synchronizerDelegate.onSynchronized(synchronizerSession.getSynchronizer());
   }
 
   @Override
   public void onSynchronizeSkipped(SynchronizerSession synchronizerSession) {
@@ -78,17 +73,17 @@ public class Synchronizer implements Syn
   }
 
   /**
    * Start synchronizing, calling delegate's callback methods.
    */
   public void synchronize(Context context, SynchronizerDelegate delegate) {
     this.synchronizerDelegate = delegate;
     this.session = newSynchronizerSession();
-    this.session.init(context, bundleA, bundleB);
+    this.session.initAndSynchronize(context, bundleA, bundleB);
   }
 
   public SynchronizerConfiguration save() {
     return new SynchronizerConfiguration(configSyncID, bundleA, bundleB);
   }
 
   /**
    * Set my repository session bundles from a SynchronizerConfiguration.
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/synchronizer/SynchronizerSession.java
+++ b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/synchronizer/SynchronizerSession.java
@@ -4,21 +4,21 @@
 
 package org.mozilla.gecko.sync.synchronizer;
 
 
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.atomic.AtomicInteger;
 
 import org.mozilla.gecko.background.common.log.Logger;
+import org.mozilla.gecko.sync.SyncException;
 import org.mozilla.gecko.sync.repositories.InactiveSessionException;
 import org.mozilla.gecko.sync.repositories.InvalidSessionTransitionException;
 import org.mozilla.gecko.sync.repositories.RepositorySession;
 import org.mozilla.gecko.sync.repositories.RepositorySessionBundle;
-import org.mozilla.gecko.sync.repositories.delegates.DeferrableRepositorySessionCreationDelegate;
 import org.mozilla.gecko.sync.repositories.delegates.DeferredRepositorySessionFinishDelegate;
 import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionFinishDelegate;
 
 import android.content.Context;
 
 /**
  * I coordinate the moving parts of a sync started by
  * {@link Synchronizer#synchronize}.
@@ -41,20 +41,17 @@ import android.content.Context;
  * I encounter a fetch, store, or session error while synchronizing.
  *
  * Typically my delegate will call `abort` in its error callbacks, which will
  * call my delegate's `onSynchronizeAborted` method and halt the sync.
  *
  * I always call exactly one of my delegate's `onSynchronized` or
  * `onSynchronizeFailed` callback methods if I have not seen an error.
  */
-public class SynchronizerSession
-extends DeferrableRepositorySessionCreationDelegate
-implements RecordsChannelDelegate,
-           RepositorySessionFinishDelegate {
+public class SynchronizerSession implements RecordsChannelDelegate, RepositorySessionFinishDelegate {
 
   protected static final String LOG_TAG = "SynchronizerSession";
   protected Synchronizer synchronizer;
   protected SynchronizerSessionDelegate delegate;
   protected Context context;
 
   /*
    * Computed during init.
@@ -99,22 +96,73 @@ implements RecordsChannelDelegate,
   public Synchronizer getSynchronizer() {
     return synchronizer;
   }
 
   public void setSynchronizer(Synchronizer synchronizer) {
     this.synchronizer = synchronizer;
   }
 
-  public void init(Context context, RepositorySessionBundle bundleA, RepositorySessionBundle bundleB) {
+  public void initAndSynchronize(Context context, RepositorySessionBundle bundleA, RepositorySessionBundle bundleB) {
     this.context = context;
     this.bundleA = bundleA;
     this.bundleB = bundleB;
-    // Begin sessionA and sessionB, call onInitialized in callbacks.
-    this.getSynchronizer().repositoryA.createSession(this, context);
+
+    try {
+      this.sessionA = this.getSynchronizer().repositoryA.createSession(context);
+    } catch (SyncException e) {
+      // We no longer need a reference to our context.
+      this.context = null;
+      this.delegate.onSynchronizeFailed(this, e, "Failed to create session");
+      return;
+    }
+
+    try {
+      this.sessionA.unbundle(bundleA);
+    } catch (Exception e) {
+      this.delegate.onSynchronizeFailed(this, new UnbundleError(e, sessionA), "Failed to unbundle first session.");
+      return;
+    }
+
+    try {
+      this.sessionB = this.getSynchronizer().repositoryB.createSession(context);
+    } catch (final SyncException createException) {
+      // We no longer need a reference to our context.
+      this.context = null;
+      // Finish already created sessionA.
+      try {
+        this.sessionA.finish(new RepositorySessionFinishDelegate() {
+          @Override
+          public void onFinishFailed(Exception ex) {
+            SynchronizerSession.this.delegate.onSynchronizeFailed(SynchronizerSession.this, createException, "Failed to create second session.");
+          }
+
+          @Override
+          public void onFinishSucceeded(RepositorySession session, RepositorySessionBundle bundle) {
+            SynchronizerSession.this.delegate.onSynchronizeFailed(SynchronizerSession.this, createException, "Failed to create second session.");
+          }
+
+          @Override
+          public RepositorySessionFinishDelegate deferredFinishDelegate(ExecutorService executor) {
+            return new DeferredRepositorySessionFinishDelegate(this, executor);
+          }
+        });
+      } catch (InactiveSessionException finishException) {
+        SynchronizerSession.this.delegate.onSynchronizeFailed(SynchronizerSession.this, createException, "Failed to create second session.");
+      }
+      return;
+    }
+
+    try {
+      this.sessionB.unbundle(bundleB);
+    } catch (Exception e) {
+      this.delegate.onSynchronizeFailed(this, new UnbundleError(e, sessionA), "Failed to unbundle second session.");
+    }
+
+    synchronize();
   }
 
   /**
    * Get the number of records fetched from the first repository (usually the
    * server, hence inbound).
    * <p>
    * Valid only after first flow has completed.
    *
@@ -167,17 +215,17 @@ implements RecordsChannelDelegate,
   // These are accessed by `abort` and `synchronize`, both of which are synchronized.
   // Guarded by `this`.
   protected RecordsChannel channelAToB;
   protected RecordsChannel channelBToA;
 
   /**
    * Please don't call this until you've been notified with onInitialized.
    */
-  public synchronized void synchronize() {
+  private synchronized void synchronize() {
     numInboundRecords.set(-1);
     numInboundRecordsStored.set(-1);
     numInboundRecordsFailed.set(-1);
     numInboundRecordsReconciled.set(-1);
     numOutboundRecords.set(-1);
     numOutboundRecordsStored.set(-1);
     numOutboundRecordsFailed.set(-1);
 
@@ -300,92 +348,16 @@ implements RecordsChannelDelegate,
   }
 
   @Override
   public void onFlowStoreFailed(RecordsChannel recordsChannel, Exception ex, String recordGuid) {
     Logger.warn(LOG_TAG, "Second RecordsChannel onFlowStoreFailed. Logging remote store error.", ex);
   }
 
   /*
-   * RepositorySessionCreationDelegate methods.
-   */
-
-  /**
-   * I could be called twice: once for sessionA and once for sessionB.
-   *
-   * I try to clean up sessionA if it is not null, since the creation of
-   * sessionB must have failed.
-   */
-  @Override
-  public void onSessionCreateFailed(Exception ex) {
-    // Attempt to finish the first session, if the second is the one that failed.
-    if (this.sessionA != null) {
-      try {
-        // We no longer need a reference to our context.
-        this.context = null;
-        this.sessionA.finish(this);
-      } catch (Exception e) {
-        // Never mind; best-effort finish.
-      }
-    }
-    // We no longer need a reference to our context.
-    this.context = null;
-    this.delegate.onSynchronizeFailed(this, ex, "Failed to create session");
-  }
-
-  /**
-   * I should be called twice: first for sessionA and second for sessionB.
-   *
-   * If I am called for sessionB, I call my delegate's `onInitialized` callback
-   * method because my repository sessions are correctly initialized.
-   */
-  // TODO: some of this "finish and clean up" code can be refactored out.
-  @Override
-  public void onSessionCreated(RepositorySession session) {
-    if (session == null ||
-        this.sessionA == session) {
-      // TODO: clean up sessionA.
-      this.delegate.onSynchronizeFailed(this, new UnexpectedSessionException(session), "Failed to create session.");
-      return;
-    }
-    if (this.sessionA == null) {
-      this.sessionA = session;
-
-      // Unbundle.
-      try {
-        this.sessionA.unbundle(this.bundleA);
-      } catch (Exception e) {
-        this.delegate.onSynchronizeFailed(this, new UnbundleError(e, sessionA), "Failed to unbundle first session.");
-        // TODO: abort
-        return;
-      }
-      this.getSynchronizer().repositoryB.createSession(this, this.context);
-      return;
-    }
-    if (this.sessionB == null) {
-      this.sessionB = session;
-      // We no longer need a reference to our context.
-      this.context = null;
-
-      // Unbundle. We unbundled sessionA when that session was created.
-      try {
-        this.sessionB.unbundle(this.bundleB);
-      } catch (Exception e) {
-        this.delegate.onSynchronizeFailed(this, new UnbundleError(e, sessionA), "Failed to unbundle second session.");
-        return;
-      }
-
-      this.delegate.onInitialized(this);
-      return;
-    }
-    // TODO: need a way to make sure we don't call any more delegate methods.
-    this.delegate.onSynchronizeFailed(this, new UnexpectedSessionException(session), "Failed to create session.");
-  }
-
-  /*
    * RepositorySessionFinishDelegate methods.
    */
 
   /**
    * I could be called twice: once for sessionA and once for sessionB.
    *
    * If sessionB couldn't be created, I don't fail again.
    */
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/synchronizer/SynchronizerSessionDelegate.java
+++ b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/synchronizer/SynchronizerSessionDelegate.java
@@ -1,13 +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.synchronizer;
 
 public interface SynchronizerSessionDelegate {
-  public void onInitialized(SynchronizerSession session);
-
   public void onSynchronized(SynchronizerSession session);
   public void onSynchronizeFailed(SynchronizerSession session, Exception lastException, String reason);
   public void onSynchronizeSkipped(SynchronizerSession synchronizerSession);
 }
--- a/mobile/android/services/src/test/java/org/mozilla/android/sync/test/SynchronizerHelpers.java
+++ b/mobile/android/services/src/test/java/org/mozilla/android/sync/test/SynchronizerHelpers.java
@@ -7,18 +7,18 @@ import android.content.Context;
 import org.mozilla.gecko.background.common.log.Logger;
 import org.mozilla.gecko.background.testhelpers.WBORepository;
 import org.mozilla.gecko.sync.CollectionConcurrentModificationException;
 import org.mozilla.gecko.sync.SyncDeadlineReachedException;
 import org.mozilla.gecko.sync.SyncException;
 import org.mozilla.gecko.sync.repositories.FetchFailedException;
 import org.mozilla.gecko.sync.repositories.InactiveSessionException;
 import org.mozilla.gecko.sync.repositories.NoStoreDelegateException;
+import org.mozilla.gecko.sync.repositories.RepositorySession;
 import org.mozilla.gecko.sync.repositories.StoreFailedException;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionCreationDelegate;
 import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionFetchRecordsDelegate;
 import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionFinishDelegate;
 import org.mozilla.gecko.sync.repositories.domain.Record;
 
 import java.util.ArrayList;
 import java.util.concurrent.ExecutorService;
 
 public class SynchronizerHelpers {
@@ -52,19 +52,18 @@ public class SynchronizerHelpers {
   public static class FailFetchWBORepository extends WBORepository {
     private final FailMode failMode;
 
     public FailFetchWBORepository(FailMode failMode) {
       this.failMode = failMode;
     }
 
     @Override
-    public void createSession(RepositorySessionCreationDelegate delegate,
-                              Context context) {
-      delegate.deferredCreationDelegate().onSessionCreated(new WBORepositorySession(this) {
+    public RepositorySession createSession(Context context) {
+      return new WBORepositorySession(this) {
         @Override
         public void fetchModified(final RepositorySessionFetchRecordsDelegate delegate) {
           super.fetchModified(new RepositorySessionFetchRecordsDelegate() {
             @Override
             public void onFetchedRecord(Record record) {
               if (record.guid.contains(FAIL_SENTINEL)) {
                 delegate.onFetchFailed(getFailException(failMode));
               } else {
@@ -88,51 +87,50 @@ public class SynchronizerHelpers {
             }
 
             @Override
             public RepositorySessionFetchRecordsDelegate deferredFetchDelegate(ExecutorService executor) {
               return this;
             }
           });
         }
-      });
+      };
     }
   }
 
   /**
    * Store one at a time, failing if the guid contains FAIL_SENTINEL.
    */
   public static class SerialFailStoreWBORepository extends WBORepository {
     private final FailMode failMode;
 
     public SerialFailStoreWBORepository(FailMode failMode) {
       this.failMode = failMode;
     }
 
     @Override
-    public void createSession(RepositorySessionCreationDelegate delegate,
-                              Context context) {
-      delegate.deferredCreationDelegate().onSessionCreated(new WBORepositorySession(this) {
+    public RepositorySession createSession(Context context) {
+      return new WBORepositorySession(this) {
         @Override
         public void store(final Record record) throws NoStoreDelegateException {
           if (storeDelegate == null) {
             throw new NoStoreDelegateException();
           }
           if (record.guid.contains(FAIL_SENTINEL)) {
             Exception ex = getFailException(failMode);
             if (ex instanceof CollectionConcurrentModificationException) {
               storeDelegate.onStoreFailed(ex);
             } else {
               storeDelegate.onRecordStoreFailed(ex, record.guid);
             }
           } else {
             super.store(record);
           }
         }
-      });
+      };
     }
   }
 
   /**
    * Store in batches, failing if any of the batch guids contains "Fail".
    * <p>
    * This will drop the final batch.
    */
@@ -211,19 +209,18 @@ public class SynchronizerHelpers {
       }
     }
     public BatchFailStoreWBORepository(int batchSize) {
       super();
       this.batchSize = batchSize;
     }
 
     @Override
-    public void createSession(RepositorySessionCreationDelegate delegate,
-                              Context context) {
-      delegate.deferredCreationDelegate().onSessionCreated(new BatchFailStoreWBORepositorySession(this));
+    public RepositorySession createSession(Context context) {
+      return new BatchFailStoreWBORepositorySession(this);
     }
   }
 
   public static class TrackingWBORepository extends WBORepository {
     @Override
     public synchronized boolean shouldTrack() {
       return true;
     }
@@ -234,38 +231,36 @@ public class SynchronizerHelpers {
   }
 
   public static class FinishFailedException extends Exception {
     private static final long serialVersionUID = -4644528423867070934L;
   }
 
   public static class BeginErrorWBORepository extends TrackingWBORepository {
     @Override
-    public void createSession(RepositorySessionCreationDelegate delegate,
-                              Context context) {
-      delegate.deferredCreationDelegate().onSessionCreated(new BeginErrorWBORepositorySession(this));
+    public RepositorySession createSession(Context context) {
+      return new BeginErrorWBORepositorySession(this);
     }
 
     public class BeginErrorWBORepositorySession extends WBORepositorySession {
       public BeginErrorWBORepositorySession(WBORepository repository) {
         super(repository);
       }
 
       @Override
       public void begin() throws SyncException {
         throw new BeginFailedException();
       }
     }
   }
 
   public static class FinishErrorWBORepository extends TrackingWBORepository {
     @Override
-    public void createSession(RepositorySessionCreationDelegate delegate,
-                              Context context) {
-      delegate.deferredCreationDelegate().onSessionCreated(new FinishErrorWBORepositorySession(this));
+    public RepositorySession createSession(Context context) {
+      return new FinishErrorWBORepositorySession(this);
     }
 
     public class FinishErrorWBORepositorySession extends WBORepositorySession {
       public FinishErrorWBORepositorySession(WBORepository repository) {
         super(repository);
       }
 
       @Override
@@ -278,19 +273,18 @@ public class SynchronizerHelpers {
   public static class DataAvailableWBORepository extends TrackingWBORepository {
     public boolean dataAvailable = true;
 
     public DataAvailableWBORepository(boolean dataAvailable) {
       this.dataAvailable = dataAvailable;
     }
 
     @Override
-    public void createSession(RepositorySessionCreationDelegate delegate,
-                              Context context) {
-      delegate.deferredCreationDelegate().onSessionCreated(new DataAvailableWBORepositorySession(this));
+    public RepositorySession createSession(Context context) {
+      return new DataAvailableWBORepositorySession(this);
     }
 
     public class DataAvailableWBORepositorySession extends WBORepositorySession {
       public DataAvailableWBORepositorySession(WBORepository repository) {
         super(repository);
       }
 
       @Override
@@ -303,19 +297,18 @@ public class SynchronizerHelpers {
   public static class ShouldSkipWBORepository extends TrackingWBORepository {
     public boolean shouldSkip = true;
 
     public ShouldSkipWBORepository(boolean shouldSkip) {
       this.shouldSkip = shouldSkip;
     }
 
     @Override
-    public void createSession(RepositorySessionCreationDelegate delegate,
-                              Context context) {
-      delegate.deferredCreationDelegate().onSessionCreated(new ShouldSkipWBORepositorySession(this));
+    public RepositorySession createSession(Context context) {
+      return new ShouldSkipWBORepositorySession(this);
     }
 
     public class ShouldSkipWBORepositorySession extends WBORepositorySession {
       public ShouldSkipWBORepositorySession(WBORepository repository) {
         super(repository);
       }
 
       @Override
--- a/mobile/android/services/src/test/java/org/mozilla/android/sync/test/TestRecordsChannel.java
+++ b/mobile/android/services/src/test/java/org/mozilla/android/sync/test/TestRecordsChannel.java
@@ -2,17 +2,16 @@
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.android.sync.test;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mozilla.android.sync.test.SynchronizerHelpers.FailFetchWBORepository;
-import org.mozilla.android.sync.test.helpers.ExpectSuccessRepositorySessionCreationDelegate;
 import org.mozilla.android.sync.test.helpers.ExpectSuccessRepositorySessionFinishDelegate;
 import org.mozilla.gecko.background.testhelpers.TestRunner;
 import org.mozilla.gecko.background.testhelpers.WBORepository;
 import org.mozilla.gecko.background.testhelpers.WaitHelper;
 import org.mozilla.gecko.sync.CollectionConcurrentModificationException;
 import org.mozilla.gecko.sync.SyncDeadlineReachedException;
 import org.mozilla.gecko.sync.repositories.InactiveSessionException;
 import org.mozilla.gecko.sync.repositories.InvalidSessionTransitionException;
@@ -104,34 +103,18 @@ public class TestRecordsChannel {
       public void onFlowBeginFailed(RecordsChannel recordsChannel, Exception ex) {
         flowBeginFailed.set(true);
         WaitHelper.getTestWaiter().performNotify();
       }
     };
   }
 
   private void createSessions() {
-    WaitHelper.getTestWaiter().performWait(new Runnable() {
-      @Override
-      public void run() {
-        sourceRepository.createSession(new ExpectSuccessRepositorySessionCreationDelegate(WaitHelper.getTestWaiter()) {
-          @Override
-          public void onSessionCreated(RepositorySession session) {
-            sourceSession = session;
-            sinkRepository.createSession(new ExpectSuccessRepositorySessionCreationDelegate(WaitHelper.getTestWaiter()) {
-              @Override
-              public void onSessionCreated(RepositorySession session) {
-                sinkSession = session;
-                WaitHelper.getTestWaiter().performNotify();
-              }
-            }, null);
-          }
-        }, null);
-      }
-    });
+    sourceSession = sourceRepository.createSession(null);
+    sinkSession = sinkRepository.createSession(null);
   }
 
   public void doFlow() throws Exception {
     createSessions();
     assertNotNull(sourceSession);
     assertNotNull(sinkSession);
     recordsChannel = new RecordsChannel(sourceSession,  sinkSession, rcDelegate);
     WaitHelper.getTestWaiter().performWait(new Runnable() {
--- a/mobile/android/services/src/test/java/org/mozilla/android/sync/test/TestSynchronizer.java
+++ b/mobile/android/services/src/test/java/org/mozilla/android/sync/test/TestSynchronizer.java
@@ -1,14 +1,15 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.android.sync.test;
 
 import android.content.Context;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mozilla.android.sync.test.SynchronizerHelpers.TrackingWBORepository;
 import org.mozilla.gecko.background.common.log.Logger;
 import org.mozilla.gecko.background.testhelpers.TestRunner;
 import org.mozilla.gecko.background.testhelpers.WBORepository;
@@ -82,28 +83,16 @@ public class TestSynchronizer {
 
     repoA.wbos.put(guidA, bookmarkRecordA);
     repoB.wbos.put(guidB, bookmarkRecordB);
     repoB.wbos.put(guidC, bookmarkRecordC);
     Synchronizer synchronizer = new Synchronizer();
     synchronizer.repositoryA = repoA;
     synchronizer.repositoryB = repoB;
     final SynchronizerSession syncSession = new SynchronizerSession(synchronizer, new SynchronizerSessionDelegate() {
-
-      @Override
-      public void onInitialized(SynchronizerSession session) {
-        assertFalse(repoA.wbos.containsKey(guidB));
-        assertFalse(repoA.wbos.containsKey(guidC));
-        assertFalse(repoB.wbos.containsKey(guidA));
-        assertTrue(repoA.wbos.containsKey(guidA));
-        assertTrue(repoB.wbos.containsKey(guidB));
-        assertTrue(repoB.wbos.containsKey(guidC));
-        session.synchronize();
-      }
-
       @Override
       public void onSynchronized(SynchronizerSession session) {
         try {
           assertEquals(1, session.getInboundCount());
           assertEquals(2, session.getOutboundCount());
           WaitHelper.getTestWaiter().performNotify();
         } catch (Throwable e) {
           WaitHelper.getTestWaiter().performNotify(e);
@@ -120,17 +109,23 @@ public class TestSynchronizer {
       public void onSynchronizeSkipped(SynchronizerSession synchronizerSession) {
         WaitHelper.getTestWaiter().performNotify(new RuntimeException());
       }
     });
 
     WaitHelper.getTestWaiter().performWait(new Runnable() {
       @Override
       public void run() {
-        syncSession.init(context, new RepositorySessionBundle(0), new RepositorySessionBundle(0));
+        assertFalse(repoA.wbos.containsKey(guidB));
+        assertFalse(repoA.wbos.containsKey(guidC));
+        assertFalse(repoB.wbos.containsKey(guidA));
+        assertTrue(repoA.wbos.containsKey(guidA));
+        assertTrue(repoB.wbos.containsKey(guidB));
+        assertTrue(repoB.wbos.containsKey(guidC));
+        syncSession.initAndSynchronize(context, new RepositorySessionBundle(0), new RepositorySessionBundle(0));
       }
     });
 
     // Verify contents.
     assertTrue(repoA.wbos.containsKey(guidA));
     assertTrue(repoA.wbos.containsKey(guidB));
     assertTrue(repoA.wbos.containsKey(guidC));
     assertTrue(repoB.wbos.containsKey(guidA));
--- a/mobile/android/services/src/test/java/org/mozilla/android/sync/test/TestSynchronizerSession.java
+++ b/mobile/android/services/src/test/java/org/mozilla/android/sync/test/TestSynchronizerSession.java
@@ -80,21 +80,16 @@ public class TestSynchronizerSession {
     originalWbosA = new HashMap<String, Record>(repoA.wbos);
     originalWbosB = new HashMap<String, Record>(repoB.wbos);
 
     Synchronizer synchronizer = new Synchronizer();
     synchronizer.repositoryA = repoA;
     synchronizer.repositoryB = repoB;
     syncSession = new SynchronizerSession(synchronizer, new SynchronizerSessionDelegate() {
       @Override
-      public void onInitialized(SynchronizerSession session) {
-        session.synchronize();
-      }
-
-      @Override
       public void onSynchronized(SynchronizerSession session) {
         WaitHelper.getTestWaiter().performNotify();
       }
 
       @Override
       public void onSynchronizeFailed(SynchronizerSession session, Exception lastException, String reason) {
         WaitHelper.getTestWaiter().performNotify(lastException);
       }
@@ -123,17 +118,17 @@ public class TestSynchronizerSession {
   protected void doTest(boolean remoteDataAvailable, boolean localDataAvailable) {
     ((DataAvailableWBORepository) repoA).dataAvailable = remoteDataAvailable;
     ((DataAvailableWBORepository) repoB).dataAvailable = localDataAvailable;
 
     WaitHelper.getTestWaiter().performWait(new Runnable() {
       @Override
       public void run() {
         final Context context = null;
-        syncSession.init(context,
+        syncSession.initAndSynchronize(context,
             new RepositorySessionBundle(0),
             new RepositorySessionBundle(0));
       }
     });
 
     logStats();
   }
 
@@ -243,21 +238,16 @@ public class TestSynchronizerSession {
     repoB = new ShouldSkipWBORepository(localShouldSkip);
 
     Synchronizer synchronizer = new Synchronizer();
     synchronizer.repositoryA = repoA;
     synchronizer.repositoryB = repoB;
 
     syncSession = new SynchronizerSession(synchronizer, new SynchronizerSessionDelegate() {
       @Override
-      public void onInitialized(SynchronizerSession session) {
-        session.synchronize();
-      }
-
-      @Override
       public void onSynchronized(SynchronizerSession session) {
         WaitHelper.getTestWaiter().performNotify(new RuntimeException("Not expecting onSynchronized"));
       }
 
       @Override
       public void onSynchronizeFailed(SynchronizerSession session, Exception lastException, String reason) {
         WaitHelper.getTestWaiter().performNotify(lastException);
       }
@@ -267,17 +257,17 @@ public class TestSynchronizerSession {
         WaitHelper.getTestWaiter().performNotify();
       }
     });
 
     WaitHelper.getTestWaiter().performWait(new Runnable() {
       @Override
       public void run() {
         final Context context = null;
-        syncSession.init(context,
+        syncSession.initAndSynchronize(context,
             new RepositorySessionBundle(100),
             new RepositorySessionBundle(200));
       }
     });
 
     // If we skip, we don't update timestamps or even un-bundle.
     SynchronizerConfiguration sc = syncSession.getSynchronizer().save();
     assertNotNull(sc);
deleted file mode 100644
--- a/mobile/android/services/src/test/java/org/mozilla/android/sync/test/helpers/ExpectSuccessRepositorySessionCreationDelegate.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
-   http://creativecommons.org/publicdomain/zero/1.0/ */
-
-package org.mozilla.android.sync.test.helpers;
-
-import junit.framework.AssertionFailedError;
-import org.mozilla.gecko.background.testhelpers.WaitHelper;
-import org.mozilla.gecko.sync.repositories.RepositorySession;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionCreationDelegate;
-
-public class ExpectSuccessRepositorySessionCreationDelegate extends
-    ExpectSuccessDelegate implements RepositorySessionCreationDelegate {
-
-  public ExpectSuccessRepositorySessionCreationDelegate(WaitHelper waitHelper) {
-    super(waitHelper);
-  }
-
-  @Override
-  public void onSessionCreateFailed(Exception ex) {
-    log("Session creation failed.", ex);
-    performNotify(new AssertionFailedError("onSessionCreateFailed: session creation should not have failed."));
-  }
-
-  @Override
-  public void onSessionCreated(RepositorySession session) {
-    log("Session creation succeeded.");
-    performNotify();
-  }
-
-  @Override
-  public RepositorySessionCreationDelegate deferredCreationDelegate() {
-    log("Session creation deferred.");
-    return this;
-  }
-
-}
--- a/mobile/android/services/src/test/java/org/mozilla/gecko/background/testhelpers/WBORepository.java
+++ b/mobile/android/services/src/test/java/org/mozilla/gecko/background/testhelpers/WBORepository.java
@@ -5,18 +5,18 @@ package org.mozilla.gecko.background.tes
 
 import android.content.Context;
 import org.mozilla.gecko.background.common.log.Logger;
 import org.mozilla.gecko.sync.SyncException;
 import org.mozilla.gecko.sync.repositories.InactiveSessionException;
 import org.mozilla.gecko.sync.repositories.NoStoreDelegateException;
 import org.mozilla.gecko.sync.repositories.RecordFilter;
 import org.mozilla.gecko.sync.repositories.Repository;
+import org.mozilla.gecko.sync.repositories.RepositorySession;
 import org.mozilla.gecko.sync.repositories.StoreTrackingRepositorySession;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionCreationDelegate;
 import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionFetchRecordsDelegate;
 import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionFinishDelegate;
 import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionWipeDelegate;
 import org.mozilla.gecko.sync.repositories.domain.Record;
 
 import java.util.Map.Entry;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ExecutorService;
@@ -206,19 +206,18 @@ public class WBORepository extends Repos
     this(false);
   }
 
   public synchronized boolean shouldTrack() {
     return false;
   }
 
   @Override
-  public void createSession(RepositorySessionCreationDelegate delegate,
-                            Context context) {
-    delegate.deferredCreationDelegate().onSessionCreated(new WBORepositorySession(this));
+  public RepositorySession createSession(Context context) {
+    return new WBORepositorySession(this);
   }
 
   public ConcurrentHashMap<String, Record> cloneWBOs() {
     ConcurrentHashMap<String, Record> out = new ConcurrentHashMap<String, Record>();
     for (Entry<String, Record> entry : wbos.entrySet()) {
       out.put(entry.getKey(), entry.getValue()); // Assume that records are
                                                  // immutable.
     }
--- a/mobile/android/services/src/test/java/org/mozilla/gecko/sync/middleware/test/TestCrypto5MiddlewareRepositorySession.java
+++ b/mobile/android/services/src/test/java/org/mozilla/gecko/sync/middleware/test/TestCrypto5MiddlewareRepositorySession.java
@@ -2,42 +2,36 @@
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.gecko.sync.middleware.test;
 
 import junit.framework.AssertionFailedError;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.mozilla.android.sync.test.helpers.ExpectSuccessRepositorySessionCreationDelegate;
 import org.mozilla.android.sync.test.helpers.ExpectSuccessRepositorySessionFetchRecordsDelegate;
 import org.mozilla.android.sync.test.helpers.ExpectSuccessRepositorySessionFinishDelegate;
 import org.mozilla.android.sync.test.helpers.ExpectSuccessRepositorySessionStoreDelegate;
 import org.mozilla.android.sync.test.helpers.ExpectSuccessRepositoryWipeDelegate;
 import org.mozilla.gecko.background.testhelpers.MockRecord;
 import org.mozilla.gecko.background.testhelpers.TestRunner;
 import org.mozilla.gecko.background.testhelpers.WBORepository;
 import org.mozilla.gecko.background.testhelpers.WaitHelper;
 import org.mozilla.gecko.sync.CryptoRecord;
-import org.mozilla.gecko.sync.NonObjectJSONException;
 import org.mozilla.gecko.sync.SyncException;
 import org.mozilla.gecko.sync.crypto.CryptoException;
 import org.mozilla.gecko.sync.crypto.KeyBundle;
 import org.mozilla.gecko.sync.middleware.Crypto5MiddlewareRepository;
 import org.mozilla.gecko.sync.middleware.Crypto5MiddlewareRepositorySession;
 import org.mozilla.gecko.sync.repositories.InactiveSessionException;
-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.domain.BookmarkRecord;
 import org.mozilla.gecko.sync.repositories.domain.Record;
 
-import java.io.IOException;
-import java.io.UnsupportedEncodingException;
-
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertSame;
 import static org.junit.Assert.assertTrue;
 
 @RunWith(TestRunner.class)
 public class TestCrypto5MiddlewareRepositorySession {
   public static WaitHelper getTestWaiter() {
@@ -72,114 +66,100 @@ public class TestCrypto5MiddlewareReposi
   @Before
   public void setUp() throws CryptoException {
     wboRepo = new WBORepository();
     keyBundle = KeyBundle.withRandomKeys();
     cmwRepo = new Crypto5MiddlewareRepository(wboRepo, keyBundle);
     cmwSession = null;
   }
 
-  /**
-   * Run `runnable` in performWait(... onBeginSucceeded { } ).
-   *
-   * The Crypto5MiddlewareRepositorySession is available in self.cmwSession.
-   *
-   * @param runnable
-   */
-  public void runInOnBeginSucceeded(final Runnable runnable) {
-    final TestCrypto5MiddlewareRepositorySession self = this;
-    performWait(onThreadRunnable(new Runnable() {
-      @Override
-      public void run() {
-        cmwRepo.createSession(new ExpectSuccessRepositorySessionCreationDelegate(getTestWaiter()) {
-          @Override
-          public void onSessionCreated(RepositorySession session) {
-            self.cmwSession = (Crypto5MiddlewareRepositorySession)session;
-            assertSame(RepositorySession.SessionStatus.UNSTARTED, cmwSession.getStatus());
-
-            try {
-              session.begin();
-            } catch (SyncException e) {
-              TestCrypto5MiddlewareRepositorySession.performNotify(e);
-            }
-            runnable.run();
-          }
-        }, null);
-      }
-    }));
+  public void beginSessionAndAssertSuccess() throws Exception{
+    cmwSession = (Crypto5MiddlewareRepositorySession) cmwRepo.createSession(null);
+    assertSame(RepositorySession.SessionStatus.UNSTARTED, cmwSession.getStatus());
+    cmwSession.begin();
+    assertSame(RepositorySession.SessionStatus.ACTIVE, cmwSession.getStatus());
   }
 
   @Test
   /**
    * Verify that the status is actually being advanced.
    */
-  public void testStatus() {
-    runInOnBeginSucceeded(new Runnable() {
-      @Override public void run() {
-        assertSame(RepositorySession.SessionStatus.ACTIVE, cmwSession.getStatus());
+  public void testStatus() throws Exception {
+    beginSessionAndAssertSuccess();
+    performWait(onThreadRunnable(new Runnable() {
+      @Override
+      public void run() {
         try {
           cmwSession.finish(new ExpectSuccessRepositorySessionFinishDelegate(getTestWaiter()));
         } catch (InactiveSessionException e) {
           performNotify(e);
         }
-        assertSame(RepositorySession.SessionStatus.DONE, cmwSession.getStatus());
       }
-    });
+    }));
+    assertSame(RepositorySession.SessionStatus.DONE, cmwSession.getStatus());
   }
 
   @Test
   /**
    * Verify that wipe is actually wiping the underlying repository.
    */
-  public void testWipe() {
+  public void testWipe() throws Exception {
     Record record = new MockRecord("nncdefghiaaa", "coll", System.currentTimeMillis(), false);
     wboRepo.wbos.put(record.guid, record);
     assertEquals(1, wboRepo.wbos.size());
 
-    runInOnBeginSucceeded(new Runnable() {
-      @Override public void run() {
+    beginSessionAndAssertSuccess();
+
+    performWait(onThreadRunnable(new Runnable() {
+      @Override
+      public void run() {
         cmwSession.wipe(new ExpectSuccessRepositoryWipeDelegate(getTestWaiter()));
       }
-    });
+    }));
+
     performWait(onThreadRunnable(new Runnable() {
       @Override public void run() {
         try {
           cmwSession.finish(new ExpectSuccessRepositorySessionFinishDelegate(getTestWaiter()));
         } catch (InactiveSessionException e) {
           performNotify(e);
         }
       }
     }));
     assertEquals(0, wboRepo.wbos.size());
   }
 
   @Test
   /**
    * Verify that store is actually writing encrypted data to the underlying repository.
    */
-  public void testStoreEncrypts() throws NonObjectJSONException, CryptoException, IOException {
+  public void testStoreEncrypts() throws Exception {
     final BookmarkRecord record = new BookmarkRecord("nncdefghiaaa", "coll", System.currentTimeMillis(), false);
     record.title = "unencrypted title";
 
-    runInOnBeginSucceeded(new Runnable() {
-      @Override public void run() {
+    beginSessionAndAssertSuccess();
+
+    performWait(onThreadRunnable(new Runnable() {
+      @Override
+      public void run() {
         try {
           try {
             cmwSession.setStoreDelegate(new ExpectSuccessRepositorySessionStoreDelegate(getTestWaiter()));
             cmwSession.store(record);
           } catch (NoStoreDelegateException e) {
             getTestWaiter().performNotify(new AssertionFailedError("Should not happen."));
           }
           cmwSession.storeDone();
           cmwSession.finish(new ExpectSuccessRepositorySessionFinishDelegate(getTestWaiter()));
         } catch (InactiveSessionException e) {
           performNotify(e);
         }
       }
-    });
+    }));
+
     assertEquals(1, wboRepo.wbos.size());
     assertTrue(wboRepo.wbos.containsKey(record.guid));
 
     Record storedRecord = wboRepo.wbos.get(record.guid);
     CryptoRecord cryptoRecord = (CryptoRecord)storedRecord;
     assertSame(cryptoRecord.keyBundle, keyBundle);
 
     cryptoRecord = cryptoRecord.decrypt();
@@ -187,42 +167,43 @@ public class TestCrypto5MiddlewareReposi
     decryptedRecord.initFromEnvelope(cryptoRecord);
     assertEquals(record.title, decryptedRecord.title);
   }
 
   @Test
   /**
    * Verify that fetch is actually retrieving encrypted data from the underlying repository and is correctly decrypting it.
    */
-  public void testFetchDecrypts() throws UnsupportedEncodingException, CryptoException {
+  public void testFetchDecrypts() throws Exception {
     final BookmarkRecord record1 = new BookmarkRecord("nncdefghiaaa", "coll", System.currentTimeMillis(), false);
     record1.title = "unencrypted title";
     final BookmarkRecord record2 = new BookmarkRecord("XXXXXXXXXXXX", "coll", System.currentTimeMillis(), false);
     record2.title = "unencrypted second title";
 
     CryptoRecord encryptedRecord1 = record1.getEnvelope();
     encryptedRecord1.keyBundle = keyBundle;
     encryptedRecord1 = encryptedRecord1.encrypt();
     wboRepo.wbos.put(record1.guid, encryptedRecord1);
 
     CryptoRecord encryptedRecord2 = record2.getEnvelope();
     encryptedRecord2.keyBundle = keyBundle;
     encryptedRecord2 = encryptedRecord2.encrypt();
     wboRepo.wbos.put(record2.guid, encryptedRecord2);
 
     final ExpectSuccessRepositorySessionFetchRecordsDelegate fetchRecordsDelegate = new ExpectSuccessRepositorySessionFetchRecordsDelegate(getTestWaiter());
-    runInOnBeginSucceeded(new Runnable() {
+    beginSessionAndAssertSuccess();
+    performWait(onThreadRunnable(new Runnable() {
       @Override public void run() {
         try {
           cmwSession.fetch(new String[] { record1.guid }, fetchRecordsDelegate);
         } catch (InactiveSessionException e) {
           performNotify(e);
         }
       }
-    });
+    }));
     performWait(onThreadRunnable(new Runnable() {
       @Override public void run() {
         try {
           cmwSession.finish(new ExpectSuccessRepositorySessionFinishDelegate(getTestWaiter()));
         } catch (InactiveSessionException e) {
           performNotify(e);
         }
       }
@@ -233,38 +214,39 @@ public class TestCrypto5MiddlewareReposi
     decryptedRecord.initFromEnvelope((CryptoRecord)fetchRecordsDelegate.fetchedRecords.get(0));
     assertEquals(record1.title, decryptedRecord.title);
   }
 
   @Test
   /**
    * Verify that fetchAll is actually retrieving encrypted data from the underlying repository and is correctly decrypting it.
    */
-  public void testFetchAllDecrypts() throws UnsupportedEncodingException, CryptoException {
+  public void testFetchAllDecrypts() throws Exception {
     final BookmarkRecord record1 = new BookmarkRecord("nncdefghiaaa", "coll", System.currentTimeMillis(), false);
     record1.title = "unencrypted title";
     final BookmarkRecord record2 = new BookmarkRecord("XXXXXXXXXXXX", "coll", System.currentTimeMillis(), false);
     record2.title = "unencrypted second title";
 
     CryptoRecord encryptedRecord1 = record1.getEnvelope();
     encryptedRecord1.keyBundle = keyBundle;
     encryptedRecord1 = encryptedRecord1.encrypt();
     wboRepo.wbos.put(record1.guid, encryptedRecord1);
 
     CryptoRecord encryptedRecord2 = record2.getEnvelope();
     encryptedRecord2.keyBundle = keyBundle;
     encryptedRecord2 = encryptedRecord2.encrypt();
     wboRepo.wbos.put(record2.guid, encryptedRecord2);
 
     final ExpectSuccessRepositorySessionFetchRecordsDelegate fetchAllRecordsDelegate = new ExpectSuccessRepositorySessionFetchRecordsDelegate(getTestWaiter());
-    runInOnBeginSucceeded(new Runnable() {
+    beginSessionAndAssertSuccess();
+    performWait(onThreadRunnable(new Runnable() {
       @Override public void run() {
         cmwSession.fetchAll(fetchAllRecordsDelegate);
       }
-    });
+    }));
     performWait(onThreadRunnable(new Runnable() {
       @Override public void run() {
         try {
           cmwSession.finish(new ExpectSuccessRepositorySessionFinishDelegate(getTestWaiter()));
         } catch (InactiveSessionException e) {
           performNotify(e);
         }
       }