diff --git a/dependencies.gradle b/dependencies.gradle
index 1a04fe07ea38b1d8829847b644837ffdce2acc0f..4a076a23bd59c449d4bea2829a977a81bdf4f4a8 100644
--- a/dependencies.gradle
+++ b/dependencies.gradle
@@ -7,11 +7,11 @@ ext.versions = [
         'targetCompat'      : JavaVersion.VERSION_11,
 ]
 
-def gradle = "7.0.3"
+def gradle = "7.0.4"
 // Ref: https://kotlinlang.org/releases.html
 def kotlin = "1.5.31"
 def kotlinCoroutines = "1.5.2"
-def dagger = "2.40.3"
+def dagger = "2.40.5"
 def retrofit = "2.9.0"
 def arrow = "0.8.2"
 def markwon = "4.6.2"
@@ -19,7 +19,7 @@ def moshi = "1.12.0"
 def lifecycle = "2.4.0"
 def flowBinding = "1.2.0"
 def epoxy = "4.6.2"
-def mavericks = "2.4.0"
+def mavericks = "2.5.0"
 def glide = "4.12.0"
 def bigImageViewer = "1.8.1"
 def jjwt = "0.11.2"
diff --git a/dependencies_groups.gradle b/dependencies_groups.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..25a78bc0c393d0765e98ed7680165a7c1e1e4fec
--- /dev/null
+++ b/dependencies_groups.gradle
@@ -0,0 +1,201 @@
+ext.groups = [
+        jitpack     : [
+                regex: [
+                ],
+                group: [
+                        'com.github.Armen101',
+                        'com.github.BillCarsonFr',
+                        'com.github.chrisbanes',
+                        'com.github.hyuwah',
+                        'com.github.jetradarmobile',
+                        'com.github.tapadoo',
+                        'com.github.vector-im',
+                        'com.github.yalantis',
+                        'com.github.Zhuinden',
+                ]
+        ],
+        olm         : [
+                regex: [
+                ],
+                group: [
+                        'org.matrix.android',
+                ]
+        ],
+        jitsi       : [
+                regex: [
+                ],
+                group: [
+                        'com.facebook.react',
+                        'org.jitsi.react',
+                        'org.webkit',
+                ]
+        ],
+        google      : [
+                regex: [
+                        'androidx\\..*',
+                        'com\\.android\\.tools\\..*',
+                        'com\\.google\\.android\\..*',
+                ],
+                group: [
+                        'com.google.firebase',
+                        'com.android',
+                        'com.android.tools',
+                ]
+        ],
+        mavenCentral: [
+                regex: [
+                ],
+                group: [
+                        'com.adevinta.android',
+                        'com.airbnb.android',
+                        'com.almworks.sqlite4java',
+                        'com.arthenica',
+                        'com.atlassian.commonmark',
+                        'com.atlassian.pom',
+                        'com.beust',
+                        'com.davemorrissey.labs',
+                        'com.dropbox.core',
+                        'com.facebook.fresco',
+                        'com.facebook.infer.annotation',
+                        'com.facebook.soloader',
+                        'com.facebook.stetho',
+                        'com.fasterxml',
+                        'com.fasterxml.jackson',
+                        'com.fasterxml.jackson.core',
+                        'com.gabrielittner.threetenbp',
+                        'com.getkeepsafe.relinker',
+                        'com.github.bumptech.glide',
+                        'com.github.filippudak',
+                        'com.github.filippudak.progresspieview',
+                        'com.github.javaparser',
+                        'com.github.piasy',
+                        'com.github.shyiko.klob',
+                        'com.google',
+                        'com.google.auto.service',
+                        'com.google.auto.value',
+                        'com.google.code.findbugs',
+                        'com.google.code.gson',
+                        'com.google.dagger',
+                        'com.google.devtools.ksp',
+                        'com.google.errorprone',
+                        'com.google.googlejavaformat',
+                        'com.google.guava',
+                        'com.google.j2objc',
+                        'com.google.jimfs',
+                        'com.google.protobuf',
+                        'com.google.zxing',
+                        'com.googlecode.htmlcompressor',
+                        'com.googlecode.json-simple',
+                        'com.googlecode.libphonenumber',
+                        'com.ibm.icu',
+                        'com.jakewharton.android.repackaged',
+                        'com.jakewharton.timber',
+                        'com.linkedin.dexmaker',
+                        'com.nulab-inc',
+                        'com.otaliastudios.opengl',
+                        'com.parse.bolts',
+                        'com.pinterest',
+                        'com.pinterest.ktlint',
+                        'com.posthog.android',
+                        'com.squareup',
+                        'com.squareup.duktape',
+                        'com.squareup.moshi',
+                        'com.squareup.okhttp3',
+                        'com.squareup.okio',
+                        'com.squareup.retrofit2',
+                        'com.sun.activation',
+                        'com.sun.istack',
+                        'com.sun.xml.bind',
+                        'com.sun.xml.bind.mvn',
+                        'com.sun.xml.fastinfoset',
+                        'com.thoughtworks.qdox',
+                        'com.vanniktech',
+                        'commons-cli',
+                        'commons-codec',
+                        'commons-io',
+                        'commons-logging',
+                        'info.picocli',
+                        'io.arrow-kt',
+                        'io.github.detekt.sarif4k',
+                        'io.github.reactivecircus.flowbinding',
+                        'io.jsonwebtoken',
+                        'io.kindedj',
+                        'io.mockk',
+                        'io.noties.markwon',
+                        'io.reactivex.rxjava2',
+                        'io.realm',
+                        'it.unimi.dsi',
+                        'jakarta.activation',
+                        'jakarta.xml.bind',
+                        'javax.annotation',
+                        'javax.inject',
+                        'jline',
+                        'jp.wasabeef',
+                        'junit',
+                        'me.leolin',
+                        'me.saket',
+                        'net.bytebuddy',
+                        'net.java',
+                        'net.java.dev.jna',
+                        'net.lachlanmckee',
+                        'net.ltgt.gradle.incap',
+                        'net.sf.jopt-simple',
+                        'net.sf.kxml',
+                        'nl.dionsegijn',
+                        'org.amshove.kluent',
+                        'org.apache',
+                        'org.apache.ant',
+                        'org.apache.commons',
+                        'org.apache.httpcomponents',
+                        'org.apache.sanselan',
+                        'org.bouncycastle',
+                        'org.checkerframework',
+                        'org.codehaus',
+                        'org.codehaus.groovy',
+                        'org.codehaus.mojo',
+                        'org.eclipse.ee4j',
+                        'org.ec4j.core',
+                        'org.glassfish.jaxb',
+                        'org.hamcrest',
+                        'org.jetbrains',
+                        'org.jetbrains.intellij.deps',
+                        'org.jetbrains.kotlin',
+                        'org.jetbrains.kotlinx',
+                        'org.jsoup',
+                        'org.junit',
+                        'org.junit.jupiter',
+                        'org.junit.platform',
+                        'org.jvnet.staxex',
+                        'org.mockito',
+                        'org.mongodb',
+                        'org.objenesis',
+                        'org.opentest4j',
+                        'org.ow2',
+                        'org.ow2.asm',
+                        'org.ow2.asm',
+                        'org.reactivestreams',
+                        'org.robolectric',
+                        'org.slf4j',
+                        'org.sonatype.oss',
+                        'org.testng',
+                        'org.threeten',
+                        'xerces',
+                        'xml-apis',
+                ]
+        ],
+        jcenter     : [
+                regex: [
+                ],
+                group: [
+                        'com.amulyakhare',
+                        'com.otaliastudios',
+                        'com.yqritc',
+                        // https://github.com/cmelchior/realmfieldnameshelper/issues/42
+                        'dk.ilios',
+                        'im.dlg',
+                        'me.dm7.barcodescanner',
+                        'me.gujun.android',
+                ]
+        ]
+]
+
diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle
index 3c59e345579eb5ce24e0d422df83e355a51c708e..b572b17623ff29948e601e8a73352a6e7377700e 100644
--- a/matrix-sdk-android/build.gradle
+++ b/matrix-sdk-android/build.gradle
@@ -11,7 +11,7 @@ buildscript {
         mavenCentral()
     }
     dependencies {
-        classpath "io.realm:realm-gradle-plugin:10.8.1"
+        classpath "io.realm:realm-gradle-plugin:10.9.0"
     }
 }
 
@@ -143,8 +143,8 @@ dependencies {
     implementation libs.arrow.core
     implementation libs.arrow.instances
 
-    // olm lib is now hosted by jitpack: https://jitpack.io/#org.matrix.gitlab.matrix-org/olm
-    implementation 'org.matrix.gitlab.matrix-org:olm:3.2.4'
+    // olm lib is now hosted by maven at https://gitlab.matrix.org/api/v4/projects/27/packages/maven
+    implementation 'org.matrix.android:olm:3.2.7'
 
     // DI
     implementation libs.dagger.dagger
@@ -161,10 +161,10 @@ dependencies {
     implementation libs.apache.commonsImaging
 
     // Phone number https://github.com/google/libphonenumber
-    implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.38'
+    implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.39'
 
     testImplementation libs.tests.junit
-    testImplementation 'org.robolectric:robolectric:4.7.2'
+    testImplementation 'org.robolectric:robolectric:4.7.3'
     //testImplementation 'org.robolectric:shadows-support-v4:3.0'
     // Note: version sticks to 1.9.2 due to https://github.com/mockk/mockk/issues/281
     testImplementation libs.mockk.mockk
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CommonTestHelper.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CommonTestHelper.kt
index cf9b8f87c1a1f0b76feb40c51592bf1640515471..8e21828562fd45fa90040c3cf9d5e836a039bcc2 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CommonTestHelper.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CommonTestHelper.kt
@@ -20,9 +20,10 @@ import android.content.Context
 import android.net.Uri
 import androidx.lifecycle.Observer
 import androidx.test.internal.runner.junit4.statement.UiThreadStatement
-import androidx.test.internal.runner.junit4.statement.UiThreadStatement.runOnUiThread
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.GlobalScope
+import kotlinx.coroutines.SupervisorJob
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.runBlocking
@@ -30,7 +31,6 @@ import kotlinx.coroutines.withTimeout
 import org.junit.Assert.assertEquals
 import org.junit.Assert.assertNotNull
 import org.junit.Assert.assertTrue
-import org.matrix.android.sdk.api.Matrix
 import org.matrix.android.sdk.api.MatrixCallback
 import org.matrix.android.sdk.api.MatrixConfiguration
 import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
@@ -45,7 +45,7 @@ import org.matrix.android.sdk.api.session.room.timeline.Timeline
 import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
 import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings
 import org.matrix.android.sdk.api.session.sync.SyncState
-import java.util.ArrayList
+import timber.log.Timber
 import java.util.UUID
 import java.util.concurrent.CountDownLatch
 import java.util.concurrent.TimeUnit
@@ -56,13 +56,14 @@ import java.util.concurrent.TimeUnit
  */
 class CommonTestHelper(context: Context) {
 
-    val matrix: Matrix
+    internal val matrix: TestMatrix
+    val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
 
-    fun getTestInterceptor(session: Session): MockOkHttpInterceptor? = TestNetworkModule.interceptorForSession(session.sessionId) as? MockOkHttpInterceptor
+    fun getTestInterceptor(session: Session): MockOkHttpInterceptor? = TestModule.interceptorForSession(session.sessionId) as? MockOkHttpInterceptor
 
     init {
         UiThreadStatement.runOnUiThread {
-            Matrix.initialize(
+            TestMatrix.initialize(
                     context,
                     MatrixConfiguration(
                             applicationFlavor = "TestFlavor",
@@ -70,7 +71,7 @@ class CommonTestHelper(context: Context) {
                     )
             )
         }
-        matrix = Matrix.getInstance(context)
+        matrix = TestMatrix.getInstance(context)
     }
 
     fun createAccount(userNamePrefix: String, testParams: SessionTestParams): Session {
@@ -95,31 +96,45 @@ class CommonTestHelper(context: Context) {
      *
      * @param session    the session to sync
      */
-    @Suppress("EXPERIMENTAL_API_USAGE")
-    fun syncSession(session: Session, timeout: Long = TestConstants.timeOutMillis) {
+    fun syncSession(session: Session, timeout: Long = TestConstants.timeOutMillis * 10) {
         val lock = CountDownLatch(1)
-
-        val job = GlobalScope.launch(Dispatchers.Main) {
-            session.open()
+        coroutineScope.launch {
+            session.startSync(true)
+            val syncLiveData = session.getSyncStateLive()
+            val syncObserver = object : Observer<SyncState> {
+                override fun onChanged(t: SyncState?) {
+                    if (session.hasAlreadySynced()) {
+                        lock.countDown()
+                        syncLiveData.removeObserver(this)
+                    }
+                }
+            }
+            syncLiveData.observeForever(syncObserver)
         }
-        runBlocking { job.join() }
-
-        session.startSync(true)
+        await(lock, timeout)
+    }
 
-        val syncLiveData = runBlocking(Dispatchers.Main) {
-            session.getSyncStateLive()
-        }
-        val syncObserver = object : Observer<SyncState> {
-            override fun onChanged(t: SyncState?) {
-                if (session.hasAlreadySynced()) {
-                    lock.countDown()
-                    syncLiveData.removeObserver(this)
+    /**
+     * This methods clear the cache and waits for initialSync
+     *
+     * @param session    the session to sync
+     */
+    fun clearCacheAndSync(session: Session, timeout: Long = TestConstants.timeOutMillis) {
+        waitWithLatch(timeout) { latch ->
+            session.clearCache()
+            val syncLiveData = session.getSyncStateLive()
+            val syncObserver = object : Observer<SyncState> {
+                override fun onChanged(t: SyncState?) {
+                    if (session.hasAlreadySynced()) {
+                        Timber.v("Clear cache and synced")
+                        syncLiveData.removeObserver(this)
+                        latch.countDown()
+                    }
                 }
             }
+            syncLiveData.observeForever(syncObserver)
+            session.startSync(true)
         }
-        GlobalScope.launch(Dispatchers.Main) { syncLiveData.observeForever(syncObserver) }
-
-        await(lock, timeout)
     }
 
     /**
@@ -130,46 +145,57 @@ class CommonTestHelper(context: Context) {
      * @param nbOfMessages the number of time the message will be sent
      */
     fun sendTextMessage(room: Room, message: String, nbOfMessages: Int, timeout: Long = TestConstants.timeOutMillis): List<TimelineEvent> {
-        val timeline = room.createTimeline(null, TimelineSettings(10))
         val sentEvents = ArrayList<TimelineEvent>(nbOfMessages)
-        val latch = CountDownLatch(1)
-        val timelineListener = object : Timeline.Listener {
-            override fun onTimelineFailure(throwable: Throwable) {
-            }
+        val timeline = room.createTimeline(null, TimelineSettings(10))
+        timeline.start()
+        waitWithLatch(timeout + 1_000L * nbOfMessages) { latch ->
+            val timelineListener = object : Timeline.Listener {
+                override fun onTimelineFailure(throwable: Throwable) {
+                }
 
-            override fun onNewTimelineEvents(eventIds: List<String>) {
-                // noop
-            }
+                override fun onNewTimelineEvents(eventIds: List<String>) {
+                    // noop
+                }
 
-            override fun onTimelineUpdated(snapshot: List<TimelineEvent>) {
-                val newMessages = snapshot
-                        .filter { it.root.sendState == SendState.SYNCED }
-                        .filter { it.root.getClearType() == EventType.MESSAGE }
-                        .filter { it.root.getClearContent().toModel<MessageContent>()?.body?.startsWith(message) == true }
-
-                if (newMessages.size == nbOfMessages) {
-                    sentEvents.addAll(newMessages)
-                    // Remove listener now, if not at the next update sendEvents could change
-                    timeline.removeListener(this)
-                    latch.countDown()
+                override fun onTimelineUpdated(snapshot: List<TimelineEvent>) {
+                    val newMessages = snapshot
+                            .filter { it.root.sendState == SendState.SYNCED }
+                            .filter { it.root.getClearType() == EventType.MESSAGE }
+                            .filter { it.root.getClearContent().toModel<MessageContent>()?.body?.startsWith(message) == true }
+
+                    Timber.v("New synced message size: ${newMessages.size}")
+                    if (newMessages.size == nbOfMessages) {
+                        sentEvents.addAll(newMessages)
+                        // Remove listener now, if not at the next update sendEvents could change
+                        timeline.removeListener(this)
+                        latch.countDown()
+                    }
                 }
             }
+            timeline.addListener(timelineListener)
+            sendTextMessagesBatched(room, message, nbOfMessages)
         }
-        timeline.start()
-        timeline.addListener(timelineListener)
-        for (i in 0 until nbOfMessages) {
-            room.sendTextMessage(message + " #" + (i + 1))
-        }
-        // Wait 3 second more per message
-        await(latch, timeout = timeout + 3_000L * nbOfMessages)
         timeline.dispose()
-
         // Check that all events has been created
         assertEquals("Message number do not match $sentEvents", nbOfMessages.toLong(), sentEvents.size.toLong())
-
         return sentEvents
     }
 
+    /**
+     * Will send nb of messages provided by count parameter but waits a bit every 10 messages to avoid gap in sync
+     */
+    private fun sendTextMessagesBatched(room: Room, message: String, count: Int) {
+        (1 until count + 1)
+                .map { "$message #$it" }
+                .chunked(10)
+                .forEach { batchedMessages ->
+                    batchedMessages.forEach { formattedMessage ->
+                        room.sendTextMessage(formattedMessage)
+                    }
+                    Thread.sleep(1_000L)
+                }
+    }
+
     // PRIVATE METHODS *****************************************************************************
 
     /**
@@ -239,10 +265,10 @@ class CommonTestHelper(context: Context) {
 
         assertTrue(registrationResult is RegistrationResult.Success)
         val session = (registrationResult as RegistrationResult.Success).session
+        session.open()
         if (sessionTestParams.withInitialSync) {
             syncSession(session, 60_000)
         }
-
         return session
     }
 
@@ -267,7 +293,7 @@ class CommonTestHelper(context: Context) {
                     .getLoginWizard()
                     .login(userName, password, "myDevice")
         }
-
+        session.open()
         if (sessionTestParams.withInitialSync) {
             syncSession(session)
         }
@@ -332,22 +358,21 @@ class CommonTestHelper(context: Context) {
         assertTrue(latch.await(timeout ?: TestConstants.timeOutMillis, TimeUnit.MILLISECONDS))
     }
 
-    @Suppress("EXPERIMENTAL_API_USAGE")
-    fun retryPeriodicallyWithLatch(latch: CountDownLatch, condition: (() -> Boolean)) {
-        GlobalScope.launch {
-            while (true) {
-                delay(1000)
-                if (condition()) {
-                    latch.countDown()
-                    return@launch
-                }
+    suspend fun retryPeriodicallyWithLatch(latch: CountDownLatch, condition: (() -> Boolean)) {
+        while (true) {
+            delay(1000)
+            if (condition()) {
+                latch.countDown()
+                return
             }
         }
     }
 
-    fun waitWithLatch(timeout: Long? = TestConstants.timeOutMillis, block: (CountDownLatch) -> Unit) {
+    fun waitWithLatch(timeout: Long? = TestConstants.timeOutMillis, dispatcher: CoroutineDispatcher = Dispatchers.Main, block: suspend (CountDownLatch) -> Unit) {
         val latch = CountDownLatch(1)
-        block(latch)
+        coroutineScope.launch(dispatcher) {
+            block(latch)
+        }
         await(latch, timeout)
     }
 
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CryptoTestHelper.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CryptoTestHelper.kt
index a8cbc160ddbe659f0ab3dee0b8053795279cb56c..ccea6f53b9a7b06cc3de5d0b5dc4bf4d58dbcb81 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CryptoTestHelper.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CryptoTestHelper.kt
@@ -19,10 +19,6 @@ package org.matrix.android.sdk.common
 import android.os.SystemClock
 import android.util.Log
 import androidx.lifecycle.Observer
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.GlobalScope
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.runBlocking
 import org.junit.Assert.assertEquals
 import org.junit.Assert.assertNotNull
 import org.junit.Assert.assertNull
@@ -31,6 +27,7 @@ import org.matrix.android.sdk.api.auth.UIABaseAuth
 import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
 import org.matrix.android.sdk.api.auth.UserPasswordAuth
 import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse
+import org.matrix.android.sdk.api.extensions.orFalse
 import org.matrix.android.sdk.api.session.Session
 import org.matrix.android.sdk.api.session.crypto.verification.IncomingSasVerificationTransaction
 import org.matrix.android.sdk.api.session.crypto.verification.OutgoingSasVerificationTransaction
@@ -44,16 +41,16 @@ import org.matrix.android.sdk.api.session.room.model.Membership
 import org.matrix.android.sdk.api.session.room.model.RoomSummary
 import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams
 import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams
+import org.matrix.android.sdk.api.util.Optional
 import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM
 import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM_BACKUP
 import org.matrix.android.sdk.internal.crypto.keysbackup.model.MegolmBackupAuthData
 import org.matrix.android.sdk.internal.crypto.keysbackup.model.MegolmBackupCreationInfo
 import java.util.UUID
-import java.util.concurrent.CountDownLatch
 import kotlin.coroutines.Continuation
 import kotlin.coroutines.resume
 
-class CryptoTestHelper(private val mTestHelper: CommonTestHelper) {
+class CryptoTestHelper(private val testHelper: CommonTestHelper) {
 
     private val messagesFromAlice: List<String> = listOf("0 - Hello I'm Alice!", "4 - Go!")
     private val messagesFromBob: List<String> = listOf("1 - Hello I'm Bob!", "2 - Isn't life grand?", "3 - Let's go to the opera.")
@@ -64,27 +61,33 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) {
      * @return alice session
      */
     fun doE2ETestWithAliceInARoom(encryptedRoom: Boolean = true): CryptoTestData {
-        val aliceSession = mTestHelper.createAccount(TestConstants.USER_ALICE, defaultSessionParams)
+        val aliceSession = testHelper.createAccount(TestConstants.USER_ALICE, defaultSessionParams)
 
-        val roomId = mTestHelper.runBlockingTest {
+        val roomId = testHelper.runBlockingTest {
             aliceSession.createRoom(CreateRoomParams().apply { name = "MyRoom" })
         }
-
         if (encryptedRoom) {
-            val room = aliceSession.getRoom(roomId)!!
-
-            mTestHelper.runBlockingTest {
+            testHelper.waitWithLatch { latch ->
+                val room = aliceSession.getRoom(roomId)!!
                 room.enableEncryption()
+                val roomSummaryLive = room.getRoomSummaryLive()
+                val roomSummaryObserver = object : Observer<Optional<RoomSummary>> {
+                    override fun onChanged(roomSummary: Optional<RoomSummary>) {
+                        if (roomSummary.getOrNull()?.isEncrypted.orFalse()) {
+                            roomSummaryLive.removeObserver(this)
+                            latch.countDown()
+                        }
+                    }
+                }
+                roomSummaryLive.observeForever(roomSummaryObserver)
             }
         }
-
         return CryptoTestData(roomId, listOf(aliceSession))
     }
 
     /**
      * @return alice and bob sessions
      */
-    @Suppress("EXPERIMENTAL_API_USAGE")
     fun doE2ETestWithAliceAndBobInARoom(encryptedRoom: Boolean = true): CryptoTestData {
         val cryptoTestData = doE2ETestWithAliceInARoom(encryptedRoom)
         val aliceSession = cryptoTestData.firstSession
@@ -92,54 +95,37 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) {
 
         val aliceRoom = aliceSession.getRoom(aliceRoomId)!!
 
-        val bobSession = mTestHelper.createAccount(TestConstants.USER_BOB, defaultSessionParams)
-
-        val lock1 = CountDownLatch(1)
+        val bobSession = testHelper.createAccount(TestConstants.USER_BOB, defaultSessionParams)
 
-        val bobRoomSummariesLive = runBlocking(Dispatchers.Main) {
-            bobSession.getRoomSummariesLive(roomSummaryQueryParams { })
-        }
-
-        val newRoomObserver = object : Observer<List<RoomSummary>> {
-            override fun onChanged(t: List<RoomSummary>?) {
-                if (t?.isNotEmpty() == true) {
-                    lock1.countDown()
-                    bobRoomSummariesLive.removeObserver(this)
+        testHelper.waitWithLatch { latch ->
+            val bobRoomSummariesLive = bobSession.getRoomSummariesLive(roomSummaryQueryParams { })
+            val newRoomObserver = object : Observer<List<RoomSummary>> {
+                override fun onChanged(t: List<RoomSummary>?) {
+                    if (t?.isNotEmpty() == true) {
+                        bobRoomSummariesLive.removeObserver(this)
+                        latch.countDown()
+                    }
                 }
             }
-        }
-
-        GlobalScope.launch(Dispatchers.Main) {
             bobRoomSummariesLive.observeForever(newRoomObserver)
-        }
-
-        mTestHelper.runBlockingTest {
             aliceRoom.invite(bobSession.myUserId)
         }
 
-        mTestHelper.await(lock1)
-
-        val lock = CountDownLatch(1)
-
-        val roomJoinedObserver = object : Observer<List<RoomSummary>> {
-            override fun onChanged(t: List<RoomSummary>?) {
-                if (bobSession.getRoom(aliceRoomId)
-                                ?.getRoomMember(aliceSession.myUserId)
-                                ?.membership == Membership.JOIN) {
-                    lock.countDown()
-                    bobRoomSummariesLive.removeObserver(this)
+        testHelper.waitWithLatch { latch ->
+            val bobRoomSummariesLive = bobSession.getRoomSummariesLive(roomSummaryQueryParams { })
+            val roomJoinedObserver = object : Observer<List<RoomSummary>> {
+                override fun onChanged(t: List<RoomSummary>?) {
+                    if (bobSession.getRoom(aliceRoomId)
+                                    ?.getRoomMember(bobSession.myUserId)
+                                    ?.membership == Membership.JOIN) {
+                        bobRoomSummariesLive.removeObserver(this)
+                        latch.countDown()
+                    }
                 }
             }
-        }
-
-        GlobalScope.launch(Dispatchers.Main) {
             bobRoomSummariesLive.observeForever(roomJoinedObserver)
+            bobSession.joinRoom(aliceRoomId)
         }
-
-        mTestHelper.runBlockingTest { bobSession.joinRoom(aliceRoomId) }
-
-        mTestHelper.await(lock)
-
         // Ensure bob can send messages to the room
 //        val roomFromBobPOV = bobSession.getRoom(aliceRoomId)!!
 //        assertNotNull(roomFromBobPOV.powerLevels)
@@ -171,13 +157,13 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) {
      * @Return Sam session
      */
     fun createSamAccountAndInviteToTheRoom(room: Room): Session {
-        val samSession = mTestHelper.createAccount(TestConstants.USER_SAM, defaultSessionParams)
+        val samSession = testHelper.createAccount(TestConstants.USER_SAM, defaultSessionParams)
 
-        mTestHelper.runBlockingTest {
+        testHelper.runBlockingTest {
             room.invite(samSession.myUserId, null)
         }
 
-        mTestHelper.runBlockingTest {
+        testHelper.runBlockingTest {
             samSession.joinRoom(room.roomId, null, emptyList())
         }
 
@@ -194,23 +180,20 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) {
         val bobSession = cryptoTestData.secondSession!!
 
         bobSession.cryptoService().setWarnOnUnknownDevices(false)
-
         aliceSession.cryptoService().setWarnOnUnknownDevices(false)
 
         val roomFromBobPOV = bobSession.getRoom(aliceRoomId)!!
         val roomFromAlicePOV = aliceSession.getRoom(aliceRoomId)!!
 
         // Alice sends a message
-        mTestHelper.sendTextMessage(roomFromAlicePOV, messagesFromAlice[0], 1)
-//        roomFromAlicePOV.sendTextMessage(messagesFromAlice[0])
+        testHelper.sendTextMessage(roomFromAlicePOV, messagesFromAlice[0], 1)
 
         // Bob send 3 messages
-        mTestHelper.sendTextMessage(roomFromBobPOV, messagesFromBob[0], 1)
-        mTestHelper.sendTextMessage(roomFromBobPOV, messagesFromBob[1], 1)
-        mTestHelper.sendTextMessage(roomFromBobPOV, messagesFromBob[2], 1)
+        testHelper.sendTextMessage(roomFromBobPOV, messagesFromBob[0], 1)
+        testHelper.sendTextMessage(roomFromBobPOV, messagesFromBob[1], 1)
+        testHelper.sendTextMessage(roomFromBobPOV, messagesFromBob[2], 1)
         // Alice sends a message
-        mTestHelper.sendTextMessage(roomFromAlicePOV, messagesFromAlice[1], 1)
-
+        testHelper.sendTextMessage(roomFromAlicePOV, messagesFromAlice[1], 1)
         return cryptoTestData
     }
 
@@ -256,60 +239,44 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) {
         )
     }
 
-    @Suppress("EXPERIMENTAL_API_USAGE")
     fun createDM(alice: Session, bob: Session): String {
-        val roomId = mTestHelper.runBlockingTest {
-            alice.createDirectRoom(bob.myUserId)
-        }
-
-        mTestHelper.waitWithLatch { latch ->
-            val bobRoomSummariesLive = runBlocking(Dispatchers.Main) {
-                bob.getRoomSummariesLive(roomSummaryQueryParams { })
-            }
-
+        var roomId: String = ""
+        testHelper.waitWithLatch { latch ->
+            roomId = alice.createDirectRoom(bob.myUserId)
+            val bobRoomSummariesLive = bob.getRoomSummariesLive(roomSummaryQueryParams { })
             val newRoomObserver = object : Observer<List<RoomSummary>> {
                 override fun onChanged(t: List<RoomSummary>?) {
                     val indexOfFirst = t?.indexOfFirst { it.roomId == roomId } ?: -1
                     if (indexOfFirst != -1) {
-                        latch.countDown()
                         bobRoomSummariesLive.removeObserver(this)
+                        latch.countDown()
                     }
                 }
             }
-
-            GlobalScope.launch(Dispatchers.Main) {
-                bobRoomSummariesLive.observeForever(newRoomObserver)
-            }
+            bobRoomSummariesLive.observeForever(newRoomObserver)
         }
 
-        mTestHelper.waitWithLatch { latch ->
-            val bobRoomSummariesLive = runBlocking(Dispatchers.Main) {
-                bob.getRoomSummariesLive(roomSummaryQueryParams { })
-            }
-
+        testHelper.waitWithLatch { latch ->
+            val bobRoomSummariesLive = bob.getRoomSummariesLive(roomSummaryQueryParams { })
             val newRoomObserver = object : Observer<List<RoomSummary>> {
                 override fun onChanged(t: List<RoomSummary>?) {
                     if (bob.getRoom(roomId)
                                     ?.getRoomMember(bob.myUserId)
                                     ?.membership == Membership.JOIN) {
-                        latch.countDown()
                         bobRoomSummariesLive.removeObserver(this)
+                        latch.countDown()
                     }
                 }
             }
-
-            GlobalScope.launch(Dispatchers.Main) {
-                bobRoomSummariesLive.observeForever(newRoomObserver)
-            }
-
-            mTestHelper.runBlockingTest { bob.joinRoom(roomId) }
+            bobRoomSummariesLive.observeForever(newRoomObserver)
+            bob.joinRoom(roomId)
         }
 
         return roomId
     }
 
     fun initializeCrossSigning(session: Session) {
-        mTestHelper.doSync<Unit> {
+        testHelper.doSync<Unit> {
             session.cryptoService().crossSigningService()
                     .initializeCrossSigning(
                             object : UserInteractiveAuthInterceptor {
@@ -346,8 +313,8 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) {
         var bobPovTx: IncomingSasVerificationTransaction? = null
 
         // wait for alice to get the ready
-        mTestHelper.waitWithLatch {
-            mTestHelper.retryPeriodicallyWithLatch(it) {
+        testHelper.waitWithLatch {
+            testHelper.retryPeriodicallyWithLatch(it) {
                 bobPovTx = bobVerificationService.getExistingTransaction(alice.myUserId, requestID) as? IncomingSasVerificationTransaction
                 Log.v("TEST", "== bobPovTx is ${alicePovTx?.uxState}")
                 if (bobPovTx?.state == VerificationTxState.OnStarted) {
@@ -359,16 +326,16 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) {
             }
         }
 
-        mTestHelper.waitWithLatch {
-            mTestHelper.retryPeriodicallyWithLatch(it) {
+        testHelper.waitWithLatch {
+            testHelper.retryPeriodicallyWithLatch(it) {
                 alicePovTx = aliceVerificationService.getExistingTransaction(bob.myUserId, requestID) as? OutgoingSasVerificationTransaction
                 Log.v("TEST", "== alicePovTx is ${alicePovTx?.uxState}")
                 alicePovTx?.state == VerificationTxState.ShortCodeReady
             }
         }
         // wait for alice to get the ready
-        mTestHelper.waitWithLatch {
-            mTestHelper.retryPeriodicallyWithLatch(it) {
+        testHelper.waitWithLatch {
+            testHelper.retryPeriodicallyWithLatch(it) {
                 bobPovTx = bobVerificationService.getExistingTransaction(alice.myUserId, requestID) as? IncomingSasVerificationTransaction
                 Log.v("TEST", "== bobPovTx is ${alicePovTx?.uxState}")
                 if (bobPovTx?.state == VerificationTxState.OnStarted) {
@@ -383,38 +350,38 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) {
         bobPovTx!!.userHasVerifiedShortCode()
         alicePovTx!!.userHasVerifiedShortCode()
 
-        mTestHelper.waitWithLatch {
-            mTestHelper.retryPeriodicallyWithLatch(it) {
+        testHelper.waitWithLatch {
+            testHelper.retryPeriodicallyWithLatch(it) {
                 alice.cryptoService().crossSigningService().isUserTrusted(bob.myUserId)
             }
         }
 
-        mTestHelper.waitWithLatch {
-            mTestHelper.retryPeriodicallyWithLatch(it) {
+        testHelper.waitWithLatch {
+            testHelper.retryPeriodicallyWithLatch(it) {
                 alice.cryptoService().crossSigningService().isUserTrusted(bob.myUserId)
             }
         }
     }
 
     fun doE2ETestWithManyMembers(numberOfMembers: Int): CryptoTestData {
-        val aliceSession = mTestHelper.createAccount(TestConstants.USER_ALICE, defaultSessionParams)
+        val aliceSession = testHelper.createAccount(TestConstants.USER_ALICE, defaultSessionParams)
         aliceSession.cryptoService().setWarnOnUnknownDevices(false)
 
-        val roomId = mTestHelper.runBlockingTest {
+        val roomId = testHelper.runBlockingTest {
             aliceSession.createRoom(CreateRoomParams().apply { name = "MyRoom" })
         }
         val room = aliceSession.getRoom(roomId)!!
 
-        mTestHelper.runBlockingTest {
+        testHelper.runBlockingTest {
             room.enableEncryption()
         }
 
         val sessions = mutableListOf(aliceSession)
         for (index in 1 until numberOfMembers) {
-            val session = mTestHelper.createAccount("User_$index", defaultSessionParams)
-            mTestHelper.runBlockingTest(timeout = 600_000) { room.invite(session.myUserId, null) }
+            val session = testHelper.createAccount("User_$index", defaultSessionParams)
+            testHelper.runBlockingTest(timeout = 600_000) { room.invite(session.myUserId, null) }
             println("TEST -> " + session.myUserId + " invited")
-            mTestHelper.runBlockingTest { session.joinRoom(room.roomId, null, emptyList()) }
+            testHelper.runBlockingTest { session.joinRoom(room.roomId, null, emptyList()) }
             println("TEST -> " + session.myUserId + " joined")
             sessions.add(session)
         }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/worker/DelegateWorkerFactory.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/TestBackgroundDetectionObserver.kt
similarity index 53%
rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/worker/DelegateWorkerFactory.kt
rename to matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/TestBackgroundDetectionObserver.kt
index 68e1953eff7d3b60b8bd0b74ea04102ce60050c2..c1596c281ce2d679e2c27656b796a0eb67f01bff 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/worker/DelegateWorkerFactory.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/TestBackgroundDetectionObserver.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright 2020 The Matrix.org Foundation C.I.C.
+ * Copyright 2021 The Matrix.org Foundation C.I.C.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,13 +14,18 @@
  * limitations under the License.
  */
 
-package org.matrix.android.sdk.internal.worker
+package org.matrix.android.sdk.common
 
-import android.content.Context
-import androidx.work.ListenableWorker
-import androidx.work.WorkerParameters
+import org.matrix.android.sdk.internal.util.BackgroundDetectionObserver
 
-interface DelegateWorkerFactory {
+/**
+ * Force foreground for testing
+ */
+internal class TestBackgroundDetectionObserver : BackgroundDetectionObserver {
+
+    override val isInBackground: Boolean = false
+
+    override fun register(listener: BackgroundDetectionObserver.Listener) = Unit
 
-    fun create(context: Context, params: WorkerParameters): ListenableWorker
+    override fun unregister(listener: BackgroundDetectionObserver.Listener) = Unit
 }
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/api/Matrix.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/TestMatrix.kt
similarity index 80%
rename from matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/api/Matrix.kt
rename to matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/TestMatrix.kt
index 8b9b6efa119b34edaf1d5ab7cb4972243276ae7f..e92232a7c5f0f984f675e184e34b9b14b46b072a 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/api/Matrix.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/TestMatrix.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright 2020 The Matrix.org Foundation C.I.C.
+ * Copyright (c) 2021 The Matrix.org Foundation C.I.C.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package org.matrix.android.sdk.api
+package org.matrix.android.sdk.common
 
 import android.content.Context
 import android.os.Handler
@@ -24,27 +24,27 @@ import androidx.work.Configuration
 import androidx.work.WorkManager
 import com.zhuinden.monarchy.Monarchy
 import org.matrix.android.sdk.BuildConfig
+import org.matrix.android.sdk.api.MatrixConfiguration
 import org.matrix.android.sdk.api.auth.AuthenticationService
 import org.matrix.android.sdk.api.auth.HomeServerHistoryService
 import org.matrix.android.sdk.api.legacy.LegacySessionImporter
 import org.matrix.android.sdk.api.network.ApiInterceptorListener
 import org.matrix.android.sdk.api.network.ApiPath
 import org.matrix.android.sdk.api.raw.RawService
-import org.matrix.android.sdk.common.DaggerTestMatrixComponent
 import org.matrix.android.sdk.internal.SessionManager
 import org.matrix.android.sdk.internal.network.ApiInterceptor
 import org.matrix.android.sdk.internal.network.UserAgentHolder
 import org.matrix.android.sdk.internal.util.BackgroundDetectionObserver
+import org.matrix.android.sdk.internal.worker.MatrixWorkerFactory
 import org.matrix.olm.OlmManager
 import java.util.concurrent.Executors
 import java.util.concurrent.atomic.AtomicBoolean
 import javax.inject.Inject
 
 /**
- * This is the main entry point to the matrix sdk.
- * To get the singleton instance, use getInstance static method.
+ * This mimics the Matrix class but using TestMatrixComponent internally instead of regular MatrixComponent.
  */
-class Matrix private constructor(context: Context, matrixConfiguration: MatrixConfiguration) {
+internal class TestMatrix constructor(context: Context, matrixConfiguration: MatrixConfiguration) {
 
     @Inject internal lateinit var legacySessionImporter: LegacySessionImporter
     @Inject internal lateinit var authenticationService: AuthenticationService
@@ -55,15 +55,18 @@ class Matrix private constructor(context: Context, matrixConfiguration: MatrixCo
     @Inject internal lateinit var sessionManager: SessionManager
     @Inject internal lateinit var homeServerHistoryService: HomeServerHistoryService
     @Inject internal lateinit var apiInterceptor: ApiInterceptor
+    @Inject internal lateinit var matrixWorkerFactory: MatrixWorkerFactory
 
     private val uiHandler = Handler(Looper.getMainLooper())
 
     init {
         Monarchy.init(context)
         DaggerTestMatrixComponent.factory().create(context, matrixConfiguration).inject(this)
-        if (context.applicationContext !is Configuration.Provider) {
-            WorkManager.initialize(context, Configuration.Builder().setExecutor(Executors.newCachedThreadPool()).build())
-        }
+        val configuration = Configuration.Builder()
+                .setExecutor(Executors.newCachedThreadPool())
+                .setWorkerFactory(matrixWorkerFactory)
+                .build()
+        WorkManager.initialize(context, configuration)
         uiHandler.post {
             ProcessLifecycleOwner.get().lifecycle.addObserver(backgroundDetectionObserver)
         }
@@ -93,21 +96,21 @@ class Matrix private constructor(context: Context, matrixConfiguration: MatrixCo
 
     companion object {
 
-        private lateinit var instance: Matrix
+        private lateinit var instance: TestMatrix
         private val isInit = AtomicBoolean(false)
 
         fun initialize(context: Context, matrixConfiguration: MatrixConfiguration) {
             if (isInit.compareAndSet(false, true)) {
-                instance = Matrix(context.applicationContext, matrixConfiguration)
+                instance = TestMatrix(context.applicationContext, matrixConfiguration)
             }
         }
 
-        fun getInstance(context: Context): Matrix {
+        fun getInstance(context: Context): TestMatrix {
             if (isInit.compareAndSet(false, true)) {
                 val appContext = context.applicationContext
                 if (appContext is MatrixConfiguration.Provider) {
                     val matrixConfiguration = (appContext as MatrixConfiguration.Provider).providesMatrixConfiguration()
-                    instance = Matrix(appContext, matrixConfiguration)
+                    instance = TestMatrix(appContext, matrixConfiguration)
                 } else {
                     throw IllegalStateException("Matrix is not initialized properly." +
                             " You should call Matrix.initialize or let your application implements MatrixConfiguration.Provider.")
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/TestMatrixComponent.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/TestMatrixComponent.kt
index 1d05e655afd8943ea7ff6f18dfb64fcad7d30a80..d0f0e2315245936aea43e2e02872324d3292ab58 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/TestMatrixComponent.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/TestMatrixComponent.kt
@@ -34,12 +34,13 @@ import org.matrix.android.sdk.internal.util.system.SystemModule
     NetworkModule::class,
     AuthModule::class,
     RawModule::class,
-    SystemModule::class,
-    TestNetworkModule::class
+    SystemModule::class
 ])
 @MatrixScope
 internal interface TestMatrixComponent : MatrixComponent {
 
+    fun inject(matrix: TestMatrix)
+
     @Component.Factory
     interface Factory {
         fun create(@BindsInstance context: Context,
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/TestModule.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/TestModule.kt
index 3e4d5fe08e105572a03e881da76c2ce3162edde3..d82bac3700f97e79bbd85bff94e5ec18a308e838 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/TestModule.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/TestModule.kt
@@ -18,10 +18,39 @@ package org.matrix.android.sdk.common
 
 import dagger.Binds
 import dagger.Module
+import dagger.Provides
 import org.matrix.android.sdk.internal.di.MatrixComponent
+import org.matrix.android.sdk.internal.di.MatrixScope
+import org.matrix.android.sdk.internal.session.MockHttpInterceptor
+import org.matrix.android.sdk.internal.session.TestInterceptor
+import org.matrix.android.sdk.internal.util.BackgroundDetectionObserver
 
 @Module
 internal abstract class TestModule {
     @Binds
     abstract fun providesMatrixComponent(testMatrixComponent: TestMatrixComponent): MatrixComponent
+
+    @Module
+    companion object {
+
+        val interceptors = ArrayList<TestInterceptor>()
+
+        fun interceptorForSession(sessionId: String): TestInterceptor? = interceptors.firstOrNull { it.sessionId == sessionId }
+
+        @Provides
+        @JvmStatic
+        @MockHttpInterceptor
+        fun providesTestInterceptor(): TestInterceptor? {
+            return MockOkHttpInterceptor().also {
+                interceptors.add(it)
+            }
+        }
+
+        @Provides
+        @JvmStatic
+        @MatrixScope
+        fun providesBackgroundDetectionObserver(): BackgroundDetectionObserver {
+            return TestBackgroundDetectionObserver()
+        }
+    }
 }
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/TestNetworkModule.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/TestNetworkModule.kt
deleted file mode 100644
index 4cd92ca272cf1ada9f3c3f1a1dd2fb77f2a47288..0000000000000000000000000000000000000000
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/TestNetworkModule.kt
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright 2020 The Matrix.org Foundation C.I.C.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.matrix.android.sdk.common
-
-import dagger.Module
-import dagger.Provides
-import org.matrix.android.sdk.internal.session.MockHttpInterceptor
-import org.matrix.android.sdk.internal.session.TestInterceptor
-
-@Module
-internal object TestNetworkModule {
-
-    val interceptors = ArrayList<TestInterceptor>()
-
-    fun interceptorForSession(sessionId: String): TestInterceptor? = interceptors.firstOrNull { it.sessionId == sessionId }
-
-    @Provides
-    @JvmStatic
-    @MockHttpInterceptor
-    fun providesTestInterceptor(): TestInterceptor? {
-        return MockOkHttpInterceptor().also {
-            interceptors.add(it)
-        }
-    }
-}
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/PreShareKeysTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/PreShareKeysTest.kt
index 825fba570a3ba22353daffc3a8a2a0b67878a1ae..d0f63227f59c424b2720e30181bd0b4d0db71aef 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/PreShareKeysTest.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/PreShareKeysTest.kt
@@ -36,12 +36,12 @@ import org.matrix.android.sdk.internal.crypto.model.event.RoomKeyContent
 @FixMethodOrder(MethodSorters.JVM)
 class PreShareKeysTest : InstrumentedTest {
 
-    private val mTestHelper = CommonTestHelper(context())
-    private val mCryptoTestHelper = CryptoTestHelper(mTestHelper)
+    private val testHelper = CommonTestHelper(context())
+    private val cryptoTestHelper = CryptoTestHelper(testHelper)
 
     @Test
     fun ensure_outbound_session_happy_path() {
-        val testData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom(true)
+        val testData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(true)
         val e2eRoomID = testData.roomId
         val aliceSession = testData.firstSession
         val bobSession = testData.secondSession!!
@@ -58,12 +58,12 @@ class PreShareKeysTest : InstrumentedTest {
         Log.d("#Test", "Room Key Received from alice $preShareCount")
 
         // Force presharing of new outbound key
-        mTestHelper.doSync<Unit> {
+        testHelper.doSync<Unit> {
             aliceSession.cryptoService().prepareToEncrypt(e2eRoomID, it)
         }
 
-        mTestHelper.waitWithLatch { latch ->
-            mTestHelper.retryPeriodicallyWithLatch(latch) {
+        testHelper.waitWithLatch { latch ->
+            testHelper.retryPeriodicallyWithLatch(latch) {
                 val newGossipCount = bobSession.cryptoService().getGossipingEvents().count {
                     it.senderId == aliceSession.myUserId &&
                             it.getClearType() == EventType.ROOM_KEY
@@ -88,16 +88,16 @@ class PreShareKeysTest : InstrumentedTest {
         assertEquals("The session received by bob should match what alice sent", 0, sharedIndex)
 
         // Just send a real message as test
-        val sentEvent = mTestHelper.sendTextMessage(aliceSession.getRoom(e2eRoomID)!!, "Allo", 1).first()
+        val sentEvent = testHelper.sendTextMessage(aliceSession.getRoom(e2eRoomID)!!, "Allo", 1).first()
 
         assertEquals(megolmSessionId, sentEvent.root.content.toModel<EncryptedEventContent>()?.sessionId, "Unexpected megolm session")
-        mTestHelper.waitWithLatch { latch ->
-            mTestHelper.retryPeriodicallyWithLatch(latch) {
+        testHelper.waitWithLatch { latch ->
+            testHelper.retryPeriodicallyWithLatch(latch) {
                 bobSession.getRoom(e2eRoomID)?.getTimeLineEvent(sentEvent.eventId)?.root?.getClearType() == EventType.MESSAGE
             }
         }
 
-        mTestHelper.signOutAndClose(aliceSession)
-        mTestHelper.signOutAndClose(bobSession)
+        testHelper.signOutAndClose(aliceSession)
+        testHelper.signOutAndClose(bobSession)
     }
 }
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/UnwedgingTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/UnwedgingTest.kt
index cf31294e2f9150d816bd0347474fa7ff3c85398f..458eae6ab237eeb0d443cceb024eee14c12f7629 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/UnwedgingTest.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/UnwedgingTest.kt
@@ -62,8 +62,8 @@ import kotlin.coroutines.resume
 class UnwedgingTest : InstrumentedTest {
 
     private lateinit var messagesReceivedByBob: List<TimelineEvent>
-    private val mTestHelper = CommonTestHelper(context())
-    private val mCryptoTestHelper = CryptoTestHelper(mTestHelper)
+    private val testHelper = CommonTestHelper(context())
+    private val cryptoTestHelper = CryptoTestHelper(testHelper)
 
     @Before
     fun init() {
@@ -85,7 +85,7 @@ class UnwedgingTest : InstrumentedTest {
      */
     @Test
     fun testUnwedging() {
-        val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
+        val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
 
         val aliceSession = cryptoTestData.firstSession
         val aliceRoomId = cryptoTestData.roomId
@@ -133,7 +133,7 @@ class UnwedgingTest : InstrumentedTest {
         roomFromAlicePOV.sendTextMessage("First message")
 
         // Wait for the message to be received by Bob
-        mTestHelper.await(latch)
+        testHelper.await(latch)
         bobTimeline.removeListener(bobEventsListener)
 
         messagesReceivedByBob.size shouldBe 1
@@ -161,7 +161,7 @@ class UnwedgingTest : InstrumentedTest {
         roomFromAlicePOV.sendTextMessage("Second message")
 
         // Wait for the message to be received by Bob
-        mTestHelper.await(latch)
+        testHelper.await(latch)
         bobTimeline.removeListener(bobEventsListener)
 
         messagesReceivedByBob.size shouldBe 2
@@ -179,7 +179,7 @@ class UnwedgingTest : InstrumentedTest {
         aliceSession.cryptoService().discardOutboundSession(roomFromAlicePOV.roomId)
 
         // Wait for the message to be received by Bob
-        mTestHelper.waitWithLatch {
+        testHelper.waitWithLatch {
             bobEventsListener = createEventListener(it, 3)
             bobTimeline.addListener(bobEventsListener)
             messagesReceivedByBob = emptyList()
@@ -201,11 +201,11 @@ class UnwedgingTest : InstrumentedTest {
         Assert.assertEquals(EventType.MESSAGE, messagesReceivedByBob[1].root.getClearType())
         Assert.assertEquals(EventType.MESSAGE, messagesReceivedByBob[2].root.getClearType())
         // Bob Should not be able to decrypt last message, because session could not be sent as the olm channel was wedged
-        mTestHelper.await(bobFinalLatch)
+        testHelper.await(bobFinalLatch)
         bobTimeline.removeListener(bobHasThreeDecryptedEventsListener)
 
         // It's a trick to force key request on fail to decrypt
-        mTestHelper.doSync<Unit> {
+        testHelper.doSync<Unit> {
             bobSession.cryptoService().crossSigningService()
                     .initializeCrossSigning(
                             object : UserInteractiveAuthInterceptor {
@@ -222,8 +222,8 @@ class UnwedgingTest : InstrumentedTest {
         }
 
         // Wait until we received back the key
-        mTestHelper.waitWithLatch {
-            mTestHelper.retryPeriodicallyWithLatch(it) {
+        testHelper.waitWithLatch {
+            testHelper.retryPeriodicallyWithLatch(it) {
                 // we should get back the key and be able to decrypt
                 val result = tryOrNull {
                     bobSession.cryptoService().decryptEvent(messagesReceivedByBob[0].root, "")
@@ -235,7 +235,7 @@ class UnwedgingTest : InstrumentedTest {
 
         bobTimeline.dispose()
 
-        cryptoTestData.cleanUp(mTestHelper)
+        cryptoTestData.cleanUp(testHelper)
     }
 
     private fun createEventListener(latch: CountDownLatch, expectedNumberOfMessages: Int): Timeline.Listener {
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/crosssigning/XSigningTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/crosssigning/XSigningTest.kt
index 44af87bcbe482d23dce25012e4939c58d36b1f5a..d9cc7a8ac0f0dd788b35cc8941aef72df018de9e 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/crosssigning/XSigningTest.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/crosssigning/XSigningTest.kt
@@ -45,14 +45,14 @@ import kotlin.coroutines.resume
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
 class XSigningTest : InstrumentedTest {
 
-    private val mTestHelper = CommonTestHelper(context())
-    private val mCryptoTestHelper = CryptoTestHelper(mTestHelper)
+    private val testHelper = CommonTestHelper(context())
+    private val cryptoTestHelper = CryptoTestHelper(testHelper)
 
     @Test
     fun test_InitializeAndStoreKeys() {
-        val aliceSession = mTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true))
+        val aliceSession = testHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true))
 
-        mTestHelper.doSync<Unit> {
+        testHelper.doSync<Unit> {
             aliceSession.cryptoService().crossSigningService()
                     .initializeCrossSigning(object : UserInteractiveAuthInterceptor {
                         override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
@@ -79,12 +79,12 @@ class XSigningTest : InstrumentedTest {
 
         assertTrue("Signing Keys should be trusted", aliceSession.cryptoService().crossSigningService().checkUserTrust(aliceSession.myUserId).isVerified())
 
-        mTestHelper.signOutAndClose(aliceSession)
+        testHelper.signOutAndClose(aliceSession)
     }
 
     @Test
     fun test_CrossSigningCheckBobSeesTheKeys() {
-        val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
+        val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
 
         val aliceSession = cryptoTestData.firstSession
         val bobSession = cryptoTestData.secondSession
@@ -98,21 +98,21 @@ class XSigningTest : InstrumentedTest {
                 password = TestConstants.PASSWORD
         )
 
-        mTestHelper.doSync<Unit> {
+        testHelper.doSync<Unit> {
             aliceSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor {
                 override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
                     promise.resume(aliceAuthParams)
                 }
             }, it)
         }
-        mTestHelper.doSync<Unit> { bobSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor {
+        testHelper.doSync<Unit> { bobSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor {
             override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
                 promise.resume(bobAuthParams)
             }
         }, it) }
 
         // Check that alice can see bob keys
-        mTestHelper.doSync<MXUsersDevicesMap<CryptoDeviceInfo>> { aliceSession.cryptoService().downloadKeys(listOf(bobSession.myUserId), true, it) }
+        testHelper.doSync<MXUsersDevicesMap<CryptoDeviceInfo>> { aliceSession.cryptoService().downloadKeys(listOf(bobSession.myUserId), true, it) }
 
         val bobKeysFromAlicePOV = aliceSession.cryptoService().crossSigningService().getUserCrossSigningKeys(bobSession.myUserId)
         assertNotNull("Alice can see bob Master key", bobKeysFromAlicePOV!!.masterKey())
@@ -124,13 +124,13 @@ class XSigningTest : InstrumentedTest {
 
         assertFalse("Bob keys from alice pov should not be trusted", bobKeysFromAlicePOV.isTrusted())
 
-        mTestHelper.signOutAndClose(aliceSession)
-        mTestHelper.signOutAndClose(bobSession)
+        testHelper.signOutAndClose(aliceSession)
+        testHelper.signOutAndClose(bobSession)
     }
 
     @Test
     fun test_CrossSigningTestAliceTrustBobNewDevice() {
-        val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
+        val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
 
         val aliceSession = cryptoTestData.firstSession
         val bobSession = cryptoTestData.secondSession
@@ -144,12 +144,12 @@ class XSigningTest : InstrumentedTest {
                 password = TestConstants.PASSWORD
         )
 
-        mTestHelper.doSync<Unit> { aliceSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor {
+        testHelper.doSync<Unit> { aliceSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor {
             override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
                 promise.resume(aliceAuthParams)
             }
         }, it) }
-        mTestHelper.doSync<Unit> { bobSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor {
+        testHelper.doSync<Unit> { bobSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor {
             override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
                 promise.resume(bobAuthParams)
             }
@@ -157,21 +157,21 @@ class XSigningTest : InstrumentedTest {
 
         // Check that alice can see bob keys
         val bobUserId = bobSession.myUserId
-        mTestHelper.doSync<MXUsersDevicesMap<CryptoDeviceInfo>> { aliceSession.cryptoService().downloadKeys(listOf(bobUserId), true, it) }
+        testHelper.doSync<MXUsersDevicesMap<CryptoDeviceInfo>> { aliceSession.cryptoService().downloadKeys(listOf(bobUserId), true, it) }
 
         val bobKeysFromAlicePOV = aliceSession.cryptoService().crossSigningService().getUserCrossSigningKeys(bobUserId)
         assertTrue("Bob keys from alice pov should not be trusted", bobKeysFromAlicePOV?.isTrusted() == false)
 
-        mTestHelper.doSync<Unit> { aliceSession.cryptoService().crossSigningService().trustUser(bobUserId, it) }
+        testHelper.doSync<Unit> { aliceSession.cryptoService().crossSigningService().trustUser(bobUserId, it) }
 
         // Now bobs logs in on a new device and verifies it
         // We will want to test that in alice POV, this new device would be trusted by cross signing
 
-        val bobSession2 = mTestHelper.logIntoAccount(bobUserId, SessionTestParams(true))
+        val bobSession2 = testHelper.logIntoAccount(bobUserId, SessionTestParams(true))
         val bobSecondDeviceId = bobSession2.sessionParams.deviceId!!
 
         // Check that bob first session sees the new login
-        val data = mTestHelper.doSync<MXUsersDevicesMap<CryptoDeviceInfo>> {
+        val data = testHelper.doSync<MXUsersDevicesMap<CryptoDeviceInfo>> {
             bobSession.cryptoService().downloadKeys(listOf(bobUserId), true, it)
         }
 
@@ -183,12 +183,12 @@ class XSigningTest : InstrumentedTest {
         assertNotNull("Bob Second device should be known and persisted from first", bobSecondDevicePOVFirstDevice)
 
         // Manually mark it as trusted from first session
-        mTestHelper.doSync<Unit> {
+        testHelper.doSync<Unit> {
             bobSession.cryptoService().crossSigningService().trustDevice(bobSecondDeviceId, it)
         }
 
         // Now alice should cross trust bob's second device
-        val data2 = mTestHelper.doSync<MXUsersDevicesMap<CryptoDeviceInfo>> {
+        val data2 = testHelper.doSync<MXUsersDevicesMap<CryptoDeviceInfo>> {
             aliceSession.cryptoService().downloadKeys(listOf(bobUserId), true, it)
         }
 
@@ -200,8 +200,8 @@ class XSigningTest : InstrumentedTest {
         val result = aliceSession.cryptoService().crossSigningService().checkDeviceTrust(bobUserId, bobSecondDeviceId, null)
         assertTrue("Bob second device should be trusted from alice POV", result.isCrossSignedVerified())
 
-        mTestHelper.signOutAndClose(aliceSession)
-        mTestHelper.signOutAndClose(bobSession)
-        mTestHelper.signOutAndClose(bobSession2)
+        testHelper.signOutAndClose(aliceSession)
+        testHelper.signOutAndClose(bobSession)
+        testHelper.signOutAndClose(bobSession2)
     }
 }
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/encryption/EncryptionTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/encryption/EncryptionTest.kt
index da5e90abddbfb58279a40ef847fc0fb006338e6f..189fc405eb8ddc4a581dc3e52563442919ffbed7 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/encryption/EncryptionTest.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/encryption/EncryptionTest.kt
@@ -40,8 +40,9 @@ import java.util.concurrent.CountDownLatch
 @RunWith(AndroidJUnit4::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
 class EncryptionTest : InstrumentedTest {
-    private val mTestHelper = CommonTestHelper(context())
-    private val mCryptoTestHelper = CryptoTestHelper(mTestHelper)
+
+    private val testHelper = CommonTestHelper(context())
+    private val cryptoTestHelper = CryptoTestHelper(testHelper)
 
     @Test
     fun test_EncryptionEvent() {
@@ -69,7 +70,7 @@ class EncryptionTest : InstrumentedTest {
     }
 
     private fun performTest(roomShouldBeEncrypted: Boolean, action: (Room) -> Unit) {
-        val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceInARoom(encryptedRoom = false)
+        val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceInARoom(encryptedRoom = false)
 
         val aliceSession = cryptoTestData.firstSession
         val room = aliceSession.getRoom(cryptoTestData.roomId)!!
@@ -101,12 +102,12 @@ class EncryptionTest : InstrumentedTest {
         timeline.addListener(timelineListener)
 
         action.invoke(room)
-
-        mTestHelper.await(latch)
+        testHelper.await(latch)
         timeline.dispose()
-
-        room.isEncrypted() shouldBe roomShouldBeEncrypted
-
-        cryptoTestData.cleanUp(mTestHelper)
+        testHelper.waitWithLatch {
+            room.isEncrypted() shouldBe roomShouldBeEncrypted
+            it.countDown()
+        }
+        cryptoTestData.cleanUp(testHelper)
     }
 }
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/KeyShareTests.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/KeyShareTests.kt
index 40659cef11a4a955e886a6eaeefe14c8f6e2724f..975d4816286fc615f0294833b5e4e0b29731dfa8 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/KeyShareTests.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/KeyShareTests.kt
@@ -44,7 +44,6 @@ import org.matrix.android.sdk.api.session.room.model.RoomDirectoryVisibility
 import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams
 import org.matrix.android.sdk.api.session.room.model.message.MessageContent
 import org.matrix.android.sdk.common.CommonTestHelper
-import org.matrix.android.sdk.common.CryptoTestHelper
 import org.matrix.android.sdk.common.SessionTestParams
 import org.matrix.android.sdk.common.TestConstants
 import org.matrix.android.sdk.internal.crypto.GossipingRequestState
@@ -55,7 +54,6 @@ import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysVersion
 import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo
 import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap
 import org.matrix.android.sdk.internal.crypto.model.event.EncryptedEventContent
-import java.util.concurrent.CountDownLatch
 import kotlin.coroutines.Continuation
 import kotlin.coroutines.resume
 
@@ -63,15 +61,14 @@ import kotlin.coroutines.resume
 @FixMethodOrder(MethodSorters.JVM)
 class KeyShareTests : InstrumentedTest {
 
-    private val mTestHelper = CommonTestHelper(context())
-    private val mCryptoTestHelper = CryptoTestHelper(mTestHelper)
+    private val commonTestHelper = CommonTestHelper(context())
 
     @Test
     fun test_DoNotSelfShareIfNotTrusted() {
-        val aliceSession = mTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true))
+        val aliceSession = commonTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true))
 
         // Create an encrypted room and add a message
-        val roomId = mTestHelper.runBlockingTest {
+        val roomId = commonTestHelper.runBlockingTest {
             aliceSession.createRoom(
                     CreateRoomParams().apply {
                         visibility = RoomDirectoryVisibility.PRIVATE
@@ -83,11 +80,11 @@ class KeyShareTests : InstrumentedTest {
         assertNotNull(room)
         Thread.sleep(4_000)
         assertTrue(room?.isEncrypted() == true)
-        val sentEventId = mTestHelper.sendTextMessage(room!!, "My Message", 1).first().eventId
+        val sentEventId = commonTestHelper.sendTextMessage(room!!, "My Message", 1).first().eventId
 
         // Open a new sessionx
 
-        val aliceSession2 = mTestHelper.logIntoAccount(aliceSession.myUserId, SessionTestParams(true))
+        val aliceSession2 = commonTestHelper.logIntoAccount(aliceSession.myUserId, SessionTestParams(true))
 
         val roomSecondSessionPOV = aliceSession2.getRoom(roomId)
 
@@ -105,25 +102,24 @@ class KeyShareTests : InstrumentedTest {
         // Try to request
         aliceSession2.cryptoService().requestRoomKeyForEvent(receivedEvent.root)
 
-        val waitLatch = CountDownLatch(1)
         val eventMegolmSessionId = receivedEvent.root.content.toModel<EncryptedEventContent>()?.sessionId
 
         var outGoingRequestId: String? = null
 
-        mTestHelper.retryPeriodicallyWithLatch(waitLatch) {
-            aliceSession2.cryptoService().getOutgoingRoomKeyRequests()
-                    .filter { req ->
-                        // filter out request that was known before
-                        !outgoingRequestsBefore.any { req.requestId == it.requestId }
-                    }
-                    .let {
-                        val outgoing = it.firstOrNull { it.sessionId == eventMegolmSessionId }
-                        outGoingRequestId = outgoing?.requestId
-                        outgoing != null
-                    }
+        commonTestHelper.waitWithLatch {  latch ->
+            commonTestHelper.retryPeriodicallyWithLatch(latch) {
+                aliceSession2.cryptoService().getOutgoingRoomKeyRequests()
+                        .filter { req ->
+                            // filter out request that was known before
+                            !outgoingRequestsBefore.any { req.requestId == it.requestId }
+                        }
+                        .let {
+                            val outgoing = it.firstOrNull { it.sessionId == eventMegolmSessionId }
+                            outGoingRequestId = outgoing?.requestId
+                            outgoing != null
+                        }
+            }
         }
-        mTestHelper.await(waitLatch)
-
         Log.v("TEST", "=======> Outgoing requet Id is $outGoingRequestId")
 
         val outgoingRequestAfter = aliceSession2.cryptoService().getOutgoingRoomKeyRequests()
@@ -134,8 +130,8 @@ class KeyShareTests : InstrumentedTest {
 
         // The first session should see an incoming request
         // the request should be refused, because the device is not trusted
-        mTestHelper.waitWithLatch { latch ->
-            mTestHelper.retryPeriodicallyWithLatch(latch) {
+        commonTestHelper.waitWithLatch { latch ->
+            commonTestHelper.retryPeriodicallyWithLatch(latch) {
                 // DEBUG LOGS
                 aliceSession.cryptoService().getIncomingRoomKeyRequests().let {
                     Log.v("TEST", "Incoming request Session 1 (looking for $outGoingRequestId)")
@@ -164,8 +160,8 @@ class KeyShareTests : InstrumentedTest {
         // Re request
         aliceSession2.cryptoService().reRequestRoomKeyForEvent(receivedEvent.root)
 
-        mTestHelper.waitWithLatch { latch ->
-            mTestHelper.retryPeriodicallyWithLatch(latch) {
+        commonTestHelper.waitWithLatch { latch ->
+            commonTestHelper.retryPeriodicallyWithLatch(latch) {
                 aliceSession.cryptoService().getIncomingRoomKeyRequests().let {
                     Log.v("TEST", "Incoming request Session 1")
                     Log.v("TEST", "=========================")
@@ -180,8 +176,8 @@ class KeyShareTests : InstrumentedTest {
         }
 
         Thread.sleep(6_000)
-        mTestHelper.waitWithLatch { latch ->
-            mTestHelper.retryPeriodicallyWithLatch(latch) {
+        commonTestHelper.waitWithLatch { latch ->
+            commonTestHelper.retryPeriodicallyWithLatch(latch) {
                 aliceSession2.cryptoService().getOutgoingRoomKeyRequests().let {
                     it.any { it.requestBody?.sessionId == eventMegolmSessionId && it.state == OutgoingGossipingRequestState.CANCELLED }
                 }
@@ -194,15 +190,15 @@ class KeyShareTests : InstrumentedTest {
             fail("should have been able to decrypt")
         }
 
-        mTestHelper.signOutAndClose(aliceSession)
-        mTestHelper.signOutAndClose(aliceSession2)
+        commonTestHelper.signOutAndClose(aliceSession)
+        commonTestHelper.signOutAndClose(aliceSession2)
     }
 
     @Test
     fun test_ShareSSSSSecret() {
-        val aliceSession1 = mTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true))
+        val aliceSession1 = commonTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true))
 
-        mTestHelper.doSync<Unit> {
+        commonTestHelper.doSync<Unit> {
             aliceSession1.cryptoService().crossSigningService()
                     .initializeCrossSigning(
                             object : UserInteractiveAuthInterceptor {
@@ -218,25 +214,25 @@ class KeyShareTests : InstrumentedTest {
         }
 
         // Also bootstrap keybackup on first session
-        val creationInfo = mTestHelper.doSync<MegolmBackupCreationInfo> {
+        val creationInfo = commonTestHelper.doSync<MegolmBackupCreationInfo> {
             aliceSession1.cryptoService().keysBackupService().prepareKeysBackupVersion(null, null, it)
         }
-        val version = mTestHelper.doSync<KeysVersion> {
+        val version = commonTestHelper.doSync<KeysVersion> {
             aliceSession1.cryptoService().keysBackupService().createKeysBackupVersion(creationInfo, it)
         }
         // Save it for gossiping
         aliceSession1.cryptoService().keysBackupService().saveBackupRecoveryKey(creationInfo.recoveryKey, version = version.version)
 
-        val aliceSession2 = mTestHelper.logIntoAccount(aliceSession1.myUserId, SessionTestParams(true))
+        val aliceSession2 = commonTestHelper.logIntoAccount(aliceSession1.myUserId, SessionTestParams(true))
 
         val aliceVerificationService1 = aliceSession1.cryptoService().verificationService()
         val aliceVerificationService2 = aliceSession2.cryptoService().verificationService()
 
         // force keys download
-        mTestHelper.doSync<MXUsersDevicesMap<CryptoDeviceInfo>> {
+        commonTestHelper.doSync<MXUsersDevicesMap<CryptoDeviceInfo>> {
             aliceSession1.cryptoService().downloadKeys(listOf(aliceSession1.myUserId), true, it)
         }
-        mTestHelper.doSync<MXUsersDevicesMap<CryptoDeviceInfo>> {
+        commonTestHelper.doSync<MXUsersDevicesMap<CryptoDeviceInfo>> {
             aliceSession2.cryptoService().downloadKeys(listOf(aliceSession2.myUserId), true, it)
         }
 
@@ -276,8 +272,8 @@ class KeyShareTests : InstrumentedTest {
         aliceVerificationService2.beginKeyVerification(VerificationMethod.SAS, aliceSession1.myUserId, aliceSession1.sessionParams.deviceId
                 ?: "", txId)
 
-        mTestHelper.waitWithLatch { latch ->
-            mTestHelper.retryPeriodicallyWithLatch(latch) {
+        commonTestHelper.waitWithLatch { latch ->
+            commonTestHelper.retryPeriodicallyWithLatch(latch) {
                 aliceSession1.cryptoService().getDeviceInfo(aliceSession1.myUserId, aliceSession2.sessionParams.deviceId ?: "")?.isVerified == true
             }
         }
@@ -290,31 +286,31 @@ class KeyShareTests : InstrumentedTest {
 
         // SSK and USK private keys should have been shared
 
-        mTestHelper.waitWithLatch(60_000) { latch ->
-            mTestHelper.retryPeriodicallyWithLatch(latch) {
+        commonTestHelper.waitWithLatch(60_000) { latch ->
+            commonTestHelper.retryPeriodicallyWithLatch(latch) {
                 Log.d("#TEST", "CAN XS :${aliceSession2.cryptoService().crossSigningService().getMyCrossSigningKeys()}")
                 aliceSession2.cryptoService().crossSigningService().canCrossSign()
             }
         }
 
         // Test that key backup key has been shared to
-        mTestHelper.waitWithLatch(60_000) { latch ->
+        commonTestHelper.waitWithLatch(60_000) { latch ->
             val keysBackupService = aliceSession2.cryptoService().keysBackupService()
-            mTestHelper.retryPeriodicallyWithLatch(latch) {
+            commonTestHelper.retryPeriodicallyWithLatch(latch) {
                 Log.d("#TEST", "Recovery :${keysBackupService.getKeyBackupRecoveryKeyInfo()?.recoveryKey}")
                 keysBackupService.getKeyBackupRecoveryKeyInfo()?.recoveryKey == creationInfo.recoveryKey
             }
         }
 
-        mTestHelper.signOutAndClose(aliceSession1)
-        mTestHelper.signOutAndClose(aliceSession2)
+        commonTestHelper.signOutAndClose(aliceSession1)
+        commonTestHelper.signOutAndClose(aliceSession2)
     }
 
     @Test
     fun test_ImproperKeyShareBug() {
-        val aliceSession = mTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true))
+        val aliceSession = commonTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true))
 
-        mTestHelper.doSync<Unit> {
+        commonTestHelper.doSync<Unit> {
             aliceSession.cryptoService().crossSigningService()
                     .initializeCrossSigning(
                             object : UserInteractiveAuthInterceptor {
@@ -331,7 +327,7 @@ class KeyShareTests : InstrumentedTest {
         }
 
         // Create an encrypted room and send a couple of messages
-        val roomId = mTestHelper.runBlockingTest {
+        val roomId = commonTestHelper.runBlockingTest {
             aliceSession.createRoom(
                     CreateRoomParams().apply {
                         visibility = RoomDirectoryVisibility.PRIVATE
@@ -343,12 +339,12 @@ class KeyShareTests : InstrumentedTest {
         assertNotNull(roomAlicePov)
         Thread.sleep(1_000)
         assertTrue(roomAlicePov?.isEncrypted() == true)
-        val secondEventId = mTestHelper.sendTextMessage(roomAlicePov!!, "Message", 3)[1].eventId
+        val secondEventId = commonTestHelper.sendTextMessage(roomAlicePov!!, "Message", 3)[1].eventId
 
         // Create bob session
 
-        val bobSession = mTestHelper.createAccount(TestConstants.USER_BOB, SessionTestParams(true))
-        mTestHelper.doSync<Unit> {
+        val bobSession = commonTestHelper.createAccount(TestConstants.USER_BOB, SessionTestParams(true))
+        commonTestHelper.doSync<Unit> {
             bobSession.cryptoService().crossSigningService()
                     .initializeCrossSigning(
                             object : UserInteractiveAuthInterceptor {
@@ -365,11 +361,11 @@ class KeyShareTests : InstrumentedTest {
         }
 
         // Let alice invite bob
-        mTestHelper.runBlockingTest {
+        commonTestHelper.runBlockingTest {
             roomAlicePov.invite(bobSession.myUserId, null)
         }
 
-        mTestHelper.runBlockingTest {
+        commonTestHelper.runBlockingTest {
             bobSession.joinRoom(roomAlicePov.roomId, null, emptyList())
         }
 
@@ -377,7 +373,7 @@ class KeyShareTests : InstrumentedTest {
         aliceSession.cryptoService().discardOutboundSession(roomAlicePov.roomId)
 
         // and now resend a new message to reset index to 0
-        mTestHelper.sendTextMessage(roomAlicePov, "After", 1)
+        commonTestHelper.sendTextMessage(roomAlicePov, "After", 1)
 
         val roomRoomBobPov = aliceSession.getRoom(roomId)
         val beforeJoin = roomRoomBobPov!!.getTimeLineEvent(secondEventId)
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/WithHeldTests.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/WithHeldTests.kt
index c939952dc9f5c639b9d39b3c0ef89f19ca9989ac..c835c2d40b083e5c8c5bdd6a0a8c0e0926430e93 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/WithHeldTests.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/WithHeldTests.kt
@@ -41,8 +41,8 @@ import org.matrix.android.sdk.internal.crypto.model.event.WithHeldCode
 @FixMethodOrder(MethodSorters.JVM)
 class WithHeldTests : InstrumentedTest {
 
-    private val mTestHelper = CommonTestHelper(context())
-    private val mCryptoTestHelper = CryptoTestHelper(mTestHelper)
+    private val testHelper = CommonTestHelper(context())
+    private val cryptoTestHelper = CryptoTestHelper(testHelper)
 
     @Test
     fun test_WithHeldUnverifiedReason() {
@@ -50,19 +50,19 @@ class WithHeldTests : InstrumentedTest {
         // ARRANGE
         // =============================
 
-        val aliceSession = mTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true))
-        val bobSession = mTestHelper.createAccount(TestConstants.USER_BOB, SessionTestParams(true))
+        val aliceSession = testHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true))
+        val bobSession = testHelper.createAccount(TestConstants.USER_BOB, SessionTestParams(true))
 
         // Initialize cross signing on both
-        mCryptoTestHelper.initializeCrossSigning(aliceSession)
-        mCryptoTestHelper.initializeCrossSigning(bobSession)
+        cryptoTestHelper.initializeCrossSigning(aliceSession)
+        cryptoTestHelper.initializeCrossSigning(bobSession)
 
-        val roomId = mCryptoTestHelper.createDM(aliceSession, bobSession)
-        mCryptoTestHelper.verifySASCrossSign(aliceSession, bobSession, roomId)
+        val roomId = cryptoTestHelper.createDM(aliceSession, bobSession)
+        cryptoTestHelper.verifySASCrossSign(aliceSession, bobSession, roomId)
 
         val roomAlicePOV = aliceSession.getRoom(roomId)!!
 
-        val bobUnverifiedSession = mTestHelper.logIntoAccount(bobSession.myUserId, SessionTestParams(true))
+        val bobUnverifiedSession = testHelper.logIntoAccount(bobSession.myUserId, SessionTestParams(true))
 
         // =============================
         // ACT
@@ -71,11 +71,11 @@ class WithHeldTests : InstrumentedTest {
         // Alice decide to not send to unverified sessions
         aliceSession.cryptoService().setGlobalBlacklistUnverifiedDevices(true)
 
-        val timelineEvent = mTestHelper.sendTextMessage(roomAlicePOV, "Hello Bob", 1).first()
+        val timelineEvent = testHelper.sendTextMessage(roomAlicePOV, "Hello Bob", 1).first()
 
         // await for bob unverified session to get the message
-        mTestHelper.waitWithLatch { latch ->
-            mTestHelper.retryPeriodicallyWithLatch(latch) {
+        testHelper.waitWithLatch { latch ->
+            testHelper.retryPeriodicallyWithLatch(latch) {
                 bobUnverifiedSession.getRoom(roomId)?.getTimeLineEvent(timelineEvent.eventId) != null
             }
         }
@@ -101,10 +101,10 @@ class WithHeldTests : InstrumentedTest {
         // enable back sending to unverified
         aliceSession.cryptoService().setGlobalBlacklistUnverifiedDevices(false)
 
-        val secondEvent = mTestHelper.sendTextMessage(roomAlicePOV, "Verify your device!!", 1).first()
+        val secondEvent = testHelper.sendTextMessage(roomAlicePOV, "Verify your device!!", 1).first()
 
-        mTestHelper.waitWithLatch { latch ->
-            mTestHelper.retryPeriodicallyWithLatch(latch) {
+        testHelper.waitWithLatch { latch ->
+            testHelper.retryPeriodicallyWithLatch(latch) {
                 val ev = bobUnverifiedSession.getRoom(roomId)?.getTimeLineEvent(secondEvent.eventId)
                 // wait until it's decrypted
                 ev?.root?.getClearType() == EventType.MESSAGE
@@ -123,17 +123,17 @@ class WithHeldTests : InstrumentedTest {
             Assert.assertEquals("Cause should be unverified", WithHeldCode.UNVERIFIED.value, technicalMessage)
         }
 
-        mTestHelper.signOutAndClose(aliceSession)
-        mTestHelper.signOutAndClose(bobSession)
-        mTestHelper.signOutAndClose(bobUnverifiedSession)
+        testHelper.signOutAndClose(aliceSession)
+        testHelper.signOutAndClose(bobSession)
+        testHelper.signOutAndClose(bobUnverifiedSession)
     }
 
     @Test
     fun  test_WithHeldNoOlm() {
-        val testData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
+        val testData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
         val aliceSession = testData.firstSession
         val bobSession = testData.secondSession!!
-        val aliceInterceptor = mTestHelper.getTestInterceptor(aliceSession)
+        val aliceInterceptor = testHelper.getTestInterceptor(aliceSession)
 
         // Simulate no OTK
         aliceInterceptor!!.addRule(MockOkHttpInterceptor.SimpleRule(
@@ -147,11 +147,11 @@ class WithHeldTests : InstrumentedTest {
 
         val roomAlicePov = aliceSession.getRoom(testData.roomId)!!
 
-        val eventId = mTestHelper.sendTextMessage(roomAlicePov, "first message", 1).first().eventId
+        val eventId = testHelper.sendTextMessage(roomAlicePov, "first message", 1).first().eventId
 
         // await for bob session to get the message
-        mTestHelper.waitWithLatch { latch ->
-            mTestHelper.retryPeriodicallyWithLatch(latch) {
+        testHelper.waitWithLatch { latch ->
+            testHelper.retryPeriodicallyWithLatch(latch) {
                 bobSession.getRoom(testData.roomId)?.getTimeLineEvent(eventId) != null
             }
         }
@@ -177,14 +177,14 @@ class WithHeldTests : InstrumentedTest {
         // Add a new device for bob
 
         aliceInterceptor.clearRules()
-        val bobSecondSession = mTestHelper.logIntoAccount(bobSession.myUserId, SessionTestParams(withInitialSync = true))
+        val bobSecondSession = testHelper.logIntoAccount(bobSession.myUserId, SessionTestParams(withInitialSync = true))
         // send a second message
-        val secondMessageId = mTestHelper.sendTextMessage(roomAlicePov, "second message", 1).first().eventId
+        val secondMessageId = testHelper.sendTextMessage(roomAlicePov, "second message", 1).first().eventId
 
         // Check that the
         // await for bob SecondSession session to get the message
-        mTestHelper.waitWithLatch { latch ->
-            mTestHelper.retryPeriodicallyWithLatch(latch) {
+        testHelper.waitWithLatch { latch ->
+            testHelper.retryPeriodicallyWithLatch(latch) {
                 bobSecondSession.getRoom(testData.roomId)?.getTimeLineEvent(secondMessageId) != null
             }
         }
@@ -194,27 +194,27 @@ class WithHeldTests : InstrumentedTest {
         Assert.assertEquals("Alice should have marked bob's device for this session", 1, chainIndex2)
 
         aliceInterceptor.clearRules()
-        testData.cleanUp(mTestHelper)
-        mTestHelper.signOutAndClose(bobSecondSession)
+        testData.cleanUp(testHelper)
+        testHelper.signOutAndClose(bobSecondSession)
     }
 
     @Test
     fun test_WithHeldKeyRequest() {
-        val testData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
+        val testData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
         val aliceSession = testData.firstSession
         val bobSession = testData.secondSession!!
 
         val roomAlicePov = aliceSession.getRoom(testData.roomId)!!
 
-        val eventId = mTestHelper.sendTextMessage(roomAlicePov, "first message", 1).first().eventId
+        val eventId = testHelper.sendTextMessage(roomAlicePov, "first message", 1).first().eventId
 
-        mTestHelper.signOutAndClose(bobSession)
+        testHelper.signOutAndClose(bobSession)
 
         // Create a new session for bob
 
-        val bobSecondSession = mTestHelper.logIntoAccount(bobSession.myUserId, SessionTestParams(true))
+        val bobSecondSession = testHelper.logIntoAccount(bobSession.myUserId, SessionTestParams(true))
         // initialize to force request keys if missing
-        mCryptoTestHelper.initializeCrossSigning(bobSecondSession)
+        cryptoTestHelper.initializeCrossSigning(bobSecondSession)
 
         // Trust bob second device from Alice POV
         aliceSession.cryptoService().crossSigningService().trustDevice(bobSecondSession.sessionParams.deviceId!!, NoOpMatrixCallback())
@@ -223,8 +223,8 @@ class WithHeldTests : InstrumentedTest {
         var sessionId: String? = null
         // Check that the
         // await for bob SecondSession session to get the message
-        mTestHelper.waitWithLatch { latch ->
-            mTestHelper.retryPeriodicallyWithLatch(latch) {
+        testHelper.waitWithLatch { latch ->
+            testHelper.retryPeriodicallyWithLatch(latch) {
                 val timeLineEvent = bobSecondSession.getRoom(testData.roomId)?.getTimeLineEvent(eventId)?.also {
                     // try to decrypt and force key request
                     tryOrNull { bobSecondSession.cryptoService().decryptEvent(it.root, "") }
@@ -235,8 +235,8 @@ class WithHeldTests : InstrumentedTest {
         }
 
         // Check that bob second session requested the key
-        mTestHelper.waitWithLatch { latch ->
-            mTestHelper.retryPeriodicallyWithLatch(latch) {
+        testHelper.waitWithLatch { latch ->
+            testHelper.retryPeriodicallyWithLatch(latch) {
                 val wc = bobSecondSession.cryptoService().getWithHeldMegolmSession(roomAlicePov.roomId, sessionId!!)
                 wc?.code == WithHeldCode.UNAUTHORISED
             }
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupTest.kt
index 0785dba8b95b1f92bded53ddb25b04b5c6e00ed8..2a07b74115dd979bb41a712c93fde374994f682e 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupTest.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupTest.kt
@@ -22,7 +22,6 @@ import org.junit.Assert.assertFalse
 import org.junit.Assert.assertNotNull
 import org.junit.Assert.assertNull
 import org.junit.Assert.assertTrue
-import org.junit.Assert.fail
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -43,7 +42,6 @@ import org.matrix.android.sdk.internal.crypto.keysbackup.model.MegolmBackupCreat
 import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysVersion
 import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysVersionResult
 import org.matrix.android.sdk.internal.crypto.model.ImportRoomKeysResult
-import java.util.ArrayList
 import java.util.Collections
 import java.util.concurrent.CountDownLatch
 
@@ -51,9 +49,9 @@ import java.util.concurrent.CountDownLatch
 @FixMethodOrder(MethodSorters.JVM)
 class KeysBackupTest : InstrumentedTest {
 
-    private val mTestHelper = CommonTestHelper(context())
-    private val mCryptoTestHelper = CryptoTestHelper(mTestHelper)
-    private val mKeysBackupTestHelper = KeysBackupTestHelper(mTestHelper, mCryptoTestHelper)
+    private val testHelper = CommonTestHelper(context())
+    private val cryptoTestHelper = CryptoTestHelper(testHelper)
+    private val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper)
 
     /**
      * - From doE2ETestWithAliceAndBobInARoomWithEncryptedMessages, we should have no backed up keys
@@ -62,7 +60,7 @@ class KeysBackupTest : InstrumentedTest {
      */
     @Test
     fun roomKeysTest_testBackupStore_ok() {
-        val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages()
+        val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages()
 
         // From doE2ETestWithAliceAndBobInARoomWithEncryptedMessages, we should have no backed up keys
         val cryptoStore = (cryptoTestData.firstSession.cryptoService().keysBackupService() as DefaultKeysBackupService).store
@@ -92,7 +90,7 @@ class KeysBackupTest : InstrumentedTest {
         assertEquals(sessionsCount, cryptoTestData.firstSession.cryptoService().inboundGroupSessionsCount(false))
         assertEquals(0, cryptoTestData.firstSession.cryptoService().inboundGroupSessionsCount(true))
 
-        cryptoTestData.cleanUp(mTestHelper)
+        cryptoTestData.cleanUp(testHelper)
     }
 
     /**
@@ -100,7 +98,7 @@ class KeysBackupTest : InstrumentedTest {
      */
     @Test
     fun prepareKeysBackupVersionTest() {
-        val bobSession = mTestHelper.createAccount(TestConstants.USER_BOB, KeysBackupTestConstants.defaultSessionParams)
+        val bobSession = testHelper.createAccount(TestConstants.USER_BOB, KeysBackupTestConstants.defaultSessionParams)
 
         assertNotNull(bobSession.cryptoService().keysBackupService())
 
@@ -110,7 +108,7 @@ class KeysBackupTest : InstrumentedTest {
 
         assertFalse(keysBackup.isEnabled)
 
-        val megolmBackupCreationInfo = mTestHelper.doSync<MegolmBackupCreationInfo> {
+        val megolmBackupCreationInfo = testHelper.doSync<MegolmBackupCreationInfo> {
             keysBackup.prepareKeysBackupVersion(null, null, it)
         }
 
@@ -120,7 +118,7 @@ class KeysBackupTest : InstrumentedTest {
         assertNotNull(megolmBackupCreationInfo.recoveryKey)
 
         stateObserver.stopAndCheckStates(null)
-        mTestHelper.signOutAndClose(bobSession)
+        testHelper.signOutAndClose(bobSession)
     }
 
     /**
@@ -128,7 +126,7 @@ class KeysBackupTest : InstrumentedTest {
      */
     @Test
     fun createKeysBackupVersionTest() {
-        val bobSession = mTestHelper.createAccount(TestConstants.USER_BOB, KeysBackupTestConstants.defaultSessionParams)
+        val bobSession = testHelper.createAccount(TestConstants.USER_BOB, KeysBackupTestConstants.defaultSessionParams)
 
         val keysBackup = bobSession.cryptoService().keysBackupService()
 
@@ -136,14 +134,14 @@ class KeysBackupTest : InstrumentedTest {
 
         assertFalse(keysBackup.isEnabled)
 
-        val megolmBackupCreationInfo = mTestHelper.doSync<MegolmBackupCreationInfo> {
+        val megolmBackupCreationInfo = testHelper.doSync<MegolmBackupCreationInfo> {
             keysBackup.prepareKeysBackupVersion(null, null, it)
         }
 
         assertFalse(keysBackup.isEnabled)
 
         // Create the version
-        mTestHelper.doSync<KeysVersion> {
+        testHelper.doSync<KeysVersion> {
             keysBackup.createKeysBackupVersion(megolmBackupCreationInfo, it)
         }
 
@@ -151,7 +149,7 @@ class KeysBackupTest : InstrumentedTest {
         assertTrue(keysBackup.isEnabled)
 
         stateObserver.stopAndCheckStates(null)
-        mTestHelper.signOutAndClose(bobSession)
+        testHelper.signOutAndClose(bobSession)
     }
 
     /**
@@ -160,8 +158,9 @@ class KeysBackupTest : InstrumentedTest {
      */
     @Test
     fun backupAfterCreateKeysBackupVersionTest() {
-        val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages()
+        val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages()
 
+        keysBackupTestHelper.waitForKeybackUpBatching()
         val keysBackup = cryptoTestData.firstSession.cryptoService().keysBackupService()
 
         val latch = CountDownLatch(1)
@@ -171,9 +170,9 @@ class KeysBackupTest : InstrumentedTest {
 
         val stateObserver = StateObserver(keysBackup, latch, 5)
 
-        mKeysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup)
+        keysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup)
 
-        mTestHelper.await(latch)
+        testHelper.await(latch)
 
         val nbOfKeys = cryptoTestData.firstSession.cryptoService().inboundGroupSessionsCount(false)
         val backedUpKeys = cryptoTestData.firstSession.cryptoService().inboundGroupSessionsCount(true)
@@ -191,7 +190,7 @@ class KeysBackupTest : InstrumentedTest {
                         KeysBackupState.ReadyToBackUp
                 )
         )
-        cryptoTestData.cleanUp(mTestHelper)
+        cryptoTestData.cleanUp(testHelper)
     }
 
     /**
@@ -199,13 +198,13 @@ class KeysBackupTest : InstrumentedTest {
      */
     @Test
     fun backupAllGroupSessionsTest() {
-        val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages()
+        val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages()
 
         val keysBackup = cryptoTestData.firstSession.cryptoService().keysBackupService()
 
         val stateObserver = StateObserver(keysBackup)
 
-        mKeysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup)
+        keysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup)
 
         // Check that backupAllGroupSessions returns valid data
         val nbOfKeys = cryptoTestData.firstSession.cryptoService().inboundGroupSessionsCount(false)
@@ -214,7 +213,7 @@ class KeysBackupTest : InstrumentedTest {
 
         var lastBackedUpKeysProgress = 0
 
-        mTestHelper.doSync<Unit> {
+        testHelper.doSync<Unit> {
             keysBackup.backupAllGroupSessions(object : ProgressListener {
                 override fun onProgress(progress: Int, total: Int) {
                     assertEquals(nbOfKeys, total)
@@ -230,7 +229,7 @@ class KeysBackupTest : InstrumentedTest {
         assertEquals("All keys must have been marked as backed up", nbOfKeys, backedUpKeys)
 
         stateObserver.stopAndCheckStates(null)
-        cryptoTestData.cleanUp(mTestHelper)
+        cryptoTestData.cleanUp(testHelper)
     }
 
     /**
@@ -243,7 +242,7 @@ class KeysBackupTest : InstrumentedTest {
      */
     @Test
     fun testEncryptAndDecryptKeysBackupData() {
-        val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages()
+        val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages()
 
         val keysBackup = cryptoTestData.firstSession.cryptoService().keysBackupService() as DefaultKeysBackupService
 
@@ -252,7 +251,7 @@ class KeysBackupTest : InstrumentedTest {
         // - Pick a megolm key
         val session = keysBackup.store.inboundGroupSessionsToBackup(1)[0]
 
-        val keyBackupCreationInfo = mKeysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup).megolmBackupCreationInfo
+        val keyBackupCreationInfo = keysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup).megolmBackupCreationInfo
 
         // - Check encryptGroupSession() returns stg
         val keyBackupData = keysBackup.encryptGroupSession(session)
@@ -270,10 +269,10 @@ class KeysBackupTest : InstrumentedTest {
                         decryption!!)
         assertNotNull(sessionData)
         // - Compare the decrypted megolm key with the original one
-        mKeysBackupTestHelper.assertKeysEquals(session.exportKeys(), sessionData)
+        keysBackupTestHelper.assertKeysEquals(session.exportKeys(), sessionData)
 
         stateObserver.stopAndCheckStates(null)
-        cryptoTestData.cleanUp(mTestHelper)
+        cryptoTestData.cleanUp(testHelper)
     }
 
     /**
@@ -284,10 +283,10 @@ class KeysBackupTest : InstrumentedTest {
      */
     @Test
     fun restoreKeysBackupTest() {
-        val testData = mKeysBackupTestHelper.createKeysBackupScenarioWithPassword(null)
+        val testData = keysBackupTestHelper.createKeysBackupScenarioWithPassword(null)
 
         // - Restore the e2e backup from the homeserver
-        val importRoomKeysResult = mTestHelper.doSync<ImportRoomKeysResult> {
+        val importRoomKeysResult = testHelper.doSync<ImportRoomKeysResult> {
             testData.aliceSession2.cryptoService().keysBackupService().restoreKeysWithRecoveryKey(testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!,
                     testData.prepareKeysBackupDataResult.megolmBackupCreationInfo.recoveryKey,
                     null,
@@ -297,9 +296,9 @@ class KeysBackupTest : InstrumentedTest {
             )
         }
 
-        mKeysBackupTestHelper.checkRestoreSuccess(testData, importRoomKeysResult.totalNumberOfKeys, importRoomKeysResult.successfullyNumberOfImportedKeys)
+        keysBackupTestHelper.checkRestoreSuccess(testData, importRoomKeysResult.totalNumberOfKeys, importRoomKeysResult.successfullyNumberOfImportedKeys)
 
-        testData.cleanUp(mTestHelper)
+        testData.cleanUp(testHelper)
     }
 
     /**
@@ -369,7 +368,7 @@ class KeysBackupTest : InstrumentedTest {
     fun trustKeyBackupVersionTest() {
         // - Do an e2e backup to the homeserver with a recovery key
         // - And log Alice on a new device
-        val testData = mKeysBackupTestHelper.createKeysBackupScenarioWithPassword(null)
+        val testData = keysBackupTestHelper.createKeysBackupScenarioWithPassword(null)
 
         val stateObserver = StateObserver(testData.aliceSession2.cryptoService().keysBackupService())
 
@@ -379,7 +378,7 @@ class KeysBackupTest : InstrumentedTest {
         assertEquals(KeysBackupState.NotTrusted, testData.aliceSession2.cryptoService().keysBackupService().state)
 
         // - Trust the backup from the new device
-        mTestHelper.doSync<Unit> {
+        testHelper.doSync<Unit> {
             testData.aliceSession2.cryptoService().keysBackupService().trustKeysBackupVersion(
                     testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!,
                     true,
@@ -388,21 +387,21 @@ class KeysBackupTest : InstrumentedTest {
         }
 
         // Wait for backup state to be ReadyToBackUp
-        mKeysBackupTestHelper.waitForKeysBackupToBeInState(testData.aliceSession2, KeysBackupState.ReadyToBackUp)
+        keysBackupTestHelper.waitForKeysBackupToBeInState(testData.aliceSession2, KeysBackupState.ReadyToBackUp)
 
         // - Backup must be enabled on the new device, on the same version
         assertEquals(testData.prepareKeysBackupDataResult.version, testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion?.version)
         assertTrue(testData.aliceSession2.cryptoService().keysBackupService().isEnabled)
 
         // - Retrieve the last version from the server
-        val keysVersionResult = mTestHelper.doSync<KeysVersionResult?> {
+        val keysVersionResult = testHelper.doSync<KeysVersionResult?> {
             testData.aliceSession2.cryptoService().keysBackupService().getCurrentVersion(it)
         }
 
         // - It must be the same
         assertEquals(testData.prepareKeysBackupDataResult.version, keysVersionResult!!.version)
 
-        val keysBackupVersionTrust = mTestHelper.doSync<KeysBackupVersionTrust> {
+        val keysBackupVersionTrust = testHelper.doSync<KeysBackupVersionTrust> {
             testData.aliceSession2.cryptoService().keysBackupService().getKeysBackupTrust(keysVersionResult, it)
         }
 
@@ -411,7 +410,7 @@ class KeysBackupTest : InstrumentedTest {
         assertEquals(2, keysBackupVersionTrust.signatures.size)
 
         stateObserver.stopAndCheckStates(null)
-        testData.cleanUp(mTestHelper)
+        testData.cleanUp(testHelper)
     }
 
     /**
@@ -428,7 +427,7 @@ class KeysBackupTest : InstrumentedTest {
     fun trustKeyBackupVersionWithRecoveryKeyTest() {
         // - Do an e2e backup to the homeserver with a recovery key
         // - And log Alice on a new device
-        val testData = mKeysBackupTestHelper.createKeysBackupScenarioWithPassword(null)
+        val testData = keysBackupTestHelper.createKeysBackupScenarioWithPassword(null)
 
         val stateObserver = StateObserver(testData.aliceSession2.cryptoService().keysBackupService())
 
@@ -438,7 +437,7 @@ class KeysBackupTest : InstrumentedTest {
         assertEquals(KeysBackupState.NotTrusted, testData.aliceSession2.cryptoService().keysBackupService().state)
 
         // - Trust the backup from the new device with the recovery key
-        mTestHelper.doSync<Unit> {
+        testHelper.doSync<Unit> {
             testData.aliceSession2.cryptoService().keysBackupService().trustKeysBackupVersionWithRecoveryKey(
                     testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!,
                     testData.prepareKeysBackupDataResult.megolmBackupCreationInfo.recoveryKey,
@@ -447,21 +446,21 @@ class KeysBackupTest : InstrumentedTest {
         }
 
         // Wait for backup state to be ReadyToBackUp
-        mKeysBackupTestHelper.waitForKeysBackupToBeInState(testData.aliceSession2, KeysBackupState.ReadyToBackUp)
+        keysBackupTestHelper.waitForKeysBackupToBeInState(testData.aliceSession2, KeysBackupState.ReadyToBackUp)
 
         // - Backup must be enabled on the new device, on the same version
         assertEquals(testData.prepareKeysBackupDataResult.version, testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion?.version)
         assertTrue(testData.aliceSession2.cryptoService().keysBackupService().isEnabled)
 
         // - Retrieve the last version from the server
-        val keysVersionResult = mTestHelper.doSync<KeysVersionResult?> {
+        val keysVersionResult = testHelper.doSync<KeysVersionResult?> {
             testData.aliceSession2.cryptoService().keysBackupService().getCurrentVersion(it)
         }
 
         // - It must be the same
         assertEquals(testData.prepareKeysBackupDataResult.version, keysVersionResult!!.version)
 
-        val keysBackupVersionTrust = mTestHelper.doSync<KeysBackupVersionTrust> {
+        val keysBackupVersionTrust = testHelper.doSync<KeysBackupVersionTrust> {
             testData.aliceSession2.cryptoService().keysBackupService().getKeysBackupTrust(keysVersionResult, it)
         }
 
@@ -470,7 +469,7 @@ class KeysBackupTest : InstrumentedTest {
         assertEquals(2, keysBackupVersionTrust.signatures.size)
 
         stateObserver.stopAndCheckStates(null)
-        testData.cleanUp(mTestHelper)
+        testData.cleanUp(testHelper)
     }
 
     /**
@@ -485,7 +484,7 @@ class KeysBackupTest : InstrumentedTest {
     fun trustKeyBackupVersionWithWrongRecoveryKeyTest() {
         // - Do an e2e backup to the homeserver with a recovery key
         // - And log Alice on a new device
-        val testData = mKeysBackupTestHelper.createKeysBackupScenarioWithPassword(null)
+        val testData = keysBackupTestHelper.createKeysBackupScenarioWithPassword(null)
 
         val stateObserver = StateObserver(testData.aliceSession2.cryptoService().keysBackupService())
 
@@ -501,7 +500,7 @@ class KeysBackupTest : InstrumentedTest {
                 "Bad recovery key",
                 TestMatrixCallback(latch, false)
         )
-        mTestHelper.await(latch)
+        testHelper.await(latch)
 
         // - The new device must still see the previous backup as not trusted
         assertNotNull(testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion)
@@ -509,7 +508,7 @@ class KeysBackupTest : InstrumentedTest {
         assertEquals(KeysBackupState.NotTrusted, testData.aliceSession2.cryptoService().keysBackupService().state)
 
         stateObserver.stopAndCheckStates(null)
-        testData.cleanUp(mTestHelper)
+        testData.cleanUp(testHelper)
     }
 
     /**
@@ -528,7 +527,7 @@ class KeysBackupTest : InstrumentedTest {
 
         // - Do an e2e backup to the homeserver with a password
         // - And log Alice on a new device
-        val testData = mKeysBackupTestHelper.createKeysBackupScenarioWithPassword(password)
+        val testData = keysBackupTestHelper.createKeysBackupScenarioWithPassword(password)
 
         val stateObserver = StateObserver(testData.aliceSession2.cryptoService().keysBackupService())
 
@@ -538,7 +537,7 @@ class KeysBackupTest : InstrumentedTest {
         assertEquals(KeysBackupState.NotTrusted, testData.aliceSession2.cryptoService().keysBackupService().state)
 
         // - Trust the backup from the new device with the password
-        mTestHelper.doSync<Unit> {
+        testHelper.doSync<Unit> {
             testData.aliceSession2.cryptoService().keysBackupService().trustKeysBackupVersionWithPassphrase(
                     testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!,
                     password,
@@ -547,21 +546,21 @@ class KeysBackupTest : InstrumentedTest {
         }
 
         // Wait for backup state to be ReadyToBackUp
-        mKeysBackupTestHelper.waitForKeysBackupToBeInState(testData.aliceSession2, KeysBackupState.ReadyToBackUp)
+        keysBackupTestHelper.waitForKeysBackupToBeInState(testData.aliceSession2, KeysBackupState.ReadyToBackUp)
 
         // - Backup must be enabled on the new device, on the same version
         assertEquals(testData.prepareKeysBackupDataResult.version, testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion?.version)
         assertTrue(testData.aliceSession2.cryptoService().keysBackupService().isEnabled)
 
         // - Retrieve the last version from the server
-        val keysVersionResult = mTestHelper.doSync<KeysVersionResult?> {
+        val keysVersionResult = testHelper.doSync<KeysVersionResult?> {
             testData.aliceSession2.cryptoService().keysBackupService().getCurrentVersion(it)
         }
 
         // - It must be the same
         assertEquals(testData.prepareKeysBackupDataResult.version, keysVersionResult!!.version)
 
-        val keysBackupVersionTrust = mTestHelper.doSync<KeysBackupVersionTrust> {
+        val keysBackupVersionTrust = testHelper.doSync<KeysBackupVersionTrust> {
             testData.aliceSession2.cryptoService().keysBackupService().getKeysBackupTrust(keysVersionResult, it)
         }
 
@@ -570,7 +569,7 @@ class KeysBackupTest : InstrumentedTest {
         assertEquals(2, keysBackupVersionTrust.signatures.size)
 
         stateObserver.stopAndCheckStates(null)
-        testData.cleanUp(mTestHelper)
+        testData.cleanUp(testHelper)
     }
 
     /**
@@ -588,7 +587,7 @@ class KeysBackupTest : InstrumentedTest {
 
         // - Do an e2e backup to the homeserver with a password
         // - And log Alice on a new device
-        val testData = mKeysBackupTestHelper.createKeysBackupScenarioWithPassword(password)
+        val testData = keysBackupTestHelper.createKeysBackupScenarioWithPassword(password)
 
         val stateObserver = StateObserver(testData.aliceSession2.cryptoService().keysBackupService())
 
@@ -604,7 +603,7 @@ class KeysBackupTest : InstrumentedTest {
                 badPassword,
                 TestMatrixCallback(latch, false)
         )
-        mTestHelper.await(latch)
+        testHelper.await(latch)
 
         // - The new device must still see the previous backup as not trusted
         assertNotNull(testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion)
@@ -612,7 +611,7 @@ class KeysBackupTest : InstrumentedTest {
         assertEquals(KeysBackupState.NotTrusted, testData.aliceSession2.cryptoService().keysBackupService().state)
 
         stateObserver.stopAndCheckStates(null)
-        testData.cleanUp(mTestHelper)
+        testData.cleanUp(testHelper)
     }
 
     /**
@@ -623,7 +622,7 @@ class KeysBackupTest : InstrumentedTest {
      */
     @Test
     fun restoreKeysBackupWithAWrongRecoveryKeyTest() {
-        val testData = mKeysBackupTestHelper.createKeysBackupScenarioWithPassword(null)
+        val testData = keysBackupTestHelper.createKeysBackupScenarioWithPassword(null)
 
         // - Try to restore the e2e backup with a wrong recovery key
         val latch2 = CountDownLatch(1)
@@ -640,12 +639,12 @@ class KeysBackupTest : InstrumentedTest {
                     }
                 }
         )
-        mTestHelper.await(latch2)
+        testHelper.await(latch2)
 
         // onSuccess may not have been called
         assertNull(importRoomKeysResult)
 
-        testData.cleanUp(mTestHelper)
+        testData.cleanUp(testHelper)
     }
 
     /**
@@ -658,12 +657,12 @@ class KeysBackupTest : InstrumentedTest {
     fun testBackupWithPassword() {
         val password = "password"
 
-        val testData = mKeysBackupTestHelper.createKeysBackupScenarioWithPassword(password)
+        val testData = keysBackupTestHelper.createKeysBackupScenarioWithPassword(password)
 
         // - Restore the e2e backup with the password
         val steps = ArrayList<StepProgressListener.Step>()
 
-        val importRoomKeysResult = mTestHelper.doSync<ImportRoomKeysResult> {
+        val importRoomKeysResult = testHelper.doSync<ImportRoomKeysResult> {
             testData.aliceSession2.cryptoService().keysBackupService().restoreKeyBackupWithPassword(testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!,
                     password,
                     null,
@@ -698,9 +697,9 @@ class KeysBackupTest : InstrumentedTest {
         assertEquals(50, (steps[103] as StepProgressListener.Step.ImportingKey).progress)
         assertEquals(100, (steps[104] as StepProgressListener.Step.ImportingKey).progress)
 
-        mKeysBackupTestHelper.checkRestoreSuccess(testData, importRoomKeysResult.totalNumberOfKeys, importRoomKeysResult.successfullyNumberOfImportedKeys)
+        keysBackupTestHelper.checkRestoreSuccess(testData, importRoomKeysResult.totalNumberOfKeys, importRoomKeysResult.successfullyNumberOfImportedKeys)
 
-        testData.cleanUp(mTestHelper)
+        testData.cleanUp(testHelper)
     }
 
     /**
@@ -714,7 +713,7 @@ class KeysBackupTest : InstrumentedTest {
         val password = "password"
         val wrongPassword = "passw0rd"
 
-        val testData = mKeysBackupTestHelper.createKeysBackupScenarioWithPassword(password)
+        val testData = keysBackupTestHelper.createKeysBackupScenarioWithPassword(password)
 
         // - Try to restore the e2e backup with a wrong password
         val latch2 = CountDownLatch(1)
@@ -731,12 +730,12 @@ class KeysBackupTest : InstrumentedTest {
                     }
                 }
         )
-        mTestHelper.await(latch2)
+        testHelper.await(latch2)
 
         // onSuccess may not have been called
         assertNull(importRoomKeysResult)
 
-        testData.cleanUp(mTestHelper)
+        testData.cleanUp(testHelper)
     }
 
     /**
@@ -749,10 +748,10 @@ class KeysBackupTest : InstrumentedTest {
     fun testUseRecoveryKeyToRestoreAPasswordBasedKeysBackup() {
         val password = "password"
 
-        val testData = mKeysBackupTestHelper.createKeysBackupScenarioWithPassword(password)
+        val testData = keysBackupTestHelper.createKeysBackupScenarioWithPassword(password)
 
         // - Restore the e2e backup with the recovery key.
-        val importRoomKeysResult = mTestHelper.doSync<ImportRoomKeysResult> {
+        val importRoomKeysResult = testHelper.doSync<ImportRoomKeysResult> {
             testData.aliceSession2.cryptoService().keysBackupService().restoreKeysWithRecoveryKey(testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!,
                     testData.prepareKeysBackupDataResult.megolmBackupCreationInfo.recoveryKey,
                     null,
@@ -762,9 +761,9 @@ class KeysBackupTest : InstrumentedTest {
             )
         }
 
-        mKeysBackupTestHelper.checkRestoreSuccess(testData, importRoomKeysResult.totalNumberOfKeys, importRoomKeysResult.successfullyNumberOfImportedKeys)
+        keysBackupTestHelper.checkRestoreSuccess(testData, importRoomKeysResult.totalNumberOfKeys, importRoomKeysResult.successfullyNumberOfImportedKeys)
 
-        testData.cleanUp(mTestHelper)
+        testData.cleanUp(testHelper)
     }
 
     /**
@@ -775,7 +774,7 @@ class KeysBackupTest : InstrumentedTest {
      */
     @Test
     fun testUsePasswordToRestoreARecoveryKeyBasedKeysBackup() {
-        val testData = mKeysBackupTestHelper.createKeysBackupScenarioWithPassword(null)
+        val testData = keysBackupTestHelper.createKeysBackupScenarioWithPassword(null)
 
         // - Try to restore the e2e backup with a password
         val latch2 = CountDownLatch(1)
@@ -792,12 +791,12 @@ class KeysBackupTest : InstrumentedTest {
                     }
                 }
         )
-        mTestHelper.await(latch2)
+        testHelper.await(latch2)
 
         // onSuccess may not have been called
         assertNull(importRoomKeysResult)
 
-        testData.cleanUp(mTestHelper)
+        testData.cleanUp(testHelper)
     }
 
     /**
@@ -807,22 +806,22 @@ class KeysBackupTest : InstrumentedTest {
     @Test
     fun testIsKeysBackupTrusted() {
         // - Create a backup version
-        val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages()
+        val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages()
 
         val keysBackup = cryptoTestData.firstSession.cryptoService().keysBackupService()
 
         val stateObserver = StateObserver(keysBackup)
 
         // - Do an e2e backup to the homeserver
-        mKeysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup)
+        keysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup)
 
         // Get key backup version from the homeserver
-        val keysVersionResult = mTestHelper.doSync<KeysVersionResult?> {
+        val keysVersionResult = testHelper.doSync<KeysVersionResult?> {
             keysBackup.getCurrentVersion(it)
         }
 
         // - Check the returned KeyBackupVersion is trusted
-        val keysBackupVersionTrust = mTestHelper.doSync<KeysBackupVersionTrust> {
+        val keysBackupVersionTrust = testHelper.doSync<KeysBackupVersionTrust> {
             keysBackup.getKeysBackupTrust(keysVersionResult!!, it)
         }
 
@@ -837,7 +836,7 @@ class KeysBackupTest : InstrumentedTest {
         assertEquals(signature.device!!.deviceId, cryptoTestData.firstSession.sessionParams.deviceId)
 
         stateObserver.stopAndCheckStates(null)
-        cryptoTestData.cleanUp(mTestHelper)
+        cryptoTestData.cleanUp(testHelper)
     }
 
     /**
@@ -849,9 +848,8 @@ class KeysBackupTest : InstrumentedTest {
      */
     @Test
     fun testCheckAndStartKeysBackupWhenRestartingAMatrixSession() {
-        fail("This test still fail. To investigate")
         // - Create a backup version
-        val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages()
+        val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages()
 
         val keysBackup = cryptoTestData.firstSession.cryptoService().keysBackupService()
 
@@ -859,15 +857,15 @@ class KeysBackupTest : InstrumentedTest {
 
         assertFalse(keysBackup.isEnabled)
 
-        val keyBackupCreationInfo = mKeysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup)
+        val keyBackupCreationInfo = keysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup)
 
         assertTrue(keysBackup.isEnabled)
 
         // - Restart alice session
         // - Log Alice on a new device
-        val aliceSession2 = mTestHelper.logIntoAccount(cryptoTestData.firstSession.myUserId, KeysBackupTestConstants.defaultSessionParamsWithInitialSync)
+        val aliceSession2 = testHelper.logIntoAccount(cryptoTestData.firstSession.myUserId, KeysBackupTestConstants.defaultSessionParamsWithInitialSync)
 
-        cryptoTestData.cleanUp(mTestHelper)
+        cryptoTestData.cleanUp(testHelper)
 
         val keysBackup2 = aliceSession2.cryptoService().keysBackupService()
 
@@ -891,13 +889,13 @@ class KeysBackupTest : InstrumentedTest {
                 }
             }
         })
-        mTestHelper.await(latch)
+        testHelper.await(latch)
 
         assertEquals(keyBackupCreationInfo.version, keysBackup2.currentBackupVersion)
 
         stateObserver.stopAndCheckStates(null)
         stateObserver2.stopAndCheckStates(null)
-        mTestHelper.signOutAndClose(aliceSession2)
+        testHelper.signOutAndClose(aliceSession2)
     }
 
     /**
@@ -911,7 +909,7 @@ class KeysBackupTest : InstrumentedTest {
     @Test
     fun testBackupWhenAnotherBackupWasCreated() {
         // - Create a backup version
-        val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages()
+        val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages()
 
         val keysBackup = cryptoTestData.firstSession.cryptoService().keysBackupService()
 
@@ -939,15 +937,15 @@ class KeysBackupTest : InstrumentedTest {
         })
 
         // - Make alice back up her keys to her homeserver
-        mKeysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup)
+        keysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup)
 
         assertTrue(keysBackup.isEnabled)
 
-        mTestHelper.await(latch0)
+        testHelper.await(latch0)
 
         // - Create a new backup with fake data on the homeserver, directly using the rest client
-        val megolmBackupCreationInfo = mCryptoTestHelper.createFakeMegolmBackupCreationInfo()
-        mTestHelper.doSync<KeysVersion> {
+        val megolmBackupCreationInfo = cryptoTestHelper.createFakeMegolmBackupCreationInfo()
+        testHelper.doSync<KeysVersion> {
             (keysBackup as DefaultKeysBackupService).createFakeKeysBackupVersion(megolmBackupCreationInfo, it)
         }
 
@@ -957,14 +955,14 @@ class KeysBackupTest : InstrumentedTest {
         // - Make alice back up all her keys again
         val latch2 = CountDownLatch(1)
         keysBackup.backupAllGroupSessions(null, TestMatrixCallback(latch2, false))
-        mTestHelper.await(latch2)
+        testHelper.await(latch2)
 
         // -> That must fail and her backup state must be WrongBackUpVersion
         assertEquals(KeysBackupState.WrongBackUpVersion, keysBackup.state)
         assertFalse(keysBackup.isEnabled)
 
         stateObserver.stopAndCheckStates(null)
-        cryptoTestData.cleanUp(mTestHelper)
+        cryptoTestData.cleanUp(testHelper)
     }
 
     /**
@@ -982,17 +980,17 @@ class KeysBackupTest : InstrumentedTest {
     @Test
     fun testBackupAfterVerifyingADevice() {
         // - Create a backup version
-        val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages()
+        val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages()
 
         val keysBackup = cryptoTestData.firstSession.cryptoService().keysBackupService()
 
         val stateObserver = StateObserver(keysBackup)
 
         // - Make alice back up her keys to her homeserver
-        mKeysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup)
+        keysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup)
 
         // Wait for keys backup to finish by asking again to backup keys.
-        mTestHelper.doSync<Unit> {
+        testHelper.doSync<Unit> {
             keysBackup.backupAllGroupSessions(null, it)
         }
 
@@ -1001,14 +999,14 @@ class KeysBackupTest : InstrumentedTest {
         val aliceUserId = cryptoTestData.firstSession.myUserId
 
         // - Log Alice on a new device
-        val aliceSession2 = mTestHelper.logIntoAccount(aliceUserId, KeysBackupTestConstants.defaultSessionParamsWithInitialSync)
+        val aliceSession2 = testHelper.logIntoAccount(aliceUserId, KeysBackupTestConstants.defaultSessionParamsWithInitialSync)
 
         // - Post a message to have a new megolm session
         aliceSession2.cryptoService().setWarnOnUnknownDevices(false)
 
         val room2 = aliceSession2.getRoom(cryptoTestData.roomId)!!
 
-        mTestHelper.sendTextMessage(room2, "New key", 1)
+        testHelper.sendTextMessage(room2, "New key", 1)
 
         // - Try to backup all in aliceSession2, it must fail
         val keysBackup2 = aliceSession2.cryptoService().keysBackupService()
@@ -1025,7 +1023,7 @@ class KeysBackupTest : InstrumentedTest {
                         super.onSuccess(data)
                     }
                 })
-        mTestHelper.await(latch2)
+        testHelper.await(latch2)
 
         assertFalse(isSuccessful)
 
@@ -1049,12 +1047,12 @@ class KeysBackupTest : InstrumentedTest {
                 }
             }
         })
-        mTestHelper.await(latch4)
+        testHelper.await(latch4)
 
         // -> It must use the same backup version
         assertEquals(oldKeyBackupVersion, aliceSession2.cryptoService().keysBackupService().currentBackupVersion)
 
-        mTestHelper.doSync<Unit> {
+        testHelper.doSync<Unit> {
             aliceSession2.cryptoService().keysBackupService().backupAllGroupSessions(null, it)
         }
 
@@ -1063,8 +1061,8 @@ class KeysBackupTest : InstrumentedTest {
 
         stateObserver.stopAndCheckStates(null)
         stateObserver2.stopAndCheckStates(null)
-        mTestHelper.signOutAndClose(aliceSession2)
-        cryptoTestData.cleanUp(mTestHelper)
+        testHelper.signOutAndClose(aliceSession2)
+        cryptoTestData.cleanUp(testHelper)
     }
 
     /**
@@ -1074,7 +1072,7 @@ class KeysBackupTest : InstrumentedTest {
     @Test
     fun deleteKeysBackupTest() {
         // - Create a backup version
-        val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages()
+        val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages()
 
         val keysBackup = cryptoTestData.firstSession.cryptoService().keysBackupService()
 
@@ -1082,17 +1080,17 @@ class KeysBackupTest : InstrumentedTest {
 
         assertFalse(keysBackup.isEnabled)
 
-        val keyBackupCreationInfo = mKeysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup)
+        val keyBackupCreationInfo = keysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup)
 
         assertTrue(keysBackup.isEnabled)
 
         // Delete the backup
-        mTestHelper.doSync<Unit> { keysBackup.deleteBackup(keyBackupCreationInfo.version, it) }
+        testHelper.doSync<Unit> { keysBackup.deleteBackup(keyBackupCreationInfo.version, it) }
 
         // Backup is now disabled
         assertFalse(keysBackup.isEnabled)
 
         stateObserver.stopAndCheckStates(null)
-        cryptoTestData.cleanUp(mTestHelper)
+        cryptoTestData.cleanUp(testHelper)
     }
 }
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupTestHelper.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupTestHelper.kt
index a625ffc0e976f69410af10c55a353877a57971b6..592b798bccee0f132bd43ee016715f8e874b0026 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupTestHelper.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupTestHelper.kt
@@ -32,8 +32,12 @@ import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysVersion
 import java.util.concurrent.CountDownLatch
 
 class KeysBackupTestHelper(
-        private val mTestHelper: CommonTestHelper,
-        private val mCryptoTestHelper: CryptoTestHelper) {
+        private val testHelper: CommonTestHelper,
+        private val cryptoTestHelper: CryptoTestHelper) {
+
+    fun waitForKeybackUpBatching() {
+        Thread.sleep(400)
+    }
 
     /**
      * Common initial condition
@@ -43,7 +47,9 @@ class KeysBackupTestHelper(
      * @param password optional password
      */
     fun createKeysBackupScenarioWithPassword(password: String?): KeysBackupScenarioData {
-        val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages()
+        val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages()
+
+        waitForKeybackUpBatching()
 
         val cryptoStore = (cryptoTestData.firstSession.cryptoService().keysBackupService() as DefaultKeysBackupService).store
         val keysBackup = cryptoTestData.firstSession.cryptoService().keysBackupService()
@@ -57,7 +63,7 @@ class KeysBackupTestHelper(
 
         var lastProgress = 0
         var lastTotal = 0
-        mTestHelper.doSync<Unit> {
+        testHelper.doSync<Unit> {
             keysBackup.backupAllGroupSessions(object : ProgressListener {
                 override fun onProgress(progress: Int, total: Int) {
                     lastProgress = progress
@@ -72,7 +78,7 @@ class KeysBackupTestHelper(
         val aliceUserId = cryptoTestData.firstSession.myUserId
 
         // - Log Alice on a new device
-        val aliceSession2 = mTestHelper.logIntoAccount(aliceUserId, KeysBackupTestConstants.defaultSessionParamsWithInitialSync)
+        val aliceSession2 = testHelper.logIntoAccount(aliceUserId, KeysBackupTestConstants.defaultSessionParamsWithInitialSync)
 
         // Test check: aliceSession2 has no keys at login
         Assert.assertEquals(0, aliceSession2.cryptoService().inboundGroupSessionsCount(false))
@@ -92,7 +98,7 @@ class KeysBackupTestHelper(
                                        password: String? = null): PrepareKeysBackupDataResult {
         val stateObserver = StateObserver(keysBackup)
 
-        val megolmBackupCreationInfo = mTestHelper.doSync<MegolmBackupCreationInfo> {
+        val megolmBackupCreationInfo = testHelper.doSync<MegolmBackupCreationInfo> {
             keysBackup.prepareKeysBackupVersion(password, null, it)
         }
 
@@ -101,7 +107,7 @@ class KeysBackupTestHelper(
         Assert.assertFalse(keysBackup.isEnabled)
 
         // Create the version
-        val keysVersion = mTestHelper.doSync<KeysVersion> {
+        val keysVersion = testHelper.doSync<KeysVersion> {
             keysBackup.createKeysBackupVersion(megolmBackupCreationInfo, it)
         }
 
@@ -136,7 +142,7 @@ class KeysBackupTestHelper(
             }
         })
 
-        mTestHelper.await(latch)
+        testHelper.await(latch)
     }
 
     fun assertKeysEquals(keys1: MegolmSessionData?, keys2: MegolmSessionData?) {
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/ssss/QuadSTests.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/ssss/QuadSTests.kt
index b343d7334acfdb264d1f1f032b661b0d8972b6a3..43f8dc07627d65c854826eba951e3e81dab857d3 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/ssss/QuadSTests.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/ssss/QuadSTests.kt
@@ -18,10 +18,6 @@ package org.matrix.android.sdk.internal.crypto.ssss
 
 import androidx.lifecycle.Observer
 import androidx.test.ext.junit.runners.AndroidJUnit4
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.GlobalScope
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.runBlocking
 import org.junit.Assert.assertEquals
 import org.junit.Assert.assertNotNull
 import org.junit.Assert.assertNull
@@ -36,6 +32,7 @@ import org.matrix.android.sdk.api.session.securestorage.EncryptedSecretContent
 import org.matrix.android.sdk.api.session.securestorage.KeySigner
 import org.matrix.android.sdk.api.session.securestorage.RawBytesKeySpec
 import org.matrix.android.sdk.api.session.securestorage.SecretStorageKeyContent
+import org.matrix.android.sdk.api.session.securestorage.SharedSecretStorageError
 import org.matrix.android.sdk.api.session.securestorage.SharedSecretStorageService
 import org.matrix.android.sdk.api.session.securestorage.SsssKeyCreationInfo
 import org.matrix.android.sdk.api.util.Optional
@@ -45,13 +42,12 @@ import org.matrix.android.sdk.common.TestConstants
 import org.matrix.android.sdk.internal.crypto.SSSS_ALGORITHM_AES_HMAC_SHA2
 import org.matrix.android.sdk.internal.crypto.crosssigning.toBase64NoPadding
 import org.matrix.android.sdk.internal.crypto.secrets.DefaultSharedSecretStorageService
-import java.util.concurrent.CountDownLatch
 
 @RunWith(AndroidJUnit4::class)
 @FixMethodOrder(MethodSorters.JVM)
 class QuadSTests : InstrumentedTest {
 
-    private val mTestHelper = CommonTestHelper(context())
+    private val testHelper = CommonTestHelper(context())
 
     private val emptyKeySigner = object : KeySigner {
         override fun sign(canonicalJson: String): Map<String, Map<String, String>>? {
@@ -60,35 +56,29 @@ class QuadSTests : InstrumentedTest {
     }
 
     @Test
-    @Suppress("EXPERIMENTAL_API_USAGE")
     fun test_Generate4SKey() {
-        val aliceSession = mTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true))
+        val aliceSession = testHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true))
 
         val quadS = aliceSession.sharedSecretStorageService
 
         val TEST_KEY_ID = "my.test.Key"
 
-        mTestHelper.runBlockingTest {
+        testHelper.runBlockingTest {
             quadS.generateKey(TEST_KEY_ID, null, "Test Key", emptyKeySigner)
         }
 
-        // Assert Account data is updated
-        val accountDataLock = CountDownLatch(1)
         var accountData: UserAccountDataEvent? = null
-
-        val liveAccountData = runBlocking(Dispatchers.Main) {
-            aliceSession.accountDataService().getLiveUserAccountDataEvent("${DefaultSharedSecretStorageService.KEY_ID_BASE}.$TEST_KEY_ID")
-        }
-        val accountDataObserver = Observer<Optional<UserAccountDataEvent>?> { t ->
-            if (t?.getOrNull()?.type == "${DefaultSharedSecretStorageService.KEY_ID_BASE}.$TEST_KEY_ID") {
-                accountData = t.getOrNull()
-                accountDataLock.countDown()
+        // Assert Account data is updated
+        testHelper.waitWithLatch {
+            val liveAccountData = aliceSession.accountDataService().getLiveUserAccountDataEvent("${DefaultSharedSecretStorageService.KEY_ID_BASE}.$TEST_KEY_ID")
+            val accountDataObserver = Observer<Optional<UserAccountDataEvent>?> { t ->
+                if (t?.getOrNull()?.type == "${DefaultSharedSecretStorageService.KEY_ID_BASE}.$TEST_KEY_ID") {
+                    accountData = t.getOrNull()
+                }
+                it.countDown()
             }
+            liveAccountData.observeForever(accountDataObserver)
         }
-        GlobalScope.launch(Dispatchers.Main) { liveAccountData.observeForever(accountDataObserver) }
-
-        mTestHelper.await(accountDataLock)
-
         assertNotNull("Key should be stored in account data", accountData)
         val parsed = SecretStorageKeyContent.fromJson(accountData!!.content)
         assertNotNull("Key Content cannot be parsed", parsed)
@@ -96,36 +86,29 @@ class QuadSTests : InstrumentedTest {
         assertEquals("Unexpected key name", "Test Key", parsed.name)
         assertNull("Key was not generated from passphrase", parsed.passphrase)
 
+        var defaultKeyAccountData: UserAccountDataEvent? = null
         // Set as default key
-        GlobalScope.launch {
+        testHelper.waitWithLatch { latch ->
             quadS.setDefaultKey(TEST_KEY_ID)
-        }
-
-        var defaultKeyAccountData: UserAccountDataEvent? = null
-        val defaultDataLock = CountDownLatch(1)
-
-        val liveDefAccountData = runBlocking(Dispatchers.Main) {
-            aliceSession.accountDataService().getLiveUserAccountDataEvent(DefaultSharedSecretStorageService.DEFAULT_KEY_ID)
-        }
-        val accountDefDataObserver = Observer<Optional<UserAccountDataEvent>?> { t ->
-            if (t?.getOrNull()?.type == DefaultSharedSecretStorageService.DEFAULT_KEY_ID) {
-                defaultKeyAccountData = t.getOrNull()!!
-                defaultDataLock.countDown()
+            val liveDefAccountData =
+                    aliceSession.accountDataService().getLiveUserAccountDataEvent(DefaultSharedSecretStorageService.DEFAULT_KEY_ID)
+            val accountDefDataObserver = Observer<Optional<UserAccountDataEvent>?> { t ->
+                if (t?.getOrNull()?.type == DefaultSharedSecretStorageService.DEFAULT_KEY_ID) {
+                    defaultKeyAccountData = t.getOrNull()!!
+                    latch.countDown()
+                }
             }
+            liveDefAccountData.observeForever(accountDefDataObserver)
         }
-        GlobalScope.launch(Dispatchers.Main) { liveDefAccountData.observeForever(accountDefDataObserver) }
-
-        mTestHelper.await(defaultDataLock)
-
         assertNotNull(defaultKeyAccountData?.content)
         assertEquals("Unexpected default key ${defaultKeyAccountData?.content}", TEST_KEY_ID, defaultKeyAccountData?.content?.get("key"))
 
-        mTestHelper.signOutAndClose(aliceSession)
+        testHelper.signOutAndClose(aliceSession)
     }
 
     @Test
     fun test_StoreSecret() {
-        val aliceSession = mTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true))
+        val aliceSession = testHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true))
         val keyId = "My.Key"
         val info = generatedSecret(aliceSession, keyId, true)
 
@@ -133,7 +116,7 @@ class QuadSTests : InstrumentedTest {
 
         // Store a secret
         val clearSecret = "42".toByteArray().toBase64NoPadding()
-        mTestHelper.runBlockingTest {
+        testHelper.runBlockingTest {
             aliceSession.sharedSecretStorageService.storeSecret(
                     "secret.of.life",
                     clearSecret,
@@ -154,7 +137,7 @@ class QuadSTests : InstrumentedTest {
 
         // Try to decrypt??
 
-        val decryptedSecret = mTestHelper.runBlockingTest {
+        val decryptedSecret = testHelper.runBlockingTest {
             aliceSession.sharedSecretStorageService.getSecret(
                     "secret.of.life",
                     null, // default key
@@ -163,32 +146,32 @@ class QuadSTests : InstrumentedTest {
         }
 
         assertEquals("Secret mismatch", clearSecret, decryptedSecret)
-        mTestHelper.signOutAndClose(aliceSession)
+        testHelper.signOutAndClose(aliceSession)
     }
 
     @Test
     fun test_SetDefaultLocalEcho() {
-        val aliceSession = mTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true))
+        val aliceSession = testHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true))
 
         val quadS = aliceSession.sharedSecretStorageService
 
         val TEST_KEY_ID = "my.test.Key"
 
-        mTestHelper.runBlockingTest {
+        testHelper.runBlockingTest {
             quadS.generateKey(TEST_KEY_ID, null, "Test Key", emptyKeySigner)
         }
 
         // Test that we don't need to wait for an account data sync to access directly the keyid from DB
-        mTestHelper.runBlockingTest {
+        testHelper.runBlockingTest {
             quadS.setDefaultKey(TEST_KEY_ID)
         }
 
-        mTestHelper.signOutAndClose(aliceSession)
+        testHelper.signOutAndClose(aliceSession)
     }
 
     @Test
     fun test_StoreSecretWithMultipleKey() {
-        val aliceSession = mTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true))
+        val aliceSession = testHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true))
         val keyId1 = "Key.1"
         val key1Info = generatedSecret(aliceSession, keyId1, true)
         val keyId2 = "Key2"
@@ -196,7 +179,7 @@ class QuadSTests : InstrumentedTest {
 
         val mySecretText = "Lorem ipsum dolor sit amet, consectetur adipiscing elit"
 
-        mTestHelper.runBlockingTest {
+        testHelper.runBlockingTest {
             aliceSession.sharedSecretStorageService.storeSecret(
                     "my.secret",
                     mySecretText.toByteArray().toBase64NoPadding(),
@@ -216,33 +199,33 @@ class QuadSTests : InstrumentedTest {
         assertNotNull(encryptedContent?.get(keyId2))
 
         // Assert that can decrypt with both keys
-        mTestHelper.runBlockingTest {
+        testHelper.runBlockingTest {
             aliceSession.sharedSecretStorageService.getSecret("my.secret",
                     keyId1,
                     RawBytesKeySpec.fromRecoveryKey(key1Info.recoveryKey)!!
             )
         }
 
-        mTestHelper.runBlockingTest {
+        testHelper.runBlockingTest {
             aliceSession.sharedSecretStorageService.getSecret("my.secret",
                     keyId2,
                     RawBytesKeySpec.fromRecoveryKey(key2Info.recoveryKey)!!
             )
         }
 
-        mTestHelper.signOutAndClose(aliceSession)
+        testHelper.signOutAndClose(aliceSession)
     }
 
     @Test
     fun test_GetSecretWithBadPassphrase() {
-        val aliceSession = mTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true))
+        val aliceSession = testHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true))
         val keyId1 = "Key.1"
         val passphrase = "The good pass phrase"
         val key1Info = generatedSecretFromPassphrase(aliceSession, passphrase, keyId1, true)
 
         val mySecretText = "Lorem ipsum dolor sit amet, consectetur adipiscing elit"
 
-        mTestHelper.runBlockingTest {
+        testHelper.runBlockingTest {
             aliceSession.sharedSecretStorageService.storeSecret(
                     "my.secret",
                     mySecretText.toByteArray().toBase64NoPadding(),
@@ -250,19 +233,23 @@ class QuadSTests : InstrumentedTest {
             )
         }
 
-        mTestHelper.runBlockingTest {
-            aliceSession.sharedSecretStorageService.getSecret("my.secret",
-                    keyId1,
-                    RawBytesKeySpec.fromPassphrase(
-                            "A bad passphrase",
-                            key1Info.content?.passphrase?.salt ?: "",
-                            key1Info.content?.passphrase?.iterations ?: 0,
-                            null)
-            )
+        testHelper.runBlockingTest {
+            try {
+                aliceSession.sharedSecretStorageService.getSecret("my.secret",
+                        keyId1,
+                        RawBytesKeySpec.fromPassphrase(
+                                "A bad passphrase",
+                                key1Info.content?.passphrase?.salt ?: "",
+                                key1Info.content?.passphrase?.iterations ?: 0,
+                                null)
+                )
+            } catch (throwable: Throwable) {
+                assert(throwable is SharedSecretStorageError.BadMac)
+            }
         }
 
         // Now try with correct key
-        mTestHelper.runBlockingTest {
+        testHelper.runBlockingTest {
             aliceSession.sharedSecretStorageService.getSecret("my.secret",
                     keyId1,
                     RawBytesKeySpec.fromPassphrase(
@@ -273,42 +260,36 @@ class QuadSTests : InstrumentedTest {
             )
         }
 
-        mTestHelper.signOutAndClose(aliceSession)
+        testHelper.signOutAndClose(aliceSession)
     }
 
-    @Suppress("EXPERIMENTAL_API_USAGE")
     private fun assertAccountData(session: Session, type: String): UserAccountDataEvent {
-        val accountDataLock = CountDownLatch(1)
         var accountData: UserAccountDataEvent? = null
-
-        val liveAccountData = runBlocking(Dispatchers.Main) {
-            session.accountDataService().getLiveUserAccountDataEvent(type)
-        }
-        val accountDataObserver = Observer<Optional<UserAccountDataEvent>?> { t ->
-            if (t?.getOrNull()?.type == type) {
-                accountData = t.getOrNull()
-                accountDataLock.countDown()
+        testHelper.waitWithLatch {
+            val liveAccountData = session.accountDataService().getLiveUserAccountDataEvent(type)
+            val accountDataObserver = Observer<Optional<UserAccountDataEvent>?> { t ->
+                if (t?.getOrNull()?.type == type) {
+                    accountData = t.getOrNull()
+                    it.countDown()
+                }
             }
+            liveAccountData.observeForever(accountDataObserver)
         }
-        GlobalScope.launch(Dispatchers.Main) { liveAccountData.observeForever(accountDataObserver) }
-        mTestHelper.await(accountDataLock)
-
         assertNotNull("Account Data type:$type should be found", accountData)
-
         return accountData!!
     }
 
     private fun generatedSecret(session: Session, keyId: String, asDefault: Boolean = true): SsssKeyCreationInfo {
         val quadS = session.sharedSecretStorageService
 
-        val creationInfo = mTestHelper.runBlockingTest {
+        val creationInfo = testHelper.runBlockingTest {
             quadS.generateKey(keyId, null, keyId, emptyKeySigner)
         }
 
         assertAccountData(session, "${DefaultSharedSecretStorageService.KEY_ID_BASE}.$keyId")
 
         if (asDefault) {
-            mTestHelper.runBlockingTest {
+            testHelper.runBlockingTest {
                 quadS.setDefaultKey(keyId)
             }
             assertAccountData(session, DefaultSharedSecretStorageService.DEFAULT_KEY_ID)
@@ -320,7 +301,7 @@ class QuadSTests : InstrumentedTest {
     private fun generatedSecretFromPassphrase(session: Session, passphrase: String, keyId: String, asDefault: Boolean = true): SsssKeyCreationInfo {
         val quadS = session.sharedSecretStorageService
 
-        val creationInfo = mTestHelper.runBlockingTest {
+        val creationInfo = testHelper.runBlockingTest {
             quadS.generateKeyWithPassphrase(
                     keyId,
                     keyId,
@@ -331,7 +312,7 @@ class QuadSTests : InstrumentedTest {
 
         assertAccountData(session, "${DefaultSharedSecretStorageService.KEY_ID_BASE}.$keyId")
         if (asDefault) {
-            mTestHelper.runBlockingTest {
+            testHelper.runBlockingTest {
                 quadS.setDefaultKey(keyId)
             }
             assertAccountData(session, DefaultSharedSecretStorageService.DEFAULT_KEY_ID)
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/SASTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/SASTest.kt
index e0d49b3f5e79d6a4fd94cd8ad22c82abf0c19307..c914da6f71fdc9ee40038971266bc948baf49c87 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/SASTest.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/SASTest.kt
@@ -53,12 +53,12 @@ import java.util.concurrent.CountDownLatch
 @RunWith(AndroidJUnit4::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
 class SASTest : InstrumentedTest {
-    private val mTestHelper = CommonTestHelper(context())
-    private val mCryptoTestHelper = CryptoTestHelper(mTestHelper)
+    private val testHelper = CommonTestHelper(context())
+    private val cryptoTestHelper = CryptoTestHelper(testHelper)
 
     @Test
     fun test_aliceStartThenAliceCancel() {
-        val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
+        val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
 
         val aliceSession = cryptoTestData.firstSession
         val bobSession = cryptoTestData.secondSession
@@ -83,7 +83,7 @@ class SASTest : InstrumentedTest {
         val aliceKeyTx = aliceVerificationService.getExistingTransaction(bobSession.myUserId, txID!!)
         assertNotNull("Alice should have a started transaction", aliceKeyTx)
 
-        mTestHelper.await(bobTxCreatedLatch)
+        testHelper.await(bobTxCreatedLatch)
         bobVerificationService.removeListener(bobListener)
 
         val bobKeyTx = bobVerificationService.getExistingTransaction(aliceSession.myUserId, txID)
@@ -116,7 +116,7 @@ class SASTest : InstrumentedTest {
         bobVerificationService.addListener(bobListener2)
 
         aliceSasTx.cancel(CancelCode.User)
-        mTestHelper.await(cancelLatch)
+        testHelper.await(cancelLatch)
 
         assertTrue("Should be cancelled on alice side", aliceSasTx.state is VerificationTxState.Cancelled)
         assertTrue("Should be cancelled on bob side", bobSasTx.state is VerificationTxState.Cancelled)
@@ -133,13 +133,13 @@ class SASTest : InstrumentedTest {
         assertNull(bobVerificationService.getExistingTransaction(aliceSession.myUserId, txID))
         assertNull(aliceVerificationService.getExistingTransaction(bobSession.myUserId, txID))
 
-        cryptoTestData.cleanUp(mTestHelper)
+        cryptoTestData.cleanUp(testHelper)
     }
 
     @Test
     fun test_key_agreement_protocols_must_include_curve25519() {
         fail("Not passing for the moment")
-        val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
+        val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
 
         val bobSession = cryptoTestData.secondSession!!
 
@@ -186,17 +186,17 @@ class SASTest : InstrumentedTest {
 
         fakeBobStart(bobSession, aliceUserID, aliceDevice, tid, protocols = protocols)
 
-        mTestHelper.await(cancelLatch)
+        testHelper.await(cancelLatch)
 
         assertEquals("Request should be cancelled with m.unknown_method", CancelCode.UnknownMethod, cancelReason)
 
-        cryptoTestData.cleanUp(mTestHelper)
+        cryptoTestData.cleanUp(testHelper)
     }
 
     @Test
     fun test_key_agreement_macs_Must_include_hmac_sha256() {
         fail("Not passing for the moment")
-        val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
+        val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
 
         val bobSession = cryptoTestData.secondSession!!
 
@@ -223,18 +223,18 @@ class SASTest : InstrumentedTest {
 
         fakeBobStart(bobSession, aliceUserID, aliceDevice, tid, mac = mac)
 
-        mTestHelper.await(cancelLatch)
+        testHelper.await(cancelLatch)
 
         val cancelReq = canceledToDeviceEvent!!.content.toModel<KeyVerificationCancel>()!!
         assertEquals("Request should be cancelled with m.unknown_method", CancelCode.UnknownMethod.value, cancelReq.code)
 
-        cryptoTestData.cleanUp(mTestHelper)
+        cryptoTestData.cleanUp(testHelper)
     }
 
     @Test
     fun test_key_agreement_short_code_include_decimal() {
         fail("Not passing for the moment")
-        val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
+        val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
 
         val bobSession = cryptoTestData.secondSession!!
 
@@ -261,12 +261,12 @@ class SASTest : InstrumentedTest {
 
         fakeBobStart(bobSession, aliceUserID, aliceDevice, tid, codes = codes)
 
-        mTestHelper.await(cancelLatch)
+        testHelper.await(cancelLatch)
 
         val cancelReq = canceledToDeviceEvent!!.content.toModel<KeyVerificationCancel>()!!
         assertEquals("Request should be cancelled with m.unknown_method", CancelCode.UnknownMethod.value, cancelReq.code)
 
-        cryptoTestData.cleanUp(mTestHelper)
+        cryptoTestData.cleanUp(testHelper)
     }
 
     private fun fakeBobStart(bobSession: Session,
@@ -303,7 +303,7 @@ class SASTest : InstrumentedTest {
     // If a device has two verifications in progress with the same device, then it should cancel both verifications.
     @Test
     fun test_aliceStartTwoRequests() {
-        val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
+        val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
 
         val aliceSession = cryptoTestData.firstSession
         val bobSession = cryptoTestData.secondSession
@@ -332,10 +332,10 @@ class SASTest : InstrumentedTest {
         aliceVerificationService.beginKeyVerification(VerificationMethod.SAS, bobUserId, bobDeviceId, null)
         aliceVerificationService.beginKeyVerification(VerificationMethod.SAS, bobUserId, bobDeviceId, null)
 
-        mTestHelper.await(aliceCreatedLatch)
-        mTestHelper.await(aliceCancelledLatch)
+        testHelper.await(aliceCreatedLatch)
+        testHelper.await(aliceCancelledLatch)
 
-        cryptoTestData.cleanUp(mTestHelper)
+        cryptoTestData.cleanUp(testHelper)
     }
 
     /**
@@ -343,7 +343,7 @@ class SASTest : InstrumentedTest {
      */
     @Test
     fun test_aliceAndBobAgreement() {
-        val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
+        val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
 
         val aliceSession = cryptoTestData.firstSession
         val bobSession = cryptoTestData.secondSession
@@ -383,7 +383,7 @@ class SASTest : InstrumentedTest {
         val bobUserId = bobSession.myUserId
         val bobDeviceId = bobSession.cryptoService().getMyDevice().deviceId
         aliceVerificationService.beginKeyVerification(VerificationMethod.SAS, bobUserId, bobDeviceId, null)
-        mTestHelper.await(aliceAcceptedLatch)
+        testHelper.await(aliceAcceptedLatch)
 
         assertTrue("Should have receive a commitment", accepted!!.commitment?.trim()?.isEmpty() == false)
 
@@ -397,12 +397,12 @@ class SASTest : InstrumentedTest {
             assertTrue("all agreed Short Code should be known by alice", startReq!!.shortAuthenticationStrings.contains(it))
         }
 
-        cryptoTestData.cleanUp(mTestHelper)
+        cryptoTestData.cleanUp(testHelper)
     }
 
     @Test
     fun test_aliceAndBobSASCode() {
-        val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
+        val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
 
         val aliceSession = cryptoTestData.firstSession
         val bobSession = cryptoTestData.secondSession
@@ -444,8 +444,8 @@ class SASTest : InstrumentedTest {
         val bobUserId = bobSession.myUserId
         val bobDeviceId = bobSession.cryptoService().getMyDevice().deviceId
         val verificationSAS = aliceVerificationService.beginKeyVerification(VerificationMethod.SAS, bobUserId, bobDeviceId, null)
-        mTestHelper.await(aliceSASLatch)
-        mTestHelper.await(bobSASLatch)
+        testHelper.await(aliceSASLatch)
+        testHelper.await(bobSASLatch)
 
         val aliceTx = aliceVerificationService.getExistingTransaction(bobUserId, verificationSAS!!) as SASDefaultVerificationTransaction
         val bobTx = bobVerificationService.getExistingTransaction(aliceSession.myUserId, verificationSAS) as SASDefaultVerificationTransaction
@@ -453,12 +453,12 @@ class SASTest : InstrumentedTest {
         assertEquals("Should have same SAS", aliceTx.getShortCodeRepresentation(SasMode.DECIMAL),
                 bobTx.getShortCodeRepresentation(SasMode.DECIMAL))
 
-        cryptoTestData.cleanUp(mTestHelper)
+        cryptoTestData.cleanUp(testHelper)
     }
 
     @Test
     fun test_happyPath() {
-        val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
+        val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
 
         val aliceSession = cryptoTestData.firstSession
         val bobSession = cryptoTestData.secondSession
@@ -520,8 +520,8 @@ class SASTest : InstrumentedTest {
         val bobUserId = bobSession.myUserId
         val bobDeviceId = bobSession.cryptoService().getMyDevice().deviceId
         aliceVerificationService.beginKeyVerification(VerificationMethod.SAS, bobUserId, bobDeviceId, null)
-        mTestHelper.await(aliceSASLatch)
-        mTestHelper.await(bobSASLatch)
+        testHelper.await(aliceSASLatch)
+        testHelper.await(bobSASLatch)
 
         // Assert that devices are verified
         val bobDeviceInfoFromAlicePOV: CryptoDeviceInfo? = aliceSession.cryptoService().getDeviceInfo(bobUserId, bobDeviceId)
@@ -532,12 +532,12 @@ class SASTest : InstrumentedTest {
 
         assertTrue("alice device should be verified from bob point of view", aliceDeviceInfoFromBobPOV!!.isVerified)
         assertTrue("bob device should be verified from alice point of view", bobDeviceInfoFromAlicePOV!!.isVerified)
-        cryptoTestData.cleanUp(mTestHelper)
+        cryptoTestData.cleanUp(testHelper)
     }
 
     @Test
     fun test_ConcurrentStart() {
-        val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
+        val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
 
         val aliceSession = cryptoTestData.firstSession
         val bobSession = cryptoTestData.secondSession
@@ -553,8 +553,8 @@ class SASTest : InstrumentedTest {
 
         var requestID: String? = null
 
-        mTestHelper.waitWithLatch {
-            mTestHelper.retryPeriodicallyWithLatch(it) {
+        testHelper.waitWithLatch {
+            testHelper.retryPeriodicallyWithLatch(it) {
                 val prAlicePOV = aliceVerificationService.getExistingVerificationRequests(bobSession.myUserId).firstOrNull()
                 requestID = prAlicePOV?.transactionId
                 Log.v("TEST", "== alicePOV is $prAlicePOV")
@@ -564,8 +564,8 @@ class SASTest : InstrumentedTest {
 
         Log.v("TEST", "== requestID is $requestID")
 
-        mTestHelper.waitWithLatch {
-            mTestHelper.retryPeriodicallyWithLatch(it) {
+        testHelper.waitWithLatch {
+            testHelper.retryPeriodicallyWithLatch(it) {
                 val prBobPOV = bobVerificationService.getExistingVerificationRequests(aliceSession.myUserId).firstOrNull()
                 Log.v("TEST", "== prBobPOV is $prBobPOV")
                 prBobPOV?.transactionId == requestID
@@ -579,8 +579,8 @@ class SASTest : InstrumentedTest {
         )
 
         // wait for alice to get the ready
-        mTestHelper.waitWithLatch {
-            mTestHelper.retryPeriodicallyWithLatch(it) {
+        testHelper.waitWithLatch {
+            testHelper.retryPeriodicallyWithLatch(it) {
                 val prAlicePOV = aliceVerificationService.getExistingVerificationRequests(bobSession.myUserId).firstOrNull()
                 Log.v("TEST", "== prAlicePOV is $prAlicePOV")
                 prAlicePOV?.transactionId == requestID && prAlicePOV?.isReady != null
@@ -606,22 +606,22 @@ class SASTest : InstrumentedTest {
         var alicePovTx: SasVerificationTransaction?
         var bobPovTx: SasVerificationTransaction?
 
-        mTestHelper.waitWithLatch {
-            mTestHelper.retryPeriodicallyWithLatch(it) {
+        testHelper.waitWithLatch {
+            testHelper.retryPeriodicallyWithLatch(it) {
                 alicePovTx = aliceVerificationService.getExistingTransaction(bobSession.myUserId, requestID!!) as? SasVerificationTransaction
                 Log.v("TEST", "== alicePovTx is $alicePovTx")
                 alicePovTx?.state == VerificationTxState.ShortCodeReady
             }
         }
         // wait for alice to get the ready
-        mTestHelper.waitWithLatch {
-            mTestHelper.retryPeriodicallyWithLatch(it) {
+        testHelper.waitWithLatch {
+            testHelper.retryPeriodicallyWithLatch(it) {
                 bobPovTx = bobVerificationService.getExistingTransaction(aliceSession.myUserId, requestID!!) as? SasVerificationTransaction
                 Log.v("TEST", "== bobPovTx is $bobPovTx")
                 bobPovTx?.state == VerificationTxState.ShortCodeReady
             }
         }
 
-        cryptoTestData.cleanUp(mTestHelper)
+        cryptoTestData.cleanUp(testHelper)
     }
 }
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/qrcode/VerificationTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/qrcode/VerificationTest.kt
index 397f7f9441bf467d3711a06d665bed4d9639d492..36306aa383f59dfb96b1f5d28b2b7204e8def41a 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/qrcode/VerificationTest.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/qrcode/VerificationTest.kt
@@ -40,8 +40,8 @@ import kotlin.coroutines.resume
 @RunWith(AndroidJUnit4::class)
 @FixMethodOrder(MethodSorters.JVM)
 class VerificationTest : InstrumentedTest {
-    private val mTestHelper = CommonTestHelper(context())
-    private val mCryptoTestHelper = CryptoTestHelper(mTestHelper)
+    private val testHelper = CommonTestHelper(context())
+    private val cryptoTestHelper = CryptoTestHelper(testHelper)
 
     data class ExpectedResult(
             val sasIsSupported: Boolean = false,
@@ -155,12 +155,12 @@ class VerificationTest : InstrumentedTest {
                        bobSupportedMethods: List<VerificationMethod>,
                        expectedResultForAlice: ExpectedResult,
                        expectedResultForBob: ExpectedResult) {
-        val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
+        val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
 
         val aliceSession = cryptoTestData.firstSession
         val bobSession = cryptoTestData.secondSession!!
 
-        mTestHelper.doSync<Unit> { callback ->
+        testHelper.doSync<Unit> { callback ->
             aliceSession.cryptoService().crossSigningService()
                     .initializeCrossSigning(
                             object : UserInteractiveAuthInterceptor {
@@ -176,7 +176,7 @@ class VerificationTest : InstrumentedTest {
                             }, callback)
         }
 
-        mTestHelper.doSync<Unit> { callback ->
+        testHelper.doSync<Unit> { callback ->
             bobSession.cryptoService().crossSigningService()
                     .initializeCrossSigning(
                             object : UserInteractiveAuthInterceptor {
@@ -234,7 +234,7 @@ class VerificationTest : InstrumentedTest {
         val bobUserId = bobSession.myUserId
         // Step 1: Alice starts a verification request
         aliceVerificationService.requestKeyVerificationInDMs(aliceSupportedMethods, bobUserId, cryptoTestData.roomId)
-        mTestHelper.await(latch)
+        testHelper.await(latch)
 
         aliceReadyPendingVerificationRequest!!.let { pr ->
             pr.isSasSupported() shouldBe expectedResultForAlice.sasIsSupported
@@ -248,6 +248,6 @@ class VerificationTest : InstrumentedTest {
             pr.otherCanScanQrCode() shouldBe expectedResultForBob.otherCanScanQrCode
         }
 
-        cryptoTestData.cleanUp(mTestHelper)
+        cryptoTestData.cleanUp(testHelper)
     }
 }
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/RoomDataHelper.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/RoomDataHelper.kt
index 1adf31be5f16bba4b437d83efdf2f9fc09682003..8a4429db456b7845f380ade3c3e5697718ec2b7e 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/RoomDataHelper.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/RoomDataHelper.kt
@@ -45,7 +45,7 @@ object RoomDataHelper {
                                 content: Content? = null,
                                 prevContent: Content? = null,
                                 sender: String = FAKE_TEST_SENDER,
-                                stateKey: String = FAKE_TEST_SENDER
+                                stateKey: String? = null
     ): Event {
         return Event(
                 type = type,
@@ -64,6 +64,6 @@ object RoomDataHelper {
 
     private fun createFakeRoomMemberEvent(): Event {
         val roomMember = RoomMemberContent(Membership.JOIN, "Fake name #${Random.nextLong()}").toContent()
-        return createFakeEvent(EventType.STATE_ROOM_MEMBER, roomMember)
+        return createFakeEvent(EventType.STATE_ROOM_MEMBER, roomMember, stateKey = FAKE_TEST_SENDER)
     }
 }
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineForwardPaginationTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineForwardPaginationTest.kt
index dfa6ec10ae03c8acf1fbab118a1811aafd1534b7..bc9722c922b89d1c78a1095faa64297d728991bd 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineForwardPaginationTest.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineForwardPaginationTest.kt
@@ -65,14 +65,8 @@ class TimelineForwardPaginationTest : InstrumentedTest {
                 message,
                 numberOfMessagesToSend)
 
-        // Alice clear the cache
-        commonTestHelper.runBlockingTest {
-            aliceSession.clearCache()
-        }
-
-        // And restarts the sync
-        aliceSession.startSync(true)
-
+        // Alice clear the cache and restart the sync
+        commonTestHelper.clearCacheAndSync(aliceSession)
         val aliceTimeline = roomFromAlicePOV.createTimeline(null, TimelineSettings(30))
         aliceTimeline.start()
 
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/search/SearchMessagesTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/search/SearchMessagesTest.kt
index 1baf490dfc70852ebe41085f4697799e8e542795..45e4b53c77f39c0e82d83cdc61224359e38b1c8d 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/search/SearchMessagesTest.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/search/SearchMessagesTest.kt
@@ -24,14 +24,10 @@ import org.junit.runners.JUnit4
 import org.junit.runners.MethodSorters
 import org.matrix.android.sdk.InstrumentedTest
 import org.matrix.android.sdk.api.extensions.orFalse
-import org.matrix.android.sdk.api.session.events.model.toModel
-import org.matrix.android.sdk.api.session.room.model.message.MessageContent
-import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings
 import org.matrix.android.sdk.api.session.search.SearchResult
 import org.matrix.android.sdk.common.CommonTestHelper
 import org.matrix.android.sdk.common.CryptoTestData
 import org.matrix.android.sdk.common.CryptoTestHelper
-import java.util.concurrent.CountDownLatch
 
 @RunWith(JUnit4::class)
 @FixMethodOrder(MethodSorters.JVM)
@@ -84,24 +80,12 @@ class SearchMessagesTest : InstrumentedTest {
         val aliceSession = cryptoTestData.firstSession
         val aliceRoomId = cryptoTestData.roomId
         val roomFromAlicePOV = aliceSession.getRoom(aliceRoomId)!!
-        val aliceTimeline = roomFromAlicePOV.createTimeline(null, TimelineSettings(10))
-        aliceTimeline.start()
-
-        val lock = CountDownLatch(1)
-
-        val eventListener = commonTestHelper.createEventListener(lock) { snapshot ->
-            snapshot.count { it.root.content.toModel<MessageContent>()?.body?.startsWith(MESSAGE).orFalse() } == 2
-        }
-
-        aliceTimeline.addListener(eventListener)
 
         commonTestHelper.sendTextMessage(
                 roomFromAlicePOV,
                 MESSAGE,
                 2)
 
-        commonTestHelper.await(lock)
-
         val data = commonTestHelper.runBlockingTest {
             block.invoke(cryptoTestData)
         }
@@ -114,7 +98,6 @@ class SearchMessagesTest : InstrumentedTest {
                         }.orFalse()
         )
 
-        aliceTimeline.removeAllListeners()
         cryptoTestData.cleanUp(commonTestHelper)
     }
 }
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceCreationTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceCreationTest.kt
index 5911414c2583db8a155c81a3e4c1bc299a5b67f2..d7be19295cb2e0ad4576adbee5a3c236cb8ccea6 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceCreationTest.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceCreationTest.kt
@@ -16,9 +16,7 @@
 
 package org.matrix.android.sdk.session.space
 
-import kotlinx.coroutines.GlobalScope
 import kotlinx.coroutines.delay
-import kotlinx.coroutines.launch
 import kotlinx.coroutines.runBlocking
 import org.junit.Assert.assertEquals
 import org.junit.Assert.assertNotNull
@@ -50,18 +48,15 @@ class SpaceCreationTest : InstrumentedTest {
     private val commonTestHelper = CommonTestHelper(context())
 
     @Test
-    @Suppress("EXPERIMENTAL_API_USAGE")
     fun createSimplePublicSpace() {
         val session = commonTestHelper.createAccount("Hubble", SessionTestParams(true))
         val roomName = "My Space"
         val topic = "A public space for test"
         var spaceId: String = ""
         commonTestHelper.waitWithLatch {
-            GlobalScope.launch {
-                spaceId = session.spaceService().createSpace(roomName, topic, null, true)
-                // wait a bit to let the summary update it self :/
-                it.countDown()
-            }
+            spaceId = session.spaceService().createSpace(roomName, topic, null, true)
+            // wait a bit to let the summary update it self :/
+            it.countDown()
         }
 
         val syncedSpace = session.spaceService().getSpace(spaceId)
@@ -134,7 +129,6 @@ class SpaceCreationTest : InstrumentedTest {
     }
 
     @Test
-    @Suppress("EXPERIMENTAL_API_USAGE")
     fun testSimplePublicSpaceWithChildren() {
         val aliceSession = commonTestHelper.createAccount("alice", SessionTestParams(true))
         val bobSession = commonTestHelper.createAccount("bob", SessionTestParams(true))
@@ -148,50 +142,40 @@ class SpaceCreationTest : InstrumentedTest {
         // create a room
         var firstChild: String? = null
         commonTestHelper.waitWithLatch {
-            GlobalScope.launch {
-                firstChild = aliceSession.createRoom(CreateRoomParams().apply {
-                    this.name = "FirstRoom"
-                    this.topic = "Description of first room"
-                    this.preset = CreateRoomPreset.PRESET_PUBLIC_CHAT
-                })
-                it.countDown()
-            }
+            firstChild = aliceSession.createRoom(CreateRoomParams().apply {
+                this.name = "FirstRoom"
+                this.topic = "Description of first room"
+                this.preset = CreateRoomPreset.PRESET_PUBLIC_CHAT
+            })
+            it.countDown()
         }
 
         commonTestHelper.waitWithLatch {
-            GlobalScope.launch {
-                syncedSpace?.addChildren(firstChild!!, listOf(aliceSession.sessionParams.homeServerHost ?: ""), "a", suggested = true)
-                it.countDown()
-            }
+            syncedSpace?.addChildren(firstChild!!, listOf(aliceSession.sessionParams.homeServerHost ?: ""), "a", suggested = true)
+            it.countDown()
         }
 
         var secondChild: String? = null
         commonTestHelper.waitWithLatch {
-            GlobalScope.launch {
-                secondChild = aliceSession.createRoom(CreateRoomParams().apply {
-                    this.name = "SecondRoom"
-                    this.topic = "Description of second room"
-                    this.preset = CreateRoomPreset.PRESET_PUBLIC_CHAT
-                })
-                it.countDown()
-            }
+            secondChild = aliceSession.createRoom(CreateRoomParams().apply {
+                this.name = "SecondRoom"
+                this.topic = "Description of second room"
+                this.preset = CreateRoomPreset.PRESET_PUBLIC_CHAT
+            })
+            it.countDown()
         }
 
         commonTestHelper.waitWithLatch {
-            GlobalScope.launch {
-                syncedSpace?.addChildren(secondChild!!, listOf(aliceSession.sessionParams.homeServerHost ?: ""), "b", suggested = true)
-                it.countDown()
-            }
+            syncedSpace?.addChildren(secondChild!!, listOf(aliceSession.sessionParams.homeServerHost ?: ""), "b", suggested = true)
+            it.countDown()
         }
 
         // Try to join from bob, it's a public space no need to invite
         var joinResult: JoinSpaceResult? = null
         commonTestHelper.waitWithLatch {
-            GlobalScope.launch {
-                joinResult = bobSession.spaceService().joinSpace(spaceId)
-                // wait a bit to let the summary update it self :/
-                it.countDown()
-            }
+            joinResult = bobSession.spaceService().joinSpace(spaceId)
+            // wait a bit to let the summary update it self :/
+            it.countDown()
         }
 
         assertEquals(JoinSpaceResult.Success, joinResult)
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceHierarchyTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceHierarchyTest.kt
index 436daf001be4f178fc69da3a04d46ce4566e70fa..1c38edbbd918df90398584d46af6549d9c2fe64b 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceHierarchyTest.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceHierarchyTest.kt
@@ -18,9 +18,6 @@ package org.matrix.android.sdk.session.space
 
 import android.util.Log
 import androidx.lifecycle.Observer
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.GlobalScope
-import kotlinx.coroutines.launch
 import kotlinx.coroutines.runBlocking
 import org.junit.Assert.assertEquals
 import org.junit.Assert.assertNotNull
@@ -56,43 +53,34 @@ class SpaceHierarchyTest : InstrumentedTest {
     private val commonTestHelper = CommonTestHelper(context())
 
     @Test
-    @Suppress("EXPERIMENTAL_API_USAGE")
     fun createCanonicalChildRelation() {
         val session = commonTestHelper.createAccount("John", SessionTestParams(true))
         val spaceName = "My Space"
         val topic = "A public space for test"
-        var spaceId: String = ""
+        var spaceId = ""
         commonTestHelper.waitWithLatch {
-            GlobalScope.launch {
-                spaceId = session.spaceService().createSpace(spaceName, topic, null, true)
-                it.countDown()
-            }
+            spaceId = session.spaceService().createSpace(spaceName, topic, null, true)
+            it.countDown()
         }
 
         val syncedSpace = session.spaceService().getSpace(spaceId)
 
-        var roomId: String = ""
+        var roomId = ""
         commonTestHelper.waitWithLatch {
-            GlobalScope.launch {
-                roomId = session.createRoom(CreateRoomParams().apply { name = "General" })
-                it.countDown()
-            }
+            roomId = session.createRoom(CreateRoomParams().apply { name = "General" })
+            it.countDown()
         }
 
         val viaServers = listOf(session.sessionParams.homeServerHost ?: "")
 
         commonTestHelper.waitWithLatch {
-            GlobalScope.launch {
-                syncedSpace!!.addChildren(roomId, viaServers, null, true)
-                it.countDown()
-            }
+            syncedSpace!!.addChildren(roomId, viaServers, null, true)
+            it.countDown()
         }
 
         commonTestHelper.waitWithLatch {
-            GlobalScope.launch {
-                session.spaceService().setSpaceParent(roomId, spaceId, true, viaServers)
-                it.countDown()
-            }
+            session.spaceService().setSpaceParent(roomId, spaceId, true, viaServers)
+            it.countDown()
         }
 
         Thread.sleep(9000)
@@ -181,7 +169,6 @@ class SpaceHierarchyTest : InstrumentedTest {
 //    }
 
     @Test
-    @Suppress("EXPERIMENTAL_API_USAGE")
     fun testFilteringBySpace() {
         val session = commonTestHelper.createAccount("John", SessionTestParams(true))
 
@@ -205,29 +192,23 @@ class SpaceHierarchyTest : InstrumentedTest {
         val spaceA = session.spaceService().getSpace(spaceAInfo.spaceId)
         val viaServers = listOf(session.sessionParams.homeServerHost ?: "")
         commonTestHelper.waitWithLatch {
-            GlobalScope.launch {
-                spaceA!!.addChildren(spaceCInfo.spaceId, viaServers, null, true)
-                session.spaceService().setSpaceParent(spaceCInfo.spaceId, spaceAInfo.spaceId, true, viaServers)
-                it.countDown()
-            }
+            spaceA!!.addChildren(spaceCInfo.spaceId, viaServers, null, true)
+            session.spaceService().setSpaceParent(spaceCInfo.spaceId, spaceAInfo.spaceId, true, viaServers)
+            it.countDown()
         }
 
         // Create orphan rooms
 
         var orphan1 = ""
         commonTestHelper.waitWithLatch {
-            GlobalScope.launch {
-                orphan1 = session.createRoom(CreateRoomParams().apply { name = "O1" })
-                it.countDown()
-            }
+            orphan1 = session.createRoom(CreateRoomParams().apply { name = "O1" })
+            it.countDown()
         }
 
         var orphan2 = ""
         commonTestHelper.waitWithLatch {
-            GlobalScope.launch {
-                orphan2 = session.createRoom(CreateRoomParams().apply { name = "O2" })
-                it.countDown()
-            }
+            orphan2 = session.createRoom(CreateRoomParams().apply { name = "O2" })
+            it.countDown()
         }
 
         val allRooms = session.getRoomSummaries(roomSummaryQueryParams { excludeType = listOf(RoomType.SPACE) })
@@ -250,11 +231,9 @@ class SpaceHierarchyTest : InstrumentedTest {
 
         // Add a non canonical child and check that it does not appear as orphan
         commonTestHelper.waitWithLatch {
-            GlobalScope.launch {
-                val a3 = session.createRoom(CreateRoomParams().apply { name = "A3" })
-                spaceA!!.addChildren(a3, viaServers, null, false)
-                it.countDown()
-            }
+            val a3 = session.createRoom(CreateRoomParams().apply { name = "A3" })
+            spaceA!!.addChildren(a3, viaServers, null, false)
+            it.countDown()
         }
 
         Thread.sleep(2_000)
@@ -265,7 +244,6 @@ class SpaceHierarchyTest : InstrumentedTest {
     }
 
     @Test
-    @Suppress("EXPERIMENTAL_API_USAGE")
     fun testBreakCycle() {
         val session = commonTestHelper.createAccount("John", SessionTestParams(true))
 
@@ -283,20 +261,16 @@ class SpaceHierarchyTest : InstrumentedTest {
         val spaceA = session.spaceService().getSpace(spaceAInfo.spaceId)
         val viaServers = listOf(session.sessionParams.homeServerHost ?: "")
         commonTestHelper.waitWithLatch {
-            GlobalScope.launch {
-                spaceA!!.addChildren(spaceCInfo.spaceId, viaServers, null, true)
-                session.spaceService().setSpaceParent(spaceCInfo.spaceId, spaceAInfo.spaceId, true, viaServers)
-                it.countDown()
-            }
+            spaceA!!.addChildren(spaceCInfo.spaceId, viaServers, null, true)
+            session.spaceService().setSpaceParent(spaceCInfo.spaceId, spaceAInfo.spaceId, true, viaServers)
+            it.countDown()
         }
 
         // add back A as subspace of C
         commonTestHelper.waitWithLatch {
-            GlobalScope.launch {
-                val spaceC = session.spaceService().getSpace(spaceCInfo.spaceId)
-                spaceC!!.addChildren(spaceAInfo.spaceId, viaServers, null, true)
-                it.countDown()
-            }
+            val spaceC = session.spaceService().getSpace(spaceCInfo.spaceId)
+            spaceC!!.addChildren(spaceAInfo.spaceId, viaServers, null, true)
+            it.countDown()
         }
 
         Thread.sleep(1000)
@@ -313,7 +287,6 @@ class SpaceHierarchyTest : InstrumentedTest {
     }
 
     @Test
-    @Suppress("EXPERIMENTAL_API_USAGE")
     fun testLiveFlatChildren() {
         val session = commonTestHelper.createAccount("John", SessionTestParams(true))
 
@@ -336,12 +309,14 @@ class SpaceHierarchyTest : InstrumentedTest {
             session.spaceService().setSpaceParent(spaceBInfo.spaceId, spaceAInfo.spaceId, true, viaServers)
         }
 
-        val flatAChildren = runBlocking(Dispatchers.Main) {
-            session.getFlattenRoomSummaryChildrenOfLive(spaceAInfo.spaceId)
-        }
+        val spaceCInfo = createPublicSpace(session, "SpaceC", listOf(
+                Triple("C1", true /*auto-join*/, true/*canonical*/),
+                Triple("C2", true, true)
+        ))
 
         commonTestHelper.waitWithLatch { latch ->
 
+            val flatAChildren = session.getFlattenRoomSummaryChildrenOfLive(spaceAInfo.spaceId)
             val childObserver = object : Observer<List<RoomSummary>> {
                 override fun onChanged(children: List<RoomSummary>?) {
 //                    Log.d("## TEST", "Space A flat children update : ${children?.map { it.name }}")
@@ -354,20 +329,13 @@ class SpaceHierarchyTest : InstrumentedTest {
                 }
             }
 
-            val spaceCInfo = createPublicSpace(session, "SpaceC", listOf(
-                    Triple("C1", true /*auto-join*/, true/*canonical*/),
-                    Triple("C2", true, true)
-            ))
-
             // add C as subspace of B
-            runBlocking {
-                val spaceB = session.spaceService().getSpace(spaceBInfo.spaceId)
-                spaceB!!.addChildren(spaceCInfo.spaceId, viaServers, null, true)
-            }
+            val spaceB = session.spaceService().getSpace(spaceBInfo.spaceId)
+            spaceB!!.addChildren(spaceCInfo.spaceId, viaServers, null, true)
 
             // C1 and C2 should be in flatten child of A now
 
-            GlobalScope.launch(Dispatchers.Main) { flatAChildren.observeForever(childObserver) }
+            flatAChildren.observeForever(childObserver)
         }
 
         // Test part one of the rooms
@@ -376,7 +344,7 @@ class SpaceHierarchyTest : InstrumentedTest {
         val bRoom = session.getRoom(bRoomId)
 
         commonTestHelper.waitWithLatch { latch ->
-
+            val flatAChildren = session.getFlattenRoomSummaryChildrenOfLive(spaceAInfo.spaceId)
             val childObserver = object : Observer<List<RoomSummary>> {
                 override fun onChanged(children: List<RoomSummary>?) {
                     System.out.println("## TEST | Space A flat children update : ${children?.map { it.name }}")
@@ -389,13 +357,10 @@ class SpaceHierarchyTest : InstrumentedTest {
             }
 
             // part from b room
-            runBlocking {
-                bRoom!!.leave(null)
-            }
+            bRoom!!.leave(null)
             // The room should have disapear from flat children
-            GlobalScope.launch(Dispatchers.Main) { flatAChildren.observeForever(childObserver) }
+            flatAChildren.observeForever(childObserver)
         }
-
         commonTestHelper.signOutAndClose(session)
     }
 
@@ -404,94 +369,66 @@ class SpaceHierarchyTest : InstrumentedTest {
             val roomIds: List<String>
     )
 
-    @Suppress("EXPERIMENTAL_API_USAGE")
     private fun createPublicSpace(session: Session,
                                   spaceName: String,
                                   childInfo: List<Triple<String, Boolean, Boolean?>>
             /** Name, auto-join, canonical*/
     ): TestSpaceCreationResult {
         var spaceId = ""
-        commonTestHelper.waitWithLatch {
-            GlobalScope.launch {
-                spaceId = session.spaceService().createSpace(spaceName, "Test Topic", null, true)
-                it.countDown()
-            }
-        }
-
-        val syncedSpace = session.spaceService().getSpace(spaceId)
-        val viaServers = listOf(session.sessionParams.homeServerHost ?: "")
-
-        val roomIds =
-                childInfo.map { entry ->
-                    var roomId = ""
-                    commonTestHelper.waitWithLatch {
-                        GlobalScope.launch {
-                            roomId = session.createRoom(CreateRoomParams().apply { name = entry.first })
-                            it.countDown()
-                        }
-                    }
-                    roomId
-                }
+        var roomIds: List<String> = emptyList()
+        commonTestHelper.waitWithLatch { latch ->
+            spaceId = session.spaceService().createSpace(spaceName, "Test Topic", null, true)
+            val syncedSpace = session.spaceService().getSpace(spaceId)
+            val viaServers = listOf(session.sessionParams.homeServerHost ?: "")
 
-        roomIds.forEachIndexed { index, roomId ->
-            runBlocking {
+            roomIds = childInfo.map { entry ->
+                session.createRoom(CreateRoomParams().apply { name = entry.first })
+            }
+            roomIds.forEachIndexed { index, roomId ->
                 syncedSpace!!.addChildren(roomId, viaServers, null, childInfo[index].second)
                 val canonical = childInfo[index].third
                 if (canonical != null) {
                     session.spaceService().setSpaceParent(roomId, spaceId, canonical, viaServers)
                 }
             }
+            latch.countDown()
         }
         return TestSpaceCreationResult(spaceId, roomIds)
     }
 
-    @Suppress("EXPERIMENTAL_API_USAGE")
     private fun createPrivateSpace(session: Session,
                                    spaceName: String,
                                    childInfo: List<Triple<String, Boolean, Boolean?>>
             /** Name, auto-join, canonical*/
     ): TestSpaceCreationResult {
         var spaceId = ""
-        commonTestHelper.waitWithLatch {
-            GlobalScope.launch {
-                spaceId = session.spaceService().createSpace(spaceName, "My Private Space", null, false)
-                it.countDown()
-            }
-        }
-
-        val syncedSpace = session.spaceService().getSpace(spaceId)
-        val viaServers = listOf(session.sessionParams.homeServerHost ?: "")
-
-        val roomIds =
-                childInfo.map { entry ->
-                    var roomId = ""
-                    commonTestHelper.waitWithLatch {
-                        GlobalScope.launch {
-                            val homeServerCapabilities = session
-                                    .getHomeServerCapabilities()
-                            roomId = session.createRoom(CreateRoomParams().apply {
-                                name = entry.first
-                                this.featurePreset = RestrictedRoomPreset(
-                                        homeServerCapabilities,
-                                        listOf(
-                                                RoomJoinRulesAllowEntry.restrictedToRoom(spaceId)
-                                        )
-                                )
-                            })
-                            it.countDown()
-                        }
+        var roomIds: List<String> = emptyList()
+        commonTestHelper.waitWithLatch { latch ->
+            spaceId = session.spaceService().createSpace(spaceName, "My Private Space", null, false)
+            val syncedSpace = session.spaceService().getSpace(spaceId)
+            val viaServers = listOf(session.sessionParams.homeServerHost ?: "")
+            roomIds =
+                    childInfo.map { entry ->
+                        val homeServerCapabilities = session
+                                .getHomeServerCapabilities()
+                        session.createRoom(CreateRoomParams().apply {
+                            name = entry.first
+                            this.featurePreset = RestrictedRoomPreset(
+                                    homeServerCapabilities,
+                                    listOf(
+                                            RoomJoinRulesAllowEntry.restrictedToRoom(spaceId)
+                                    )
+                            )
+                        })
                     }
-                    roomId
-                }
-
-        roomIds.forEachIndexed { index, roomId ->
-            runBlocking {
+            roomIds.forEachIndexed { index, roomId ->
                 syncedSpace!!.addChildren(roomId, viaServers, null, childInfo[index].second)
                 val canonical = childInfo[index].third
                 if (canonical != null) {
                     session.spaceService().setSpaceParent(roomId, spaceId, canonical, viaServers)
                 }
             }
+            latch.countDown()
         }
         return TestSpaceCreationResult(spaceId, roomIds)
     }
@@ -559,11 +496,9 @@ class SpaceHierarchyTest : InstrumentedTest {
 
         var bobRoomId = ""
         commonTestHelper.waitWithLatch {
-            GlobalScope.launch {
-                bobRoomId = bobSession.createRoom(CreateRoomParams().apply { name = "A Bob Room" })
-                bobSession.getRoom(bobRoomId)!!.invite(aliceSession.myUserId)
-                it.countDown()
-            }
+            bobRoomId = bobSession.createRoom(CreateRoomParams().apply { name = "A Bob Room" })
+            bobSession.getRoom(bobRoomId)!!.invite(aliceSession.myUserId)
+            it.countDown()
         }
 
         commonTestHelper.runBlockingTest {
@@ -577,10 +512,8 @@ class SpaceHierarchyTest : InstrumentedTest {
         }
 
         commonTestHelper.waitWithLatch {
-            GlobalScope.launch {
-                bobSession.spaceService().setSpaceParent(bobRoomId, spaceAInfo.spaceId, false, listOf(bobSession.sessionParams.homeServerHost ?: ""))
-                it.countDown()
-            }
+            bobSession.spaceService().setSpaceParent(bobRoomId, spaceAInfo.spaceId, false, listOf(bobSession.sessionParams.homeServerHost ?: ""))
+            it.countDown()
         }
 
         commonTestHelper.waitWithLatch { latch ->
@@ -600,19 +533,17 @@ class SpaceHierarchyTest : InstrumentedTest {
         // Let's now try to make alice admin of the room
 
         commonTestHelper.waitWithLatch {
-            GlobalScope.launch {
-                val room = bobSession.getRoom(bobRoomId)!!
-                val currentPLContent = room
-                        .getStateEvent(EventType.STATE_ROOM_POWER_LEVELS)
-                        ?.let { it.content.toModel<PowerLevelsContent>() }
+            val room = bobSession.getRoom(bobRoomId)!!
+            val currentPLContent = room
+                    .getStateEvent(EventType.STATE_ROOM_POWER_LEVELS)
+                    ?.let { it.content.toModel<PowerLevelsContent>() }
 
-                val newPowerLevelsContent = currentPLContent
-                        ?.setUserPowerLevel(aliceSession.myUserId, Role.Admin.value)
-                        ?.toContent()
+            val newPowerLevelsContent = currentPLContent
+                    ?.setUserPowerLevel(aliceSession.myUserId, Role.Admin.value)
+                    ?.toContent()
 
-                room.sendStateEvent(EventType.STATE_ROOM_POWER_LEVELS, null, newPowerLevelsContent!!)
-                it.countDown()
-            }
+            room.sendStateEvent(EventType.STATE_ROOM_POWER_LEVELS, null, newPowerLevelsContent!!)
+            it.countDown()
         }
 
         commonTestHelper.waitWithLatch { latch ->
@@ -627,10 +558,8 @@ class SpaceHierarchyTest : InstrumentedTest {
         }
 
         commonTestHelper.waitWithLatch {
-            GlobalScope.launch {
-                aliceSession.spaceService().setSpaceParent(bobRoomId, spaceAInfo.spaceId, false, listOf(bobSession.sessionParams.homeServerHost ?: ""))
-                it.countDown()
-            }
+            aliceSession.spaceService().setSpaceParent(bobRoomId, spaceAInfo.spaceId, false, listOf(bobSession.sessionParams.homeServerHost ?: ""))
+            it.countDown()
         }
 
         commonTestHelper.waitWithLatch { latch ->
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/Matrix.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/Matrix.kt
index 8a4526a5e123731e6ed4efdbe997af2bf9a66068..901ba75d169738aed6cc8d97fbf73d9dfdd88b8d 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/Matrix.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/Matrix.kt
@@ -20,6 +20,7 @@ import android.content.Context
 import androidx.lifecycle.ProcessLifecycleOwner
 import androidx.work.Configuration
 import androidx.work.WorkManager
+import androidx.work.WorkerFactory
 import com.zhuinden.monarchy.Monarchy
 import org.matrix.android.sdk.BuildConfig
 import org.matrix.android.sdk.api.auth.AuthenticationService
@@ -33,6 +34,7 @@ import org.matrix.android.sdk.internal.di.DaggerMatrixComponent
 import org.matrix.android.sdk.internal.network.ApiInterceptor
 import org.matrix.android.sdk.internal.network.UserAgentHolder
 import org.matrix.android.sdk.internal.util.BackgroundDetectionObserver
+import org.matrix.android.sdk.internal.worker.MatrixWorkerFactory
 import org.matrix.olm.OlmManager
 import java.util.concurrent.Executors
 import java.util.concurrent.atomic.AtomicBoolean
@@ -53,12 +55,17 @@ class Matrix private constructor(context: Context, matrixConfiguration: MatrixCo
     @Inject internal lateinit var sessionManager: SessionManager
     @Inject internal lateinit var homeServerHistoryService: HomeServerHistoryService
     @Inject internal lateinit var apiInterceptor: ApiInterceptor
+    @Inject internal lateinit var matrixWorkerFactory: MatrixWorkerFactory
 
     init {
         Monarchy.init(context)
         DaggerMatrixComponent.factory().create(context, matrixConfiguration).inject(this)
         if (context.applicationContext !is Configuration.Provider) {
-            WorkManager.initialize(context, Configuration.Builder().setExecutor(Executors.newCachedThreadPool()).build())
+            val configuration = Configuration.Builder()
+                    .setExecutor(Executors.newCachedThreadPool())
+                    .setWorkerFactory(matrixWorkerFactory)
+                    .build()
+            WorkManager.initialize(context, configuration)
         }
         ProcessLifecycleOwner.get().lifecycle.addObserver(backgroundDetectionObserver)
     }
@@ -77,6 +84,8 @@ class Matrix private constructor(context: Context, matrixConfiguration: MatrixCo
         return legacySessionImporter
     }
 
+    fun workerFactory(): WorkerFactory = matrixWorkerFactory
+
     fun registerApiInterceptorListener(path: ApiPath, listener: ApiInterceptorListener) {
         apiInterceptor.addListener(path, listener)
     }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/EventType.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/EventType.kt
index a39ca5b4f487ca55dd9440c8d786944f20e0a6f3..0c77b574e7266dbc71a36c9f615428c3fbed0ea2 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/EventType.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/EventType.kt
@@ -104,6 +104,8 @@ object EventType {
 
     // Poll
     const val POLL_START = "org.matrix.msc3381.poll.start"
+    const val POLL_RESPONSE = "org.matrix.msc3381.poll.response"
+    const val POLL_END = "org.matrix.msc3381.poll.end"
 
     // Unwedging
     internal const val DUMMY = "m.dummy"
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/PollSummaryContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/PollSummaryContent.kt
index 844ef6c1c8971bbb4c95b9e0a53d4b18f0916a24..f1e4354314d6cd402e9c94527668fbe14eabdb59 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/PollSummaryContent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/PollSummaryContent.kt
@@ -24,25 +24,24 @@ import com.squareup.moshi.JsonClass
  */
 @JsonClass(generateAdapter = true)
 data class PollSummaryContent(
-        // Index of my vote
-        var myVote: Int? = null,
+        var myVote: String? = null,
         // Array of VoteInfo, list is constructed so that there is only one vote by user
         // And that optionIndex is valid
-        var votes: List<VoteInfo>? = null
-) {
-
-    fun voteCount(): Int {
-        return votes?.size ?: 0
-    }
+        var votes: List<VoteInfo>? = null,
+        var votesSummary: Map<String, VoteSummary>? = null,
+        var totalVotes: Int = 0,
+        var winnerVoteCount: Int = 0
+)
 
-    fun voteCountForOption(optionIndex: Int): Int {
-        return votes?.filter { it.optionIndex == optionIndex }?.count() ?: 0
-    }
-}
+@JsonClass(generateAdapter = true)
+data class VoteSummary(
+        val total: Int = 0,
+        val percentage: Double = 0.0
+)
 
 @JsonClass(generateAdapter = true)
 data class VoteInfo(
         val userId: String,
-        val optionIndex: Int,
+        val option: String,
         val voteTimestamp: Long
 )
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageEndPollContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageEndPollContent.kt
new file mode 100644
index 0000000000000000000000000000000000000000..491b71477e472619bdef14faf08b14c98cb9b432
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageEndPollContent.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2021 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.api.session.room.model.message
+
+import com.squareup.moshi.Json
+import com.squareup.moshi.JsonClass
+import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent
+
+/**
+ * Class representing the org.matrix.msc3381.poll.end event content
+ */
+@JsonClass(generateAdapter = true)
+data class MessageEndPollContent(
+        @Json(name = "m.relates_to") val relatesTo: RelationDefaultContent? = null
+)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageOptionsContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageOptionsContent.kt
deleted file mode 100644
index 7a1a99bd5fd2b53304e6fff87550a76ff0869fbd..0000000000000000000000000000000000000000
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageOptionsContent.kt
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright 2020 The Matrix.org Foundation C.I.C.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.matrix.android.sdk.api.session.room.model.message
-
-import com.squareup.moshi.Json
-import com.squareup.moshi.JsonClass
-import org.matrix.android.sdk.api.session.events.model.Content
-import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent
-
-// Possible values for optionType
-const val OPTION_TYPE_POLL = "org.matrix.poll"
-const val OPTION_TYPE_BUTTONS = "org.matrix.buttons"
-
-/**
- * Polls and bot buttons are m.room.message events with a msgtype of m.options,
- * Ref: https://github.com/matrix-org/matrix-doc/pull/2192
- */
-@JsonClass(generateAdapter = true)
-data class MessageOptionsContent(
-        @Json(name = MessageContent.MSG_TYPE_JSON_KEY) override val msgType: String = MessageType.MSGTYPE_OPTIONS,
-        @Json(name = "type") val optionType: String? = null,
-        @Json(name = "body") override val body: String,
-        @Json(name = "label") val label: String?,
-        @Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null,
-        @Json(name = "options") val options: List<OptionItem>? = null,
-        @Json(name = "m.new_content") override val newContent: Content? = null
-) : MessageContent
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessagePollContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessagePollContent.kt
index ef2fd1867a7a7ec4d7fadd94acfad7bc8fcff2d5..a4e131729066227a6655dd9f364ee333660ec94a 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessagePollContent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessagePollContent.kt
@@ -18,8 +18,18 @@ package org.matrix.android.sdk.api.session.room.model.message
 
 import com.squareup.moshi.Json
 import com.squareup.moshi.JsonClass
+import org.matrix.android.sdk.api.session.events.model.Content
+import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent
 
 @JsonClass(generateAdapter = true)
 data class MessagePollContent(
-    @Json(name = "org.matrix.msc3381.poll.start") val pollCreationInfo: PollCreationInfo? = null
-)
+        /**
+         * Local message type, not from server
+         */
+        @Transient
+        override val msgType: String = MessageType.MSGTYPE_POLL_START,
+        @Json(name = "body") override val body: String = "",
+        @Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null,
+        @Json(name = "m.new_content") override val newContent: Content? = null,
+        @Json(name = "org.matrix.msc3381.poll.start") val pollCreationInfo: PollCreationInfo? = null
+) : MessageContent
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessagePollResponseContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessagePollResponseContent.kt
index 9edfe118b09f27b31148ee5f30c4d28968c28629..f3b4e3dc23c1d1e8f0b9b1cdce9e59ed7f0ee415 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessagePollResponseContent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessagePollResponseContent.kt
@@ -21,13 +21,15 @@ import com.squareup.moshi.JsonClass
 import org.matrix.android.sdk.api.session.events.model.Content
 import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent
 
-/**
- * Ref: https://github.com/matrix-org/matrix-doc/pull/2192
- */
 @JsonClass(generateAdapter = true)
 data class MessagePollResponseContent(
-        @Json(name = MessageContent.MSG_TYPE_JSON_KEY) override val msgType: String = MessageType.MSGTYPE_RESPONSE,
-        @Json(name = "body") override val body: String,
+        /**
+         * Local message type, not from server
+         */
+        @Transient
+        override val msgType: String = MessageType.MSGTYPE_POLL_RESPONSE,
+        @Json(name = "body") override val body: String = "",
         @Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null,
-        @Json(name = "m.new_content") override val newContent: Content? = null
+        @Json(name = "m.new_content") override val newContent: Content? = null,
+        @Json(name = "org.matrix.msc3381.poll.response") val response: PollResponse? = null
 ) : MessageContent
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageType.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageType.kt
index 1e8959afc3d6c0bcbe82f4ab2c2e3ccbcabdc806..2a6138ae60ca4fb0c10855ad9aa6d0a1fec19aa0 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageType.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageType.kt
@@ -25,15 +25,18 @@ object MessageType {
     const val MSGTYPE_VIDEO = "m.video"
     const val MSGTYPE_LOCATION = "m.location"
     const val MSGTYPE_FILE = "m.file"
-    const val MSGTYPE_OPTIONS = "org.matrix.options"
-    const val MSGTYPE_RESPONSE = "org.matrix.response"
-    const val MSGTYPE_POLL_CLOSED = "org.matrix.poll_closed"
+
     const val MSGTYPE_VERIFICATION_REQUEST = "m.key.verification.request"
 
     // Add, in local, a fake message type in order to StickerMessage can inherit Message class
     // Because sticker isn't a message type but a event type without msgtype field
     const val MSGTYPE_STICKER_LOCAL = "org.matrix.android.sdk.sticker"
 
+    // Fake message types for poll events to be able to inherit them from MessageContent
+    // Because poll events are not message events and they don't hanve msgtype field
+    const val MSGTYPE_POLL_START = "org.matrix.android.sdk.poll.start"
+    const val MSGTYPE_POLL_RESPONSE = "org.matrix.android.sdk.poll.response"
+
     const val MSGTYPE_CONFETTI = "nic.custom.confetti"
     const val MSGTYPE_SNOWFALL = "io.element.effect.snowfall"
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/OptionItem.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/PollResponse.kt
similarity index 75%
rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/OptionItem.kt
rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/PollResponse.kt
index 625043df87496ef0836a562e29f98b57bc587b64..ddeec5cd5bc5877d0dcd9fb57c70746b69160792 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/OptionItem.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/PollResponse.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright 2020 The Matrix.org Foundation C.I.C.
+ * Copyright 2021 The Matrix.org Foundation C.I.C.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -19,11 +19,7 @@ package org.matrix.android.sdk.api.session.room.model.message
 import com.squareup.moshi.Json
 import com.squareup.moshi.JsonClass
 
-/**
- * Ref: https://github.com/matrix-org/matrix-doc/pull/2192
- */
 @JsonClass(generateAdapter = true)
-data class OptionItem(
-        @Json(name = "label") val label: String?,
-        @Json(name = "value") val value: String?
+data class PollResponse(
+        @Json(name = "answers") val answers: List<String>? = null
 )
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/SendService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/SendService.kt
index a2b38b66061b9ff683bc9c2e207c8ae5894c7a91..5b387c3413e876c6c14e9fea5ed5c418666a6ebc 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/SendService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/SendService.kt
@@ -91,11 +91,17 @@ interface SendService {
     /**
      * Method to send a poll response.
      * @param pollEventId the poll currently replied to
-     * @param optionIndex The reply index
-     * @param optionValue The option value (for compatibility)
+     * @param answerId The id of the answer
      * @return a [Cancelable]
      */
-    fun sendOptionsReply(pollEventId: String, optionIndex: Int, optionValue: String): Cancelable
+    fun voteToPoll(pollEventId: String, answerId: String): Cancelable
+
+    /**
+     * End a poll in the room.
+     * @param pollEventId event id of the poll
+     * @return a [Cancelable]
+     */
+    fun endPoll(pollEventId: String): Cancelable
 
     /**
      * Redact (delete) the given event.
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/summary/RoomSummaryConstants.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/summary/RoomSummaryConstants.kt
index ef6300eae2ac011939a0b384a8e319711aeeb87f..3bba2deae53019430464d9c80d81f1aad9186c7a 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/summary/RoomSummaryConstants.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/summary/RoomSummaryConstants.kt
@@ -32,6 +32,7 @@ object RoomSummaryConstants {
             EventType.CALL_ANSWER,
             EventType.ENCRYPTED,
             EventType.STICKER,
-            EventType.REACTION
+            EventType.REACTION,
+            EventType.POLL_START
     )
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEvent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEvent.kt
index 86cb10bfe80637c7ae58537d2850f62e91fa6bed..932439c81c6fa94c24874562add7da74edd62f79 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEvent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEvent.kt
@@ -27,6 +27,7 @@ import org.matrix.android.sdk.api.session.events.model.toModel
 import org.matrix.android.sdk.api.session.room.model.EventAnnotationsSummary
 import org.matrix.android.sdk.api.session.room.model.ReadReceipt
 import org.matrix.android.sdk.api.session.room.model.message.MessageContent
+import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent
 import org.matrix.android.sdk.api.session.room.model.message.MessageStickerContent
 import org.matrix.android.sdk.api.session.room.model.message.MessageTextContent
 import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent
@@ -126,10 +127,10 @@ fun TimelineEvent.getEditedEventId(): String? {
  * Get last MessageContent, after a possible edition
  */
 fun TimelineEvent.getLastMessageContent(): MessageContent? {
-    return if (root.getClearType() == EventType.STICKER) {
-        root.getClearContent().toModel<MessageStickerContent>()
-    } else {
-        (annotations?.editSummary?.latestContent ?: root.getClearContent()).toModel()
+    return when (root.getClearType()) {
+        EventType.STICKER    -> root.getClearContent().toModel<MessageStickerContent>()
+        EventType.POLL_START -> root.getClearContent().toModel<MessagePollContent>()
+        else                 -> (annotations?.editSummary?.latestContent ?: root.getClearContent()).toModel()
     }
 }
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/model/SyncResponse.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/model/SyncResponse.kt
index 876e99da636b46d5ba5055a6fdd9aa93ba5b0948..d7dff72288f8ad64d513f3ac5b7e336f2e63b85a 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/model/SyncResponse.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/model/SyncResponse.kt
@@ -58,6 +58,14 @@ data class SyncResponse(
         @Json(name = "device_one_time_keys_count")
         val deviceOneTimeKeysCount: DeviceOneTimeKeysCountSyncResponse? = null,
 
+        /**
+         * The key algorithms for which the server has an unused fallback key for the device.
+         * If the client wants the server to have a fallback key for a given key algorithm,
+         * but that algorithm is not listed in device_unused_fallback_key_types, the client will upload a new key.
+         */
+        @Json(name = "org.matrix.msc2732.device_unused_fallback_key_types")
+        val deviceUnusedFallbackKeyTypes: List<String>? = null,
+
         /**
          * List of groups.
          */
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/terms/TermsService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/terms/TermsService.kt
index 10ce0829d0b86dc584f00cfde0f202002d625927..e64cf1872ee3c73fc60e2ffa7e81110742ddfafa 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/terms/TermsService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/terms/TermsService.kt
@@ -16,6 +16,8 @@
 
 package org.matrix.android.sdk.api.session.terms
 
+import org.matrix.android.sdk.internal.session.terms.TermsResponse
+
 interface TermsService {
     enum class ServiceType {
         IntegrationManager,
@@ -28,4 +30,10 @@ interface TermsService {
                              baseUrl: String,
                              agreedUrls: List<String>,
                              token: String?)
+
+    /**
+     * Get the homeserver terms, from the register API.
+     * Will be updated once https://github.com/matrix-org/matrix-doc/pull/3012 will be implemented.
+     */
+    suspend fun getHomeserverTerms(baseUrl: String): TermsResponse
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CancelGossipRequestWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CancelGossipRequestWorker.kt
index c11d00278b0bd6f44855466e0e2153b0f67a5693..3a5f8e7668f1de044f9ccd23b174f0b7e4a44a78 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CancelGossipRequestWorker.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CancelGossipRequestWorker.kt
@@ -24,6 +24,7 @@ import org.matrix.android.sdk.api.failure.shouldBeRetried
 import org.matrix.android.sdk.api.session.events.model.Event
 import org.matrix.android.sdk.api.session.events.model.EventType
 import org.matrix.android.sdk.api.session.events.model.toContent
+import org.matrix.android.sdk.internal.SessionManager
 import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap
 import org.matrix.android.sdk.internal.crypto.model.rest.ShareRequestCancellation
 import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
@@ -34,9 +35,8 @@ import org.matrix.android.sdk.internal.worker.SessionSafeCoroutineWorker
 import org.matrix.android.sdk.internal.worker.SessionWorkerParams
 import javax.inject.Inject
 
-internal class CancelGossipRequestWorker(context: Context,
-                                         params: WorkerParameters) :
-    SessionSafeCoroutineWorker<CancelGossipRequestWorker.Params>(context, params, Params::class.java) {
+internal class CancelGossipRequestWorker(context: Context, params: WorkerParameters, sessionManager: SessionManager) :
+    SessionSafeCoroutineWorker<CancelGossipRequestWorker.Params>(context, params, sessionManager, Params::class.java) {
 
     @JsonClass(generateAdapter = true)
     internal data class Params(
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt
index 18f344882c632c7e87e86d9ab26d9a7e2aaecf1f..7d9c35141002290d10b12201cb3c3be83ed69727 100755
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt
@@ -67,6 +67,7 @@ import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo
 import org.matrix.android.sdk.internal.crypto.model.ImportRoomKeysResult
 import org.matrix.android.sdk.internal.crypto.model.MXDeviceInfo
 import org.matrix.android.sdk.internal.crypto.model.MXEncryptEventContentResult
+import org.matrix.android.sdk.internal.crypto.model.MXKey.Companion.KEY_SIGNED_CURVE_25519_TYPE
 import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap
 import org.matrix.android.sdk.internal.crypto.model.event.EncryptedEventContent
 import org.matrix.android.sdk.internal.crypto.model.event.RoomKeyContent
@@ -431,6 +432,14 @@ internal class DefaultCryptoService @Inject constructor(
                 if (isStarted()) {
                     // Make sure we process to-device messages before generating new one-time-keys #2782
                     deviceListManager.refreshOutdatedDeviceLists()
+                    // The presence of device_unused_fallback_key_types indicates that the server supports fallback keys.
+                    // If there's no unused signed_curve25519 fallback key we need a new one.
+                    if (syncResponse.deviceUnusedFallbackKeyTypes != null &&
+                            // Generate a fallback key only if the server does not already have an unused fallback key.
+                            !syncResponse.deviceUnusedFallbackKeyTypes.contains(KEY_SIGNED_CURVE_25519_TYPE)) {
+                        oneTimeKeysUploader.needsNewFallback()
+                    }
+
                     oneTimeKeysUploader.maybeUploadOneTimeKeys()
                     incomingGossipingRequestManager.processReceivedGossipingRequests()
                 }
@@ -928,7 +937,7 @@ internal class DefaultCryptoService @Inject constructor(
                 signatures = objectSigner.signObject(canonicalJson)
         )
 
-        val uploadDeviceKeysParams = UploadKeysTask.Params(rest, null)
+        val uploadDeviceKeysParams = UploadKeysTask.Params(rest, null, null)
         uploadKeysTask.execute(uploadDeviceKeysParams)
 
         cryptoStore.setDeviceKeysUploaded(true)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MXOlmDevice.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MXOlmDevice.kt
index 441dfe4a5d38d278b651bcbd68bd8deb8fd6ee61..e1a706df799a971f1d161eb5ba78da9408996902 100755
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MXOlmDevice.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MXOlmDevice.kt
@@ -136,6 +136,51 @@ internal class MXOlmDevice @Inject constructor(
         return store.getOlmAccount().maxOneTimeKeys()
     }
 
+    /**
+     * Returns an unpublished fallback key
+     * A call to markKeysAsPublished will mark it as published and this
+     * call will return null (until a call to generateFallbackKey is made)
+     */
+    fun getFallbackKey(): MutableMap<String, MutableMap<String, String>>? {
+        try {
+            return store.getOlmAccount().fallbackKey()
+        } catch (e: Exception) {
+            Timber.e("## getFallbackKey() : failed")
+        }
+        return null
+    }
+
+    /**
+     * Generates a new fallback key if there is not already
+     * an unpublished one.
+     * @return true if a new key was generated
+     */
+    fun generateFallbackKeyIfNeeded(): Boolean {
+        try {
+            if (!hasUnpublishedFallbackKey()) {
+                store.getOlmAccount().generateFallbackKey()
+                store.saveOlmAccount()
+                return true
+            }
+        } catch (e: Exception) {
+            Timber.e("## generateFallbackKey() : failed")
+        }
+        return false
+    }
+
+    internal fun hasUnpublishedFallbackKey(): Boolean {
+        return getFallbackKey()?.get(OlmAccount.JSON_KEY_ONE_TIME_KEY).orEmpty().isNotEmpty()
+    }
+
+    fun forgetFallbackKey() {
+        try {
+            store.getOlmAccount().forgetFallbackKey()
+            store.saveOlmAccount()
+        } catch (e: Exception) {
+            Timber.e("## forgetFallbackKey() : failed")
+        }
+    }
+
     /**
      * Release the instance
      */
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OneTimeKeysUploader.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OneTimeKeysUploader.kt
index c4b62fe9fee560c047786e10499b13eb817156ed..4aebe091c48118ae807850cdb26c297d4abc6763 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OneTimeKeysUploader.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OneTimeKeysUploader.kt
@@ -16,6 +16,7 @@
 
 package org.matrix.android.sdk.internal.crypto
 
+import android.content.Context
 import org.matrix.android.sdk.api.extensions.tryOrNull
 import org.matrix.android.sdk.internal.crypto.model.MXKey
 import org.matrix.android.sdk.internal.crypto.model.rest.KeysUploadResponse
@@ -28,11 +29,16 @@ import javax.inject.Inject
 import kotlin.math.floor
 import kotlin.math.min
 
+// The spec recommend a 5mn delay, but due to federation
+// or server downtime we give it a bit more time (1 hour)
+const val FALLBACK_KEY_FORGET_DELAY = 60 * 60_000L
+
 @SessionScope
 internal class OneTimeKeysUploader @Inject constructor(
         private val olmDevice: MXOlmDevice,
         private val objectSigner: ObjectSigner,
-        private val uploadKeysTask: UploadKeysTask
+        private val uploadKeysTask: UploadKeysTask,
+        context: Context
 ) {
     // tell if there is a OTK check in progress
     private var oneTimeKeyCheckInProgress = false
@@ -41,6 +47,9 @@ internal class OneTimeKeysUploader @Inject constructor(
     private var lastOneTimeKeyCheck: Long = 0
     private var oneTimeKeyCount: Int? = null
 
+    // Simple storage to remember when was uploaded the last fallback key
+    private val storage = context.getSharedPreferences("OneTimeKeysUploader_${olmDevice.deviceEd25519Key.hashCode()}", Context.MODE_PRIVATE)
+
     /**
      * Stores the current one_time_key count which will be handled later (in a call of
      * _onSyncCompleted). The count is e.g. coming from a /sync response.
@@ -51,6 +60,15 @@ internal class OneTimeKeysUploader @Inject constructor(
         oneTimeKeyCount = currentCount
     }
 
+    fun needsNewFallback() {
+        if (olmDevice.generateFallbackKeyIfNeeded()) {
+            // As we generated a new one, it's already forgetting one
+            // so we can clear the last publish time
+            // (in case the network calls fails after to avoid calling forgetKey)
+            saveLastFallbackKeyPublishTime(0L)
+        }
+    }
+
     /**
      * Check if the OTK must be uploaded.
      */
@@ -65,9 +83,19 @@ internal class OneTimeKeysUploader @Inject constructor(
             return
         }
 
-        lastOneTimeKeyCheck = System.currentTimeMillis()
         oneTimeKeyCheckInProgress = true
 
+        val oneTimeKeyCountFromSync = oneTimeKeyCount
+                ?: fetchOtkCount() // we don't have count from sync so get from server
+                ?: return Unit.also {
+                    oneTimeKeyCheckInProgress = false
+                    Timber.w("maybeUploadOneTimeKeys: Failed to get otk count from server")
+                }
+
+        Timber.d("maybeUploadOneTimeKeys: otk count $oneTimeKeyCountFromSync , unpublished fallback key ${olmDevice.hasUnpublishedFallbackKey()}")
+
+        lastOneTimeKeyCheck = System.currentTimeMillis()
+
         // We then check how many keys we can store in the Account object.
         val maxOneTimeKeys = olmDevice.getMaxNumberOfOneTimeKeys()
 
@@ -78,37 +106,37 @@ internal class OneTimeKeysUploader @Inject constructor(
         // discard the oldest private keys first. This will eventually clean
         // out stale private keys that won't receive a message.
         val keyLimit = floor(maxOneTimeKeys / 2.0).toInt()
-        if (oneTimeKeyCount == null) {
-            // Ask the server how many otk he has
-            oneTimeKeyCount = fetchOtkCount()
-        }
-        val oneTimeKeyCountFromSync = oneTimeKeyCount
-        if (oneTimeKeyCountFromSync != null) {
-            // We need to keep a pool of one time public keys on the server so that
-            // other devices can start conversations with us. But we can only store
-            // a finite number of private keys in the olm Account object.
-            // To complicate things further then can be a delay between a device
-            // claiming a public one time key from the server and it sending us a
-            // message. We need to keep the corresponding private key locally until
-            // we receive the message.
-            // But that message might never arrive leaving us stuck with duff
-            // private keys clogging up our local storage.
-            // So we need some kind of engineering compromise to balance all of
-            // these factors.
-            tryOrNull("Unable to upload OTK") {
-                val uploadedKeys = uploadOTK(oneTimeKeyCountFromSync, keyLimit)
-                Timber.v("## uploadKeys() : success, $uploadedKeys key(s) sent")
-            }
-        } else {
-            Timber.w("maybeUploadOneTimeKeys: waiting to know the number of OTK from the sync")
-            lastOneTimeKeyCheck = 0
+
+        // We need to keep a pool of one time public keys on the server so that
+        // other devices can start conversations with us. But we can only store
+        // a finite number of private keys in the olm Account object.
+        // To complicate things further then can be a delay between a device
+        // claiming a public one time key from the server and it sending us a
+        // message. We need to keep the corresponding private key locally until
+        // we receive the message.
+        // But that message might never arrive leaving us stuck with duff
+        // private keys clogging up our local storage.
+        // So we need some kind of engineering compromise to balance all of
+        // these factors.
+        tryOrNull("Unable to upload OTK") {
+            val uploadedKeys = uploadOTK(oneTimeKeyCountFromSync, keyLimit)
+            Timber.v("## uploadKeys() : success, $uploadedKeys key(s) sent")
         }
         oneTimeKeyCheckInProgress = false
+
+        // Check if we need to forget a fallback key
+        val latestPublishedTime = getLastFallbackKeyPublishTime()
+        if (latestPublishedTime != 0L && System.currentTimeMillis() - latestPublishedTime > FALLBACK_KEY_FORGET_DELAY) {
+            // This should be called once you are reasonably certain that you will not receive any more messages
+            // that use the old fallback key
+            Timber.d("## forgetFallbackKey()")
+            olmDevice.forgetFallbackKey()
+        }
     }
 
     private suspend fun fetchOtkCount(): Int? {
         return tryOrNull("Unable to get OTK count") {
-            val result = uploadKeysTask.execute(UploadKeysTask.Params(null, null))
+            val result = uploadKeysTask.execute(UploadKeysTask.Params(null, null, null))
             result.oneTimeKeyCountsForAlgorithm(MXKey.KEY_SIGNED_CURVE_25519_TYPE)
         }
     }
@@ -121,24 +149,47 @@ internal class OneTimeKeysUploader @Inject constructor(
      * @return the number of uploaded keys
      */
     private suspend fun uploadOTK(keyCount: Int, keyLimit: Int): Int {
-        if (keyLimit <= keyCount) {
+        if (keyLimit <= keyCount && !olmDevice.hasUnpublishedFallbackKey()) {
             // If we don't need to generate any more keys then we are done.
             return 0
         }
-        val keysThisLoop = min(keyLimit - keyCount, ONE_TIME_KEY_GENERATION_MAX_NUMBER)
-        olmDevice.generateOneTimeKeys(keysThisLoop)
+        var keysThisLoop = 0
+        if (keyLimit > keyCount) {
+            // Creating keys can be an expensive operation so we limit the
+            // number we generate in one go to avoid blocking the application
+            // for too long.
+            keysThisLoop = min(keyLimit - keyCount, ONE_TIME_KEY_GENERATION_MAX_NUMBER)
+            olmDevice.generateOneTimeKeys(keysThisLoop)
+        }
+
+        // We check before sending if there is an unpublished key in order to saveLastFallbackKeyPublishTime if needed
+        val hadUnpublishedFallbackKey = olmDevice.hasUnpublishedFallbackKey()
         val response = uploadOneTimeKeys(olmDevice.getOneTimeKeys())
         olmDevice.markKeysAsPublished()
+        if (hadUnpublishedFallbackKey) {
+            // It had an unpublished fallback key that was published just now
+            saveLastFallbackKeyPublishTime(System.currentTimeMillis())
+        }
 
         if (response.hasOneTimeKeyCountsForAlgorithm(MXKey.KEY_SIGNED_CURVE_25519_TYPE)) {
             // Maybe upload other keys
-            return keysThisLoop + uploadOTK(response.oneTimeKeyCountsForAlgorithm(MXKey.KEY_SIGNED_CURVE_25519_TYPE), keyLimit)
+            return keysThisLoop +
+                    uploadOTK(response.oneTimeKeyCountsForAlgorithm(MXKey.KEY_SIGNED_CURVE_25519_TYPE), keyLimit) +
+                    (if (hadUnpublishedFallbackKey) 1 else 0)
         } else {
             Timber.e("## uploadOTK() : response for uploading keys does not contain one_time_key_counts.signed_curve25519")
             throw Exception("response for uploading keys does not contain one_time_key_counts.signed_curve25519")
         }
     }
 
+    private fun saveLastFallbackKeyPublishTime(timeMillis: Long) {
+        storage.edit().putLong("last_fb_key_publish", timeMillis).apply()
+    }
+
+    private fun getLastFallbackKeyPublishTime(): Long {
+        return storage.getLong("last_fb_key_publish", 0)
+    }
+
     /**
      * Upload curve25519 one time keys.
      */
@@ -159,10 +210,26 @@ internal class OneTimeKeysUploader @Inject constructor(
             oneTimeJson["signed_curve25519:$key_id"] = k
         }
 
+        val fallbackJson = mutableMapOf<String, Any>()
+        val fallbackCurve25519Map = olmDevice.getFallbackKey()?.get(OlmAccount.JSON_KEY_ONE_TIME_KEY).orEmpty()
+        fallbackCurve25519Map.forEach { (key_id, key) ->
+            val k = mutableMapOf<String, Any>()
+            k["key"] = key
+            k["fallback"] = true
+            val canonicalJson = JsonCanonicalizer.getCanonicalJson(Map::class.java, k)
+            k["signatures"] = objectSigner.signObject(canonicalJson)
+
+            fallbackJson["signed_curve25519:$key_id"] = k
+        }
+
         // For now, we set the device id explicitly, as we may not be using the
         // same one as used in login.
-        val uploadParams = UploadKeysTask.Params(null, oneTimeJson)
-        return uploadKeysTask.execute(uploadParams)
+        val uploadParams = UploadKeysTask.Params(
+                deviceKeys = null,
+                oneTimeKeys = oneTimeJson,
+                fallbackKeys = fallbackJson.takeIf { fallbackJson.isNotEmpty() }
+        )
+        return uploadKeysTask.executeRetry(uploadParams, 3)
     }
 
     companion object {
@@ -173,6 +240,6 @@ internal class OneTimeKeysUploader @Inject constructor(
         private const val ONE_TIME_KEY_GENERATION_MAX_NUMBER = 5
 
         // frequency with which to check & upload one-time keys
-        private const val ONE_TIME_KEY_UPLOAD_PERIOD = (60 * 1000).toLong() // one minute
+        private const val ONE_TIME_KEY_UPLOAD_PERIOD = (60_000).toLong() // one minute
     }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/SendGossipRequestWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/SendGossipRequestWorker.kt
index b2ba189b65e9958e3bd12948832e8d3481d74a15..3129ccae3b0b691cc36536f60c6bb5165f2609e8 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/SendGossipRequestWorker.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/SendGossipRequestWorker.kt
@@ -24,6 +24,7 @@ import org.matrix.android.sdk.api.failure.shouldBeRetried
 import org.matrix.android.sdk.api.session.events.model.Event
 import org.matrix.android.sdk.api.session.events.model.EventType
 import org.matrix.android.sdk.api.session.events.model.toContent
+import org.matrix.android.sdk.internal.SessionManager
 import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap
 import org.matrix.android.sdk.internal.crypto.model.rest.GossipingToDeviceObject
 import org.matrix.android.sdk.internal.crypto.model.rest.RoomKeyShareRequest
@@ -37,9 +38,8 @@ import org.matrix.android.sdk.internal.worker.SessionWorkerParams
 import timber.log.Timber
 import javax.inject.Inject
 
-internal class SendGossipRequestWorker(context: Context,
-                                       params: WorkerParameters) :
-    SessionSafeCoroutineWorker<SendGossipRequestWorker.Params>(context, params, Params::class.java) {
+internal class SendGossipRequestWorker(context: Context, params: WorkerParameters, sessionManager: SessionManager) :
+    SessionSafeCoroutineWorker<SendGossipRequestWorker.Params>(context, params, sessionManager, Params::class.java) {
 
     @JsonClass(generateAdapter = true)
     internal data class Params(
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/SendGossipWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/SendGossipWorker.kt
index b96943e4aefef58a2c1f30dec7ff5277a0469da7..ff206a3c96819706ecfab5d31e18bd2fb48c0a81 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/SendGossipWorker.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/SendGossipWorker.kt
@@ -24,6 +24,7 @@ import org.matrix.android.sdk.api.failure.shouldBeRetried
 import org.matrix.android.sdk.api.session.events.model.Event
 import org.matrix.android.sdk.api.session.events.model.EventType
 import org.matrix.android.sdk.api.session.events.model.toContent
+import org.matrix.android.sdk.internal.SessionManager
 import org.matrix.android.sdk.internal.crypto.actions.EnsureOlmSessionsForDevicesAction
 import org.matrix.android.sdk.internal.crypto.actions.MessageEncrypter
 import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap
@@ -37,9 +38,8 @@ import org.matrix.android.sdk.internal.worker.SessionWorkerParams
 import timber.log.Timber
 import javax.inject.Inject
 
-internal class SendGossipWorker(context: Context,
-                                params: WorkerParameters) :
-    SessionSafeCoroutineWorker<SendGossipWorker.Params>(context, params, Params::class.java) {
+internal class SendGossipWorker(context: Context, params: WorkerParameters, sessionManager: SessionManager) :
+    SessionSafeCoroutineWorker<SendGossipWorker.Params>(context, params, sessionManager, Params::class.java) {
 
     @JsonClass(generateAdapter = true)
     internal data class Params(
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/UpdateTrustWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/UpdateTrustWorker.kt
index 3326d3707a56927a1ba753a7ec1a9ac2b9fc53b0..5cd647ff6fa05058ca1189f4638c32e0a1686341 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/UpdateTrustWorker.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/UpdateTrustWorker.kt
@@ -25,6 +25,7 @@ import io.realm.kotlin.where
 import org.matrix.android.sdk.api.crypto.RoomEncryptionTrustLevel
 import org.matrix.android.sdk.api.extensions.orFalse
 import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo
+import org.matrix.android.sdk.internal.SessionManager
 import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
 import org.matrix.android.sdk.internal.crypto.store.db.mapper.CrossSigningKeysMapper
 import org.matrix.android.sdk.internal.crypto.store.db.model.CrossSigningInfoEntity
@@ -50,9 +51,8 @@ import org.matrix.android.sdk.internal.worker.SessionWorkerParams
 import timber.log.Timber
 import javax.inject.Inject
 
-internal class UpdateTrustWorker(context: Context,
-                                 params: WorkerParameters) :
-    SessionSafeCoroutineWorker<UpdateTrustWorker.Params>(context, params, Params::class.java) {
+internal class UpdateTrustWorker(context: Context, params: WorkerParameters, sessionManager: SessionManager) :
+    SessionSafeCoroutineWorker<UpdateTrustWorker.Params>(context, params, sessionManager, Params::class.java) {
 
     @JsonClass(generateAdapter = true)
     internal data class Params(
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/KeysUploadBody.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/KeysUploadBody.kt
index 69b3992374d8d90dd0eead55ca187d66ee89e95d..363dee9a8d8aab2dbf4c283a40e742ea61d0a23f 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/KeysUploadBody.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/KeysUploadBody.kt
@@ -40,5 +40,12 @@ internal data class KeysUploadBody(
          * May be absent if no new one-time keys are required.
          */
         @Json(name = "one_time_keys")
-        val oneTimeKeys: JsonDict? = null
+        val oneTimeKeys: JsonDict? = null,
+
+        /**
+         * If the user had previously uploaded a fallback key for a given algorithm, it is replaced.
+         * The server will only keep one fallback key per algorithm for each user.
+         */
+        @Json(name = "org.matrix.msc2732.fallback_keys")
+        val fallbackKeys: JsonDict? = null
 )
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/UploadKeysTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/UploadKeysTask.kt
index cac4dadd935ec4b97136b3d1ed8b8b03209a1843..30de8e871a39223c3cfd49fc60170c06ef036df8 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/UploadKeysTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/UploadKeysTask.kt
@@ -32,7 +32,8 @@ internal interface UploadKeysTask : Task<UploadKeysTask.Params, KeysUploadRespon
             // the device keys to send.
             val deviceKeys: DeviceKeys?,
             // the one-time keys to send.
-            val oneTimeKeys: JsonDict?
+            val oneTimeKeys: JsonDict?,
+            val fallbackKeys: JsonDict?
     )
 }
 
@@ -44,7 +45,8 @@ internal class DefaultUploadKeysTask @Inject constructor(
     override suspend fun execute(params: UploadKeysTask.Params): KeysUploadResponse {
         val body = KeysUploadBody(
                 deviceKeys = params.deviceKeys,
-                oneTimeKeys = params.oneTimeKeys
+                oneTimeKeys = params.oneTimeKeys,
+                fallbackKeys = params.fallbackKeys
         )
 
         Timber.i("## Uploading device keys -> $body")
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/SendVerificationMessageWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/SendVerificationMessageWorker.kt
index 481ce85f70b3d8dfa67cdea48ae85903ddea1c71..a763c05e07696f6c20ae584214b3930229db840a 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/SendVerificationMessageWorker.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/SendVerificationMessageWorker.kt
@@ -20,6 +20,7 @@ import androidx.work.Data
 import androidx.work.WorkerParameters
 import com.squareup.moshi.JsonClass
 import org.matrix.android.sdk.api.failure.shouldBeRetried
+import org.matrix.android.sdk.internal.SessionManager
 import org.matrix.android.sdk.internal.crypto.tasks.SendVerificationMessageTask
 import org.matrix.android.sdk.internal.session.SessionComponent
 import org.matrix.android.sdk.internal.session.room.send.CancelSendTracker
@@ -33,9 +34,8 @@ import javax.inject.Inject
  * Possible previous worker: None
  * Possible next worker    : None
  */
-internal class SendVerificationMessageWorker(context: Context,
-                                             params: WorkerParameters) :
-    SessionSafeCoroutineWorker<SendVerificationMessageWorker.Params>(context, params, Params::class.java) {
+internal class SendVerificationMessageWorker(context: Context, params: WorkerParameters, sessionManager: SessionManager) :
+    SessionSafeCoroutineWorker<SendVerificationMessageWorker.Params>(context, params, sessionManager, Params::class.java) {
 
     @JsonClass(generateAdapter = true)
     internal data class Params(
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/MatrixComponent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/MatrixComponent.kt
index 81a067f2c010a2898551a4ad9272dafb04080f0d..d9a4f1bde15611254a4365699c0d9f14da9d6965 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/MatrixComponent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/MatrixComponent.kt
@@ -37,6 +37,7 @@ import org.matrix.android.sdk.internal.session.TestInterceptor
 import org.matrix.android.sdk.internal.task.TaskExecutor
 import org.matrix.android.sdk.internal.util.BackgroundDetectionObserver
 import org.matrix.android.sdk.internal.util.system.SystemModule
+import org.matrix.android.sdk.internal.worker.MatrixWorkerFactory
 import org.matrix.olm.OlmManager
 import java.io.File
 
@@ -86,6 +87,8 @@ internal interface MatrixComponent {
 
     fun sessionManager(): SessionManager
 
+    fun matrixWorkerFactory(): MatrixWorkerFactory
+
     fun inject(matrix: Matrix)
 
     @Component.Factory
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/MoshiProvider.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/MoshiProvider.kt
index 074f8dc43e193e5eba72fe38d008bf89f9796223..9e50e9efe82e162a5a71e12ac26e0100fc948b7a 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/MoshiProvider.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/MoshiProvider.kt
@@ -25,7 +25,6 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageFileContent
 import org.matrix.android.sdk.api.session.room.model.message.MessageImageContent
 import org.matrix.android.sdk.api.session.room.model.message.MessageLocationContent
 import org.matrix.android.sdk.api.session.room.model.message.MessageNoticeContent
-import org.matrix.android.sdk.api.session.room.model.message.MessageOptionsContent
 import org.matrix.android.sdk.api.session.room.model.message.MessagePollResponseContent
 import org.matrix.android.sdk.api.session.room.model.message.MessageTextContent
 import org.matrix.android.sdk.api.session.room.model.message.MessageType
@@ -57,8 +56,7 @@ object MoshiProvider {
                     .registerSubtype(MessageLocationContent::class.java, MessageType.MSGTYPE_LOCATION)
                     .registerSubtype(MessageFileContent::class.java, MessageType.MSGTYPE_FILE)
                     .registerSubtype(MessageVerificationRequestContent::class.java, MessageType.MSGTYPE_VERIFICATION_REQUEST)
-                    .registerSubtype(MessageOptionsContent::class.java, MessageType.MSGTYPE_OPTIONS)
-                    .registerSubtype(MessagePollResponseContent::class.java, MessageType.MSGTYPE_RESPONSE)
+                    .registerSubtype(MessagePollResponseContent::class.java, MessageType.MSGTYPE_POLL_RESPONSE)
             )
             .add(SerializeNulls.JSON_ADAPTER_FACTORY)
             .build()
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/NoOpTestModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/NoOpTestModule.kt
index 210eadeff7e7fc72610962b88383a2e601b2eff7..b136041f7999dd9a86735cc4643d551bf8c475f5 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/NoOpTestModule.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/NoOpTestModule.kt
@@ -20,6 +20,8 @@ import dagger.Module
 import dagger.Provides
 import org.matrix.android.sdk.internal.session.MockHttpInterceptor
 import org.matrix.android.sdk.internal.session.TestInterceptor
+import org.matrix.android.sdk.internal.util.BackgroundDetectionObserver
+import org.matrix.android.sdk.internal.util.DefaultBackgroundDetectionObserver
 
 @Module
 internal object NoOpTestModule {
@@ -28,4 +30,11 @@ internal object NoOpTestModule {
     @JvmStatic
     @MockHttpInterceptor
     fun providesTestInterceptor(): TestInterceptor? = null
+
+    @Provides
+    @JvmStatic
+    @MatrixScope
+    fun providesBackgroundDetectionObserver(): BackgroundDetectionObserver {
+        return DefaultBackgroundDetectionObserver()
+    }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/WorkManagerProvider.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/WorkManagerProvider.kt
index bafffdf852006ea9666862210926a333990cc6a7..7d004bc5c075514596956145013e8f7195e7a51c 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/WorkManagerProvider.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/WorkManagerProvider.kt
@@ -17,24 +17,38 @@
 package org.matrix.android.sdk.internal.di
 
 import android.content.Context
+import androidx.lifecycle.Observer
 import androidx.work.Constraints
 import androidx.work.ListenableWorker
 import androidx.work.NetworkType
 import androidx.work.OneTimeWorkRequestBuilder
 import androidx.work.PeriodicWorkRequestBuilder
+import androidx.work.WorkInfo
 import androidx.work.WorkManager
 import androidx.work.WorkRequest
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
+import org.matrix.android.sdk.internal.session.SessionScope
+import org.matrix.android.sdk.internal.worker.MatrixWorkerFactory
 import java.util.concurrent.TimeUnit
 import javax.inject.Inject
 
+@SessionScope
 internal class WorkManagerProvider @Inject constructor(
         context: Context,
-        @SessionId private val sessionId: String
+        @SessionId private val sessionId: String,
+        private val coroutineDispatchers: MatrixCoroutineDispatchers,
+        private val sessionScope: CoroutineScope
 ) {
     private val tag = MATRIX_SDK_TAG_PREFIX + sessionId
 
     val workManager = WorkManager.getInstance(context)
 
+    init {
+        checkIfWorkerFactoryIsSetup()
+    }
+
     /**
      * Create a OneTimeWorkRequestBuilder, with the Matrix SDK tag
      */
@@ -60,6 +74,27 @@ internal class WorkManagerProvider @Inject constructor(
         }
     }
 
+    private fun checkIfWorkerFactoryIsSetup() {
+        sessionScope.launch(coroutineDispatchers.main) {
+            val checkWorkerRequest = OneTimeWorkRequestBuilder<MatrixWorkerFactory.CheckFactoryWorker>().build()
+            workManager.enqueue(checkWorkerRequest)
+            val checkWorkerLiveState = workManager.getWorkInfoByIdLiveData(checkWorkerRequest.id)
+            val observer = object : Observer<WorkInfo> {
+                override fun onChanged(workInfo: WorkInfo) {
+                    if (workInfo.state.isFinished) {
+                        checkWorkerLiveState.removeObserver(this)
+                        if (workInfo.state == WorkInfo.State.FAILED) {
+                            throw RuntimeException("MatrixWorkerFactory is not being set on your worker configuration.\n" +
+                                    "Makes sure to add it to a DelegatingWorkerFactory if you have your own factory or use it directly.\n" +
+                                    "You can grab the instance through the Matrix class.")
+                        }
+                    }
+                }
+            }
+            checkWorkerLiveState.observeForever(observer)
+        }
+    }
+
     companion object {
         private const val MATRIX_SDK_TAG_PREFIX = "MatrixSDK-"
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/model/MxCallImpl.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/model/MxCallImpl.kt
index 9fc84e6fe534644e8daa4236be64acf0c88438ee..a89713870a7513bd4eabffb530bd0efe71ae677b 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/model/MxCallImpl.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/model/MxCallImpl.kt
@@ -203,8 +203,11 @@ internal class MxCallImpl(
 
     override fun selectAnswer() {
         Timber.tag(loggerTag.value).v("select answer $callId")
-        if (isOutgoing) return
-        state = CallState.Answering
+        if (!isOutgoing) return
+        // This is an outgoing call, select the remote client that answered.
+        if (state != CallState.Dialing && state !is CallState.Connected) {
+            Timber.tag(loggerTag.value).w("Expected state is CallState.Dialing or CallState.Connected got $state.")
+        }
         CallSelectAnswerContent(
                 callId = callId,
                 partyId = ourPartyId,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/UploadContentWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/UploadContentWorker.kt
index 7f35c910109ffbb588fd9c2f1a4a7b1f9c4e4c8b..52dee0ee55c509afa5452e39d5783b3682c80b19 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/UploadContentWorker.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/UploadContentWorker.kt
@@ -33,6 +33,7 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageFileContent
 import org.matrix.android.sdk.api.session.room.model.message.MessageImageContent
 import org.matrix.android.sdk.api.session.room.model.message.MessageVideoContent
 import org.matrix.android.sdk.api.util.MimeTypes
+import org.matrix.android.sdk.internal.SessionManager
 import org.matrix.android.sdk.internal.crypto.attachments.MXEncryptedAttachments
 import org.matrix.android.sdk.internal.crypto.model.rest.EncryptedFileInfo
 import org.matrix.android.sdk.internal.database.mapper.ContentMapper
@@ -63,8 +64,8 @@ private data class NewAttachmentAttributes(
  * Possible previous worker: None
  * Possible next worker    : Always [MultipleEventSendingDispatcherWorker]
  */
-internal class UploadContentWorker(val context: Context, params: WorkerParameters) :
-        SessionSafeCoroutineWorker<UploadContentWorker.Params>(context, params, Params::class.java) {
+internal class UploadContentWorker(val context: Context, params: WorkerParameters, sessionManager: SessionManager) :
+        SessionSafeCoroutineWorker<UploadContentWorker.Params>(context, params, sessionManager, Params::class.java) {
 
     @JsonClass(generateAdapter = true)
     internal data class Params(
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/group/GetGroupDataWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/group/GetGroupDataWorker.kt
index 338f43bdbb505db56f40e15be393296e557524d8..716859f195f75736591bdc8f9192eb7996cbd6ea 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/group/GetGroupDataWorker.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/group/GetGroupDataWorker.kt
@@ -19,6 +19,7 @@ package org.matrix.android.sdk.internal.session.group
 import android.content.Context
 import androidx.work.WorkerParameters
 import com.squareup.moshi.JsonClass
+import org.matrix.android.sdk.internal.SessionManager
 import org.matrix.android.sdk.internal.session.SessionComponent
 import org.matrix.android.sdk.internal.worker.SessionSafeCoroutineWorker
 import org.matrix.android.sdk.internal.worker.SessionWorkerParams
@@ -28,8 +29,8 @@ import javax.inject.Inject
  * Possible previous worker: None
  * Possible next worker    : None
  */
-internal class GetGroupDataWorker(context: Context, params: WorkerParameters) :
-    SessionSafeCoroutineWorker<GetGroupDataWorker.Params>(context, params, Params::class.java) {
+internal class GetGroupDataWorker(context: Context, params: WorkerParameters, sessionManager: SessionManager) :
+        SessionSafeCoroutineWorker<GetGroupDataWorker.Params>(context, params, sessionManager, Params::class.java) {
 
     @JsonClass(generateAdapter = true)
     internal data class Params(
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/notification/ProcessEventForPushTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/notification/ProcessEventForPushTask.kt
index 0ac21b555e840cd4268c683a0586e851928d8276..da15e158e5a1dec144e271b38ee1ba04244cd999 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/notification/ProcessEventForPushTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/notification/ProcessEventForPushTask.kt
@@ -56,6 +56,7 @@ internal class DefaultProcessEventForPushTask @Inject constructor(
 
         val allEvents = (newJoinEvents + inviteEvents).filter { event ->
             when (event.type) {
+                EventType.POLL_START,
                 EventType.MESSAGE,
                 EventType.REDACTION,
                 EventType.ENCRYPTED,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/AddPusherWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/AddPusherWorker.kt
index 4df42b2cfbef50715b2614d953bdb3ab86aab181..ce29efaaac5160fd750de83f14185777a97c6141 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/AddPusherWorker.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/AddPusherWorker.kt
@@ -19,13 +19,14 @@ import android.content.Context
 import androidx.work.WorkerParameters
 import com.squareup.moshi.JsonClass
 import org.matrix.android.sdk.api.failure.Failure
+import org.matrix.android.sdk.internal.SessionManager
 import org.matrix.android.sdk.internal.session.SessionComponent
 import org.matrix.android.sdk.internal.worker.SessionSafeCoroutineWorker
 import org.matrix.android.sdk.internal.worker.SessionWorkerParams
 import javax.inject.Inject
 
-internal class AddPusherWorker(context: Context, params: WorkerParameters) :
-    SessionSafeCoroutineWorker<AddPusherWorker.Params>(context, params, Params::class.java) {
+internal class AddPusherWorker(context: Context, params: WorkerParameters, sessionManager: SessionManager) :
+    SessionSafeCoroutineWorker<AddPusherWorker.Params>(context, params, sessionManager, Params::class.java) {
 
     @JsonClass(generateAdapter = true)
     internal data class Params(
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt
index 5a1eb190a8dd4f22f5f8678262389058cf888590..62b6d626f5e509058ab2d22f2220cb9f43b545b3 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt
@@ -17,20 +17,27 @@ package org.matrix.android.sdk.internal.session.room
 
 import io.realm.Realm
 import org.matrix.android.sdk.api.crypto.VerificationState
+import org.matrix.android.sdk.api.extensions.orFalse
+import org.matrix.android.sdk.api.query.QueryStringValue
 import org.matrix.android.sdk.api.session.events.model.AggregatedAnnotation
 import org.matrix.android.sdk.api.session.events.model.Event
 import org.matrix.android.sdk.api.session.events.model.EventType
 import org.matrix.android.sdk.api.session.events.model.LocalEcho
 import org.matrix.android.sdk.api.session.events.model.RelationType
+import org.matrix.android.sdk.api.session.events.model.getRelationContent
 import org.matrix.android.sdk.api.session.events.model.toContent
 import org.matrix.android.sdk.api.session.events.model.toModel
 import org.matrix.android.sdk.api.session.room.model.PollSummaryContent
+import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent
 import org.matrix.android.sdk.api.session.room.model.ReferencesAggregatedContent
 import org.matrix.android.sdk.api.session.room.model.VoteInfo
+import org.matrix.android.sdk.api.session.room.model.VoteSummary
 import org.matrix.android.sdk.api.session.room.model.message.MessageContent
+import org.matrix.android.sdk.api.session.room.model.message.MessageEndPollContent
 import org.matrix.android.sdk.api.session.room.model.message.MessagePollResponseContent
 import org.matrix.android.sdk.api.session.room.model.message.MessageRelationContent
 import org.matrix.android.sdk.api.session.room.model.relation.ReactionContent
+import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper
 import org.matrix.android.sdk.internal.crypto.model.event.EncryptedEventContent
 import org.matrix.android.sdk.internal.crypto.verification.toState
 import org.matrix.android.sdk.internal.database.mapper.ContentMapper
@@ -50,11 +57,13 @@ import org.matrix.android.sdk.internal.database.query.getOrCreate
 import org.matrix.android.sdk.internal.database.query.where
 import org.matrix.android.sdk.internal.di.UserId
 import org.matrix.android.sdk.internal.session.EventInsertLiveProcessor
+import org.matrix.android.sdk.internal.session.room.state.StateEventDataSource
 import timber.log.Timber
 import javax.inject.Inject
 
 internal class EventRelationsAggregationProcessor @Inject constructor(
-        @UserId private val userId: String
+        @UserId private val userId: String,
+        private val stateEventDataSource: StateEventDataSource
 ) : EventInsertLiveProcessor {
 
     private val allowedTypes = listOf(
@@ -69,7 +78,9 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
             // TODO Add ?
             // EventType.KEY_VERIFICATION_READY,
             EventType.KEY_VERIFICATION_KEY,
-            EventType.ENCRYPTED
+            EventType.ENCRYPTED,
+            EventType.POLL_RESPONSE,
+            EventType.POLL_END
     )
 
     override fun shouldProcess(eventId: String, eventType: String, insertType: EventInsertType): Boolean {
@@ -107,9 +118,6 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
                         Timber.v("###REPLACE in room $roomId for event ${event.eventId}")
                         // A replace!
                         handleReplace(realm, event, content, roomId, isLocalEcho)
-                    } else if (content?.relatesTo?.type == RelationType.RESPONSE) {
-                        Timber.v("###RESPONSE in room $roomId for event ${event.eventId}")
-                        handleResponse(realm, event, content, roomId, isLocalEcho)
                     }
                 }
 
@@ -139,9 +147,11 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
                                 Timber.v("###REPLACE in room $roomId for event ${event.eventId}")
                                 // A replace!
                                 handleReplace(realm, event, it, roomId, isLocalEcho, encryptedEventContent.relatesTo.eventId)
-                            } else if (encryptedEventContent.relatesTo.type == RelationType.RESPONSE) {
-                                Timber.v("###RESPONSE in room $roomId for event ${event.eventId}")
-                                handleResponse(realm, event, it, roomId, isLocalEcho, encryptedEventContent.relatesTo.eventId)
+                            } else if (event.getClearType() == EventType.POLL_RESPONSE) {
+                                event.getClearContent().toModel<MessagePollResponseContent>(catchError = true)?.let { pollResponseContent ->
+                                    Timber.v("###RESPONSE in room $roomId for event ${event.eventId}")
+                                    handleResponse(realm, event, pollResponseContent, roomId, isLocalEcho, encryptedEventContent.relatesTo.eventId)
+                                }
                             }
                         }
                     } else if (encryptedEventContent?.relatesTo?.type == RelationType.REFERENCE) {
@@ -158,6 +168,16 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
                                     handleVerification(realm, event, roomId, isLocalEcho, it)
                                 }
                             }
+                            EventType.POLL_RESPONSE        -> {
+                                event.getClearContent().toModel<MessagePollResponseContent>(catchError = true)?.let {
+                                    handleResponse(realm, event, it, roomId, isLocalEcho, event.getRelationContent()?.eventId)
+                                }
+                            }
+                            EventType.POLL_END             -> {
+                                event.content.toModel<MessageEndPollContent>(catchError = true)?.let {
+                                    handleEndPoll(realm, event, it, roomId, isLocalEcho)
+                                }
+                            }
                         }
                     } else if (encryptedEventContent?.relatesTo?.type == RelationType.ANNOTATION) {
                         // Reaction
@@ -188,6 +208,16 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
                         }
                     }
                 }
+                EventType.POLL_RESPONSE        -> {
+                    event.content.toModel<MessagePollResponseContent>(catchError = true)?.let {
+                        handleResponse(realm, event, it, roomId, isLocalEcho)
+                    }
+                }
+                EventType.POLL_END             -> {
+                    event.content.toModel<MessageEndPollContent>(catchError = true)?.let {
+                        handleEndPoll(realm, event, it, roomId, isLocalEcho)
+                    }
+                }
                 else                           -> Timber.v("UnHandled event ${event.eventId}")
             }
         } catch (t: Throwable) {
@@ -276,7 +306,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
 
     private fun handleResponse(realm: Realm,
                                event: Event,
-                               content: MessageContent,
+                               content: MessagePollResponseContent,
                                roomId: String,
                                isLocalEcho: Boolean,
                                relatedEventId: String? = null) {
@@ -321,11 +351,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
             return
         }
 
-        val responseContent = event.content.toModel<MessagePollResponseContent>() ?: return Unit.also {
-            Timber.d("## POLL  Receiving malformed response eventId:$eventId content: ${event.content}")
-        }
-
-        val optionIndex = responseContent.relatesTo?.option ?: return Unit.also {
+        val option = content.response?.answers?.first() ?: return Unit.also {
             Timber.d("## POLL Ignoring malformed response no option eventId:$eventId content: ${event.content}")
         }
 
@@ -336,22 +362,36 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
             val existingVote = votes[existingVoteIndex]
             if (existingVote.voteTimestamp < eventTimestamp) {
                 // Take the new one
-                votes[existingVoteIndex] = VoteInfo(senderId, optionIndex, eventTimestamp)
+                votes[existingVoteIndex] = VoteInfo(senderId, option, eventTimestamp)
                 if (userId == senderId) {
-                    sumModel.myVote = optionIndex
+                    sumModel.myVote = option
                 }
-                Timber.v("## POLL adding vote $optionIndex for user $senderId in poll :$targetEventId ")
+                Timber.v("## POLL adding vote $option for user $senderId in poll :$targetEventId ")
             } else {
                 Timber.v("## POLL Ignoring vote (older than known one)  eventId:$eventId ")
             }
         } else {
-            votes.add(VoteInfo(senderId, optionIndex, eventTimestamp))
+            votes.add(VoteInfo(senderId, option, eventTimestamp))
             if (userId == senderId) {
-                sumModel.myVote = optionIndex
+                sumModel.myVote = option
             }
-            Timber.v("## POLL adding vote $optionIndex for user $senderId in poll :$targetEventId ")
+            Timber.v("## POLL adding vote $option for user $senderId in poll :$targetEventId ")
         }
         sumModel.votes = votes
+
+        // Precompute the percentage of votes for all options
+        val totalVotes = votes.size
+        sumModel.totalVotes = totalVotes
+        sumModel.votesSummary = votes
+                .groupBy({ it.option }, { it.userId })
+                .mapValues {
+                    VoteSummary(
+                            total = it.value.size,
+                            percentage = if (totalVotes == 0 && it.value.isEmpty()) 0.0 else it.value.size.toDouble() / totalVotes
+                    )
+                }
+        sumModel.winnerVoteCount = sumModel.votesSummary?.maxOf { it.value.total } ?: 0
+
         if (isLocalEcho) {
             existingPollSummary.sourceLocalEchoEvents.add(eventId)
         } else {
@@ -361,6 +401,51 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
         existingPollSummary.aggregatedContent = ContentMapper.map(sumModel.toContent())
     }
 
+    private fun handleEndPoll(realm: Realm,
+                              event: Event,
+                              content: MessageEndPollContent,
+                              roomId: String,
+                              isLocalEcho: Boolean) {
+        val pollEventId = content.relatesTo?.eventId ?: return
+
+        var existing = EventAnnotationsSummaryEntity.where(realm, roomId, pollEventId).findFirst()
+        if (existing == null) {
+            Timber.v("## POLL creating new relation summary for $pollEventId")
+            existing = EventAnnotationsSummaryEntity.create(realm, roomId, pollEventId)
+        }
+
+        // we have it
+        val existingPollSummary = existing.pollResponseSummary
+                ?: realm.createObject(PollResponseAggregatedSummaryEntity::class.java).also {
+                    existing.pollResponseSummary = it
+                }
+
+        if (existingPollSummary.closedTime != null) {
+            Timber.v("## Received poll.end event for already ended poll $pollEventId")
+            return
+        }
+
+        val powerLevelsHelper = stateEventDataSource.getStateEvent(roomId, EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.NoCondition)
+                ?.content?.toModel<PowerLevelsContent>()
+                ?.let { PowerLevelsHelper(it) }
+        if (!powerLevelsHelper?.isUserAbleToRedact(event.senderId ?: "").orFalse()) {
+            Timber.v("## Received poll.end event $pollEventId but user ${event.senderId} doesn't have enough power level in room $roomId")
+            return
+        }
+
+        val txId = event.unsignedData?.transactionId
+        // is it a remote echo?
+        if (!isLocalEcho && existingPollSummary.sourceLocalEchoEvents.contains(txId)) {
+            // ok it has already been managed
+            Timber.v("## POLL  Receiving remote echo of response eventId:$pollEventId")
+            existingPollSummary.sourceLocalEchoEvents.remove(txId)
+            existingPollSummary.sourceEvents.add(event.eventId)
+            return
+        }
+
+        existingPollSummary.closedTime = event.originServerTs
+    }
+
     private fun handleInitialAggregatedRelations(realm: Realm,
                                                  event: Event,
                                                  roomId: String,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/prune/RedactionEventProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/prune/RedactionEventProcessor.kt
index 23b776781664b5bf62756aa91940e1c101f95539..5ae4007c639863d9eed4e4528a1acdda213dc8ac 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/prune/RedactionEventProcessor.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/prune/RedactionEventProcessor.kt
@@ -70,7 +70,8 @@ internal class RedactionEventProcessor @Inject constructor() : EventInsertLivePr
         } else {
             when (typeToPrune) {
                 EventType.ENCRYPTED,
-                EventType.MESSAGE -> {
+                EventType.MESSAGE,
+                EventType.POLL_START -> {
                     Timber.d("REDACTION for message ${eventToPrune.eventId}")
                     val unsignedData = EventMapper.map(eventToPrune).unsignedData
                             ?: UnsignedData(null, null)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/DefaultSendService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/DefaultSendService.kt
index 77aadef6bd3560e7f705c57cd5c1afad076d2794..d3162aef796c76e2034e174463dae9b60673f091 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/DefaultSendService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/DefaultSendService.kt
@@ -103,8 +103,14 @@ internal class DefaultSendService @AssistedInject constructor(
                 .let { sendEvent(it) }
     }
 
-    override fun sendOptionsReply(pollEventId: String, optionIndex: Int, optionValue: String): Cancelable {
-        return localEchoEventFactory.createOptionsReplyEvent(roomId, pollEventId, optionIndex, optionValue)
+    override fun voteToPoll(pollEventId: String, answerId: String): Cancelable {
+        return localEchoEventFactory.createPollReplyEvent(roomId, pollEventId, answerId)
+                .also { createLocalEcho(it) }
+                .let { sendEvent(it) }
+    }
+
+    override fun endPoll(pollEventId: String): Cancelable {
+        return localEchoEventFactory.createEndPollEvent(roomId, pollEventId)
                 .also { createLocalEcho(it) }
                 .let { sendEvent(it) }
     }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt
index a31d0cdec3798563c3742e8dfb0ab3b03678e021..85b22628d7813b939e3fc5663a3996751db68f59 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt
@@ -35,6 +35,7 @@ import org.matrix.android.sdk.api.session.room.model.message.ImageInfo
 import org.matrix.android.sdk.api.session.room.model.message.MessageAudioContent
 import org.matrix.android.sdk.api.session.room.model.message.MessageContent
 import org.matrix.android.sdk.api.session.room.model.message.MessageContentWithFormattedBody
+import org.matrix.android.sdk.api.session.room.model.message.MessageEndPollContent
 import org.matrix.android.sdk.api.session.room.model.message.MessageFileContent
 import org.matrix.android.sdk.api.session.room.model.message.MessageFormat
 import org.matrix.android.sdk.api.session.room.model.message.MessageImageContent
@@ -46,6 +47,7 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageVideoContent
 import org.matrix.android.sdk.api.session.room.model.message.PollAnswer
 import org.matrix.android.sdk.api.session.room.model.message.PollCreationInfo
 import org.matrix.android.sdk.api.session.room.model.message.PollQuestion
+import org.matrix.android.sdk.api.session.room.model.message.PollResponse
 import org.matrix.android.sdk.api.session.room.model.message.ThumbnailInfo
 import org.matrix.android.sdk.api.session.room.model.message.VideoInfo
 import org.matrix.android.sdk.api.session.room.model.relation.ReactionContent
@@ -122,19 +124,28 @@ internal class LocalEchoEventFactory @Inject constructor(
                 ))
     }
 
-    fun createOptionsReplyEvent(roomId: String,
-                                pollEventId: String,
-                                optionIndex: Int,
-                                optionLabel: String): Event {
-        return createMessageEvent(roomId,
-                MessagePollResponseContent(
-                        body = optionLabel,
-                        relatesTo = RelationDefaultContent(
-                                type = RelationType.RESPONSE,
-                                option = optionIndex,
-                                eventId = pollEventId)
+    fun createPollReplyEvent(roomId: String,
+                             pollEventId: String,
+                             answerId: String): Event {
+        val content = MessagePollResponseContent(
+                body = answerId,
+                relatesTo = RelationDefaultContent(
+                        type = RelationType.REFERENCE,
+                        eventId = pollEventId),
+                response = PollResponse(
+                        answers = listOf(answerId)
+                )
 
-                ))
+        )
+        val localId = LocalEcho.createLocalEchoId()
+        return Event(
+                roomId = roomId,
+                originServerTs = dummyOriginServerTs(),
+                senderId = userId,
+                eventId = localId,
+                type = EventType.POLL_RESPONSE,
+                content = content.toContent(),
+                unsignedData = UnsignedData(age = null, transactionId = localId))
     }
 
     fun createPollEvent(roomId: String,
@@ -147,7 +158,7 @@ internal class LocalEchoEventFactory @Inject constructor(
                         ),
                         answers = options.mapIndexed { index, option ->
                             PollAnswer(
-                                    id = index.toString(),
+                                    id = "$index-$option",
                                     answer = option
                             )
                         }
@@ -164,6 +175,25 @@ internal class LocalEchoEventFactory @Inject constructor(
                 unsignedData = UnsignedData(age = null, transactionId = localId))
     }
 
+    fun createEndPollEvent(roomId: String,
+                           eventId: String): Event {
+        val content = MessageEndPollContent(
+                relatesTo = RelationDefaultContent(
+                        type = RelationType.REFERENCE,
+                        eventId = eventId
+                )
+        )
+        val localId = LocalEcho.createLocalEchoId()
+        return Event(
+                roomId = roomId,
+                originServerTs = dummyOriginServerTs(),
+                senderId = userId,
+                eventId = localId,
+                type = EventType.POLL_END,
+                content = content.toContent(),
+                unsignedData = UnsignedData(age = null, transactionId = localId))
+    }
+
     fun createReplaceTextOfReply(roomId: String,
                                  eventReplaced: TimelineEvent,
                                  originalEvent: TimelineEvent,
@@ -413,7 +443,7 @@ internal class LocalEchoEventFactory @Inject constructor(
         when (content?.msgType) {
             MessageType.MSGTYPE_EMOTE,
             MessageType.MSGTYPE_TEXT,
-            MessageType.MSGTYPE_NOTICE -> {
+            MessageType.MSGTYPE_NOTICE     -> {
                 var formattedText: String? = null
                 if (content is MessageContentWithFormattedBody) {
                     formattedText = content.matrixFormattedBody
@@ -424,11 +454,12 @@ internal class LocalEchoEventFactory @Inject constructor(
                     TextContent(content.body, formattedText)
                 }
             }
-            MessageType.MSGTYPE_FILE   -> return TextContent("sent a file.")
-            MessageType.MSGTYPE_AUDIO  -> return TextContent("sent an audio file.")
-            MessageType.MSGTYPE_IMAGE  -> return TextContent("sent an image.")
-            MessageType.MSGTYPE_VIDEO  -> return TextContent("sent a video.")
-            else                       -> return TextContent(content?.body ?: "")
+            MessageType.MSGTYPE_FILE       -> return TextContent("sent a file.")
+            MessageType.MSGTYPE_AUDIO      -> return TextContent("sent an audio file.")
+            MessageType.MSGTYPE_IMAGE      -> return TextContent("sent an image.")
+            MessageType.MSGTYPE_VIDEO      -> return TextContent("sent a video.")
+            MessageType.MSGTYPE_POLL_START -> return TextContent((content as? MessagePollContent)?.pollCreationInfo?.question?.question ?: "")
+            else                           -> return TextContent(content?.body ?: "")
         }
     }
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/MultipleEventSendingDispatcherWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/MultipleEventSendingDispatcherWorker.kt
index 16a9eba3636192aa17b258896f1dbd5535488fae..f44c255f1e0a1620acbdffcaa5abb94f580b8153 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/MultipleEventSendingDispatcherWorker.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/MultipleEventSendingDispatcherWorker.kt
@@ -21,6 +21,7 @@ import androidx.work.OneTimeWorkRequest
 import androidx.work.WorkerParameters
 import com.squareup.moshi.JsonClass
 import org.matrix.android.sdk.api.session.room.send.SendState
+import org.matrix.android.sdk.internal.SessionManager
 import org.matrix.android.sdk.internal.di.WorkManagerProvider
 import org.matrix.android.sdk.internal.session.SessionComponent
 import org.matrix.android.sdk.internal.session.content.UploadContentWorker
@@ -38,8 +39,8 @@ import javax.inject.Inject
  * Possible previous worker: Always [UploadContentWorker]
  * Possible next worker    : None, but it will post new work to send events, encrypted or not
  */
-internal class MultipleEventSendingDispatcherWorker(context: Context, params: WorkerParameters) :
-    SessionSafeCoroutineWorker<MultipleEventSendingDispatcherWorker.Params>(context, params, Params::class.java) {
+internal class MultipleEventSendingDispatcherWorker(context: Context, params: WorkerParameters, sessionManager: SessionManager) :
+    SessionSafeCoroutineWorker<MultipleEventSendingDispatcherWorker.Params>(context, params, sessionManager, Params::class.java) {
 
     @JsonClass(generateAdapter = true)
     internal data class Params(
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/RedactEventWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/RedactEventWorker.kt
index b4436bfcbf616e7fe5be4d3e60927ce04be0dcb3..c03d1fa81e1dba968ad255ad852d6ffc63721290 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/RedactEventWorker.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/RedactEventWorker.kt
@@ -19,6 +19,7 @@ import android.content.Context
 import androidx.work.WorkerParameters
 import com.squareup.moshi.JsonClass
 import org.matrix.android.sdk.api.failure.Failure
+import org.matrix.android.sdk.internal.SessionManager
 import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import org.matrix.android.sdk.internal.network.executeRequest
 import org.matrix.android.sdk.internal.session.SessionComponent
@@ -32,8 +33,8 @@ import javax.inject.Inject
  * Possible previous worker: None
  * Possible next worker    : None
  */
-internal class RedactEventWorker(context: Context, params: WorkerParameters) :
-    SessionSafeCoroutineWorker<RedactEventWorker.Params>(context, params, Params::class.java) {
+internal class RedactEventWorker(context: Context, params: WorkerParameters, sessionManager: SessionManager) :
+    SessionSafeCoroutineWorker<RedactEventWorker.Params>(context, params, sessionManager, Params::class.java) {
 
     @JsonClass(generateAdapter = true)
     internal data class Params(
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/SendEventWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/SendEventWorker.kt
index 8b7fe4b9072357f1811d4ad69441e546ecb07a5d..7f24688ecea1cb17d3570cfd900c8dee9426883d 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/SendEventWorker.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/SendEventWorker.kt
@@ -23,6 +23,7 @@ import io.realm.RealmConfiguration
 import org.matrix.android.sdk.api.failure.shouldBeRetried
 import org.matrix.android.sdk.api.session.crypto.CryptoService
 import org.matrix.android.sdk.api.session.room.send.SendState
+import org.matrix.android.sdk.internal.SessionManager
 import org.matrix.android.sdk.internal.crypto.tasks.SendEventTask
 import org.matrix.android.sdk.internal.di.SessionDatabase
 import org.matrix.android.sdk.internal.session.SessionComponent
@@ -38,9 +39,8 @@ import javax.inject.Inject
  * Possible previous worker: [EncryptEventWorker] or first worker
  * Possible next worker    : None
  */
-internal class SendEventWorker(context: Context,
-                               params: WorkerParameters) :
-    SessionSafeCoroutineWorker<SendEventWorker.Params>(context, params, Params::class.java) {
+internal class SendEventWorker(context: Context, params: WorkerParameters, sessionManager: SessionManager) :
+    SessionSafeCoroutineWorker<SendEventWorker.Params>(context, params, sessionManager, Params::class.java) {
 
     @JsonClass(generateAdapter = true)
     internal data class Params(
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncTokenStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncTokenStore.kt
index 35e561a106007346a879a681e99aa4be1173355d..869a4d425aaa57e263bdfda6de192cccc1e833f2 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncTokenStore.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncTokenStore.kt
@@ -25,9 +25,12 @@ import javax.inject.Inject
 internal class SyncTokenStore @Inject constructor(@SessionDatabase private val monarchy: Monarchy) {
 
     fun getLastToken(): String? {
-        return Realm.getInstance(monarchy.realmConfiguration).use {
+        val token = Realm.getInstance(monarchy.realmConfiguration).use {
+            // Makes sure realm is up-to-date as it's used for querying internally on non looper thread.
+            it.refresh()
             it.where(SyncEntity::class.java).findFirst()?.nextBatch
         }
+        return token
     }
 
     fun saveToken(realm: Realm, token: String?) {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncWorker.kt
index 41bb1a44a63589e19e9edfc72788cdfd1fae30b9..763cd55714b4885ded32315a3c2fc19f6799e86b 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncWorker.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncWorker.kt
@@ -21,8 +21,8 @@ import androidx.work.ExistingWorkPolicy
 import androidx.work.WorkerParameters
 import com.squareup.moshi.JsonClass
 import org.matrix.android.sdk.api.failure.isTokenError
+import org.matrix.android.sdk.internal.SessionManager
 import org.matrix.android.sdk.internal.di.WorkManagerProvider
-import org.matrix.android.sdk.internal.network.NetworkConnectivityChecker
 import org.matrix.android.sdk.internal.session.SessionComponent
 import org.matrix.android.sdk.internal.session.sync.SyncPresence
 import org.matrix.android.sdk.internal.session.sync.SyncTask
@@ -41,9 +41,8 @@ private const val DEFAULT_DELAY_TIMEOUT = 30_000L
  * Possible previous worker: None
  * Possible next worker    : None
  */
-internal class SyncWorker(context: Context,
-                          workerParameters: WorkerParameters
-) : SessionSafeCoroutineWorker<SyncWorker.Params>(context, workerParameters, Params::class.java) {
+internal class SyncWorker(context: Context, workerParameters: WorkerParameters, sessionManager: SessionManager) :
+    SessionSafeCoroutineWorker<SyncWorker.Params>(context, workerParameters, sessionManager, Params::class.java) {
 
     @JsonClass(generateAdapter = true)
     internal data class Params(
@@ -56,7 +55,6 @@ internal class SyncWorker(context: Context,
 
     @Inject lateinit var syncTask: SyncTask
     @Inject lateinit var taskExecutor: TaskExecutor
-    @Inject lateinit var networkConnectivityChecker: NetworkConnectivityChecker
     @Inject lateinit var workManagerProvider: WorkManagerProvider
 
     override fun injectWith(injector: SessionComponent) {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/terms/DefaultTermsService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/terms/DefaultTermsService.kt
index d40fd8d076cf64d49f1aea8f8a336726763dd407..c52c6a404edbd8c341c745eca9149dfb80b5bf04 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/terms/DefaultTermsService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/terms/DefaultTermsService.kt
@@ -18,10 +18,13 @@ package org.matrix.android.sdk.internal.session.terms
 
 import dagger.Lazy
 import okhttp3.OkHttpClient
+import org.matrix.android.sdk.api.auth.data.LoginFlowTypes
+import org.matrix.android.sdk.api.failure.toRegistrationFlowResponse
 import org.matrix.android.sdk.api.session.accountdata.UserAccountDataTypes
 import org.matrix.android.sdk.api.session.events.model.toModel
 import org.matrix.android.sdk.api.session.terms.GetTermsResponse
 import org.matrix.android.sdk.api.session.terms.TermsService
+import org.matrix.android.sdk.api.util.JsonDict
 import org.matrix.android.sdk.internal.di.UnauthenticatedWithCertificate
 import org.matrix.android.sdk.internal.network.NetworkConstants
 import org.matrix.android.sdk.internal.network.RetrofitFactory
@@ -55,6 +58,27 @@ internal class DefaultTermsService @Inject constructor(
         return GetTermsResponse(termsResponse, getAlreadyAcceptedTermUrlsFromAccountData())
     }
 
+    /**
+     * We use a trick here to get the homeserver T&C, we use the register API
+     */
+    override suspend fun getHomeserverTerms(baseUrl: String): TermsResponse {
+        return try {
+            executeRequest(null) {
+                termsAPI.register(baseUrl + NetworkConstants.URI_API_PREFIX_PATH_R0 + "register")
+            }
+            // Return empty result if it succeed, but it should never happen
+            TermsResponse()
+        } catch (throwable: Throwable) {
+            @Suppress("UNCHECKED_CAST")
+            TermsResponse(
+                    policies = (throwable.toRegistrationFlowResponse()
+                            ?.params
+                            ?.get(LoginFlowTypes.TERMS) as? JsonDict)
+                            ?.get("policies") as? JsonDict
+            )
+        }
+    }
+
     override suspend fun agreeToTerms(serviceType: TermsService.ServiceType,
                                       baseUrl: String,
                                       agreedUrls: List<String>,
@@ -91,7 +115,7 @@ internal class DefaultTermsService @Inject constructor(
     private fun buildUrl(baseUrl: String, serviceType: TermsService.ServiceType): String {
         val servicePath = when (serviceType) {
             TermsService.ServiceType.IntegrationManager -> NetworkConstants.URI_INTEGRATION_MANAGER_PATH
-            TermsService.ServiceType.IdentityService -> NetworkConstants.URI_IDENTITY_PATH_V2
+            TermsService.ServiceType.IdentityService    -> NetworkConstants.URI_IDENTITY_PATH_V2
         }
         return "${baseUrl.ensureTrailingSlash()}$servicePath"
     }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/terms/TermsAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/terms/TermsAPI.kt
index 91d27030de905d3d20172a2decd09eee08568005..fb6aff5a9ed4aaa626d2ccb4631af0a9edfd2e12 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/terms/TermsAPI.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/terms/TermsAPI.kt
@@ -16,6 +16,8 @@
 
 package org.matrix.android.sdk.internal.session.terms
 
+import org.matrix.android.sdk.api.util.JsonDict
+import org.matrix.android.sdk.api.util.emptyJsonDict
 import org.matrix.android.sdk.internal.network.HttpHeaders
 import retrofit2.http.Body
 import retrofit2.http.GET
@@ -37,4 +39,12 @@ internal interface TermsAPI {
     suspend fun agreeToTerms(@Url url: String,
                              @Body params: AcceptTermsBody,
                              @Header(HttpHeaders.Authorization) token: String)
+
+    /**
+     * API to retrieve the terms for a homeserver. The API /terms does not exist yet, so retrieve the terms from the login flow.
+     * We do not care about the result (Credentials)
+     */
+    @POST
+    suspend fun register(@Url url: String,
+                         @Body body: JsonDict = emptyJsonDict)
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/BackgroundDetectionObserver.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/BackgroundDetectionObserver.kt
index 3e977b31fb3173c7705fdfa61466fe34ba614d48..9c8b36a3ed000d5b56c179e00c2717b6372c5d23 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/BackgroundDetectionObserver.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/BackgroundDetectionObserver.kt
@@ -18,26 +18,32 @@ package org.matrix.android.sdk.internal.util
 
 import androidx.lifecycle.DefaultLifecycleObserver
 import androidx.lifecycle.LifecycleOwner
-import org.matrix.android.sdk.internal.di.MatrixScope
 import timber.log.Timber
-import javax.inject.Inject
 
-/**
- * To be attached to ProcessLifecycleOwner lifecycle
- */
-@MatrixScope
-internal class BackgroundDetectionObserver @Inject constructor() : DefaultLifecycleObserver {
+interface BackgroundDetectionObserver : DefaultLifecycleObserver {
+    val isInBackground: Boolean
+
+    fun register(listener: Listener)
+    fun unregister(listener: Listener)
+
+    interface Listener {
+        fun onMoveToForeground()
+        fun onMoveToBackground()
+    }
+}
+
+internal class DefaultBackgroundDetectionObserver : BackgroundDetectionObserver {
 
-    var isInBackground: Boolean = true
+    override var isInBackground: Boolean = true
         private set
 
-    private val listeners = LinkedHashSet<Listener>()
+    private val listeners = LinkedHashSet<BackgroundDetectionObserver.Listener>()
 
-    fun register(listener: Listener) {
+    override fun register(listener: BackgroundDetectionObserver.Listener) {
         listeners.add(listener)
     }
 
-    fun unregister(listener: Listener) {
+    override fun unregister(listener: BackgroundDetectionObserver.Listener) {
         listeners.remove(listener)
     }
 
@@ -52,9 +58,4 @@ internal class BackgroundDetectionObserver @Inject constructor() : DefaultLifecy
         isInBackground = true
         listeners.forEach { it.onMoveToBackground() }
     }
-
-    interface Listener {
-        fun onMoveToForeground()
-        fun onMoveToBackground()
-    }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/worker/MatrixWorkerFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/worker/MatrixWorkerFactory.kt
index b58cab99b5d4fbcf6ab5f524965483880d8e175e..0b451e9c34f7621a0a02da13ad658c81e87d6836 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/worker/MatrixWorkerFactory.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/worker/MatrixWorkerFactory.kt
@@ -17,16 +17,33 @@
 package org.matrix.android.sdk.internal.worker
 
 import android.content.Context
+import androidx.work.CoroutineWorker
 import androidx.work.ListenableWorker
 import androidx.work.WorkerFactory
 import androidx.work.WorkerParameters
+import org.matrix.android.sdk.internal.SessionManager
+import org.matrix.android.sdk.internal.crypto.CancelGossipRequestWorker
+import org.matrix.android.sdk.internal.crypto.SendGossipRequestWorker
+import org.matrix.android.sdk.internal.crypto.SendGossipWorker
+import org.matrix.android.sdk.internal.crypto.crosssigning.UpdateTrustWorker
+import org.matrix.android.sdk.internal.crypto.verification.SendVerificationMessageWorker
+import org.matrix.android.sdk.internal.di.MatrixScope
+import org.matrix.android.sdk.internal.session.content.UploadContentWorker
+import org.matrix.android.sdk.internal.session.group.GetGroupDataWorker
+import org.matrix.android.sdk.internal.session.pushers.AddPusherWorker
+import org.matrix.android.sdk.internal.session.room.send.MultipleEventSendingDispatcherWorker
+import org.matrix.android.sdk.internal.session.room.send.RedactEventWorker
+import org.matrix.android.sdk.internal.session.room.send.SendEventWorker
+import org.matrix.android.sdk.internal.session.sync.job.SyncWorker
 import timber.log.Timber
 import javax.inject.Inject
-import javax.inject.Provider
 
-class MatrixWorkerFactory @Inject constructor(
-        private val workerFactories: Map<Class<out ListenableWorker>, @JvmSuppressWildcards Provider<DelegateWorkerFactory>>
-) : WorkerFactory() {
+/**
+ * This factory is responsible of creating Workers by giving the session manager.
+ * This is not the cleanest way but getting SessionComponent is dependant of args type.
+ */
+@MatrixScope
+internal class MatrixWorkerFactory @Inject constructor(private val sessionManager: SessionManager) : WorkerFactory() {
 
     override fun createWorker(
             appContext: Context,
@@ -34,11 +51,61 @@ class MatrixWorkerFactory @Inject constructor(
             workerParameters: WorkerParameters
     ): ListenableWorker? {
         Timber.d("MatrixWorkerFactory.createWorker for $workerClassName")
+        return when (workerClassName) {
+            CheckFactoryWorker::class.java.name                   ->
+                CheckFactoryWorker(appContext, workerParameters, true)
+            AddPusherWorker::class.java.name                      ->
+                AddPusherWorker(appContext, workerParameters, sessionManager)
+            CancelGossipRequestWorker::class.java.name            ->
+                CancelGossipRequestWorker(appContext, workerParameters, sessionManager)
+            GetGroupDataWorker::class.java.name                   ->
+                GetGroupDataWorker(appContext, workerParameters, sessionManager)
+            MultipleEventSendingDispatcherWorker::class.java.name ->
+                MultipleEventSendingDispatcherWorker(appContext, workerParameters, sessionManager)
+            RedactEventWorker::class.java.name                    ->
+                RedactEventWorker(appContext, workerParameters, sessionManager)
+            SendEventWorker::class.java.name                      ->
+                SendEventWorker(appContext, workerParameters, sessionManager)
+            SendGossipRequestWorker::class.java.name              ->
+                SendGossipRequestWorker(appContext, workerParameters, sessionManager)
+            SendGossipWorker::class.java.name                     ->
+                SendGossipWorker(appContext, workerParameters, sessionManager)
+            SendVerificationMessageWorker::class.java.name        ->
+                SendVerificationMessageWorker(appContext, workerParameters, sessionManager)
+            SyncWorker::class.java.name                           ->
+                SyncWorker(appContext, workerParameters, sessionManager)
+            UpdateTrustWorker::class.java.name                    ->
+                UpdateTrustWorker(appContext, workerParameters, sessionManager)
+            UploadContentWorker::class.java.name                  ->
+                UploadContentWorker(appContext, workerParameters, sessionManager)
+            else                                                  -> {
+                Timber.w("No worker defined on MatrixWorkerFactory for $workerClassName will delegate to default.")
+                // Return null to delegate to the default WorkerFactory.
+                null
+            }
+        }
+    }
+
+    /**
+     * This worker is launched by the factory with the isCreatedByMatrixWorkerFactory flag to true.
+     * If the MatrixWorkerFactory is not set up, it will default to the other constructor and it will throw
+     */
+    class CheckFactoryWorker(context: Context,
+                             workerParameters: WorkerParameters,
+                             private val isCreatedByMatrixWorkerFactory: Boolean) :
+        CoroutineWorker(context, workerParameters) {
+
+        // Called by WorkManager if there is no MatrixWorkerFactory
+        constructor(context: Context, workerParameters: WorkerParameters) : this(context,
+                workerParameters,
+                isCreatedByMatrixWorkerFactory = false)
 
-        val foundEntry =
-                workerFactories.entries.find { Class.forName(workerClassName).isAssignableFrom(it.key) }
-        val factoryProvider = foundEntry?.value
-                ?: throw IllegalArgumentException("unknown worker class name: $workerClassName")
-        return factoryProvider.get().create(appContext, workerParameters)
+        override suspend fun doWork(): Result {
+            return if (!isCreatedByMatrixWorkerFactory) {
+                Result.failure()
+            } else {
+                Result.success()
+            }
+        }
     }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/worker/SessionSafeCoroutineWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/worker/SessionSafeCoroutineWorker.kt
index d4179e22722e82e07892cd1969add23fdb01abfa..334c4580e91dcb254d619a587c18d58fb808a0bd 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/worker/SessionSafeCoroutineWorker.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/worker/SessionSafeCoroutineWorker.kt
@@ -22,6 +22,7 @@ import androidx.work.CoroutineWorker
 import androidx.work.Data
 import androidx.work.WorkerParameters
 import com.squareup.moshi.JsonClass
+import org.matrix.android.sdk.internal.SessionManager
 import org.matrix.android.sdk.internal.session.SessionComponent
 import timber.log.Timber
 
@@ -33,6 +34,7 @@ import timber.log.Timber
 internal abstract class SessionSafeCoroutineWorker<PARAM : SessionWorkerParams>(
         context: Context,
         workerParameters: WorkerParameters,
+        private val sessionManager: SessionManager,
         private val paramClass: Class<PARAM>
 ) : CoroutineWorker(context, workerParameters) {
 
@@ -48,7 +50,7 @@ internal abstract class SessionSafeCoroutineWorker<PARAM : SessionWorkerParams>(
                         .also { Timber.e("Unable to parse work parameters") }
 
         return try {
-            val sessionComponent = getSessionComponent(params.sessionId)
+            val sessionComponent = sessionManager.getSessionComponent(params.sessionId)
                     ?: return buildErrorResult(params, "No session")
 
             // Make sure to inject before handling error as you may need some dependencies to process them.
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/worker/Worker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/worker/Worker.kt
deleted file mode 100644
index 24035355f7575eec397353fc269b6db0252b4af0..0000000000000000000000000000000000000000
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/worker/Worker.kt
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright 2020 The Matrix.org Foundation C.I.C.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.matrix.android.sdk.internal.worker
-
-import androidx.work.ListenableWorker
-import org.matrix.android.sdk.api.Matrix
-import org.matrix.android.sdk.internal.session.SessionComponent
-
-internal fun ListenableWorker.getSessionComponent(sessionId: String): SessionComponent? {
-    return Matrix.getInstance(applicationContext).sessionManager.getSessionComponent(sessionId)
-}