diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle
index 446edfbae000ca7c4037273be3c0643250b40142..836b49b3f2dd13315263bad5854ef72cec77e8cb 100644
--- a/matrix-sdk-android/build.gradle
+++ b/matrix-sdk-android/build.gradle
@@ -1,7 +1,7 @@
 apply plugin: 'com.android.library'
 apply plugin: 'kotlin-android'
 apply plugin: 'kotlin-kapt'
-apply plugin: 'kotlin-android-extensions'
+apply plugin: 'kotlin-parcelize'
 apply plugin: 'realm-android'
 
 buildscript {
@@ -13,19 +13,15 @@ buildscript {
     }
 }
 
-androidExtensions {
-    experimental = true
-}
-
 android {
-    compileSdkVersion 29
+    compileSdkVersion 30
     testOptions.unitTests.includeAndroidResources = true
 
     defaultConfig {
         minSdkVersion 21
-        targetSdkVersion 29
+        targetSdkVersion 30
         versionCode 1
-        versionName "1.0.13"
+        versionName "0.0.1"
         // Multidex is useful for tests
         multiDexEnabled true
         testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
@@ -116,7 +112,7 @@ dependencies {
     def lifecycle_version = '2.2.0'
     def arch_version = '2.1.0'
     def markwon_version = '3.1.0'
-    def daggerVersion = '2.29.1'
+    def daggerVersion = '2.31'
     def work_version = '2.4.0'
     def retrofit_version = '2.6.2'
 
@@ -164,16 +160,11 @@ dependencies {
     // DI
     implementation "com.google.dagger:dagger:$daggerVersion"
     kapt "com.google.dagger:dagger-compiler:$daggerVersion"
-    compileOnly 'com.squareup.inject:assisted-inject-annotations-dagger2:0.5.0'
-    kapt 'com.squareup.inject:assisted-inject-processor-dagger2:0.5.0'
 
     // Logging
     implementation 'com.jakewharton.timber:timber:4.7.1'
     implementation 'com.facebook.stetho:stetho-okhttp3:1.5.1'
 
-    // Bus
-    implementation 'org.greenrobot:eventbus:3.1.1'
-
     // Phone number https://github.com/google/libphonenumber
     implementation 'com.googlecode.libphonenumber:libphonenumber:8.10.23'
 
diff --git a/matrix-sdk-android/proguard-rules.pro b/matrix-sdk-android/proguard-rules.pro
index fa860d8049e259f7494b4a2b0621eecadee2f365..182f9473e84b53602ecd5d24e3b20bca048653a6 100644
--- a/matrix-sdk-android/proguard-rules.pro
+++ b/matrix-sdk-android/proguard-rules.pro
@@ -20,14 +20,8 @@
 # hide the original source file name.
 #-renamesourcefileattribute SourceFile
 
-
-### EVENT BUS ###
-
+# BMA: Not sure I can delete this one without side effect
 -keepattributes *Annotation*
--keepclassmembers class * {
-    @org.greenrobot.eventbus.Subscribe <methods>;
-}
--keep enum org.greenrobot.eventbus.ThreadMode { *; }
 
 ### MOSHI ###
 
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/account/DeactivateAccountTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/account/DeactivateAccountTest.kt
index 9996eef0a87eac74106c3544483af0008521206b..b0df6fcb44ba66b82ca490721e01584d667a0539 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/account/DeactivateAccountTest.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/account/DeactivateAccountTest.kt
@@ -16,8 +16,18 @@
 
 package org.matrix.android.sdk.account
 
+import org.junit.Assert.assertTrue
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.junit.runners.MethodSorters
 import org.matrix.android.sdk.InstrumentedTest
+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.data.LoginFlowResult
+import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse
 import org.matrix.android.sdk.api.auth.registration.RegistrationResult
 import org.matrix.android.sdk.api.failure.Failure
 import org.matrix.android.sdk.api.failure.MatrixError
@@ -25,12 +35,8 @@ import org.matrix.android.sdk.common.CommonTestHelper
 import org.matrix.android.sdk.common.SessionTestParams
 import org.matrix.android.sdk.common.TestConstants
 import org.matrix.android.sdk.common.TestMatrixCallback
-import org.junit.Assert.assertTrue
-import org.junit.FixMethodOrder
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-import org.junit.runners.MethodSorters
+import kotlin.coroutines.Continuation
+import kotlin.coroutines.resume
 
 @RunWith(JUnit4::class)
 @FixMethodOrder(MethodSorters.JVM)
@@ -44,7 +50,18 @@ class DeactivateAccountTest : InstrumentedTest {
 
         // Deactivate the account
         commonTestHelper.runBlockingTest {
-            session.deactivateAccount(TestConstants.PASSWORD, false)
+            session.deactivateAccount(
+                    object : UserInteractiveAuthInterceptor {
+                        override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
+                            promise.resume(
+                                    UserPasswordAuth(
+                                            user = session.myUserId,
+                                            password = TestConstants.PASSWORD,
+                                            session = flowResponse.session
+                                    )
+                            )
+                        }
+                    }, false)
         }
 
         // Try to login on the previous account, it will fail (M_USER_DEACTIVATED)
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 0e7088a6a5a6dec08b83147f063adc5d73e8a0e8..a4dbd70b116bbf97e14ff3ed58e31a7e6445d613 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
@@ -86,7 +86,7 @@ class CommonTestHelper(context: Context) {
      *
      * @param session    the session to sync
      */
-    fun syncSession(session: Session) {
+    fun syncSession(session: Session, timeout: Long = TestConstants.timeOutMillis) {
         val lock = CountDownLatch(1)
 
         val job = GlobalScope.launch(Dispatchers.Main) {
@@ -109,7 +109,7 @@ class CommonTestHelper(context: Context) {
         }
         GlobalScope.launch(Dispatchers.Main) { syncLiveData.observeForever(syncObserver) }
 
-        await(lock)
+        await(lock, timeout)
     }
 
     /**
@@ -119,7 +119,7 @@ class CommonTestHelper(context: Context) {
      * @param message      the message to send
      * @param nbOfMessages the number of time the message will be sent
      */
-    fun sendTextMessage(room: Room, message: String, nbOfMessages: Int): List<TimelineEvent> {
+    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)
@@ -151,7 +151,7 @@ class CommonTestHelper(context: Context) {
             room.sendTextMessage(message + " #" + (i + 1))
         }
         // Wait 3 second more per message
-        await(latch, timeout = TestConstants.timeOutMillis + 3_000L * nbOfMessages)
+        await(latch, timeout = timeout + 3_000L * nbOfMessages)
         timeline.dispose()
 
         // Check that all events has been created
@@ -215,14 +215,14 @@ class CommonTestHelper(context: Context) {
                     .getLoginFlow(hs, it)
         }
 
-        doSync<RegistrationResult> {
+        doSync<RegistrationResult>(timeout = 60_000) {
             matrix.authenticationService
                     .getRegistrationWizard()
                     .createAccount(userName, password, null, it)
         }
 
         // Perform dummy step
-        val registrationResult = doSync<RegistrationResult> {
+        val registrationResult = doSync<RegistrationResult>(timeout = 60_000) {
             matrix.authenticationService
                     .getRegistrationWizard()
                     .dummy(it)
@@ -231,7 +231,7 @@ class CommonTestHelper(context: Context) {
         assertTrue(registrationResult is RegistrationResult.Success)
         val session = (registrationResult as RegistrationResult.Success).session
         if (sessionTestParams.withInitialSync) {
-            syncSession(session)
+            syncSession(session, 60_000)
         }
 
         return session
@@ -378,7 +378,9 @@ class CommonTestHelper(context: Context) {
     fun Iterable<Session>.signOutAndClose() = forEach { signOutAndClose(it) }
 
     fun signOutAndClose(session: Session) {
-        doSync<Unit>(60_000) { session.signOut(true, it) }
+        runBlockingTest(timeout = 60_000) {
+            session.signOut(true)
+        }
         // no need signout will close
         // session.close()
     }
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CryptoTestData.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CryptoTestData.kt
index 76e59d9a902658035a2463f3386d1a884f47c71f..b6bedbd7197eff6d73c3d54a063600b81f463791 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CryptoTestData.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CryptoTestData.kt
@@ -18,14 +18,21 @@ package org.matrix.android.sdk.common
 
 import org.matrix.android.sdk.api.session.Session
 
-data class CryptoTestData(val firstSession: Session,
-                          val roomId: String,
-                          val secondSession: Session? = null,
-                          val thirdSession: Session? = null) {
+data class CryptoTestData(val roomId: String,
+                          val sessions: List<Session>) {
+
+    val firstSession: Session
+        get() = sessions.first()
+
+    val secondSession: Session?
+        get() = sessions.getOrNull(1)
+
+    val thirdSession: Session?
+        get() = sessions.getOrNull(2)
 
     fun cleanUp(testHelper: CommonTestHelper) {
-        testHelper.signOutAndClose(firstSession)
-        secondSession?.let { testHelper.signOutAndClose(it) }
-        thirdSession?.let { testHelper.signOutAndClose(it) }
+        sessions.forEach {
+            testHelper.signOutAndClose(it)
+        }
     }
 }
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 cbb22daf0f14ab7d4eaa089836408aaf5d929ecf..eb7e4a9fbec2c3a0f68b242b6f98f2a7cb01f71f 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,6 +19,18 @@ 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
+import org.junit.Assert.assertTrue
+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.session.Session
 import org.matrix.android.sdk.api.session.crypto.verification.IncomingSasVerificationTransaction
 import org.matrix.android.sdk.api.session.crypto.verification.OutgoingSasVerificationTransaction
@@ -36,17 +48,10 @@ 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 org.matrix.android.sdk.internal.crypto.model.rest.UserPasswordAuth
-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
-import org.junit.Assert.assertTrue
 import java.util.UUID
 import java.util.concurrent.CountDownLatch
+import kotlin.coroutines.Continuation
+import kotlin.coroutines.resume
 
 class CryptoTestHelper(private val mTestHelper: CommonTestHelper) {
 
@@ -73,7 +78,7 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) {
             }
         }
 
-        return CryptoTestData(aliceSession, roomId)
+        return CryptoTestData(roomId, listOf(aliceSession))
     }
 
     /**
@@ -139,7 +144,7 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) {
 //        assertNotNull(roomFromBobPOV.powerLevels)
 //        assertTrue(roomFromBobPOV.powerLevels.maySendMessage(bobSession.myUserId))
 
-        return CryptoTestData(aliceSession, aliceRoomId, bobSession)
+        return CryptoTestData(aliceRoomId, listOf(aliceSession, bobSession))
     }
 
     /**
@@ -157,7 +162,7 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) {
         // wait the initial sync
         SystemClock.sleep(1000)
 
-        return CryptoTestData(aliceSession, aliceRoomId, cryptoTestData.secondSession, samSession)
+        return CryptoTestData(aliceRoomId, listOf(aliceSession, cryptoTestData.secondSession!!, samSession))
     }
 
     /**
@@ -304,10 +309,18 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) {
     fun initializeCrossSigning(session: Session) {
         mTestHelper.doSync<Unit> {
             session.cryptoService().crossSigningService()
-                    .initializeCrossSigning(UserPasswordAuth(
-                            user = session.myUserId,
-                            password = TestConstants.PASSWORD
-                    ), it)
+                    .initializeCrossSigning(
+                            object : UserInteractiveAuthInterceptor {
+                                override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
+                                    promise.resume(
+                                            UserPasswordAuth(
+                                                    user = session.myUserId,
+                                                    password = TestConstants.PASSWORD,
+                                                    session = flowResponse.session
+                                            )
+                                    )
+                                }
+                            }, it)
         }
     }
 
@@ -381,4 +394,30 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) {
             }
         }
     }
+
+    fun doE2ETestWithManyMembers(numberOfMembers: Int): CryptoTestData {
+        val aliceSession = mTestHelper.createAccount(TestConstants.USER_ALICE, defaultSessionParams)
+        aliceSession.cryptoService().setWarnOnUnknownDevices(false)
+
+        val roomId = mTestHelper.doSync<String> {
+            aliceSession.createRoom(CreateRoomParams().apply { name = "MyRoom" }, it)
+        }
+        val room = aliceSession.getRoom(roomId)!!
+
+        mTestHelper.runBlockingTest {
+            room.enableEncryption()
+        }
+
+        val sessions = mutableListOf(aliceSession)
+        for (index in 1 until numberOfMembers) {
+            val session = mTestHelper.createAccount("User_$index", defaultSessionParams)
+            mTestHelper.doSync<Unit>(timeout = 600_000) { room.invite(session.myUserId, null, it) }
+            println("TEST -> " + session.myUserId + " invited")
+            mTestHelper.doSync<Unit> { session.joinRoom(room.roomId, null, emptyList(), it) }
+            println("TEST -> " + session.myUserId + " joined")
+            sessions.add(session)
+        }
+
+        return CryptoTestData(roomId, sessions)
+    }
 }
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 0e3b29118c5a149c305b2a865a3f8ec896239753..cf31294e2f9150d816bd0347474fa7ff3c85398f 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
@@ -17,7 +17,18 @@
 package org.matrix.android.sdk.internal.crypto
 
 import androidx.test.ext.junit.runners.AndroidJUnit4
+import org.amshove.kluent.shouldBe
+import org.junit.Assert
+import org.junit.Before
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
 import org.matrix.android.sdk.InstrumentedTest
+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.tryOrNull
 import org.matrix.android.sdk.api.session.crypto.MXCryptoError
 import org.matrix.android.sdk.api.session.events.model.EventType
@@ -30,19 +41,13 @@ import org.matrix.android.sdk.common.CryptoTestHelper
 import org.matrix.android.sdk.common.TestConstants
 import org.matrix.android.sdk.internal.crypto.model.OlmSessionWrapper
 import org.matrix.android.sdk.internal.crypto.model.event.EncryptedEventContent
-import org.matrix.android.sdk.internal.crypto.model.rest.UserPasswordAuth
 import org.matrix.android.sdk.internal.crypto.store.db.deserializeFromRealm
 import org.matrix.android.sdk.internal.crypto.store.db.serializeForRealm
-import org.amshove.kluent.shouldBe
-import org.junit.Assert
-import org.junit.Before
-import org.junit.FixMethodOrder
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
 import org.matrix.olm.OlmSession
 import timber.log.Timber
 import java.util.concurrent.CountDownLatch
+import kotlin.coroutines.Continuation
+import kotlin.coroutines.resume
 
 /**
  * Ref:
@@ -202,10 +207,18 @@ class UnwedgingTest : InstrumentedTest {
         // It's a trick to force key request on fail to decrypt
         mTestHelper.doSync<Unit> {
             bobSession.cryptoService().crossSigningService()
-                    .initializeCrossSigning(UserPasswordAuth(
-                            user = bobSession.myUserId,
-                            password = TestConstants.PASSWORD
-                    ), it)
+                    .initializeCrossSigning(
+                            object : UserInteractiveAuthInterceptor {
+                                override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
+                                    promise.resume(
+                                            UserPasswordAuth(
+                                                    user = bobSession.myUserId,
+                                                    password = TestConstants.PASSWORD,
+                                                    session = flowResponse.session
+                                            )
+                                    )
+                                }
+                            }, it)
         }
 
         // Wait until we received back the key
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 38c57bd22a6288c8b7e2031ee165c7e901de9623..44af87bcbe482d23dce25012e4939c58d36b1f5a 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
@@ -17,14 +17,6 @@
 package org.matrix.android.sdk.internal.crypto.crosssigning
 
 import androidx.test.ext.junit.runners.AndroidJUnit4
-import org.matrix.android.sdk.InstrumentedTest
-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.model.CryptoDeviceInfo
-import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap
-import org.matrix.android.sdk.internal.crypto.model.rest.UserPasswordAuth
 import org.junit.Assert.assertEquals
 import org.junit.Assert.assertFalse
 import org.junit.Assert.assertNotNull
@@ -35,6 +27,19 @@ import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
+import org.matrix.android.sdk.InstrumentedTest
+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.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.model.CryptoDeviceInfo
+import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap
+import kotlin.coroutines.Continuation
+import kotlin.coroutines.resume
 
 @RunWith(AndroidJUnit4::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
@@ -49,10 +54,17 @@ class XSigningTest : InstrumentedTest {
 
         mTestHelper.doSync<Unit> {
             aliceSession.cryptoService().crossSigningService()
-                    .initializeCrossSigning(UserPasswordAuth(
-                            user = aliceSession.myUserId,
-                            password = TestConstants.PASSWORD
-                    ), it)
+                    .initializeCrossSigning(object : UserInteractiveAuthInterceptor {
+                        override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
+                            promise.resume(
+                                    UserPasswordAuth(
+                                            user = aliceSession.myUserId,
+                                            password = TestConstants.PASSWORD,
+                                            session = flowResponse.session
+                                    )
+                            )
+                        }
+                    }, it)
         }
 
         val myCrossSigningKeys = aliceSession.cryptoService().crossSigningService().getMyCrossSigningKeys()
@@ -86,8 +98,18 @@ class XSigningTest : InstrumentedTest {
                 password = TestConstants.PASSWORD
         )
 
-        mTestHelper.doSync<Unit> { aliceSession.cryptoService().crossSigningService().initializeCrossSigning(aliceAuthParams, it) }
-        mTestHelper.doSync<Unit> { bobSession.cryptoService().crossSigningService().initializeCrossSigning(bobAuthParams, it) }
+        mTestHelper.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 {
+            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) }
@@ -122,8 +144,16 @@ class XSigningTest : InstrumentedTest {
                 password = TestConstants.PASSWORD
         )
 
-        mTestHelper.doSync<Unit> { aliceSession.cryptoService().crossSigningService().initializeCrossSigning(aliceAuthParams, it) }
-        mTestHelper.doSync<Unit> { bobSession.cryptoService().crossSigningService().initializeCrossSigning(bobAuthParams, it) }
+        mTestHelper.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 {
+            override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
+                promise.resume(bobAuthParams)
+            }
+        }, it) }
 
         // Check that alice can see bob keys
         val bobUserId = bobSession.myUserId
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 197e36df0686cf4ba87bf6376cf49a8ed1b66784..8c3917adc131e32027c75be629c3790de2c9582f 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
@@ -18,7 +18,21 @@ package org.matrix.android.sdk.internal.crypto.gossiping
 
 import android.util.Log
 import androidx.test.ext.junit.runners.AndroidJUnit4
+import junit.framework.TestCase.assertEquals
+import junit.framework.TestCase.assertNotNull
+import junit.framework.TestCase.assertTrue
+import junit.framework.TestCase.fail
+import org.junit.Assert
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
 import org.matrix.android.sdk.InstrumentedTest
+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.tryOrNull
 import org.matrix.android.sdk.api.session.crypto.verification.IncomingSasVerificationTransaction
 import org.matrix.android.sdk.api.session.crypto.verification.SasVerificationTransaction
 import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod
@@ -28,6 +42,7 @@ import org.matrix.android.sdk.api.session.crypto.verification.VerificationTxStat
 import org.matrix.android.sdk.api.session.events.model.toModel
 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
@@ -40,17 +55,9 @@ 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 org.matrix.android.sdk.internal.crypto.model.rest.UserPasswordAuth
-import junit.framework.TestCase.assertEquals
-import junit.framework.TestCase.assertNotNull
-import junit.framework.TestCase.assertTrue
-import junit.framework.TestCase.fail
-import org.junit.Assert
-import org.junit.FixMethodOrder
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
 import java.util.concurrent.CountDownLatch
+import kotlin.coroutines.Continuation
+import kotlin.coroutines.resume
 
 @RunWith(AndroidJUnit4::class)
 @FixMethodOrder(MethodSorters.JVM)
@@ -198,10 +205,17 @@ class KeyShareTests : InstrumentedTest {
 
         mTestHelper.doSync<Unit> {
             aliceSession1.cryptoService().crossSigningService()
-                    .initializeCrossSigning(UserPasswordAuth(
-                            user = aliceSession1.myUserId,
-                            password = TestConstants.PASSWORD
-                    ), it)
+                    .initializeCrossSigning(
+                            object : UserInteractiveAuthInterceptor {
+                                override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
+                                    promise.resume(
+                                            UserPasswordAuth(
+                                                    user = aliceSession1.myUserId,
+                                                    password = TestConstants.PASSWORD
+                                            )
+                                    )
+                                }
+                            }, it)
         }
 
         // Also bootstrap keybackup on first session
@@ -296,4 +310,93 @@ class KeyShareTests : InstrumentedTest {
         mTestHelper.signOutAndClose(aliceSession1)
         mTestHelper.signOutAndClose(aliceSession2)
     }
+
+    @Test
+    fun test_ImproperKeyShareBug() {
+        val aliceSession = mTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true))
+
+        mTestHelper.doSync<Unit> {
+            aliceSession.cryptoService().crossSigningService()
+                    .initializeCrossSigning(
+                            object : UserInteractiveAuthInterceptor {
+                                override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
+                                    promise.resume(
+                                            UserPasswordAuth(
+                                                    user = aliceSession.myUserId,
+                                                    password = TestConstants.PASSWORD,
+                                                    session = flowResponse.session
+                                            )
+                                    )
+                                }
+                            }, it)
+        }
+
+        // Create an encrypted room and send a couple of messages
+        val roomId = mTestHelper.doSync<String> {
+            aliceSession.createRoom(
+                    CreateRoomParams().apply {
+                        visibility = RoomDirectoryVisibility.PRIVATE
+                        enableEncryption()
+                    },
+                    it
+            )
+        }
+        val roomAlicePov = aliceSession.getRoom(roomId)
+        assertNotNull(roomAlicePov)
+        Thread.sleep(1_000)
+        assertTrue(roomAlicePov?.isEncrypted() == true)
+        val secondEventId = mTestHelper.sendTextMessage(roomAlicePov!!, "Message", 3)[1].eventId
+
+        // Create bob session
+
+        val bobSession = mTestHelper.createAccount(TestConstants.USER_BOB, SessionTestParams(true))
+        mTestHelper.doSync<Unit> {
+            bobSession.cryptoService().crossSigningService()
+                    .initializeCrossSigning(
+                            object : UserInteractiveAuthInterceptor {
+                                override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
+                                    promise.resume(
+                                            UserPasswordAuth(
+                                                    user = bobSession.myUserId,
+                                                    password = TestConstants.PASSWORD,
+                                                    session = flowResponse.session
+                                            )
+                                    )
+                                }
+                            }, it)
+        }
+
+        // Let alice invite bob
+        mTestHelper.doSync<Unit> {
+            roomAlicePov.invite(bobSession.myUserId, null, it)
+        }
+
+        mTestHelper.doSync<Unit> {
+            bobSession.joinRoom(roomAlicePov.roomId, null, emptyList(), it)
+        }
+
+        // we want to discard alice outbound session
+        aliceSession.cryptoService().discardOutboundSession(roomAlicePov.roomId)
+
+        // and now resend a new message to reset index to 0
+        mTestHelper.sendTextMessage(roomAlicePov, "After", 1)
+
+        val roomRoomBobPov = aliceSession.getRoom(roomId)
+        val beforeJoin = roomRoomBobPov!!.getTimeLineEvent(secondEventId)
+
+        var dRes = tryOrNull { bobSession.cryptoService().decryptEvent(beforeJoin!!.root, "") }
+
+        assert(dRes == null)
+
+        // Try to re-ask the keys
+
+        bobSession.cryptoService().reRequestRoomKeyForEvent(beforeJoin!!.root)
+
+        Thread.sleep(3_000)
+
+        // With the bug the first session would have improperly reshare that key :/
+        dRes = tryOrNull { bobSession.cryptoService().decryptEvent(beforeJoin.root, "") }
+        Log.d("#TEST", "KS: sgould not decrypt that ${beforeJoin.root.getClearContent().toModel<MessageContent>()?.body}")
+        assert(dRes?.clearEvent == null)
+    }
 }
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 1385dac1ec6a344fb0833330410cec571fbae213..397f7f9441bf467d3711a06d665bed4d9639d492 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
@@ -17,20 +17,25 @@
 package org.matrix.android.sdk.internal.crypto.verification.qrcode
 
 import androidx.test.ext.junit.runners.AndroidJUnit4
+import org.amshove.kluent.shouldBe
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
 import org.matrix.android.sdk.InstrumentedTest
+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.session.crypto.verification.PendingVerificationRequest
 import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod
 import org.matrix.android.sdk.api.session.crypto.verification.VerificationService
 import org.matrix.android.sdk.common.CommonTestHelper
 import org.matrix.android.sdk.common.CryptoTestHelper
 import org.matrix.android.sdk.common.TestConstants
-import org.matrix.android.sdk.internal.crypto.model.rest.UserPasswordAuth
-import org.matrix.android.sdk.api.session.crypto.verification.PendingVerificationRequest
-import org.amshove.kluent.shouldBe
-import org.junit.FixMethodOrder
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
 import java.util.concurrent.CountDownLatch
+import kotlin.coroutines.Continuation
+import kotlin.coroutines.resume
 
 @RunWith(AndroidJUnit4::class)
 @FixMethodOrder(MethodSorters.JVM)
@@ -157,18 +162,34 @@ class VerificationTest : InstrumentedTest {
 
         mTestHelper.doSync<Unit> { callback ->
             aliceSession.cryptoService().crossSigningService()
-                    .initializeCrossSigning(UserPasswordAuth(
-                            user = aliceSession.myUserId,
-                            password = TestConstants.PASSWORD
-                    ), callback)
+                    .initializeCrossSigning(
+                            object : UserInteractiveAuthInterceptor {
+                                override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
+                                    promise.resume(
+                                            UserPasswordAuth(
+                                                    user = aliceSession.myUserId,
+                                                    password = TestConstants.PASSWORD,
+                                                    session = flowResponse.session
+                                            )
+                                    )
+                                }
+                            }, callback)
         }
 
         mTestHelper.doSync<Unit> { callback ->
             bobSession.cryptoService().crossSigningService()
-                    .initializeCrossSigning(UserPasswordAuth(
-                            user = bobSession.myUserId,
-                            password = TestConstants.PASSWORD
-                    ), callback)
+                    .initializeCrossSigning(
+                            object : UserInteractiveAuthInterceptor {
+                                override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
+                                    promise.resume(
+                                            UserPasswordAuth(
+                                                    user = bobSession.myUserId,
+                                                    password = TestConstants.PASSWORD,
+                                                    session = flowResponse.session
+                                            )
+                                    )
+                                }
+                            }, callback)
         }
 
         val aliceVerificationService = aliceSession.cryptoService().verificationService()
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/session/media/UrlsExtractorTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/session/media/UrlsExtractorTest.kt
index 9ee84fdfc684a9c50299e0854fd065194c6d23cb..473b18b31b62b9f05fd34e1e1ed12b811d88b62c 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/session/media/UrlsExtractorTest.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/session/media/UrlsExtractorTest.kt
@@ -26,6 +26,8 @@ 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.api.session.room.model.message.MessageTextContent
 import org.matrix.android.sdk.api.session.room.model.message.MessageType
+import org.matrix.android.sdk.api.session.room.sender.SenderInfo
+import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
 
 @RunWith(AndroidJUnit4::class)
 internal class UrlsExtractorTest : InstrumentedTest {
@@ -36,6 +38,7 @@ internal class UrlsExtractorTest : InstrumentedTest {
     fun wrongEventTypeTest() {
         createEvent(body = "https://matrix.org")
                 .copy(type = EventType.STATE_ROOM_GUEST_ACCESS)
+                .toFakeTimelineEvent()
                 .let { urlsExtractor.extract(it) }
                 .size shouldBeEqualTo 0
     }
@@ -43,6 +46,7 @@ internal class UrlsExtractorTest : InstrumentedTest {
     @Test
     fun oneUrlTest() {
         createEvent(body = "https://matrix.org")
+                .toFakeTimelineEvent()
                 .let { urlsExtractor.extract(it) }
                 .let { result ->
                     result.size shouldBeEqualTo 1
@@ -53,6 +57,7 @@ internal class UrlsExtractorTest : InstrumentedTest {
     @Test
     fun withoutProtocolTest() {
         createEvent(body = "www.matrix.org")
+                .toFakeTimelineEvent()
                 .let { urlsExtractor.extract(it) }
                 .size shouldBeEqualTo 0
     }
@@ -60,6 +65,7 @@ internal class UrlsExtractorTest : InstrumentedTest {
     @Test
     fun oneUrlWithParamTest() {
         createEvent(body = "https://matrix.org?foo=bar")
+                .toFakeTimelineEvent()
                 .let { urlsExtractor.extract(it) }
                 .let { result ->
                     result.size shouldBeEqualTo 1
@@ -70,6 +76,7 @@ internal class UrlsExtractorTest : InstrumentedTest {
     @Test
     fun oneUrlWithParamsTest() {
         createEvent(body = "https://matrix.org?foo=bar&bar=foo")
+                .toFakeTimelineEvent()
                 .let { urlsExtractor.extract(it) }
                 .let { result ->
                     result.size shouldBeEqualTo 1
@@ -80,16 +87,18 @@ internal class UrlsExtractorTest : InstrumentedTest {
     @Test
     fun oneUrlInlinedTest() {
         createEvent(body = "Hello https://matrix.org, how are you?")
+                .toFakeTimelineEvent()
                 .let { urlsExtractor.extract(it) }
                 .let { result ->
                     result.size shouldBeEqualTo 1
-                    result[0] shouldBeEqualTo  "https://matrix.org"
+                    result[0] shouldBeEqualTo "https://matrix.org"
                 }
     }
 
     @Test
     fun twoUrlsTest() {
         createEvent(body = "https://matrix.org https://example.org")
+                .toFakeTimelineEvent()
                 .let { urlsExtractor.extract(it) }
                 .let { result ->
                     result.size shouldBeEqualTo 2
@@ -99,10 +108,26 @@ internal class UrlsExtractorTest : InstrumentedTest {
     }
 
     private fun createEvent(body: String): Event = Event(
+            eventId = "!fake",
             type = EventType.MESSAGE,
             content = MessageTextContent(
                     msgType = MessageType.MSGTYPE_TEXT,
                     body = body
             ).toContent()
     )
+
+    private fun Event.toFakeTimelineEvent(): TimelineEvent {
+        return TimelineEvent(
+                root = this,
+                localId = 0L,
+                eventId = eventId!!,
+                displayIndex = 0,
+                senderInfo = SenderInfo(
+                        userId = "",
+                        displayName = null,
+                        isUniqueDisplayName = true,
+                        avatarUrl = null
+                )
+        )
+    }
 }
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 34edf37733614146b295c74fab9fcbf5cd4b3617..f156a5eb647c3cb6450ec6df11a03d41439993a4 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
@@ -66,8 +66,8 @@ class TimelineForwardPaginationTest : InstrumentedTest {
                 numberOfMessagesToSend)
 
         // Alice clear the cache
-        commonTestHelper.doSync<Unit> {
-            aliceSession.clearCache(it)
+        commonTestHelper.runBlockingTest {
+            aliceSession.clearCache()
         }
 
         // And restarts the sync
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineWithManyMembersTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineWithManyMembersTest.kt
new file mode 100644
index 0000000000000000000000000000000000000000..ff07cf1d1d4ab473b389cc8e6d2509f818b5dae9
--- /dev/null
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineWithManyMembersTest.kt
@@ -0,0 +1,92 @@
+/*
+ * 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.session.room.timeline
+
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+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.common.CommonTestHelper
+import org.matrix.android.sdk.common.CryptoTestHelper
+import java.util.concurrent.CountDownLatch
+import kotlin.test.fail
+
+@RunWith(JUnit4::class)
+@FixMethodOrder(MethodSorters.JVM)
+class TimelineWithManyMembersTest : InstrumentedTest {
+
+    companion object {
+        private const val NUMBER_OF_MEMBERS = 6
+    }
+
+    private val commonTestHelper = CommonTestHelper(context())
+    private val cryptoTestHelper = CryptoTestHelper(commonTestHelper)
+
+    /**
+     * Ensures when someone sends a message to a crowded room, everyone can decrypt the message.
+     */
+    @Test
+    fun everyone_should_decrypt_message_in_a_crowded_room() {
+        val cryptoTestData = cryptoTestHelper.doE2ETestWithManyMembers(NUMBER_OF_MEMBERS)
+
+        val sessionForFirstMember = cryptoTestData.firstSession
+        val roomForFirstMember = sessionForFirstMember.getRoom(cryptoTestData.roomId)!!
+
+        val firstMessage = "First messages from Alice"
+        commonTestHelper.sendTextMessage(
+                roomForFirstMember,
+                firstMessage,
+                1,
+                600_000
+        )
+
+        for (index in 1 until cryptoTestData.sessions.size) {
+            val session = cryptoTestData.sessions[index]
+            val roomForCurrentMember = session.getRoom(cryptoTestData.roomId)!!
+            val timelineForCurrentMember = roomForCurrentMember.createTimeline(null, TimelineSettings(30))
+            timelineForCurrentMember.start()
+
+            session.startSync(true)
+
+            run {
+                val lock = CountDownLatch(1)
+                val eventsListener = commonTestHelper.createEventListener(lock) { snapshot ->
+                    snapshot
+                            .find { it.isEncrypted() }
+                            ?.let {
+                                val body = it.root.getClearContent()?.toModel<MessageContent>()?.body
+                                if (body?.startsWith(firstMessage).orFalse()) {
+                                    println("User " + session.myUserId + " decrypted as " + body)
+                                    return@createEventListener true
+                                } else {
+                                    fail("User " + session.myUserId + " decrypted as " + body + " CryptoError: " + it.root.mCryptoError)
+                                }
+                            } ?: return@createEventListener false
+                }
+                timelineForCurrentMember.addListener(eventsListener)
+                commonTestHelper.await(lock, 600_000)
+            }
+            session.stopSync()
+        }
+    }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/AuthenticationService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/AuthenticationService.kt
index 360b9558694839bd67bb32f5f73e2fc023a68331..bf21941e0c99c5ee5715bcc26b3212420a57ff00 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/AuthenticationService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/AuthenticationService.kt
@@ -41,6 +41,16 @@ interface AuthenticationService {
      */
     fun getLoginFlowOfSession(sessionId: String, callback: MatrixCallback<LoginFlowResult>): Cancelable
 
+    /**
+     * Get a SSO url
+     */
+    fun getSsoUrl(redirectUrl: String, deviceId: String?, providerId: String?): String?
+
+    /**
+     * Get the sign in or sign up fallback URL
+     */
+    fun getFallbackUrl(forSignIn: Boolean, deviceId: String?): String?
+
     /**
      * Return a LoginWizard, to login to the homeserver. The login flow has to be retrieved first.
      */
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/TokenBasedAuth.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/TokenBasedAuth.kt
new file mode 100644
index 0000000000000000000000000000000000000000..e522352c3870735644f295556c5815651664c765
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/TokenBasedAuth.kt
@@ -0,0 +1,69 @@
+/*
+ * 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.auth
+
+import com.squareup.moshi.Json
+import com.squareup.moshi.JsonClass
+import org.matrix.android.sdk.api.auth.data.LoginFlowTypes
+
+/**
+ * This class provides the authentication data by using user and password
+ */
+@JsonClass(generateAdapter = true)
+data class TokenBasedAuth(
+
+        /**
+         * This is a session identifier that the client must pass back to the homeserver,
+         * if one is provided, in subsequent attempts to authenticate in the same API call.
+         */
+        @Json(name = "session")
+        override val session: String? = null,
+
+        /**
+         * A client may receive a login token via some external service, such as email or SMS.
+         * Note that a login token is separate from an access token, the latter providing general authentication to various API endpoints.
+         */
+        @Json(name = "token")
+        val token: String? = null,
+
+        /**
+         * The txn_id should be a random string generated by the client for the request.
+         * The same txn_id should be used if retrying the request.
+         * The txn_id may be used by the server to disallow other devices from using the token,
+         * thus providing "single use" tokens while still allowing the device to retry the request.
+         * This would be done by tying the token to the txn_id server side, as well as potentially invalidating
+         * the token completely once the device has successfully logged in
+         * (e.g. when we receive a request from the newly provisioned access_token).
+         */
+        @Json(name = "txn_id")
+        val transactionId: String? = null,
+
+        // registration information
+        @Json(name = "type")
+        val type: String? = LoginFlowTypes.TOKEN
+
+) : UIABaseAuth {
+    override fun hasAuthInfo() = token != null
+
+    override fun copyWithSession(session: String) = this.copy(session = session)
+
+    override fun asMap(): Map<String, *> = mapOf(
+            "session" to session,
+            "token" to token,
+            "transactionId" to transactionId,
+            "type" to type
+    )
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/UIABaseAuth.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/UIABaseAuth.kt
new file mode 100644
index 0000000000000000000000000000000000000000..d5e323e457fe3d7b6beba22e1ebfda5b1fb2989a
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/UIABaseAuth.kt
@@ -0,0 +1,31 @@
+/*
+ * 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.auth
+
+interface UIABaseAuth {
+    /**
+     * This is a session identifier that the client must pass back to the homeserver,
+     * if one is provided, in subsequent attempts to authenticate in the same API call.
+     */
+    val session: String?
+
+    fun hasAuthInfo(): Boolean
+
+    fun copyWithSession(session: String): UIABaseAuth
+
+    fun asMap() : Map<String, *>
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/UserInteractiveAuthInterceptor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/UserInteractiveAuthInterceptor.kt
new file mode 100644
index 0000000000000000000000000000000000000000..16a5c8073df734ff5671b0a13ebccff306320e5c
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/UserInteractiveAuthInterceptor.kt
@@ -0,0 +1,47 @@
+/*
+ * 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.auth
+
+import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse
+import kotlin.coroutines.Continuation
+
+/**
+ * Some API endpoints require authentication that interacts with the user.
+ * The homeserver may provide many different ways of authenticating, such as user/password auth, login via a social network (OAuth2),
+ * login by confirming a token sent to their email address, etc.
+ *
+ * The process takes the form of one or more 'stages'.
+ * At each stage the client submits a set of data for a given authentication type and awaits a response from the server,
+ * which will either be a final success or a request to perform an additional stage.
+ * This exchange continues until the final success.
+ *
+ * For each endpoint, a server offers one or more 'flows' that the client can use to authenticate itself.
+ * Each flow comprises a series of stages, as described above.
+ * The client is free to choose which flow it follows, however the flow's stages must be completed in order.
+ * Failing to follow the flows in order must result in an HTTP 401 response.
+ * When all stages in a flow are complete, authentication is complete and the API call succeeds.
+ */
+interface UserInteractiveAuthInterceptor {
+
+    /**
+     * When the API needs additional auth, this will be called.
+     * Implementation should check the flows from flow response and act accordingly.
+     * Updated auth should be provided using promise.resume, this allow implementation to perform
+     * an async operation (prompt for user password, open sso fallback) and then resume initial API call when done.
+     */
+    fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>)
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/UserPasswordAuth.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/UserPasswordAuth.kt
similarity index 74%
rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/UserPasswordAuth.kt
rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/UserPasswordAuth.kt
index ba8b34096cb1fc112d3ca56fa8d53739e718234b..e985c5f08abfdeee95c5dcc214f4af169024f9a2 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/UserPasswordAuth.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/UserPasswordAuth.kt
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.matrix.android.sdk.internal.crypto.model.rest
+package org.matrix.android.sdk.api.auth
 
 import com.squareup.moshi.Json
 import com.squareup.moshi.JsonClass
@@ -27,7 +27,7 @@ data class UserPasswordAuth(
 
         // device device session id
         @Json(name = "session")
-        val session: String? = null,
+        override val session: String? = null,
 
         // registration information
         @Json(name = "type")
@@ -38,4 +38,16 @@ data class UserPasswordAuth(
 
         @Json(name = "password")
         val password: String? = null
-)
+) : UIABaseAuth {
+
+    override fun hasAuthInfo() = password != null
+
+    override fun copyWithSession(session: String) = this.copy(session = session)
+
+    override fun asMap(): Map<String, *> = mapOf(
+            "session" to session,
+            "user" to user,
+            "password" to password,
+            "type" to type
+    )
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/SsoIdentityProvider.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/SsoIdentityProvider.kt
index d89607843f1d745e3ce67de8c213ab598c032bce..cfaf74ce240f4ee8c9bf4650b9302815a92a641c 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/SsoIdentityProvider.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/SsoIdentityProvider.kt
@@ -19,7 +19,7 @@ package org.matrix.android.sdk.api.auth.data
 import android.os.Parcelable
 import com.squareup.moshi.Json
 import com.squareup.moshi.JsonClass
-import kotlinx.android.parcel.Parcelize
+import kotlinx.parcelize.Parcelize
 
 @JsonClass(generateAdapter = true)
 @Parcelize
@@ -38,15 +38,24 @@ data class SsoIdentityProvider(
          * If present then it must be an HTTPS URL to an image resource.
          * This should be hosted by the homeserver service provider to not leak the client's IP address unnecessarily.
          */
-        @Json(name = "icon") val iconUrl: String?
+        @Json(name = "icon") val iconUrl: String?,
+
+        /**
+         * The `brand` field is **optional**. It allows the client to style the login
+         * button to suit a particular brand. It should be a string matching the
+         * "Common namespaced identifier grammar" as defined in
+         * [MSC2758](https://github.com/matrix-org/matrix-doc/pull/2758).
+         */
+        @Json(name = "brand") val brand: String?
+
 ) : Parcelable {
 
     companion object {
-        // Not really defined by the spec, but we may define some ids here
-        const val ID_GOOGLE = "google"
-        const val ID_GITHUB = "github"
-        const val ID_APPLE = "apple"
-        const val ID_FACEBOOK = "facebook"
-        const val ID_TWITTER = "twitter"
+        const val BRAND_GOOGLE = "org.matrix.google"
+        const val BRAND_GITHUB = "org.matrix.github"
+        const val BRAND_APPLE = "org.matrix.apple"
+        const val BRAND_FACEBOOK = "org.matrix.facebook"
+        const val BRAND_TWITTER = "org.matrix.twitter"
+        const val BRAND_GITLAB = "org.matrix.gitlab"
     }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/RegistrationFlowResponse.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/registration/RegistrationFlowResponse.kt
similarity index 93%
rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/RegistrationFlowResponse.kt
rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/registration/RegistrationFlowResponse.kt
index 3461a4d738c7447472e2381fd41ab77dff1e4912..2b1c1c09b3d7b466d752ff3f7c89fbe4e1131a41 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/RegistrationFlowResponse.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/registration/RegistrationFlowResponse.kt
@@ -14,14 +14,11 @@
  * limitations under the License.
  */
 
-package org.matrix.android.sdk.internal.auth.registration
+package org.matrix.android.sdk.api.auth.registration
 
 import com.squareup.moshi.Json
 import com.squareup.moshi.JsonClass
 import org.matrix.android.sdk.api.auth.data.LoginFlowTypes
-import org.matrix.android.sdk.api.auth.registration.FlowResult
-import org.matrix.android.sdk.api.auth.registration.Stage
-import org.matrix.android.sdk.api.auth.registration.TermPolicies
 import org.matrix.android.sdk.api.util.JsonDict
 import org.matrix.android.sdk.internal.auth.data.InteractiveAuthenticationFlow
 
@@ -109,3 +106,8 @@ fun RegistrationFlowResponse.toFlowResult(): FlowResult {
 
     return FlowResult(missingStage, completedStage)
 }
+
+fun RegistrationFlowResponse.nextUncompletedStage(flowIndex: Int = 0): String? {
+    val completed = completedStages ?: emptyList()
+    return flows?.getOrNull(flowIndex)?.stages?.firstOrNull { completed.contains(it).not() }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/Extensions.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/Extensions.kt
index 4711f7957d2f88c2fddc62bd0e8dacb7958ad9a6..c06cdd9e23a4efdf8e399259419b3101d1052bcd 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/Extensions.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/Extensions.kt
@@ -16,8 +16,8 @@
 
 package org.matrix.android.sdk.api.failure
 
+import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse
 import org.matrix.android.sdk.api.extensions.tryOrNull
-import org.matrix.android.sdk.internal.auth.registration.RegistrationFlowResponse
 import org.matrix.android.sdk.internal.di.MoshiProvider
 import java.io.IOException
 import javax.net.ssl.HttpsURLConnection
@@ -43,6 +43,12 @@ fun Throwable.isInvalidPassword(): Boolean {
             && error.message == "Invalid password"
 }
 
+fun Throwable.isInvalidUIAAuth(): Boolean {
+    return this is Failure.ServerError
+            && error.code == MatrixError.M_FORBIDDEN
+            && error.flows != null
+}
+
 /**
  * Try to convert to a RegistrationFlowResponse. Return null in the cases it's not possible
  */
@@ -53,6 +59,16 @@ fun Throwable.toRegistrationFlowResponse(): RegistrationFlowResponse? {
                     .adapter(RegistrationFlowResponse::class.java)
                     .fromJson(this.errorBody)
         }
+    } else if (this is Failure.ServerError && this.httpCode == 401 && this.error.code == MatrixError.M_FORBIDDEN) {
+        // This happens when the submission for this stage was bad (like bad password)
+        if (this.error.session != null && this.error.flows != null) {
+            RegistrationFlowResponse(
+                    flows = this.error.flows,
+                    session = this.error.session,
+                    completedStages = this.error.completedStages,
+                    params = this.error.params
+            )
+        } else null
     } else {
         null
     }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/Failure.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/Failure.kt
index de881b9e02656c1787894f931589c79d05bac0af..b2419033649c36684ff79cd54bff16dcd71a08f3 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/Failure.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/Failure.kt
@@ -16,8 +16,8 @@
 
 package org.matrix.android.sdk.api.failure
 
+import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse
 import org.matrix.android.sdk.api.session.crypto.MXCryptoError
-import org.matrix.android.sdk.internal.auth.registration.RegistrationFlowResponse
 import org.matrix.android.sdk.internal.network.ssl.Fingerprint
 import java.io.IOException
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/MatrixError.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/MatrixError.kt
index 895be0031a38a8b1c5e15a7e224c8bdd9c6713f9..3820a442aaa7ef42416a7c7301539e39eebf6f50 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/MatrixError.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/MatrixError.kt
@@ -18,6 +18,8 @@ package org.matrix.android.sdk.api.failure
 
 import com.squareup.moshi.Json
 import com.squareup.moshi.JsonClass
+import org.matrix.android.sdk.api.util.JsonDict
+import org.matrix.android.sdk.internal.auth.data.InteractiveAuthenticationFlow
 
 /**
  * This data class holds the error defined by the matrix specifications.
@@ -42,7 +44,17 @@ data class MatrixError(
         @Json(name = "soft_logout") val isSoftLogout: Boolean = false,
         // For M_INVALID_PEPPER
         // {"error": "pepper does not match 'erZvr'", "lookup_pepper": "pQgMS", "algorithm": "sha256", "errcode": "M_INVALID_PEPPER"}
-        @Json(name = "lookup_pepper") val newLookupPepper: String? = null
+        @Json(name = "lookup_pepper") val newLookupPepper: String? = null,
+
+        // For M_FORBIDDEN UIA
+        @Json(name = "session")
+        val session: String? = null,
+        @Json(name = "completed")
+        val completedStages: List<String>? = null,
+        @Json(name = "flows")
+        val flows: List<InteractiveAuthenticationFlow>? = null,
+        @Json(name = "params")
+        val params: JsonDict? = null
 ) {
 
     companion object {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/SenderNotificationPermissionCondition.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/SenderNotificationPermissionCondition.kt
index 4f9c84a47c2b1171b2f08255205eb8da07bb3db2..6675fb0ff5082b6b0d9169ecaf45937a3026ef73 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/SenderNotificationPermissionCondition.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/SenderNotificationPermissionCondition.kt
@@ -37,6 +37,6 @@ class SenderNotificationPermissionCondition(
 
     fun isSatisfied(event: Event, powerLevels: PowerLevelsContent): Boolean {
         val powerLevelsHelper = PowerLevelsHelper(powerLevels)
-        return event.senderId != null && powerLevelsHelper.getUserPowerLevelValue(event.senderId) >= powerLevelsHelper.notificationLevel(key)
+        return event.senderId != null && powerLevelsHelper.getUserPowerLevelValue(event.senderId) >= powerLevels.notificationLevel(key)
     }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/Session.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/Session.kt
index 8a95baf3cb82e991a12c38539d732395ecea58fa..ff7c9f0521c6d698c59e202f98b105e7207755b4 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/Session.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/Session.kt
@@ -245,6 +245,8 @@ interface Session :
 
     val sharedSecretStorageService: SharedSecretStorageService
 
+    fun getUiaSsoFallbackUrl(authenticationSessionId: String): String
+
     /**
      * Maintenance API, allows to print outs info on DB size to logcat
      */
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/account/AccountService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/account/AccountService.kt
index 8915202f355d6d38cb2a6fee3684f721f9c4c282..eb327dfd56c18b6df0e75db011a8f6eabce9561f 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/account/AccountService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/account/AccountService.kt
@@ -16,6 +16,8 @@
 
 package org.matrix.android.sdk.api.session.account
 
+import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
+
 /**
  * This interface defines methods to manage the account. It's implemented at the session level.
  */
@@ -43,5 +45,5 @@ interface AccountService {
      * @param eraseAllData set to true to forget all messages that have been sent. Warning: this will cause future users to see
      * an incomplete view of conversations
      */
-    suspend fun deactivateAccount(password: String, eraseAllData: Boolean)
+    suspend fun deactivateAccount(userInteractiveAuthInterceptor: UserInteractiveAuthInterceptor, eraseAllData: Boolean)
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/cache/CacheService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/cache/CacheService.kt
index c1c56632277df7db8f56fcbd1f89744d70ed1956..2945cc45d6444d1e083948274dc3a85143956bbb 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/cache/CacheService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/cache/CacheService.kt
@@ -16,8 +16,6 @@
 
 package org.matrix.android.sdk.api.session.cache
 
-import org.matrix.android.sdk.api.MatrixCallback
-
 /**
  * This interface defines a method to clear the cache. It's implemented at the session level.
  */
@@ -26,5 +24,5 @@ interface CacheService {
     /**
      * Clear the whole cached data, except credentials. Once done, the sync has to be restarted by the sdk user.
      */
-    fun clearCache(callback: MatrixCallback<Unit>)
+    suspend fun clearCache()
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/content/ContentAttachmentData.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/content/ContentAttachmentData.kt
index 4164b84ecdc38fdf47dab0436907999983fdff6a..98a84b8b662176a71685f37e52fd8cfe83e18d37 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/content/ContentAttachmentData.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/content/ContentAttachmentData.kt
@@ -20,7 +20,7 @@ import android.net.Uri
 import android.os.Parcelable
 import androidx.exifinterface.media.ExifInterface
 import com.squareup.moshi.JsonClass
-import kotlinx.android.parcel.Parcelize
+import kotlinx.parcelize.Parcelize
 import org.matrix.android.sdk.api.util.MimeTypes.normalizeMimeType
 
 @Parcelize
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt
index 0eefca1b4ce23c622c3b46cf9a1ba0f59c89bf1e..fa5ea359e8ff9ff7849bb91f5015fbdca97c10f1 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt
@@ -20,6 +20,7 @@ import android.content.Context
 import androidx.lifecycle.LiveData
 import androidx.paging.PagedList
 import org.matrix.android.sdk.api.MatrixCallback
+import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
 import org.matrix.android.sdk.api.listeners.ProgressListener
 import org.matrix.android.sdk.api.session.crypto.crosssigning.CrossSigningService
 import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupService
@@ -53,7 +54,7 @@ interface CryptoService {
 
     fun setDeviceName(deviceId: String, deviceName: String, callback: MatrixCallback<Unit>)
 
-    fun deleteDevice(deviceId: String, callback: MatrixCallback<Unit>)
+    fun deleteDevice(deviceId: String, userInteractiveAuthInterceptor: UserInteractiveAuthInterceptor, callback: MatrixCallback<Unit>)
 
     fun deleteDeviceWithUserPassword(deviceId: String, authSession: String?, password: String, callback: MatrixCallback<Unit>)
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/crosssigning/CrossSigningService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/crosssigning/CrossSigningService.kt
index 6a646cd4c717ce0a0ef1a2997e55b537a0e80434..359e33cc2c6bcad8a8be51faa74ae5b2eb0a66c9 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/crosssigning/CrossSigningService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/crosssigning/CrossSigningService.kt
@@ -18,10 +18,10 @@ package org.matrix.android.sdk.api.session.crypto.crosssigning
 
 import androidx.lifecycle.LiveData
 import org.matrix.android.sdk.api.MatrixCallback
+import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
 import org.matrix.android.sdk.api.util.Optional
 import org.matrix.android.sdk.internal.crypto.crosssigning.DeviceTrustResult
 import org.matrix.android.sdk.internal.crypto.crosssigning.UserTrustResult
-import org.matrix.android.sdk.internal.crypto.model.rest.UserPasswordAuth
 import org.matrix.android.sdk.internal.crypto.store.PrivateKeysInfo
 
 interface CrossSigningService {
@@ -40,7 +40,7 @@ interface CrossSigningService {
      * Initialize cross signing for this user.
      * Users needs to enter credentials
      */
-    fun initializeCrossSigning(authParams: UserPasswordAuth?,
+    fun initializeCrossSigning(uiaInterceptor: UserInteractiveAuthInterceptor?,
                                callback: MatrixCallback<Unit>)
 
     fun isCrossSigningInitialized(): Boolean = getMyCrossSigningKeys() != null
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/media/MediaService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/media/MediaService.kt
index 9040ec7d5c53aab4027d7b5aba114c4dbe997a3d..3b3ef57d738da17fe793709884f74e825181886a 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/media/MediaService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/media/MediaService.kt
@@ -17,15 +17,16 @@
 package org.matrix.android.sdk.api.session.media
 
 import org.matrix.android.sdk.api.cache.CacheStrategy
-import org.matrix.android.sdk.api.session.events.model.Event
+import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
 import org.matrix.android.sdk.api.util.JsonDict
 
 interface MediaService {
     /**
-     * Extract URLs from an Event.
-     * @return the list of URLs contains in the body of the Event. It does not mean that URLs in this list have UrlPreview data
+     * Extract URLs from a TimelineEvent.
+     * @param event TimelineEvent to extract the URL from.
+     * @return the list of URLs contains in the body of the TimelineEvent. It does not mean that URLs in this list have UrlPreview data
      */
-    fun extractUrls(event: Event): List<String>
+    fun extractUrls(event: TimelineEvent): List<String>
 
     /**
      * Get Raw Url Preview data from the homeserver. There is no cache management for this request
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/PermalinkService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/PermalinkService.kt
index aefc086b4307d09c9e9f2d782185286935fb772d..ac1d726d0324eaafc7ba3f014e5f787c3bc51fab 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/PermalinkService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/PermalinkService.kt
@@ -25,7 +25,6 @@ interface PermalinkService {
 
     companion object {
         const val MATRIX_TO_URL_BASE = "https://matrix.to/#/"
-        const val MATRIX_TO_CUSTOM_SCHEME_URL_BASE = "element://"
     }
 
     /**
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/profile/ProfileService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/profile/ProfileService.kt
index 1fd83602531de42168737e119a3c8e1609a97fb5..a4d5b665c648f453056e68623db2408e817a1050 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/profile/ProfileService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/profile/ProfileService.kt
@@ -20,6 +20,7 @@ package org.matrix.android.sdk.api.session.profile
 import android.net.Uri
 import androidx.lifecycle.LiveData
 import org.matrix.android.sdk.api.MatrixCallback
+import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
 import org.matrix.android.sdk.api.session.identity.ThreePid
 import org.matrix.android.sdk.api.util.Cancelable
 import org.matrix.android.sdk.api.util.JsonDict
@@ -107,8 +108,7 @@ interface ProfileService {
      * Finalize adding a 3Pids. Call this method once the user has validated that he owns the ThreePid
      */
     fun finalizeAddingThreePid(threePid: ThreePid,
-                               uiaSession: String?,
-                               accountPassword: String?,
+                               userInteractiveAuthInterceptor: UserInteractiveAuthInterceptor,
                                matrixCallback: MatrixCallback<Unit>): Cancelable
 
     /**
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/PowerLevelsContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/PowerLevelsContent.kt
index 696b612389ea37eb5f8ca8cfe925e6042878a575..e778f5740ddff6e2ab5eae1b546715f75fa8fa22 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/PowerLevelsContent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/PowerLevelsContent.kt
@@ -25,28 +25,85 @@ import org.matrix.android.sdk.api.session.room.powerlevels.Role
  */
 @JsonClass(generateAdapter = true)
 data class PowerLevelsContent(
+        /**
+         * The level required to ban a user. Defaults to 50 if unspecified.
+         */
         @Json(name = "ban") val ban: Int = Role.Moderator.value,
+        /**
+         * The level required to kick a user. Defaults to 50 if unspecified.
+         */
         @Json(name = "kick") val kick: Int = Role.Moderator.value,
+        /**
+         * The level required to invite a user. Defaults to 50 if unspecified.
+         */
         @Json(name = "invite") val invite: Int = Role.Moderator.value,
+        /**
+         * The level required to redact an event. Defaults to 50 if unspecified.
+         */
         @Json(name = "redact") val redact: Int = Role.Moderator.value,
+        /**
+         * The default level required to send message events. Can be overridden by the events key. Defaults to 0 if unspecified.
+         */
         @Json(name = "events_default") val eventsDefault: Int = Role.Default.value,
-        @Json(name = "events") val events: MutableMap<String, Int> = HashMap(),
+        /**
+         * The level required to send specific event types. This is a mapping from event type to power level required.
+         */
+        @Json(name = "events") val events: Map<String, Int> = emptyMap(),
+        /**
+         * The default power level for every user in the room, unless their user_id is mentioned in the users key. Defaults to 0 if unspecified.
+         */
         @Json(name = "users_default") val usersDefault: Int = Role.Default.value,
-        @Json(name = "users") val users: MutableMap<String, Int> = HashMap(),
+        /**
+         * The power levels for specific users. This is a mapping from user_id to power level for that user.
+         */
+        @Json(name = "users") val users: Map<String, Int> = emptyMap(),
+        /**
+         * The default level required to send state events. Can be overridden by the events key. Defaults to 50 if unspecified.
+         */
         @Json(name = "state_default") val stateDefault: Int = Role.Moderator.value,
-        @Json(name = "notifications") val notifications: Map<String, Any> = HashMap()
+        /**
+         * The power level requirements for specific notification types. This is a mapping from key to power level for that notifications key.
+         */
+        @Json(name = "notifications") val notifications: Map<String, Any> = emptyMap()
 ) {
     /**
-     * Alter this content with a new power level for the specified user
+     * Return a copy of this content with a new power level for the specified user
      *
      * @param userId the userId to alter the power level of
      * @param powerLevel the new power level, or null to set the default value.
      */
-    fun setUserPowerLevel(userId: String, powerLevel: Int?) {
-        if (powerLevel == null || powerLevel == usersDefault) {
-            users.remove(userId)
-        } else {
-            users[userId] = powerLevel
+    fun setUserPowerLevel(userId: String, powerLevel: Int?): PowerLevelsContent {
+        return copy(
+                users = users.toMutableMap().apply {
+                    if (powerLevel == null || powerLevel == usersDefault) {
+                        remove(userId)
+                    } else {
+                        put(userId, powerLevel)
+                    }
+                }
+        )
+    }
+
+    /**
+     * Get the notification level for a dedicated key.
+     *
+     * @param key the notification key
+     * @return the level, default to Moderator if the key is not found
+     */
+    fun notificationLevel(key: String): Int {
+        return when (val value = notifications[key]) {
+            // the first implementation was a string value
+            is String -> value.toInt()
+            is Double -> value.toInt()
+            is Int    -> value
+            else      -> Role.Moderator.value
         }
     }
+
+    companion object {
+        /**
+         * Key to use for content.notifications and get the level required to trigger an @room notification. Defaults to 50 if unspecified.
+         */
+        const val NOTIFICATIONS_ROOM_KEY = "room"
+    }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageStickerContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageStickerContent.kt
index 00fa68c0ac444cd709d266d1f641380fc42d6787..280316d4b5208ee1280cbb5740d9826460b80b93 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageStickerContent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageStickerContent.kt
@@ -27,6 +27,7 @@ data class MessageStickerContent(
         /**
          * Set in local, not from server
          */
+        @Transient
         override val msgType: String = MessageType.MSGTYPE_STICKER_LOCAL,
 
         /**
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/powerlevels/PowerLevelsHelper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/powerlevels/PowerLevelsHelper.kt
index 47922f6968414b10576c08c4b1d203dfaec7c887..4f1253c6df9d33936267145fef863bb0a36ea9de 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/powerlevels/PowerLevelsHelper.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/powerlevels/PowerLevelsHelper.kt
@@ -108,19 +108,4 @@ class PowerLevelsHelper(private val powerLevelsContent: PowerLevelsContent) {
         val powerLevel = getUserPowerLevelValue(userId)
         return powerLevel >= powerLevelsContent.redact
     }
-
-    /**
-     * Get the notification level for a dedicated key.
-     *
-     * @param key the notification key
-     * @return the level
-     */
-    fun notificationLevel(key: String): Int {
-        return when (val value = powerLevelsContent.notifications[key]) {
-            // the first implementation was a string value
-            is String -> value.toInt()
-            is Int    -> value
-            else      -> Role.Moderator.value
-        }
-    }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/state/StateServiceExtension.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/state/StateServiceExtension.kt
new file mode 100644
index 0000000000000000000000000000000000000000..c625a7f0885196a70085fab71ab14a2cceb61a35
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/state/StateServiceExtension.kt
@@ -0,0 +1,33 @@
+/*
+ * 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.state
+
+import org.matrix.android.sdk.api.query.QueryStringValue
+import org.matrix.android.sdk.api.session.events.model.EventType
+import org.matrix.android.sdk.api.session.events.model.toModel
+import org.matrix.android.sdk.api.session.room.model.RoomJoinRules
+import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesContent
+
+/**
+ * Return true if a room can be joined by anyone (RoomJoinRules.PUBLIC)
+ */
+fun StateService.isPublic(): Boolean {
+    return getStateEvent(EventType.STATE_ROOM_JOIN_RULES, QueryStringValue.NoCondition)
+            ?.content
+            ?.toModel<RoomJoinRulesContent>()
+            ?.joinRules == RoomJoinRules.PUBLIC
+}
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 73cb94b4175c3acf8c085bfc62f4dbe49b4b9995..b10fb540e112e44c8fb5917251661cb10d9e2dcd 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
@@ -89,6 +89,17 @@ data class TimelineEvent(
  */
 fun TimelineEvent.hasBeenEdited() = annotations?.editSummary != null
 
+/**
+ * Get the latest known eventId for an edited event, or the eventId for an Event which has not been edited
+ */
+fun TimelineEvent.getLatestEventId(): String {
+    return annotations
+            ?.editSummary
+            ?.sourceEvents
+            ?.lastOrNull()
+            ?: eventId
+}
+
 /**
  * Get the relation content if any
  */
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/signout/SignOutService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/signout/SignOutService.kt
index ebbbac527a9804e4a2ecebcb14a1950fb38f2000..4e4eba274e242cf076907dbe1c3db8d8385578fd 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/signout/SignOutService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/signout/SignOutService.kt
@@ -16,9 +16,7 @@
 
 package org.matrix.android.sdk.api.session.signout
 
-import org.matrix.android.sdk.api.MatrixCallback
 import org.matrix.android.sdk.api.auth.data.Credentials
-import org.matrix.android.sdk.api.util.Cancelable
 
 /**
  * This interface defines a method to sign out, or to renew the token. It's implemented at the session level.
@@ -29,19 +27,16 @@ interface SignOutService {
      * Ask the homeserver for a new access token.
      * The same deviceId will be used
      */
-    fun signInAgain(password: String,
-                    callback: MatrixCallback<Unit>): Cancelable
+    suspend fun signInAgain(password: String)
 
     /**
      * Update the session with credentials received after SSO
      */
-    fun updateCredentials(credentials: Credentials,
-                          callback: MatrixCallback<Unit>): Cancelable
+    suspend fun updateCredentials(credentials: Credentials)
 
     /**
      * Sign out, and release the session, clear all the session data, including crypto data
      * @param signOutFromHomeserver true if the sign out request has to be done
      */
-    fun signOut(signOutFromHomeserver: Boolean,
-                callback: MatrixCallback<Unit>): Cancelable
+    suspend fun signOut(signOutFromHomeserver: Boolean)
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/UrlExtensions.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/UrlExtensions.kt
new file mode 100644
index 0000000000000000000000000000000000000000..17f27b251491c227c80df9d2f398fc09a384f9ac
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/UrlExtensions.kt
@@ -0,0 +1,37 @@
+/*
+ * 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.util
+
+import java.net.URLEncoder
+
+/**
+ * Append param and value to a Url, using "?" or "&". Value parameter will be encoded
+ * Return this for chaining purpose
+ */
+fun StringBuilder.appendParamToUrl(param: String, value: String): StringBuilder {
+    if (contains("?")) {
+        append("&")
+    } else {
+        append("?")
+    }
+
+    append(param)
+    append("=")
+    append(URLEncoder.encode(value, "utf-8"))
+
+    return this
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/Constants.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/Constants.kt
similarity index 62%
rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/Constants.kt
rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/Constants.kt
index 7d18aba627c8e63922b26df72295c81360a4f996..e0c52cf9ca95a451b35c9703837846135e9fb435 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/Constants.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/Constants.kt
@@ -14,25 +14,28 @@
  * limitations under the License.
  */
 
-package org.matrix.android.sdk.api.auth
+package org.matrix.android.sdk.internal.auth
 
 /**
  * Path to use when the client does not supported any or all login flows
  * Ref: https://matrix.org/docs/spec/client_server/latest#login-fallback
  */
-const val LOGIN_FALLBACK_PATH = "/_matrix/static/client/login/"
+internal const val LOGIN_FALLBACK_PATH = "/_matrix/static/client/login/"
 
 /**
  * Path to use when the client does not supported any or all registration flows
  * Not documented
  */
-const val REGISTER_FALLBACK_PATH = "/_matrix/static/client/register/"
+internal const val REGISTER_FALLBACK_PATH = "/_matrix/static/client/register/"
 
 /**
  * Path to use when the client want to connect using SSO
  * Ref: https://matrix.org/docs/spec/client_server/latest#sso-client-login
  */
-const val SSO_REDIRECT_PATH = "/_matrix/client/r0/login/sso/redirect"
-const val MSC2858_SSO_REDIRECT_PATH = "/_matrix/client/unstable/org.matrix.msc2858/login/sso/redirect"
+internal const val SSO_REDIRECT_PATH = "/_matrix/client/r0/login/sso/redirect"
+internal const val MSC2858_SSO_REDIRECT_PATH = "/_matrix/client/unstable/org.matrix.msc2858/login/sso/redirect"
 
-const val SSO_REDIRECT_URL_PARAM = "redirectUrl"
+internal const val SSO_REDIRECT_URL_PARAM = "redirectUrl"
+
+// Ref: https://matrix.org/docs/spec/client_server/r0.6.1#single-sign-on
+internal const val SSO_UIA_FALLBACK_PATH = "/_matrix/client/r0/auth/m.login.sso/fallback/web"
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt
index 55f053de8debcf084f74e0b325ed31e25dcce0de..c99e9bd81c88b1c314a1c5fa3abeae4b7cf233c0 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt
@@ -34,6 +34,7 @@ import org.matrix.android.sdk.api.failure.Failure
 import org.matrix.android.sdk.api.session.Session
 import org.matrix.android.sdk.api.util.Cancelable
 import org.matrix.android.sdk.api.util.NoOpCancellable
+import org.matrix.android.sdk.api.util.appendParamToUrl
 import org.matrix.android.sdk.internal.SessionManager
 import org.matrix.android.sdk.internal.auth.data.LoginFlowResponse
 import org.matrix.android.sdk.internal.auth.data.RiotConfig
@@ -99,6 +100,52 @@ internal class DefaultAuthenticationService @Inject constructor(
         }
     }
 
+    override fun getSsoUrl(redirectUrl: String, deviceId: String?, providerId: String?): String? {
+        val homeServerUrlBase = getHomeServerUrlBase() ?: return null
+
+        return buildString {
+            append(homeServerUrlBase)
+            if (providerId != null) {
+                append(MSC2858_SSO_REDIRECT_PATH)
+                append("/$providerId")
+            } else {
+                append(SSO_REDIRECT_PATH)
+            }
+            // Set the redirect url
+            appendParamToUrl(SSO_REDIRECT_URL_PARAM, redirectUrl)
+            deviceId?.takeIf { it.isNotBlank() }?.let {
+                // But https://github.com/matrix-org/synapse/issues/5755
+                appendParamToUrl("device_id", it)
+            }
+        }
+    }
+
+    override fun getFallbackUrl(forSignIn: Boolean, deviceId: String?): String? {
+        val homeServerUrlBase = getHomeServerUrlBase() ?: return null
+
+        return buildString {
+            append(homeServerUrlBase)
+            if (forSignIn) {
+                append(LOGIN_FALLBACK_PATH)
+                deviceId?.takeIf { it.isNotBlank() }?.let {
+                    // But https://github.com/matrix-org/synapse/issues/5755
+                    appendParamToUrl("device_id", it)
+                }
+            } else {
+                // For sign up
+                append(REGISTER_FALLBACK_PATH)
+            }
+        }
+    }
+
+    private fun getHomeServerUrlBase(): String? {
+        return pendingSessionData
+                ?.homeServerConnectionConfig
+                ?.homeServerUri
+                ?.toString()
+                ?.trim { it == '/' }
+    }
+
     override fun getLoginFlow(homeServerConnectionConfig: HomeServerConnectionConfig, callback: MatrixCallback<LoginFlowResult>): Cancelable {
         pendingSessionData = null
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/LoginFlowResponse.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/LoginFlowResponse.kt
index 2b26115f30da3f9f4a4ee9a53e4e2304b4e859c8..d0d17e2cd5378874e93335cfb0cf1392bbd59214 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/LoginFlowResponse.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/LoginFlowResponse.kt
@@ -43,5 +43,6 @@ internal data class LoginFlow(
          * See MSC #2858
          */
         @Json(name = "org.matrix.msc2858.identity_providers")
-        val ssoIdentityProvider: List<SsoIdentityProvider>?
+        val ssoIdentityProvider: List<SsoIdentityProvider>? = null
+
 )
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/DefaultRegistrationWizard.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/DefaultRegistrationWizard.kt
index 9c6b942a4fc6faed2c81a0de88319e9ad6cedea3..163009d9184866f1046a81c3275417a999588cb7 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/DefaultRegistrationWizard.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/DefaultRegistrationWizard.kt
@@ -24,6 +24,7 @@ import org.matrix.android.sdk.api.auth.data.LoginFlowTypes
 import org.matrix.android.sdk.api.auth.registration.RegisterThreePid
 import org.matrix.android.sdk.api.auth.registration.RegistrationResult
 import org.matrix.android.sdk.api.auth.registration.RegistrationWizard
+import org.matrix.android.sdk.api.auth.registration.toFlowResult
 import org.matrix.android.sdk.api.failure.Failure
 import org.matrix.android.sdk.api.failure.Failure.RegistrationFlowError
 import org.matrix.android.sdk.api.util.Cancelable
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/LocalizedFlowDataLoginTerms.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/LocalizedFlowDataLoginTerms.kt
index 1e18887008b411fd000519b90a02193505534b54..5d119bb6177678fe6883903ba10411070b5f4739 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/LocalizedFlowDataLoginTerms.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/LocalizedFlowDataLoginTerms.kt
@@ -17,7 +17,7 @@
 package org.matrix.android.sdk.internal.auth.registration
 
 import android.os.Parcelable
-import kotlinx.android.parcel.Parcelize
+import kotlinx.parcelize.Parcelize
 
 /**
  * This class represent a localized privacy policy for registration Flow.
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/UIAExt.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/UIAExt.kt
new file mode 100644
index 0000000000000000000000000000000000000000..1a0383cb220a8d031d80aca9a54a4ce9eff9148b
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/UIAExt.kt
@@ -0,0 +1,53 @@
+/*
+ * 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.auth.registration
+
+import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
+import org.matrix.android.sdk.api.failure.Failure
+import org.matrix.android.sdk.api.failure.toRegistrationFlowResponse
+import org.matrix.android.sdk.api.auth.UIABaseAuth
+import timber.log.Timber
+import kotlin.coroutines.suspendCoroutine
+
+internal suspend fun handleUIA(failure: Throwable, interceptor: UserInteractiveAuthInterceptor, retryBlock: suspend (UIABaseAuth) -> Unit): Boolean {
+    Timber.d("## UIA: check error ${failure.message}")
+    val flowResponse = failure.toRegistrationFlowResponse()
+            ?: return false.also {
+                Timber.d("## UIA: not a UIA error")
+            }
+
+    Timber.d("## UIA: error can be passed to interceptor")
+    Timber.d("## UIA: type = ${flowResponse.flows}")
+
+    Timber.d("## UIA: delegate to interceptor...")
+    val authUpdate = try {
+        suspendCoroutine<UIABaseAuth> { continuation ->
+            interceptor.performStage(flowResponse, (failure as? Failure.ServerError)?.error?.code, continuation)
+        }
+    } catch (failure: Throwable) {
+        Timber.w(failure, "## UIA: failed to participate")
+        return false
+    }
+
+    Timber.d("## UIA: updated auth $authUpdate")
+    return try {
+        retryBlock(authUpdate)
+        true
+    } catch (failure: Throwable) {
+        handleUIA(failure, interceptor, retryBlock)
+    }
+}
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 c2c81894fb3d53f75f82b5105b493918a77318f8..7e92ff3e88be724e182cee9b7cc5df99cc2e04ed 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
@@ -19,7 +19,6 @@ package org.matrix.android.sdk.internal.crypto
 import android.content.Context
 import androidx.work.WorkerParameters
 import com.squareup.moshi.JsonClass
-import org.greenrobot.eventbus.EventBus
 import org.matrix.android.sdk.api.auth.data.Credentials
 import org.matrix.android.sdk.api.failure.shouldBeRetried
 import org.matrix.android.sdk.api.session.events.model.Event
@@ -60,7 +59,6 @@ internal class CancelGossipRequestWorker(context: Context,
 
     @Inject lateinit var sendToDeviceTask: SendToDeviceTask
     @Inject lateinit var cryptoStore: IMXCryptoStore
-    @Inject lateinit var eventBus: EventBus
     @Inject lateinit var credentials: Credentials
 
     override fun injectWith(injector: SessionComponent) {
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 ebd809f7774ee2375a61e4677a73a662cbf6f92d..678bc9819f12dc8bc2ba4d7be820b1d8e6481fb0 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
@@ -30,6 +30,7 @@ import kotlinx.coroutines.runBlocking
 import kotlinx.coroutines.withContext
 import org.matrix.android.sdk.api.MatrixCallback
 import org.matrix.android.sdk.api.NoOpMatrixCallback
+import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
 import org.matrix.android.sdk.api.crypto.MXCryptoConfig
 import org.matrix.android.sdk.api.extensions.tryOrNull
 import org.matrix.android.sdk.api.failure.Failure
@@ -207,9 +208,9 @@ internal class DefaultCryptoService @Inject constructor(
                 .executeBy(taskExecutor)
     }
 
-    override fun deleteDevice(deviceId: String, callback: MatrixCallback<Unit>) {
+    override fun deleteDevice(deviceId: String, userInteractiveAuthInterceptor: UserInteractiveAuthInterceptor, callback: MatrixCallback<Unit>) {
         deleteDeviceTask
-                .configureWith(DeleteDeviceTask.Params(deviceId)) {
+                .configureWith(DeleteDeviceTask.Params(deviceId, userInteractiveAuthInterceptor, null)) {
                     this.executionThread = TaskThread.CRYPTO
                     this.callback = callback
                 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/IncomingGossipingRequestManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/IncomingGossipingRequestManager.kt
index 4f94a27bbd9ed7814309bbcba711e94cdbe96c58..c075c90eb9f09718941be100bab94b54e4673015 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/IncomingGossipingRequestManager.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/IncomingGossipingRequestManager.kt
@@ -32,6 +32,7 @@ import org.matrix.android.sdk.internal.crypto.crosssigning.toBase64NoPadding
 import org.matrix.android.sdk.internal.crypto.keysbackup.util.extractCurveKeyFromRecoveryKey
 import org.matrix.android.sdk.internal.crypto.model.rest.GossipingDefaultContent
 import org.matrix.android.sdk.internal.crypto.model.rest.GossipingToDeviceObject
+import org.matrix.android.sdk.internal.crypto.model.rest.RoomKeyRequestBody
 import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
 import org.matrix.android.sdk.internal.di.SessionId
 import org.matrix.android.sdk.internal.session.SessionScope
@@ -206,34 +207,7 @@ internal class IncomingGossipingRequestManager @Inject constructor(
 
         Timber.v("## CRYPTO | GOSSIP processIncomingRoomKeyRequest from $userId:$deviceId for $roomId / ${body.sessionId} id ${request.requestId}")
         if (credentials.userId != userId) {
-            Timber.w("## CRYPTO | GOSSIP processReceivedGossipingRequests() : room key request from other user")
-            val senderKey = body.senderKey ?: return Unit
-                    .also { Timber.w("missing senderKey") }
-                    .also { cryptoStore.updateGossipingRequestState(request, GossipingRequestState.REJECTED) }
-            val sessionId = body.sessionId ?: return Unit
-                    .also { Timber.w("missing sessionId") }
-                    .also { cryptoStore.updateGossipingRequestState(request, GossipingRequestState.REJECTED) }
-
-            if (alg != MXCRYPTO_ALGORITHM_MEGOLM) {
-                return Unit
-                        .also { Timber.w("Only megolm is accepted here") }
-                        .also { cryptoStore.updateGossipingRequestState(request, GossipingRequestState.REJECTED) }
-            }
-
-            val roomEncryptor = roomEncryptorsStore.get(roomId) ?: return Unit
-                    .also { Timber.w("no room Encryptor") }
-                    .also { cryptoStore.updateGossipingRequestState(request, GossipingRequestState.REJECTED) }
-
-            cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
-                val isSuccess = roomEncryptor.reshareKey(sessionId, userId, deviceId, senderKey)
-
-                if (isSuccess) {
-                    cryptoStore.updateGossipingRequestState(request, GossipingRequestState.ACCEPTED)
-                } else {
-                    cryptoStore.updateGossipingRequestState(request, GossipingRequestState.UNABLE_TO_PROCESS)
-                }
-            }
-            cryptoStore.updateGossipingRequestState(request, GossipingRequestState.RE_REQUESTED)
+            handleKeyRequestFromOtherUser(body, request, alg, roomId, userId, deviceId)
             return
         }
         // TODO: should we queue up requests we don't yet have keys for, in case they turn up later?
@@ -291,6 +265,42 @@ internal class IncomingGossipingRequestManager @Inject constructor(
         onRoomKeyRequest(request)
     }
 
+    private fun handleKeyRequestFromOtherUser(body: RoomKeyRequestBody,
+                                              request: IncomingRoomKeyRequest,
+                                              alg: String,
+                                              roomId: String,
+                                              userId: String,
+                                              deviceId: String) {
+        Timber.w("## CRYPTO | GOSSIP processReceivedGossipingRequests() : room key request from other user")
+        val senderKey = body.senderKey ?: return Unit
+                .also { Timber.w("missing senderKey") }
+                .also { cryptoStore.updateGossipingRequestState(request, GossipingRequestState.REJECTED) }
+        val sessionId = body.sessionId ?: return Unit
+                .also { Timber.w("missing sessionId") }
+                .also { cryptoStore.updateGossipingRequestState(request, GossipingRequestState.REJECTED) }
+
+        if (alg != MXCRYPTO_ALGORITHM_MEGOLM) {
+            return Unit
+                    .also { Timber.w("Only megolm is accepted here") }
+                    .also { cryptoStore.updateGossipingRequestState(request, GossipingRequestState.REJECTED) }
+        }
+
+        val roomEncryptor = roomEncryptorsStore.get(roomId) ?: return Unit
+                .also { Timber.w("no room Encryptor") }
+                .also { cryptoStore.updateGossipingRequestState(request, GossipingRequestState.REJECTED) }
+
+        cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
+            val isSuccess = roomEncryptor.reshareKey(sessionId, userId, deviceId, senderKey)
+
+            if (isSuccess) {
+                cryptoStore.updateGossipingRequestState(request, GossipingRequestState.ACCEPTED)
+            } else {
+                cryptoStore.updateGossipingRequestState(request, GossipingRequestState.UNABLE_TO_PROCESS)
+            }
+        }
+        cryptoStore.updateGossipingRequestState(request, GossipingRequestState.RE_REQUESTED)
+    }
+
     private fun processIncomingSecretShareRequest(request: IncomingSecretShareRequest) {
         val secretName = request.secretName ?: return Unit.also {
             cryptoStore.updateGossipingRequestState(request, GossipingRequestState.REJECTED)
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 c952602d93f40517e352abbcda76bb1129579b51..b1e91e8d50760b84915b63237ed1773051035291 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
@@ -19,6 +19,8 @@ package org.matrix.android.sdk.internal.crypto
 import org.matrix.android.sdk.api.session.crypto.MXCryptoError
 import org.matrix.android.sdk.api.util.JSON_DICT_PARAMETERIZED_TYPE
 import org.matrix.android.sdk.api.util.JsonDict
+import org.matrix.android.sdk.internal.crypto.algorithms.megolm.MXOutboundSessionInfo
+import org.matrix.android.sdk.internal.crypto.algorithms.megolm.SharedWithHelper
 import org.matrix.android.sdk.internal.crypto.algorithms.olm.OlmDecryptionResult
 import org.matrix.android.sdk.internal.crypto.model.OlmInboundGroupSessionWrapper2
 import org.matrix.android.sdk.internal.crypto.model.OlmSessionWrapper
@@ -46,7 +48,7 @@ internal class MXOlmDevice @Inject constructor(
          */
         private val store: IMXCryptoStore,
         private val inboundGroupSessionStore: InboundGroupSessionStore
-        ) {
+) {
 
     /**
      * @return the Curve25519 key for the account.
@@ -63,11 +65,15 @@ internal class MXOlmDevice @Inject constructor(
     // The OLM lib utility instance.
     private var olmUtility: OlmUtility? = null
 
+    private data class GroupSessionCacheItem(
+            val groupId: String,
+            val groupSession: OlmOutboundGroupSession
+    )
+
     // The outbound group session.
-    // They are not stored in 'store' to avoid to remember to which devices we sent the session key.
-    // Plus, in cryptography, it is good to refresh sessions from time to time.
-    // The key is the session id, the value the outbound group session.
-    private val outboundGroupSessionStore: MutableMap<String, OlmOutboundGroupSession> = HashMap()
+    // Caches active outbound session to avoid to sync with DB before read
+    // The key is the session id, the value the <roomID,outbound group session>.
+    private val outboundGroupSessionCache: MutableMap<String, GroupSessionCacheItem> = HashMap()
 
     // Store a set of decrypted message indexes for each group session.
     // This partially mitigates a replay attack where a MITM resends a group
@@ -135,6 +141,10 @@ internal class MXOlmDevice @Inject constructor(
      */
     fun release() {
         olmUtility?.releaseUtility()
+        outboundGroupSessionCache.values.forEach {
+            it.groupSession.releaseSession()
+        }
+        outboundGroupSessionCache.clear()
     }
 
     /**
@@ -406,11 +416,12 @@ internal class MXOlmDevice @Inject constructor(
      *
      * @return the session id for the outbound session.
      */
-    fun createOutboundGroupSession(): String? {
+    fun createOutboundGroupSessionForRoom(roomId: String): String? {
         var session: OlmOutboundGroupSession? = null
         try {
             session = OlmOutboundGroupSession()
-            outboundGroupSessionStore[session.sessionIdentifier()] = session
+            outboundGroupSessionCache[session.sessionIdentifier()] = GroupSessionCacheItem(roomId, session)
+            store.storeCurrentOutboundGroupSessionForRoom(roomId, session)
             return session.sessionIdentifier()
         } catch (e: Exception) {
             Timber.e(e, "createOutboundGroupSession")
@@ -421,6 +432,39 @@ internal class MXOlmDevice @Inject constructor(
         return null
     }
 
+    fun storeOutboundGroupSessionForRoom(roomId: String, sessionId: String) {
+        outboundGroupSessionCache[sessionId]?.let {
+            store.storeCurrentOutboundGroupSessionForRoom(roomId, it.groupSession)
+        }
+    }
+
+    fun restoreOutboundGroupSessionForRoom(roomId: String): MXOutboundSessionInfo? {
+        val restoredOutboundGroupSession = store.getCurrentOutboundGroupSessionForRoom(roomId)
+        if (restoredOutboundGroupSession != null) {
+            val sessionId = restoredOutboundGroupSession.outboundGroupSession.sessionIdentifier()
+            // cache it
+            outboundGroupSessionCache[sessionId] = GroupSessionCacheItem(roomId, restoredOutboundGroupSession.outboundGroupSession)
+
+            return MXOutboundSessionInfo(
+                    sessionId = sessionId,
+                    sharedWithHelper = SharedWithHelper(roomId, sessionId, store),
+                    restoredOutboundGroupSession.creationTime
+            )
+        }
+        return null
+    }
+
+    fun discardOutboundGroupSessionForRoom(roomId: String) {
+        val toDiscard = outboundGroupSessionCache.filter {
+            it.value.groupId == roomId
+        }
+        toDiscard.forEach { (sessionId, cacheItem) ->
+            cacheItem.groupSession.releaseSession()
+            outboundGroupSessionCache.remove(sessionId)
+        }
+        store.storeCurrentOutboundGroupSessionForRoom(roomId, null)
+    }
+
     /**
      * Get the current session key of  an outbound group session.
      *
@@ -430,7 +474,7 @@ internal class MXOlmDevice @Inject constructor(
     fun getSessionKey(sessionId: String): String? {
         if (sessionId.isNotEmpty()) {
             try {
-                return outboundGroupSessionStore[sessionId]!!.sessionKey()
+                return outboundGroupSessionCache[sessionId]!!.groupSession.sessionKey()
             } catch (e: Exception) {
                 Timber.e(e, "## getSessionKey() : failed")
             }
@@ -446,7 +490,7 @@ internal class MXOlmDevice @Inject constructor(
      */
     fun getMessageIndex(sessionId: String): Int {
         return if (sessionId.isNotEmpty()) {
-            outboundGroupSessionStore[sessionId]!!.messageIndex()
+            outboundGroupSessionCache[sessionId]!!.groupSession.messageIndex()
         } else 0
     }
 
@@ -460,7 +504,7 @@ internal class MXOlmDevice @Inject constructor(
     fun encryptGroupMessage(sessionId: String, payloadString: String): String? {
         if (sessionId.isNotEmpty() && payloadString.isNotEmpty()) {
             try {
-                return outboundGroupSessionStore[sessionId]!!.encryptMessage(payloadString)
+                return outboundGroupSessionCache[sessionId]!!.groupSession.encryptMessage(payloadString)
             } catch (e: Exception) {
                 Timber.e(e, "## encryptGroupMessage() : failed")
             }
@@ -747,7 +791,7 @@ internal class MXOlmDevice @Inject constructor(
             throw MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_SENDER_KEY, MXCryptoError.ERROR_MISSING_PROPERTY_REASON)
         }
 
-        val session =  inboundGroupSessionStore.getInboundGroupSession(sessionId, senderKey)
+        val session = inboundGroupSessionStore.getInboundGroupSession(sessionId, senderKey)
 
         if (session != null) {
             // Check that the room id matches the original one for the session. This stops
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 085469e9d99572ddf3d9a5ac975bdf32fcab9f63..e8d567b9441d7facade4f9dc09595a1a50162613 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
@@ -19,7 +19,6 @@ package org.matrix.android.sdk.internal.crypto
 import android.content.Context
 import androidx.work.WorkerParameters
 import com.squareup.moshi.JsonClass
-import org.greenrobot.eventbus.EventBus
 import org.matrix.android.sdk.api.auth.data.Credentials
 import org.matrix.android.sdk.api.failure.shouldBeRetried
 import org.matrix.android.sdk.api.session.events.model.Event
@@ -52,7 +51,6 @@ internal class SendGossipRequestWorker(context: Context,
 
     @Inject lateinit var sendToDeviceTask: SendToDeviceTask
     @Inject lateinit var cryptoStore: IMXCryptoStore
-    @Inject lateinit var eventBus: EventBus
     @Inject lateinit var credentials: Credentials
 
     override fun injectWith(injector: SessionComponent) {
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 bcaa16f35637b524da116d45ab5052e2504200f6..8c680570562b5ac41a34c19100d92b646433a4cf 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
@@ -19,7 +19,6 @@ package org.matrix.android.sdk.internal.crypto
 import android.content.Context
 import androidx.work.WorkerParameters
 import com.squareup.moshi.JsonClass
-import org.greenrobot.eventbus.EventBus
 import org.matrix.android.sdk.api.auth.data.Credentials
 import org.matrix.android.sdk.api.failure.shouldBeRetried
 import org.matrix.android.sdk.api.session.events.model.Event
@@ -54,7 +53,6 @@ internal class SendGossipWorker(context: Context,
 
     @Inject lateinit var sendToDeviceTask: SendToDeviceTask
     @Inject lateinit var cryptoStore: IMXCryptoStore
-    @Inject lateinit var eventBus: EventBus
     @Inject lateinit var credentials: Credentials
     @Inject lateinit var messageEncrypter: MessageEncrypter
     @Inject lateinit var ensureOlmSessionsForDevicesAction: EnsureOlmSessionsForDevicesAction
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt
index fd431ce735655ce1f79a9277b0aab09bf80e02bb..fa8acafb83fabd4dcaffb55b8018bc79a4da536d 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt
@@ -68,6 +68,10 @@ internal class MXMegolmEncryption(
     // case outboundSession.shareOperation will be non-null.)
     private var outboundSession: MXOutboundSessionInfo? = null
 
+    init {
+        // restore existing outbound session if any
+       outboundSession = olmDevice.restoreOutboundGroupSessionForRoom(roomId)
+    }
     // Default rotation periods
     // TODO: Make it configurable via parameters
     // Session rotation periods
@@ -86,6 +90,9 @@ internal class MXMegolmEncryption(
         return encryptContent(outboundSession, eventType, eventContent)
                 .also {
                     notifyWithheldForSession(devices.withHeldDevices, outboundSession)
+                    // annoyingly we have to serialize again the saved outbound session to store message index :/
+                    // if not we would see duplicate message index errors
+                    olmDevice.storeOutboundGroupSessionForRoom(roomId, outboundSession.sessionId)
                 }
     }
 
@@ -107,6 +114,7 @@ internal class MXMegolmEncryption(
 
     override fun discardSessionKey() {
         outboundSession = null
+        olmDevice.discardOutboundGroupSessionForRoom(roomId)
     }
 
     /**
@@ -116,7 +124,7 @@ internal class MXMegolmEncryption(
      */
     private fun prepareNewSessionInRoom(): MXOutboundSessionInfo {
         Timber.v("## CRYPTO | prepareNewSessionInRoom() ")
-        val sessionId = olmDevice.createOutboundGroupSession()
+        val sessionId = olmDevice.createOutboundGroupSessionForRoom(roomId)
 
         val keysClaimedMap = HashMap<String, String>()
         keysClaimedMap["ed25519"] = olmDevice.deviceEd25519Key!!
@@ -152,7 +160,7 @@ internal class MXMegolmEncryption(
             val deviceIds = devicesInRoom.getUserDeviceIds(userId)
             for (deviceId in deviceIds!!) {
                 val deviceInfo = devicesInRoom.getObject(userId, deviceId)
-                if (deviceInfo != null && !cryptoStore.wasSessionSharedWithUser(roomId, safeSession.sessionId, userId, deviceId).found) {
+                if (deviceInfo != null && !cryptoStore.getSharedSessionInfo(roomId, safeSession.sessionId, userId, deviceId).found) {
                     val devices = shareMap.getOrPut(userId) { ArrayList() }
                     devices.add(deviceInfo)
                 }
@@ -401,11 +409,18 @@ internal class MXMegolmEncryption(
                 .also { Timber.w("## Crypto reshareKey: Device not found") }
 
         // Get the chain index of the key we previously sent this device
-        val chainIndex = outboundSession?.sharedWithHelper?.wasSharedWith(userId, deviceId) ?: return false
+        val wasSessionSharedWithUser = cryptoStore.getSharedSessionInfo(roomId, sessionId, userId, deviceId)
+        if (!wasSessionSharedWithUser.found) {
+            // This session was never shared with this user
+            // Send a room key with held
+            notifyKeyWithHeld(listOf(UserDevice(userId, deviceId)), sessionId, senderKey, WithHeldCode.UNAUTHORISED)
+            Timber.w("## Crypto reshareKey: ERROR : Never shared megolm with this device")
+            return false
+        }
+        // if found chain index should not be null
+        val chainIndex = wasSessionSharedWithUser.chainIndex ?: return false
                 .also {
-                    // Send a room key with held
-                    notifyKeyWithHeld(listOf(UserDevice(userId, deviceId)), sessionId, senderKey, WithHeldCode.UNAUTHORISED)
-                    Timber.w("## Crypto reshareKey: ERROR : Never share megolm with this device")
+                    Timber.w("## Crypto reshareKey: Null chain index")
                 }
 
         val devicesByUser = mapOf(userId to listOf(deviceInfo))
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXOutboundSessionInfo.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXOutboundSessionInfo.kt
index 9244b4d5e73069ad4c4ee60ebccefa21d88bce63..5a689378683b141bcd304e9b0cfee2eef70a8a8b 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXOutboundSessionInfo.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXOutboundSessionInfo.kt
@@ -23,9 +23,9 @@ import timber.log.Timber
 internal class MXOutboundSessionInfo(
         // The id of the session
         val sessionId: String,
-        val sharedWithHelper: SharedWithHelper) {
-    // When the session was created
-    private val creationTime = System.currentTimeMillis()
+        val sharedWithHelper: SharedWithHelper,
+        // When the session was created
+        private val creationTime: Long = System.currentTimeMillis()) {
 
     // Number of times this session has been used
     var useCount: Int = 0
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/SharedWithHelper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/SharedWithHelper.kt
index 921f9b2cdc3831425e5852924d4af738e8e2ef87..f17168a6d22adb708647202484d55c6f50d396aa 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/SharedWithHelper.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/SharedWithHelper.kt
@@ -28,10 +28,6 @@ internal class SharedWithHelper(
         return cryptoStore.getSharedWithInfo(roomId, sessionId)
     }
 
-    fun wasSharedWith(userId: String, deviceId: String): Int? {
-        return cryptoStore.wasSessionSharedWithUser(roomId, sessionId, userId, deviceId).chainIndex
-    }
-
     fun markedSessionAsShared(userId: String, deviceId: String, chainIndex: Int) {
         cryptoStore.markedSessionAsShared(roomId, sessionId, userId, deviceId, chainIndex)
     }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/attachments/ElementToDecrypt.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/attachments/ElementToDecrypt.kt
index b77006aa3a9b9a1a1299aa0aa3fc8cd1ca251a96..c071384df4603cb08bdc5ed333591dbf89782bc9 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/attachments/ElementToDecrypt.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/attachments/ElementToDecrypt.kt
@@ -18,7 +18,7 @@ package org.matrix.android.sdk.internal.crypto.attachments
 
 import android.os.Parcelable
 import org.matrix.android.sdk.internal.crypto.model.rest.EncryptedFileInfo
-import kotlinx.android.parcel.Parcelize
+import kotlinx.parcelize.Parcelize
 
 fun EncryptedFileInfo.toElementToDecrypt(): ElementToDecrypt? {
     // Check the validity of some fields
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/DefaultCrossSigningService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/DefaultCrossSigningService.kt
index bcad448eb6497928fd3b23e732a2444c19d9d7ba..9b282f0a84bd2235b15bc9415a3d5503891dd533 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/DefaultCrossSigningService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/DefaultCrossSigningService.kt
@@ -19,30 +19,30 @@ package org.matrix.android.sdk.internal.crypto.crosssigning
 import androidx.lifecycle.LiveData
 import androidx.work.BackoffPolicy
 import androidx.work.ExistingWorkPolicy
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
 import org.matrix.android.sdk.api.MatrixCallback
+import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
 import org.matrix.android.sdk.api.extensions.orFalse
 import org.matrix.android.sdk.api.session.crypto.crosssigning.CrossSigningService
 import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo
 import org.matrix.android.sdk.api.util.Optional
 import org.matrix.android.sdk.internal.crypto.DeviceListManager
+import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo
 import org.matrix.android.sdk.internal.crypto.model.rest.UploadSignatureQueryBuilder
-import org.matrix.android.sdk.internal.crypto.model.rest.UserPasswordAuth
 import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
 import org.matrix.android.sdk.internal.crypto.store.PrivateKeysInfo
 import org.matrix.android.sdk.internal.crypto.tasks.InitializeCrossSigningTask
 import org.matrix.android.sdk.internal.crypto.tasks.UploadSignaturesTask
+import org.matrix.android.sdk.internal.di.SessionId
 import org.matrix.android.sdk.internal.di.UserId
+import org.matrix.android.sdk.internal.di.WorkManagerProvider
 import org.matrix.android.sdk.internal.session.SessionScope
 import org.matrix.android.sdk.internal.task.TaskExecutor
 import org.matrix.android.sdk.internal.task.TaskThread
 import org.matrix.android.sdk.internal.task.configureWith
 import org.matrix.android.sdk.internal.util.JsonCanonicalizer
 import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.launch
-import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo
-import org.matrix.android.sdk.internal.di.SessionId
-import org.matrix.android.sdk.internal.di.WorkManagerProvider
 import org.matrix.android.sdk.internal.worker.WorkerParamsFactory
 import org.matrix.olm.OlmPkSigning
 import org.matrix.olm.OlmUtility
@@ -61,7 +61,10 @@ internal class DefaultCrossSigningService @Inject constructor(
         private val taskExecutor: TaskExecutor,
         private val coroutineDispatchers: MatrixCoroutineDispatchers,
         private val cryptoCoroutineScope: CoroutineScope,
-        private val workManagerProvider: WorkManagerProvider) : CrossSigningService, DeviceListManager.UserDevicesUpdateListener {
+        private val workManagerProvider: WorkManagerProvider,
+        private val updateTrustWorkerDataRepository: UpdateTrustWorkerDataRepository
+) : CrossSigningService,
+        DeviceListManager.UserDevicesUpdateListener {
 
     private var olmUtility: OlmUtility? = null
 
@@ -147,11 +150,11 @@ internal class DefaultCrossSigningService @Inject constructor(
      *   - Sign the keys and upload them
      *   - Sign the current device with SSK and sign MSK with device key (migration) and upload signatures
      */
-    override fun initializeCrossSigning(authParams: UserPasswordAuth?, callback: MatrixCallback<Unit>) {
+    override fun initializeCrossSigning(uiaInterceptor: UserInteractiveAuthInterceptor?, callback: MatrixCallback<Unit>) {
         Timber.d("## CrossSigning  initializeCrossSigning")
 
         val params = InitializeCrossSigningTask.Params(
-                authParams = authParams
+                interactiveAuthInterceptor = uiaInterceptor
         )
         initializeCrossSigningTask.configureWith(params) {
             this.callbackThread = TaskThread.CRYPTO
@@ -689,7 +692,7 @@ internal class DefaultCrossSigningService @Inject constructor(
         return DeviceTrustResult.Success(DeviceTrustLevel(crossSigningVerified = true, locallyVerified = locallyTrusted))
     }
 
-    fun checkDeviceTrust(myKeys: MXCrossSigningInfo?, otherKeys: MXCrossSigningInfo?, otherDevice: CryptoDeviceInfo) : DeviceTrustResult {
+    fun checkDeviceTrust(myKeys: MXCrossSigningInfo?, otherKeys: MXCrossSigningInfo?, otherDevice: CryptoDeviceInfo): DeviceTrustResult {
         val locallyTrusted = otherDevice.trustLevel?.isLocallyVerified()
         myKeys ?: return legacyFallbackTrust(locallyTrusted, DeviceTrustResult.CrossSigningNotConfigured(userId))
 
@@ -747,8 +750,11 @@ internal class DefaultCrossSigningService @Inject constructor(
     }
 
     override fun onUsersDeviceUpdate(userIds: List<String>) {
-        Timber.d("## CrossSigning - onUsersDeviceUpdate for $userIds")
-        val workerParams = UpdateTrustWorker.Params(sessionId = sessionId, updatedUserIds = userIds)
+        Timber.d("## CrossSigning - onUsersDeviceUpdate for ${userIds.size} users: $userIds")
+        val workerParams = UpdateTrustWorker.Params(
+                sessionId = sessionId,
+                filename = updateTrustWorkerDataRepository.createParam(userIds)
+        )
         val workerData = WorkerParamsFactory.toData(workerParams)
 
         val workRequest = workManagerProvider.matrixOneTimeWorkRequestBuilder<UpdateTrustWorker>()
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 665d770e7f69780469d55661c7538162878af49f..1660bae0b7f1bd5635943bf5705c2240713c1ed0 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
@@ -55,7 +55,11 @@ internal class UpdateTrustWorker(context: Context,
     internal data class Params(
             override val sessionId: String,
             override val lastFailureMessage: String? = null,
-            val updatedUserIds: List<String>
+            // Kept for compatibility, but not used anymore (can be used for pending Worker)
+            val updatedUserIds: List<String>? = null,
+            // Passing a long list of userId can break the Work Manager due to data size limitation.
+            // so now we use a temporary file to store the data
+            val filename: String? = null
     ) : SessionWorkerParams
 
     @Inject lateinit var crossSigningService: DefaultCrossSigningService
@@ -64,6 +68,7 @@ internal class UpdateTrustWorker(context: Context,
     @CryptoDatabase @Inject lateinit var realmConfiguration: RealmConfiguration
     @UserId @Inject lateinit var myUserId: String
     @Inject lateinit var crossSigningKeysMapper: CrossSigningKeysMapper
+    @Inject lateinit var updateTrustWorkerDataRepository: UpdateTrustWorkerDataRepository
     @SessionDatabase @Inject lateinit var sessionRealmConfiguration: RealmConfiguration
 
     //    @Inject lateinit var roomSummaryUpdater: RoomSummaryUpdater
@@ -74,7 +79,17 @@ internal class UpdateTrustWorker(context: Context,
     }
 
     override suspend fun doSafeWork(params: Params): Result {
-        var userList = params.updatedUserIds
+        var userList = params.filename
+                ?.let { updateTrustWorkerDataRepository.getParam(it) }
+                ?.userIds
+                ?: params.updatedUserIds.orEmpty()
+
+        if (userList.isEmpty()) {
+            // This should not happen, but let's avoid go further in case of empty list
+            cleanup(params)
+            return Result.success()
+        }
+
         // Unfortunately we don't have much info on what did exactly changed (is it the cross signing keys of that user,
         // or a new device?) So we check all again :/
 
@@ -213,9 +228,15 @@ internal class UpdateTrustWorker(context: Context,
             }
         }
 
+        cleanup(params)
         return Result.success()
     }
 
+    private fun cleanup(params: Params) {
+        params.filename
+                ?.let { updateTrustWorkerDataRepository.delete(it) }
+    }
+
     private fun updateCrossSigningKeysTrust(realm: Realm, userId: String, verified: Boolean) {
         val xInfoEntity = realm.where(CrossSigningInfoEntity::class.java)
                 .equalTo(CrossSigningInfoEntityFields.USER_ID, userId)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/UpdateTrustWorkerDataRepository.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/UpdateTrustWorkerDataRepository.kt
new file mode 100644
index 0000000000000000000000000000000000000000..0878a9f7654d0d19b302d5f18bfb4e39db1f1a9e
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/UpdateTrustWorkerDataRepository.kt
@@ -0,0 +1,65 @@
+/*
+ * 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.
+ * 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.crypto.crosssigning
+
+import com.squareup.moshi.Json
+import com.squareup.moshi.JsonClass
+import org.matrix.android.sdk.api.extensions.tryOrNull
+import org.matrix.android.sdk.internal.di.MoshiProvider
+import org.matrix.android.sdk.internal.di.SessionFilesDirectory
+import java.io.File
+import java.util.UUID
+import javax.inject.Inject
+
+@JsonClass(generateAdapter = true)
+internal data class UpdateTrustWorkerData(
+        @Json(name = "userIds")
+        val userIds: List<String>
+)
+
+internal class UpdateTrustWorkerDataRepository @Inject constructor(
+        @SessionFilesDirectory parentDir: File
+) {
+    private val workingDirectory = File(parentDir, "tw")
+    private val jsonAdapter = MoshiProvider.providesMoshi().adapter(UpdateTrustWorkerData::class.java)
+
+    // Return the path of the created file
+    fun createParam(userIds: List<String>): String {
+        val filename = "${UUID.randomUUID()}.json"
+        workingDirectory.mkdirs()
+        val file = File(workingDirectory, filename)
+
+        UpdateTrustWorkerData(userIds = userIds)
+                .let { jsonAdapter.toJson(it) }
+                .let { file.writeText(it) }
+
+        return filename
+    }
+
+    fun getParam(filename: String): UpdateTrustWorkerData? {
+        return File(workingDirectory, filename)
+                .takeIf { it.exists() }
+                ?.readText()
+                ?.let { jsonAdapter.fromJson(it) }
+    }
+
+    fun delete(filename: String) {
+        tryOrNull("Unable to delete $filename") {
+            File(workingDirectory, filename).delete()
+        }
+    }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/CreateKeysBackupVersionTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/CreateKeysBackupVersionTask.kt
index 36b667911d42492f8b0a40768f6ae60b51762a62..5c59cfd80e3215ae47e104f15e03548bc02bd6c5 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/CreateKeysBackupVersionTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/CreateKeysBackupVersionTask.kt
@@ -19,20 +19,20 @@ package org.matrix.android.sdk.internal.crypto.keysbackup.tasks
 import org.matrix.android.sdk.internal.crypto.keysbackup.api.RoomKeysApi
 import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.CreateKeysBackupVersionBody
 import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysVersion
+import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import org.matrix.android.sdk.internal.network.executeRequest
 import org.matrix.android.sdk.internal.task.Task
-import org.greenrobot.eventbus.EventBus
 import javax.inject.Inject
 
 internal interface CreateKeysBackupVersionTask : Task<CreateKeysBackupVersionBody, KeysVersion>
 
 internal class DefaultCreateKeysBackupVersionTask @Inject constructor(
         private val roomKeysApi: RoomKeysApi,
-        private val eventBus: EventBus
+        private val globalErrorReceiver: GlobalErrorReceiver
 ) : CreateKeysBackupVersionTask {
 
     override suspend fun execute(params: CreateKeysBackupVersionBody): KeysVersion {
-        return executeRequest(eventBus) {
+        return executeRequest(globalErrorReceiver) {
             apiCall = roomKeysApi.createKeysBackupVersion(params)
         }
     }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/DeleteBackupTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/DeleteBackupTask.kt
index d174be20a2a99ee4adb56bd1945d69e4a01e1058..ec09da7240cc48a97116fa0cb71ea191613253c8 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/DeleteBackupTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/DeleteBackupTask.kt
@@ -17,9 +17,9 @@
 package org.matrix.android.sdk.internal.crypto.keysbackup.tasks
 
 import org.matrix.android.sdk.internal.crypto.keysbackup.api.RoomKeysApi
+import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import org.matrix.android.sdk.internal.network.executeRequest
 import org.matrix.android.sdk.internal.task.Task
-import org.greenrobot.eventbus.EventBus
 import javax.inject.Inject
 
 internal interface DeleteBackupTask : Task<DeleteBackupTask.Params, Unit> {
@@ -30,11 +30,11 @@ internal interface DeleteBackupTask : Task<DeleteBackupTask.Params, Unit> {
 
 internal class DefaultDeleteBackupTask @Inject constructor(
         private val roomKeysApi: RoomKeysApi,
-        private val eventBus: EventBus
+        private val globalErrorReceiver: GlobalErrorReceiver
 ) : DeleteBackupTask {
 
     override suspend fun execute(params: DeleteBackupTask.Params) {
-        return executeRequest(eventBus) {
+        return executeRequest(globalErrorReceiver) {
             apiCall = roomKeysApi.deleteBackup(params.version)
         }
     }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/DeleteRoomSessionDataTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/DeleteRoomSessionDataTask.kt
index 6826596ba44788f04fe697a9f1e200a81e2eb48a..9c477efb7821e6af51e2bca87fec6621b545d267 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/DeleteRoomSessionDataTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/DeleteRoomSessionDataTask.kt
@@ -17,9 +17,9 @@
 package org.matrix.android.sdk.internal.crypto.keysbackup.tasks
 
 import org.matrix.android.sdk.internal.crypto.keysbackup.api.RoomKeysApi
+import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import org.matrix.android.sdk.internal.network.executeRequest
 import org.matrix.android.sdk.internal.task.Task
-import org.greenrobot.eventbus.EventBus
 import javax.inject.Inject
 
 internal interface DeleteRoomSessionDataTask : Task<DeleteRoomSessionDataTask.Params, Unit> {
@@ -32,11 +32,11 @@ internal interface DeleteRoomSessionDataTask : Task<DeleteRoomSessionDataTask.Pa
 
 internal class DefaultDeleteRoomSessionDataTask @Inject constructor(
         private val roomKeysApi: RoomKeysApi,
-        private val eventBus: EventBus
+        private val globalErrorReceiver: GlobalErrorReceiver
 ) : DeleteRoomSessionDataTask {
 
     override suspend fun execute(params: DeleteRoomSessionDataTask.Params) {
-        return executeRequest(eventBus) {
+        return executeRequest(globalErrorReceiver) {
             apiCall = roomKeysApi.deleteRoomSessionData(
                     params.roomId,
                     params.sessionId,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/DeleteRoomSessionsDataTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/DeleteRoomSessionsDataTask.kt
index 5c9aacc1ac3f2f37d1c679a2a52bd6a3fb7cdb59..82d022f3abdb90478cfa620567bc5643721eb9d8 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/DeleteRoomSessionsDataTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/DeleteRoomSessionsDataTask.kt
@@ -17,9 +17,9 @@
 package org.matrix.android.sdk.internal.crypto.keysbackup.tasks
 
 import org.matrix.android.sdk.internal.crypto.keysbackup.api.RoomKeysApi
+import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import org.matrix.android.sdk.internal.network.executeRequest
 import org.matrix.android.sdk.internal.task.Task
-import org.greenrobot.eventbus.EventBus
 import javax.inject.Inject
 
 internal interface DeleteRoomSessionsDataTask : Task<DeleteRoomSessionsDataTask.Params, Unit> {
@@ -31,11 +31,11 @@ internal interface DeleteRoomSessionsDataTask : Task<DeleteRoomSessionsDataTask.
 
 internal class DefaultDeleteRoomSessionsDataTask @Inject constructor(
         private val roomKeysApi: RoomKeysApi,
-        private val eventBus: EventBus
+        private val globalErrorReceiver: GlobalErrorReceiver
 ) : DeleteRoomSessionsDataTask {
 
     override suspend fun execute(params: DeleteRoomSessionsDataTask.Params) {
-        return executeRequest(eventBus) {
+        return executeRequest(globalErrorReceiver) {
             apiCall = roomKeysApi.deleteRoomSessionsData(
                     params.roomId,
                     params.version)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/DeleteSessionsDataTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/DeleteSessionsDataTask.kt
index 3c9cab3fa07c92112bb4f3f238a5a6df06a87385..e4df379963db0a2864d13efba815acc3c6c4e303 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/DeleteSessionsDataTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/DeleteSessionsDataTask.kt
@@ -17,9 +17,9 @@
 package org.matrix.android.sdk.internal.crypto.keysbackup.tasks
 
 import org.matrix.android.sdk.internal.crypto.keysbackup.api.RoomKeysApi
+import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import org.matrix.android.sdk.internal.network.executeRequest
 import org.matrix.android.sdk.internal.task.Task
-import org.greenrobot.eventbus.EventBus
 import javax.inject.Inject
 
 internal interface DeleteSessionsDataTask : Task<DeleteSessionsDataTask.Params, Unit> {
@@ -30,11 +30,11 @@ internal interface DeleteSessionsDataTask : Task<DeleteSessionsDataTask.Params,
 
 internal class DefaultDeleteSessionsDataTask @Inject constructor(
         private val roomKeysApi: RoomKeysApi,
-        private val eventBus: EventBus
+        private val globalErrorReceiver: GlobalErrorReceiver
 ) : DeleteSessionsDataTask {
 
     override suspend fun execute(params: DeleteSessionsDataTask.Params) {
-        return executeRequest(eventBus) {
+        return executeRequest(globalErrorReceiver) {
             apiCall = roomKeysApi.deleteSessionsData(params.version)
         }
     }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/GetKeysBackupLastVersionTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/GetKeysBackupLastVersionTask.kt
index 25f8f854483623a1e0e7d6acb1d75b9aafc683c1..3566ff0e6856832ac3346b81946e7016a512744a 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/GetKeysBackupLastVersionTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/GetKeysBackupLastVersionTask.kt
@@ -18,20 +18,20 @@ package org.matrix.android.sdk.internal.crypto.keysbackup.tasks
 
 import org.matrix.android.sdk.internal.crypto.keysbackup.api.RoomKeysApi
 import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysVersionResult
+import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import org.matrix.android.sdk.internal.network.executeRequest
 import org.matrix.android.sdk.internal.task.Task
-import org.greenrobot.eventbus.EventBus
 import javax.inject.Inject
 
 internal interface GetKeysBackupLastVersionTask : Task<Unit, KeysVersionResult>
 
 internal class DefaultGetKeysBackupLastVersionTask @Inject constructor(
         private val roomKeysApi: RoomKeysApi,
-        private val eventBus: EventBus
+        private val globalErrorReceiver: GlobalErrorReceiver
 ) : GetKeysBackupLastVersionTask {
 
     override suspend fun execute(params: Unit): KeysVersionResult {
-        return executeRequest(eventBus) {
+        return executeRequest(globalErrorReceiver) {
             apiCall = roomKeysApi.getKeysBackupLastVersion()
         }
     }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/GetKeysBackupVersionTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/GetKeysBackupVersionTask.kt
index dd2dd70e4d2ae01bbc198a508f0a08c4fcbba87f..13c99fb0f4235a1278fdb23129e778e428b78858 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/GetKeysBackupVersionTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/GetKeysBackupVersionTask.kt
@@ -18,20 +18,20 @@ package org.matrix.android.sdk.internal.crypto.keysbackup.tasks
 
 import org.matrix.android.sdk.internal.crypto.keysbackup.api.RoomKeysApi
 import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysVersionResult
+import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import org.matrix.android.sdk.internal.network.executeRequest
 import org.matrix.android.sdk.internal.task.Task
-import org.greenrobot.eventbus.EventBus
 import javax.inject.Inject
 
 internal interface GetKeysBackupVersionTask : Task<String, KeysVersionResult>
 
 internal class DefaultGetKeysBackupVersionTask @Inject constructor(
         private val roomKeysApi: RoomKeysApi,
-        private val eventBus: EventBus
+        private val globalErrorReceiver: GlobalErrorReceiver
 ) : GetKeysBackupVersionTask {
 
     override suspend fun execute(params: String): KeysVersionResult {
-        return executeRequest(eventBus) {
+        return executeRequest(globalErrorReceiver) {
             apiCall = roomKeysApi.getKeysBackupVersion(params)
         }
     }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/GetRoomSessionDataTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/GetRoomSessionDataTask.kt
index 8ca03491a1f2fe1c20d48b066269e5df1585ba90..168020d9cd233459645b4860a1a7887bd3fc682e 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/GetRoomSessionDataTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/GetRoomSessionDataTask.kt
@@ -18,9 +18,9 @@ package org.matrix.android.sdk.internal.crypto.keysbackup.tasks
 
 import org.matrix.android.sdk.internal.crypto.keysbackup.api.RoomKeysApi
 import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeyBackupData
+import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import org.matrix.android.sdk.internal.network.executeRequest
 import org.matrix.android.sdk.internal.task.Task
-import org.greenrobot.eventbus.EventBus
 import javax.inject.Inject
 
 internal interface GetRoomSessionDataTask : Task<GetRoomSessionDataTask.Params, KeyBackupData> {
@@ -33,11 +33,11 @@ internal interface GetRoomSessionDataTask : Task<GetRoomSessionDataTask.Params,
 
 internal class DefaultGetRoomSessionDataTask @Inject constructor(
         private val roomKeysApi: RoomKeysApi,
-        private val eventBus: EventBus
+        private val globalErrorReceiver: GlobalErrorReceiver
 ) : GetRoomSessionDataTask {
 
     override suspend fun execute(params: GetRoomSessionDataTask.Params): KeyBackupData {
-        return executeRequest(eventBus) {
+        return executeRequest(globalErrorReceiver) {
             apiCall = roomKeysApi.getRoomSessionData(
                     params.roomId,
                     params.sessionId,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/GetRoomSessionsDataTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/GetRoomSessionsDataTask.kt
index d4c28418b55aab8057b107532b2bb2d15c6bc184..95d5ef2e53ffdee71a311236ba69ba81baa398d4 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/GetRoomSessionsDataTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/GetRoomSessionsDataTask.kt
@@ -18,9 +18,9 @@ package org.matrix.android.sdk.internal.crypto.keysbackup.tasks
 
 import org.matrix.android.sdk.internal.crypto.keysbackup.api.RoomKeysApi
 import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.RoomKeysBackupData
+import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import org.matrix.android.sdk.internal.network.executeRequest
 import org.matrix.android.sdk.internal.task.Task
-import org.greenrobot.eventbus.EventBus
 import javax.inject.Inject
 
 internal interface GetRoomSessionsDataTask : Task<GetRoomSessionsDataTask.Params, RoomKeysBackupData> {
@@ -32,11 +32,11 @@ internal interface GetRoomSessionsDataTask : Task<GetRoomSessionsDataTask.Params
 
 internal class DefaultGetRoomSessionsDataTask @Inject constructor(
         private val roomKeysApi: RoomKeysApi,
-        private val eventBus: EventBus
+        private val globalErrorReceiver: GlobalErrorReceiver
 ) : GetRoomSessionsDataTask {
 
     override suspend fun execute(params: GetRoomSessionsDataTask.Params): RoomKeysBackupData {
-        return executeRequest(eventBus) {
+        return executeRequest(globalErrorReceiver) {
             apiCall = roomKeysApi.getRoomSessionsData(
                     params.roomId,
                     params.version)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/GetSessionsDataTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/GetSessionsDataTask.kt
index c5df82b5aedb53e6e3a10d7a160308b1eed12e6c..e41a13e3eb258df56fe922c83794642020ab1c98 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/GetSessionsDataTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/GetSessionsDataTask.kt
@@ -18,9 +18,9 @@ package org.matrix.android.sdk.internal.crypto.keysbackup.tasks
 
 import org.matrix.android.sdk.internal.crypto.keysbackup.api.RoomKeysApi
 import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysBackupData
+import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import org.matrix.android.sdk.internal.network.executeRequest
 import org.matrix.android.sdk.internal.task.Task
-import org.greenrobot.eventbus.EventBus
 import javax.inject.Inject
 
 internal interface GetSessionsDataTask : Task<GetSessionsDataTask.Params, KeysBackupData> {
@@ -31,11 +31,11 @@ internal interface GetSessionsDataTask : Task<GetSessionsDataTask.Params, KeysBa
 
 internal class DefaultGetSessionsDataTask @Inject constructor(
         private val roomKeysApi: RoomKeysApi,
-        private val eventBus: EventBus
+        private val globalErrorReceiver: GlobalErrorReceiver
 ) : GetSessionsDataTask {
 
     override suspend fun execute(params: GetSessionsDataTask.Params): KeysBackupData {
-        return executeRequest(eventBus) {
+        return executeRequest(globalErrorReceiver) {
             apiCall = roomKeysApi.getSessionsData(params.version)
         }
     }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/StoreRoomSessionDataTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/StoreRoomSessionDataTask.kt
index 588a861a8af54bb26a1fe24d629e885ce9597dd2..3954277e3925d75c88d90b31697aa2c5c4d50080 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/StoreRoomSessionDataTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/StoreRoomSessionDataTask.kt
@@ -19,9 +19,9 @@ package org.matrix.android.sdk.internal.crypto.keysbackup.tasks
 import org.matrix.android.sdk.internal.crypto.keysbackup.api.RoomKeysApi
 import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.BackupKeysResult
 import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeyBackupData
+import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import org.matrix.android.sdk.internal.network.executeRequest
 import org.matrix.android.sdk.internal.task.Task
-import org.greenrobot.eventbus.EventBus
 import javax.inject.Inject
 
 internal interface StoreRoomSessionDataTask : Task<StoreRoomSessionDataTask.Params, BackupKeysResult> {
@@ -35,11 +35,11 @@ internal interface StoreRoomSessionDataTask : Task<StoreRoomSessionDataTask.Para
 
 internal class DefaultStoreRoomSessionDataTask @Inject constructor(
         private val roomKeysApi: RoomKeysApi,
-        private val eventBus: EventBus
+        private val globalErrorReceiver: GlobalErrorReceiver
 ) : StoreRoomSessionDataTask {
 
     override suspend fun execute(params: StoreRoomSessionDataTask.Params): BackupKeysResult {
-        return executeRequest(eventBus) {
+        return executeRequest(globalErrorReceiver) {
             apiCall = roomKeysApi.storeRoomSessionData(
                     params.roomId,
                     params.sessionId,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/StoreRoomSessionsDataTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/StoreRoomSessionsDataTask.kt
index b77e31e3878479730cdb33745d98d3c402add73e..4e209b4abc4f6eb0d9aca677cd9803be1c8c5112 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/StoreRoomSessionsDataTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/StoreRoomSessionsDataTask.kt
@@ -19,9 +19,9 @@ package org.matrix.android.sdk.internal.crypto.keysbackup.tasks
 import org.matrix.android.sdk.internal.crypto.keysbackup.api.RoomKeysApi
 import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.BackupKeysResult
 import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.RoomKeysBackupData
+import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import org.matrix.android.sdk.internal.network.executeRequest
 import org.matrix.android.sdk.internal.task.Task
-import org.greenrobot.eventbus.EventBus
 import javax.inject.Inject
 
 internal interface StoreRoomSessionsDataTask : Task<StoreRoomSessionsDataTask.Params, BackupKeysResult> {
@@ -34,11 +34,11 @@ internal interface StoreRoomSessionsDataTask : Task<StoreRoomSessionsDataTask.Pa
 
 internal class DefaultStoreRoomSessionsDataTask @Inject constructor(
         private val roomKeysApi: RoomKeysApi,
-        private val eventBus: EventBus
+        private val globalErrorReceiver: GlobalErrorReceiver
 ) : StoreRoomSessionsDataTask {
 
     override suspend fun execute(params: StoreRoomSessionsDataTask.Params): BackupKeysResult {
-        return executeRequest(eventBus) {
+        return executeRequest(globalErrorReceiver) {
             apiCall = roomKeysApi.storeRoomSessionsData(
                     params.roomId,
                     params.version,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/StoreSessionsDataTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/StoreSessionsDataTask.kt
index 3a8198073ee66236f7b2ff4a1dd4704adab9e544..a607477d21712a6369428d2927cf236b4cc342d0 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/StoreSessionsDataTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/StoreSessionsDataTask.kt
@@ -19,9 +19,9 @@ package org.matrix.android.sdk.internal.crypto.keysbackup.tasks
 import org.matrix.android.sdk.internal.crypto.keysbackup.api.RoomKeysApi
 import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.BackupKeysResult
 import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysBackupData
+import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import org.matrix.android.sdk.internal.network.executeRequest
 import org.matrix.android.sdk.internal.task.Task
-import org.greenrobot.eventbus.EventBus
 import javax.inject.Inject
 
 internal interface StoreSessionsDataTask : Task<StoreSessionsDataTask.Params, BackupKeysResult> {
@@ -33,11 +33,11 @@ internal interface StoreSessionsDataTask : Task<StoreSessionsDataTask.Params, Ba
 
 internal class DefaultStoreSessionsDataTask @Inject constructor(
         private val roomKeysApi: RoomKeysApi,
-        private val eventBus: EventBus
+        private val globalErrorReceiver: GlobalErrorReceiver
 ) : StoreSessionsDataTask {
 
     override suspend fun execute(params: StoreSessionsDataTask.Params): BackupKeysResult {
-        return executeRequest(eventBus) {
+        return executeRequest(globalErrorReceiver) {
             apiCall = roomKeysApi.storeSessionsData(
                     params.version,
                     params.keysBackupData)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/UpdateKeysBackupVersionTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/UpdateKeysBackupVersionTask.kt
index 50726c66ac0a83308861ca65e0fbf029bf1552ae..f012cd13ebd48ce1e1dda4d19b6988a93ddf905a 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/UpdateKeysBackupVersionTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/UpdateKeysBackupVersionTask.kt
@@ -18,9 +18,9 @@ package org.matrix.android.sdk.internal.crypto.keysbackup.tasks
 
 import org.matrix.android.sdk.internal.crypto.keysbackup.api.RoomKeysApi
 import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.UpdateKeysBackupVersionBody
+import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import org.matrix.android.sdk.internal.network.executeRequest
 import org.matrix.android.sdk.internal.task.Task
-import org.greenrobot.eventbus.EventBus
 import javax.inject.Inject
 
 internal interface UpdateKeysBackupVersionTask : Task<UpdateKeysBackupVersionTask.Params, Unit> {
@@ -32,11 +32,11 @@ internal interface UpdateKeysBackupVersionTask : Task<UpdateKeysBackupVersionTas
 
 internal class DefaultUpdateKeysBackupVersionTask @Inject constructor(
         private val roomKeysApi: RoomKeysApi,
-        private val eventBus: EventBus
+        private val globalErrorReceiver: GlobalErrorReceiver
 ) : UpdateKeysBackupVersionTask {
 
     override suspend fun execute(params: UpdateKeysBackupVersionTask.Params) {
-        return executeRequest(eventBus) {
+        return executeRequest(globalErrorReceiver) {
             apiCall = roomKeysApi.updateKeysBackupVersion(params.version, params.keysBackupVersionBody)
         }
     }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/SessionAssistedInjectModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/OutboundGroupSessionWrapper.kt
similarity index 65%
rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/SessionAssistedInjectModule.kt
rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/OutboundGroupSessionWrapper.kt
index da3ecfb907818821050770754057424fd2fdff38..b616284e14a93c2bac4e08ae9effea6f0fca8886 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/SessionAssistedInjectModule.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/OutboundGroupSessionWrapper.kt
@@ -5,7 +5,7 @@
  * 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
+ *     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,
@@ -14,11 +14,11 @@
  * limitations under the License.
  */
 
-package org.matrix.android.sdk.internal.di
+package org.matrix.android.sdk.internal.crypto.model
 
-import com.squareup.inject.assisted.dagger2.AssistedModule
-import dagger.Module
+import org.matrix.olm.OlmOutboundGroupSession
 
-@AssistedModule
-@Module(includes = [AssistedInject_SessionAssistedInjectModule::class])
-interface SessionAssistedInjectModule
+data class OutboundGroupSessionWrapper(
+        val outboundGroupSession: OlmOutboundGroupSession,
+        val creationTime: Long
+)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/DefaultBaseAuth.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/DefaultBaseAuth.kt
new file mode 100644
index 0000000000000000000000000000000000000000..bbb4a3a654844487b330858c8cf7054d15b76388
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/DefaultBaseAuth.kt
@@ -0,0 +1,34 @@
+/*
+ * 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.crypto.model.rest
+
+import org.matrix.android.sdk.api.auth.UIABaseAuth
+
+data class DefaultBaseAuth(
+        /**
+         * This is a session identifier that the client must pass back to the homeserver,
+         * if one is provided, in subsequent attempts to authenticate in the same API call.
+         */
+        override val session: String? = null
+
+) : UIABaseAuth {
+    override fun hasAuthInfo() = true
+
+    override fun copyWithSession(session: String) = this.copy(session = session)
+
+    override fun asMap(): Map<String, *> = mapOf("session" to session)
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/DeleteDeviceParams.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/DeleteDeviceParams.kt
index 0ce6f1f41cf515d5bc7efd35d08f31e84e6784b3..f636ab890d92224cfa236e658cf281fe3401d5a8 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/DeleteDeviceParams.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/DeleteDeviceParams.kt
@@ -24,5 +24,5 @@ import com.squareup.moshi.JsonClass
 @JsonClass(generateAdapter = true)
 internal data class DeleteDeviceParams(
         @Json(name = "auth")
-        val userPasswordAuth: UserPasswordAuth? = null
+        val auth: Map<String, *>? = null
 )
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/UploadSigningKeysBody.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/UploadSigningKeysBody.kt
index 3418bb327de22da4cedad79417a36e860b0a068b..d24b7ae5f0d26761ec4092cf8f95f15676ee28e0 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/UploadSigningKeysBody.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/UploadSigningKeysBody.kt
@@ -30,5 +30,5 @@ internal data class UploadSigningKeysBody(
         val userSigningKey: RestKeyInfo? = null,
 
         @Json(name = "auth")
-        val auth: UserPasswordAuth? = null
+        val auth: Map<String, *>? = null
 )
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt
index 9a9f645b49be7025bd4435beed01abb01c23cef5..6f1487f80a46da8694bff3ea046b2dab37fe6709 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt
@@ -33,11 +33,13 @@ 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.OlmInboundGroupSessionWrapper2
 import org.matrix.android.sdk.internal.crypto.model.OlmSessionWrapper
+import org.matrix.android.sdk.internal.crypto.model.OutboundGroupSessionWrapper
 import org.matrix.android.sdk.internal.crypto.model.event.RoomKeyWithHeldContent
 import org.matrix.android.sdk.internal.crypto.model.rest.DeviceInfo
 import org.matrix.android.sdk.internal.crypto.model.rest.RoomKeyRequestBody
 import org.matrix.android.sdk.internal.crypto.store.db.model.KeysBackupDataEntity
 import org.matrix.olm.OlmAccount
+import org.matrix.olm.OlmOutboundGroupSession
 
 /**
  * the crypto data store
@@ -293,6 +295,16 @@ internal interface IMXCryptoStore {
      */
     fun getInboundGroupSession(sessionId: String, senderKey: String): OlmInboundGroupSessionWrapper2?
 
+    /**
+     * Get the current outbound group session for this encrypted room
+     */
+    fun getCurrentOutboundGroupSessionForRoom(roomId: String): OutboundGroupSessionWrapper?
+
+    /**
+     * Get the current outbound group session for this encrypted room
+     */
+    fun storeCurrentOutboundGroupSessionForRoom(roomId: String, outboundGroupSession: OlmOutboundGroupSession?)
+
     /**
      * Remove an inbound group session
      *
@@ -439,7 +451,15 @@ internal interface IMXCryptoStore {
     fun getWithHeldMegolmSession(roomId: String, sessionId: String): RoomKeyWithHeldContent?
 
     fun markedSessionAsShared(roomId: String?, sessionId: String, userId: String, deviceId: String, chainIndex: Int)
-    fun wasSessionSharedWithUser(roomId: String?, sessionId: String, userId: String, deviceId: String): SharedSessionResult
+
+    /**
+     * Query for information on this session sharing history.
+     * @return SharedSessionResult
+     * if found is true then this session was initialy shared with that user|device,
+     * in this case chainIndex is not nullindicates the ratchet position.
+     * In found is false, chainIndex is null
+     */
+    fun getSharedSessionInfo(roomId: String?, sessionId: String, userId: String, deviceId: String): SharedSessionResult
     data class SharedSessionResult(val found: Boolean, val chainIndex: Int?)
 
     fun getSharedWithInfo(roomId: String?, sessionId: String): MXUsersDevicesMap<Int>
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt
index 6b83c4f7d1ba801a84881c7a5dae4b12cbe18318..369a4976c9683a7799f8809e44584a8896bfe97c 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt
@@ -47,6 +47,7 @@ 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.OlmInboundGroupSessionWrapper2
 import org.matrix.android.sdk.internal.crypto.model.OlmSessionWrapper
+import org.matrix.android.sdk.internal.crypto.model.OutboundGroupSessionWrapper
 import org.matrix.android.sdk.internal.crypto.model.event.RoomKeyWithHeldContent
 import org.matrix.android.sdk.internal.crypto.model.rest.DeviceInfo
 import org.matrix.android.sdk.internal.crypto.model.rest.RoomKeyRequestBody
@@ -73,6 +74,7 @@ import org.matrix.android.sdk.internal.crypto.store.db.model.OlmInboundGroupSess
 import org.matrix.android.sdk.internal.crypto.store.db.model.OlmInboundGroupSessionEntityFields
 import org.matrix.android.sdk.internal.crypto.store.db.model.OlmSessionEntity
 import org.matrix.android.sdk.internal.crypto.store.db.model.OlmSessionEntityFields
+import org.matrix.android.sdk.internal.crypto.store.db.model.OutboundGroupSessionInfoEntity
 import org.matrix.android.sdk.internal.crypto.store.db.model.OutgoingGossipingRequestEntity
 import org.matrix.android.sdk.internal.crypto.store.db.model.OutgoingGossipingRequestEntityFields
 import org.matrix.android.sdk.internal.crypto.store.db.model.SharedSessionEntity
@@ -95,6 +97,7 @@ import org.matrix.android.sdk.internal.di.UserId
 import org.matrix.android.sdk.internal.session.SessionScope
 import org.matrix.olm.OlmAccount
 import org.matrix.olm.OlmException
+import org.matrix.olm.OlmOutboundGroupSession
 import timber.log.Timber
 import javax.inject.Inject
 import kotlin.collections.set
@@ -756,6 +759,42 @@ internal class RealmCryptoStore @Inject constructor(
         return inboundGroupSessionToRelease[key]
     }
 
+    override fun getCurrentOutboundGroupSessionForRoom(roomId: String): OutboundGroupSessionWrapper? {
+        return doWithRealm(realmConfiguration) { realm ->
+            realm.where<CryptoRoomEntity>()
+                    .equalTo(CryptoRoomEntityFields.ROOM_ID, roomId)
+                    .findFirst()?.outboundSessionInfo?.let { entity ->
+                        entity.getOutboundGroupSession()?.let {
+                            OutboundGroupSessionWrapper(
+                                    it,
+                                    entity.creationTime ?: 0
+                            )
+                        }
+                    }
+        }
+    }
+
+    override fun storeCurrentOutboundGroupSessionForRoom(roomId: String, outboundGroupSession: OlmOutboundGroupSession?) {
+        // we can do this async, as it's just for restoring on next launch
+        // the olmdevice is caching the active instance
+        // this is called for each sent message (so not high frequency), thus we can use basic realm async without
+        // risk of reaching max async operation limit?
+        doRealmTransactionAsync(realmConfiguration) { realm ->
+            CryptoRoomEntity.getById(realm, roomId)?.let { entity ->
+                // we should delete existing outbound session info if any
+                entity.outboundSessionInfo?.deleteFromRealm()
+
+                if (outboundGroupSession != null) {
+                    val info = realm.createObject(OutboundGroupSessionInfoEntity::class.java).apply {
+                        creationTime = System.currentTimeMillis()
+                        putOutboundGroupSession(outboundGroupSession)
+                    }
+                    entity.outboundSessionInfo = info
+                }
+            }
+        }
+    }
+
     /**
      * Note: the result will be only use to export all the keys and not to use the OlmInboundGroupSessionWrapper2,
      * so there is no need to use or update `inboundGroupSessionToRelease` for native memory management
@@ -1645,7 +1684,7 @@ internal class RealmCryptoStore @Inject constructor(
         }
     }
 
-    override fun wasSessionSharedWithUser(roomId: String?, sessionId: String, userId: String, deviceId: String): IMXCryptoStore.SharedSessionResult {
+    override fun getSharedSessionInfo(roomId: String?, sessionId: String, userId: String, deviceId: String): IMXCryptoStore.SharedSessionResult {
         return doWithRealm(realmConfiguration) { realm ->
             SharedSessionEntity.get(realm, roomId, sessionId, userId, deviceId)?.let {
                 IMXCryptoStore.SharedSessionResult(true, it.chainIndex)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStoreMigration.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStoreMigration.kt
index 08806b0627de75a22b120b10b2ce60302d969b0c..bca79143885373c71a286b2b5cd5c276551a1b39 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStoreMigration.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStoreMigration.kt
@@ -44,6 +44,7 @@ import org.matrix.android.sdk.internal.di.SerializeNulls
 import io.realm.DynamicRealm
 import io.realm.RealmMigration
 import io.realm.RealmObjectSchema
+import org.matrix.android.sdk.internal.crypto.store.db.model.OutboundGroupSessionInfoEntityFields
 import org.matrix.androidsdk.crypto.data.MXOlmInboundGroupSession2
 import timber.log.Timber
 import javax.inject.Inject
@@ -55,7 +56,7 @@ internal class RealmCryptoStoreMigration @Inject constructor(private val crossSi
         // 0, 1, 2: legacy Riot-Android
         // 3: migrate to RiotX schema
         // 4, 5, 6, 7, 8, 9: migrations from RiotX (which was previously 1, 2, 3, 4, 5, 6)
-        const val CRYPTO_STORE_SCHEMA_VERSION = 11L
+        const val CRYPTO_STORE_SCHEMA_VERSION = 12L
     }
 
     private fun RealmObjectSchema.addFieldIfNotExists(fieldName: String, fieldType: Class<*>): RealmObjectSchema {
@@ -93,6 +94,7 @@ internal class RealmCryptoStoreMigration @Inject constructor(private val crossSi
         if (oldVersion <= 8) migrateTo9(realm)
         if (oldVersion <= 9) migrateTo10(realm)
         if (oldVersion <= 10) migrateTo11(realm)
+        if (oldVersion <= 11) migrateTo12(realm)
     }
 
     private fun migrateTo1Legacy(realm: DynamicRealm) {
@@ -483,4 +485,16 @@ internal class RealmCryptoStoreMigration @Inject constructor(private val crossSi
         realm.schema.get("CryptoMetadataEntity")
                 ?.addField(CryptoMetadataEntityFields.DEVICE_KEYS_SENT_TO_SERVER, Boolean::class.java)
     }
+
+    // Version 12L added outbound group session persistence
+    private fun migrateTo12(realm: DynamicRealm) {
+        Timber.d("Step 11 -> 12")
+        val outboundEntitySchema = realm.schema.create("OutboundGroupSessionInfoEntity")
+                .addField(OutboundGroupSessionInfoEntityFields.SERIALIZED_OUTBOUND_SESSION_DATA, String::class.java)
+                .addField(OutboundGroupSessionInfoEntityFields.CREATION_TIME, Long::class.java)
+                .setNullable(OutboundGroupSessionInfoEntityFields.CREATION_TIME, true)
+
+        realm.schema.get("CryptoRoomEntity")
+                ?.addRealmObjectField(CryptoRoomEntityFields.OUTBOUND_SESSION_INFO.`$`, outboundEntitySchema)
+    }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStoreModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStoreModule.kt
index a453fc3ed00db028e546491b4752f999f0f3654f..6aae68c83eeb3a533daded01446f3a0518228a90 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStoreModule.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStoreModule.kt
@@ -33,6 +33,7 @@ import org.matrix.android.sdk.internal.crypto.store.db.model.TrustLevelEntity
 import org.matrix.android.sdk.internal.crypto.store.db.model.UserEntity
 import org.matrix.android.sdk.internal.crypto.store.db.model.WithHeldSessionEntity
 import io.realm.annotations.RealmModule
+import org.matrix.android.sdk.internal.crypto.store.db.model.OutboundGroupSessionInfoEntity
 
 /**
  * Realm module for Crypto store classes
@@ -54,6 +55,7 @@ import io.realm.annotations.RealmModule
             OutgoingGossipingRequestEntity::class,
             MyDeviceLastSeenInfoEntity::class,
             WithHeldSessionEntity::class,
-            SharedSessionEntity::class
+            SharedSessionEntity::class,
+            OutboundGroupSessionInfoEntity::class
         ])
 internal class RealmCryptoStoreModule
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/CryptoRoomEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/CryptoRoomEntity.kt
index 4c19b5eb0ea610c7c208fda7d1519e4a5caa9d02..e226f3eaa8f8838c1c78cd46a82d9448cc90663a 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/CryptoRoomEntity.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/CryptoRoomEntity.kt
@@ -23,7 +23,12 @@ internal open class CryptoRoomEntity(
         @PrimaryKey var roomId: String? = null,
         var algorithm: String? = null,
         var shouldEncryptForInvitedMembers: Boolean? = null,
-        var blacklistUnverifiedDevices: Boolean = false)
+        var blacklistUnverifiedDevices: Boolean = false,
+        // Store the current outbound session for this room,
+        // to avoid re-create and re-share at each startup (if rotation not needed..)
+        // This is specific to megolm but not sure how to model it better
+        var outboundSessionInfo: OutboundGroupSessionInfoEntity? = null
+        )
     : RealmObject() {
 
     companion object
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/OutboundGroupSessionInfoEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/OutboundGroupSessionInfoEntity.kt
new file mode 100644
index 0000000000000000000000000000000000000000..d50db78415dad5128b9d85b1f0d4cd92915e91d0
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/OutboundGroupSessionInfoEntity.kt
@@ -0,0 +1,44 @@
+/*
+ * 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.crypto.store.db.model
+
+import io.realm.RealmObject
+import org.matrix.android.sdk.internal.crypto.store.db.deserializeFromRealm
+import org.matrix.android.sdk.internal.crypto.store.db.serializeForRealm
+import org.matrix.olm.OlmOutboundGroupSession
+import timber.log.Timber
+
+internal open class OutboundGroupSessionInfoEntity(
+        var serializedOutboundSessionData: String? = null,
+        var creationTime: Long? = null
+) : RealmObject() {
+
+    fun getOutboundGroupSession(): OlmOutboundGroupSession? {
+        return try {
+            deserializeFromRealm(serializedOutboundSessionData)
+        } catch (failure: Throwable) {
+            Timber.e(failure, "## getOutboundGroupSession() Deserialization failure")
+            return null
+        }
+    }
+
+    fun putOutboundGroupSession(olmOutboundGroupSession: OlmOutboundGroupSession?) {
+        serializedOutboundSessionData = serializeForRealm(olmOutboundGroupSession)
+    }
+
+    companion object
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/ClaimOneTimeKeysForUsersDeviceTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/ClaimOneTimeKeysForUsersDeviceTask.kt
index ae72c7198a48d24ff98c43321be5cd9e3413770e..3df6312adb63663c9f12e12f5579d9aed0d71812 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/ClaimOneTimeKeysForUsersDeviceTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/ClaimOneTimeKeysForUsersDeviceTask.kt
@@ -21,9 +21,9 @@ import org.matrix.android.sdk.internal.crypto.model.MXKey
 import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap
 import org.matrix.android.sdk.internal.crypto.model.rest.KeysClaimBody
 import org.matrix.android.sdk.internal.crypto.model.rest.KeysClaimResponse
+import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import org.matrix.android.sdk.internal.network.executeRequest
 import org.matrix.android.sdk.internal.task.Task
-import org.greenrobot.eventbus.EventBus
 import timber.log.Timber
 import javax.inject.Inject
 
@@ -36,13 +36,13 @@ internal interface ClaimOneTimeKeysForUsersDeviceTask : Task<ClaimOneTimeKeysFor
 
 internal class DefaultClaimOneTimeKeysForUsersDevice @Inject constructor(
         private val cryptoApi: CryptoApi,
-        private val eventBus: EventBus
+        private val globalErrorReceiver: GlobalErrorReceiver
 ) : ClaimOneTimeKeysForUsersDeviceTask {
 
     override suspend fun execute(params: ClaimOneTimeKeysForUsersDeviceTask.Params): MXUsersDevicesMap<MXKey> {
         val body = KeysClaimBody(oneTimeKeys = params.usersDevicesKeyTypesMap.map)
 
-        val keysClaimResponse = executeRequest<KeysClaimResponse>(eventBus) {
+        val keysClaimResponse = executeRequest<KeysClaimResponse>(globalErrorReceiver) {
             apiCall = cryptoApi.claimOneTimeKeysForUsersDevices(body)
         }
         val map = MXUsersDevicesMap<MXKey>()
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/DeleteDeviceTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/DeleteDeviceTask.kt
index e5078d5b4e3d223f54aeb6edd033589d8ebeda3b..ff25ac0f66d16e5d0021ba92a766f82833bbe234 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/DeleteDeviceTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/DeleteDeviceTask.kt
@@ -16,35 +16,44 @@
 
 package org.matrix.android.sdk.internal.crypto.tasks
 
-import org.matrix.android.sdk.api.failure.Failure
-import org.matrix.android.sdk.api.failure.toRegistrationFlowResponse
+import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
+import org.matrix.android.sdk.internal.auth.registration.handleUIA
 import org.matrix.android.sdk.internal.crypto.api.CryptoApi
 import org.matrix.android.sdk.internal.crypto.model.rest.DeleteDeviceParams
+import org.matrix.android.sdk.api.auth.UIABaseAuth
+import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import org.matrix.android.sdk.internal.network.executeRequest
 import org.matrix.android.sdk.internal.task.Task
-import org.greenrobot.eventbus.EventBus
+import timber.log.Timber
 import javax.inject.Inject
 
 internal interface DeleteDeviceTask : Task<DeleteDeviceTask.Params, Unit> {
     data class Params(
-            val deviceId: String
+            val deviceId: String,
+            val userInteractiveAuthInterceptor: UserInteractiveAuthInterceptor?,
+            val userAuthParam: UIABaseAuth?
     )
 }
 
 internal class DefaultDeleteDeviceTask @Inject constructor(
         private val cryptoApi: CryptoApi,
-        private val eventBus: EventBus
+        private val globalErrorReceiver: GlobalErrorReceiver
 ) : DeleteDeviceTask {
 
     override suspend fun execute(params: DeleteDeviceTask.Params) {
         try {
-            executeRequest<Unit>(eventBus) {
-                apiCall = cryptoApi.deleteDevice(params.deviceId, DeleteDeviceParams())
+            executeRequest<Unit>(globalErrorReceiver) {
+                apiCall = cryptoApi.deleteDevice(params.deviceId, DeleteDeviceParams(params.userAuthParam?.asMap()))
             }
         } catch (throwable: Throwable) {
-            throw throwable.toRegistrationFlowResponse()
-                    ?.let { Failure.RegistrationFlowError(it) }
-                    ?: throwable
+            if (params.userInteractiveAuthInterceptor == null
+                    || !handleUIA(throwable, params.userInteractiveAuthInterceptor) { auth ->
+                        execute(params.copy(userAuthParam = auth))
+                    }
+            ) {
+                Timber.d("## UIA: propagate failure")
+                throw  throwable
+            }
         }
     }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/DeleteDeviceWithUserPasswordTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/DeleteDeviceWithUserPasswordTask.kt
index 38eee7f932076cfade423958ea7e0d4d4ccecf46..dc0077425ed58b6cf89650fc54ca13677cce5e3d 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/DeleteDeviceWithUserPasswordTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/DeleteDeviceWithUserPasswordTask.kt
@@ -19,11 +19,11 @@ package org.matrix.android.sdk.internal.crypto.tasks
 import org.matrix.android.sdk.api.auth.data.LoginFlowTypes
 import org.matrix.android.sdk.internal.crypto.api.CryptoApi
 import org.matrix.android.sdk.internal.crypto.model.rest.DeleteDeviceParams
-import org.matrix.android.sdk.internal.crypto.model.rest.UserPasswordAuth
+import org.matrix.android.sdk.api.auth.UserPasswordAuth
 import org.matrix.android.sdk.internal.di.UserId
+import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import org.matrix.android.sdk.internal.network.executeRequest
 import org.matrix.android.sdk.internal.task.Task
-import org.greenrobot.eventbus.EventBus
 import javax.inject.Inject
 
 internal interface DeleteDeviceWithUserPasswordTask : Task<DeleteDeviceWithUserPasswordTask.Params, Unit> {
@@ -37,19 +37,19 @@ internal interface DeleteDeviceWithUserPasswordTask : Task<DeleteDeviceWithUserP
 internal class DefaultDeleteDeviceWithUserPasswordTask @Inject constructor(
         private val cryptoApi: CryptoApi,
         @UserId private val userId: String,
-        private val eventBus: EventBus
+        private val globalErrorReceiver: GlobalErrorReceiver
 ) : DeleteDeviceWithUserPasswordTask {
 
     override suspend fun execute(params: DeleteDeviceWithUserPasswordTask.Params) {
-        return executeRequest(eventBus) {
+        return executeRequest(globalErrorReceiver) {
             apiCall = cryptoApi.deleteDevice(params.deviceId,
                     DeleteDeviceParams(
-                            userPasswordAuth = UserPasswordAuth(
+                            auth = UserPasswordAuth(
                                     type = LoginFlowTypes.PASSWORD,
                                     session = params.authSession,
                                     user = userId,
                                     password = params.password
-                            )
+                            ).asMap()
                     )
             )
         }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/DownloadKeysForUsersTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/DownloadKeysForUsersTask.kt
index 7268c48113a082e28d7e482780402d43415616ef..5eb24b116ad00b2073ff065d046ef92c915b34df 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/DownloadKeysForUsersTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/DownloadKeysForUsersTask.kt
@@ -19,9 +19,9 @@ package org.matrix.android.sdk.internal.crypto.tasks
 import org.matrix.android.sdk.internal.crypto.api.CryptoApi
 import org.matrix.android.sdk.internal.crypto.model.rest.KeysQueryBody
 import org.matrix.android.sdk.internal.crypto.model.rest.KeysQueryResponse
+import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import org.matrix.android.sdk.internal.network.executeRequest
 import org.matrix.android.sdk.internal.task.Task
-import org.greenrobot.eventbus.EventBus
 import javax.inject.Inject
 
 internal interface DownloadKeysForUsersTask : Task<DownloadKeysForUsersTask.Params, KeysQueryResponse> {
@@ -35,7 +35,7 @@ internal interface DownloadKeysForUsersTask : Task<DownloadKeysForUsersTask.Para
 
 internal class DefaultDownloadKeysForUsers @Inject constructor(
         private val cryptoApi: CryptoApi,
-        private val eventBus: EventBus
+        private val globalErrorReceiver: GlobalErrorReceiver
 ) : DownloadKeysForUsersTask {
 
     override suspend fun execute(params: DownloadKeysForUsersTask.Params): KeysQueryResponse {
@@ -46,7 +46,7 @@ internal class DefaultDownloadKeysForUsers @Inject constructor(
                 token = params.token?.takeIf { it.isNotEmpty() }
         )
 
-        return executeRequest(eventBus) {
+        return executeRequest(globalErrorReceiver) {
             apiCall = cryptoApi.downloadKeysForUsers(body)
         }
     }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/GetDeviceInfoTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/GetDeviceInfoTask.kt
index 6bd69c6a0743815b99d5f2900b8cd6cdb0e70298..5f6d2e344fc30871cc4f89ec61b863e51d10509f 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/GetDeviceInfoTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/GetDeviceInfoTask.kt
@@ -18,9 +18,9 @@ package org.matrix.android.sdk.internal.crypto.tasks
 
 import org.matrix.android.sdk.internal.crypto.api.CryptoApi
 import org.matrix.android.sdk.internal.crypto.model.rest.DeviceInfo
+import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import org.matrix.android.sdk.internal.network.executeRequest
 import org.matrix.android.sdk.internal.task.Task
-import org.greenrobot.eventbus.EventBus
 import javax.inject.Inject
 
 internal interface GetDeviceInfoTask : Task<GetDeviceInfoTask.Params, DeviceInfo> {
@@ -29,11 +29,11 @@ internal interface GetDeviceInfoTask : Task<GetDeviceInfoTask.Params, DeviceInfo
 
 internal class DefaultGetDeviceInfoTask @Inject constructor(
         private val cryptoApi: CryptoApi,
-        private val eventBus: EventBus
+        private val globalErrorReceiver: GlobalErrorReceiver
 ) : GetDeviceInfoTask {
 
     override suspend fun execute(params: GetDeviceInfoTask.Params): DeviceInfo {
-        return executeRequest(eventBus) {
+        return executeRequest(globalErrorReceiver) {
             apiCall = cryptoApi.getDeviceInfo(params.deviceId)
         }
     }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/GetDevicesTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/GetDevicesTask.kt
index 731e1ca0312263dff938d88fd48559849a13e3f4..ea33a918bcf9ec91916a0335bdd6234ba0611b5f 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/GetDevicesTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/GetDevicesTask.kt
@@ -18,20 +18,20 @@ package org.matrix.android.sdk.internal.crypto.tasks
 
 import org.matrix.android.sdk.internal.crypto.api.CryptoApi
 import org.matrix.android.sdk.internal.crypto.model.rest.DevicesListResponse
+import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import org.matrix.android.sdk.internal.network.executeRequest
 import org.matrix.android.sdk.internal.task.Task
-import org.greenrobot.eventbus.EventBus
 import javax.inject.Inject
 
 internal interface GetDevicesTask : Task<Unit, DevicesListResponse>
 
 internal class DefaultGetDevicesTask @Inject constructor(
         private val cryptoApi: CryptoApi,
-        private val eventBus: EventBus
+        private val globalErrorReceiver: GlobalErrorReceiver
 ) : GetDevicesTask {
 
     override suspend fun execute(params: Unit): DevicesListResponse {
-        return executeRequest(eventBus) {
+        return executeRequest(globalErrorReceiver) {
             apiCall = cryptoApi.getDevices()
         }
     }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/GetKeyChangesTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/GetKeyChangesTask.kt
index 289a5226f58218d463b8a2202bf115dcc8b3a433..4cc9ab2fcb3cec2b27f38297f33463654a86611d 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/GetKeyChangesTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/GetKeyChangesTask.kt
@@ -18,9 +18,9 @@ package org.matrix.android.sdk.internal.crypto.tasks
 
 import org.matrix.android.sdk.internal.crypto.api.CryptoApi
 import org.matrix.android.sdk.internal.crypto.model.rest.KeyChangesResponse
+import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import org.matrix.android.sdk.internal.network.executeRequest
 import org.matrix.android.sdk.internal.task.Task
-import org.greenrobot.eventbus.EventBus
 import javax.inject.Inject
 
 internal interface GetKeyChangesTask : Task<GetKeyChangesTask.Params, KeyChangesResponse> {
@@ -34,11 +34,11 @@ internal interface GetKeyChangesTask : Task<GetKeyChangesTask.Params, KeyChanges
 
 internal class DefaultGetKeyChangesTask @Inject constructor(
         private val cryptoApi: CryptoApi,
-        private val eventBus: EventBus
+        private val globalErrorReceiver: GlobalErrorReceiver
 ) : GetKeyChangesTask {
 
     override suspend fun execute(params: GetKeyChangesTask.Params): KeyChangesResponse {
-        return executeRequest(eventBus) {
+        return executeRequest(globalErrorReceiver) {
             apiCall = cryptoApi.getKeyChanges(params.from, params.to)
         }
     }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/InitializeCrossSigningTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/InitializeCrossSigningTask.kt
index 6c0a76fa7d3bebc9a9f2434bf12b027d530229d5..ef31130f55c48f862f2502b1baeb26735ed1e683 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/InitializeCrossSigningTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/InitializeCrossSigningTask.kt
@@ -17,6 +17,8 @@
 package org.matrix.android.sdk.internal.crypto.tasks
 
 import dagger.Lazy
+import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
+import org.matrix.android.sdk.internal.auth.registration.handleUIA
 import org.matrix.android.sdk.internal.crypto.MXOlmDevice
 import org.matrix.android.sdk.internal.crypto.MyDeviceInfoHolder
 import org.matrix.android.sdk.internal.crypto.crosssigning.canonicalSignable
@@ -24,7 +26,6 @@ import org.matrix.android.sdk.internal.crypto.crosssigning.toBase64NoPadding
 import org.matrix.android.sdk.internal.crypto.model.CryptoCrossSigningKey
 import org.matrix.android.sdk.internal.crypto.model.KeyUsage
 import org.matrix.android.sdk.internal.crypto.model.rest.UploadSignatureQueryBuilder
-import org.matrix.android.sdk.internal.crypto.model.rest.UserPasswordAuth
 import org.matrix.android.sdk.internal.di.UserId
 import org.matrix.android.sdk.internal.task.Task
 import org.matrix.android.sdk.internal.util.JsonCanonicalizer
@@ -34,7 +35,7 @@ import javax.inject.Inject
 
 internal interface InitializeCrossSigningTask : Task<InitializeCrossSigningTask.Params, InitializeCrossSigningTask.Result> {
     data class Params(
-            val authParams: UserPasswordAuth?
+            val interactiveAuthInterceptor: UserInteractiveAuthInterceptor?
     )
 
     data class Result(
@@ -117,10 +118,21 @@ internal class DefaultInitializeCrossSigningTask @Inject constructor(
                             .key(sskPublicKey)
                             .signature(userId, masterPublicKey, signedSSK)
                             .build(),
-                    userPasswordAuth = params.authParams
+                    userAuthParam = null
+//                    userAuthParam = params.authParams
             )
 
-            uploadSigningKeysTask.execute(uploadSigningKeysParams)
+            try {
+                uploadSigningKeysTask.execute(uploadSigningKeysParams)
+            } catch (failure: Throwable) {
+                if (params.interactiveAuthInterceptor == null
+                        || !handleUIA(failure, params.interactiveAuthInterceptor) { authUpdate ->
+                            uploadSigningKeysTask.execute(uploadSigningKeysParams.copy(userAuthParam = authUpdate))
+                        }) {
+                    Timber.d("## UIA: propagate failure")
+                    throw  failure
+                }
+            }
 
             //  Sign the current device with SSK
             val uploadSignatureQueryBuilder = UploadSignatureQueryBuilder()
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/RedactEventTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/RedactEventTask.kt
index f35d1b63e8fb772be348ec4e09d932c964b56872..5226e52b33f2daaf7fe6b0bce5e8e0b2927e43d1 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/RedactEventTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/RedactEventTask.kt
@@ -15,7 +15,7 @@
  */
 package org.matrix.android.sdk.internal.crypto.tasks
 
-import org.greenrobot.eventbus.EventBus
+import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import org.matrix.android.sdk.internal.network.executeRequest
 import org.matrix.android.sdk.internal.session.room.RoomAPI
 import org.matrix.android.sdk.internal.session.room.send.SendResponse
@@ -33,10 +33,10 @@ internal interface RedactEventTask : Task<RedactEventTask.Params, String> {
 
 internal class DefaultRedactEventTask @Inject constructor(
         private val roomAPI: RoomAPI,
-        private val eventBus: EventBus) : RedactEventTask {
+        private val globalErrorReceiver: GlobalErrorReceiver) : RedactEventTask {
 
     override suspend fun execute(params: RedactEventTask.Params): String {
-        val executeRequest = executeRequest<SendResponse>(eventBus) {
+        val executeRequest = executeRequest<SendResponse>(globalErrorReceiver) {
             apiCall = roomAPI.redactEvent(
                     txId = params.txID,
                     roomId = params.roomId,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendEventTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendEventTask.kt
index 8b739c4b64ba2f12cb9fe9025c19277b5009d254..b772bfbce227e365833162ee89f097d88613f6f9 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendEventTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendEventTask.kt
@@ -15,11 +15,12 @@
  */
 package org.matrix.android.sdk.internal.crypto.tasks
 
-import org.greenrobot.eventbus.EventBus
 import org.matrix.android.sdk.api.session.events.model.Event
 import org.matrix.android.sdk.api.session.room.send.SendState
+import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import org.matrix.android.sdk.internal.network.executeRequest
 import org.matrix.android.sdk.internal.session.room.RoomAPI
+import org.matrix.android.sdk.internal.session.room.membership.LoadRoomMembersTask
 import org.matrix.android.sdk.internal.session.room.send.LocalEchoRepository
 import org.matrix.android.sdk.internal.session.room.send.SendResponse
 import org.matrix.android.sdk.internal.task.Task
@@ -35,16 +36,24 @@ internal interface SendEventTask : Task<SendEventTask.Params, String> {
 internal class DefaultSendEventTask @Inject constructor(
         private val localEchoRepository: LocalEchoRepository,
         private val encryptEventTask: DefaultEncryptEventTask,
+        private val loadRoomMembersTask: LoadRoomMembersTask,
         private val roomAPI: RoomAPI,
-        private val eventBus: EventBus) : SendEventTask {
+        private val globalErrorReceiver: GlobalErrorReceiver) : SendEventTask {
 
     override suspend fun execute(params: SendEventTask.Params): String {
         try {
+            // Make sure to load all members in the room before sending the event.
+            params.event.roomId
+                    ?.takeIf { params.encrypt }
+                    ?.let { roomId ->
+                        loadRoomMembersTask.execute(LoadRoomMembersTask.Params(roomId))
+                    }
+
             val event = handleEncryption(params)
             val localId = event.eventId!!
 
             localEchoRepository.updateSendState(localId, params.event.roomId, SendState.SENDING)
-            val executeRequest = executeRequest<SendResponse>(eventBus) {
+            val executeRequest = executeRequest<SendResponse>(globalErrorReceiver) {
                 apiCall = roomAPI.send(
                         localId,
                         roomId = event.roomId ?: "",
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendToDeviceTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendToDeviceTask.kt
index 37e0bbc887c92468b54a2497cafaa7515627b8a5..d2af91601b49a994ae4de8ed37a9e7480465ee18 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendToDeviceTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendToDeviceTask.kt
@@ -19,9 +19,9 @@ package org.matrix.android.sdk.internal.crypto.tasks
 import org.matrix.android.sdk.internal.crypto.api.CryptoApi
 import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap
 import org.matrix.android.sdk.internal.crypto.model.rest.SendToDeviceBody
+import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import org.matrix.android.sdk.internal.network.executeRequest
 import org.matrix.android.sdk.internal.task.Task
-import org.greenrobot.eventbus.EventBus
 import javax.inject.Inject
 import kotlin.random.Random
 
@@ -38,7 +38,7 @@ internal interface SendToDeviceTask : Task<SendToDeviceTask.Params, Unit> {
 
 internal class DefaultSendToDeviceTask @Inject constructor(
         private val cryptoApi: CryptoApi,
-        private val eventBus: EventBus
+        private val globalErrorReceiver: GlobalErrorReceiver
 ) : SendToDeviceTask {
 
     override suspend fun execute(params: SendToDeviceTask.Params) {
@@ -46,7 +46,7 @@ internal class DefaultSendToDeviceTask @Inject constructor(
                 messages = params.contentMap.map
         )
 
-        return executeRequest(eventBus) {
+        return executeRequest(globalErrorReceiver) {
             apiCall = cryptoApi.sendToDevice(
                     params.eventType,
                     params.transactionId ?: Random.nextInt(Integer.MAX_VALUE).toString(),
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendVerificationMessageTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendVerificationMessageTask.kt
index cedb7a6618aecb8313a86f16d1bb0715e4443905..c39dfb101668ed56ad6ace386a61395ac36de769 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendVerificationMessageTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendVerificationMessageTask.kt
@@ -15,10 +15,10 @@
  */
 package org.matrix.android.sdk.internal.crypto.tasks
 
-import org.greenrobot.eventbus.EventBus
 import org.matrix.android.sdk.api.session.events.model.Event
 import org.matrix.android.sdk.api.session.room.send.SendState
 import org.matrix.android.sdk.internal.crypto.CryptoSessionInfoProvider
+import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import org.matrix.android.sdk.internal.network.executeRequest
 import org.matrix.android.sdk.internal.session.room.RoomAPI
 import org.matrix.android.sdk.internal.session.room.send.LocalEchoRepository
@@ -37,7 +37,7 @@ internal class DefaultSendVerificationMessageTask @Inject constructor(
         private val encryptEventTask: DefaultEncryptEventTask,
         private val roomAPI: RoomAPI,
         private val cryptoSessionInfoProvider: CryptoSessionInfoProvider,
-        private val eventBus: EventBus) : SendVerificationMessageTask {
+        private val globalErrorReceiver: GlobalErrorReceiver) : SendVerificationMessageTask {
 
     override suspend fun execute(params: SendVerificationMessageTask.Params): String {
         val event = handleEncryption(params)
@@ -45,7 +45,7 @@ internal class DefaultSendVerificationMessageTask @Inject constructor(
 
         try {
             localEchoRepository.updateSendState(localId, event.roomId, SendState.SENDING)
-            val executeRequest = executeRequest<SendResponse>(eventBus) {
+            val executeRequest = executeRequest<SendResponse>(globalErrorReceiver) {
                 apiCall = roomAPI.send(
                         localId,
                         roomId = event.roomId ?: "",
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SetDeviceNameTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SetDeviceNameTask.kt
index 51b9624554b9e11e840ef0c52302f7a950ba2ef6..b835d46236f2a2000e99375f3702c17808255e9f 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SetDeviceNameTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SetDeviceNameTask.kt
@@ -18,9 +18,9 @@ package org.matrix.android.sdk.internal.crypto.tasks
 
 import org.matrix.android.sdk.internal.crypto.api.CryptoApi
 import org.matrix.android.sdk.internal.crypto.model.rest.UpdateDeviceInfoBody
+import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import org.matrix.android.sdk.internal.network.executeRequest
 import org.matrix.android.sdk.internal.task.Task
-import org.greenrobot.eventbus.EventBus
 import javax.inject.Inject
 
 internal interface SetDeviceNameTask : Task<SetDeviceNameTask.Params, Unit> {
@@ -34,14 +34,14 @@ internal interface SetDeviceNameTask : Task<SetDeviceNameTask.Params, Unit> {
 
 internal class DefaultSetDeviceNameTask @Inject constructor(
         private val cryptoApi: CryptoApi,
-        private val eventBus: EventBus
+        private val globalErrorReceiver: GlobalErrorReceiver
 ) : SetDeviceNameTask {
 
     override suspend fun execute(params: SetDeviceNameTask.Params) {
         val body = UpdateDeviceInfoBody(
                 displayName = params.deviceName
         )
-        return executeRequest(eventBus) {
+        return executeRequest(globalErrorReceiver) {
             apiCall = cryptoApi.updateDeviceInfo(params.deviceId, body)
         }
     }
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 6216a3a305944bdb28258c99e2efec005c345490..eb53bbbf8d289375e782c51c77d86e5cefc9013e 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
@@ -21,9 +21,9 @@ import org.matrix.android.sdk.internal.crypto.api.CryptoApi
 import org.matrix.android.sdk.internal.crypto.model.rest.DeviceKeys
 import org.matrix.android.sdk.internal.crypto.model.rest.KeysUploadBody
 import org.matrix.android.sdk.internal.crypto.model.rest.KeysUploadResponse
+import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import org.matrix.android.sdk.internal.network.executeRequest
 import org.matrix.android.sdk.internal.task.Task
-import org.greenrobot.eventbus.EventBus
 import timber.log.Timber
 import javax.inject.Inject
 
@@ -38,7 +38,7 @@ internal interface UploadKeysTask : Task<UploadKeysTask.Params, KeysUploadRespon
 
 internal class DefaultUploadKeysTask @Inject constructor(
         private val cryptoApi: CryptoApi,
-        private val eventBus: EventBus
+        private val globalErrorReceiver: GlobalErrorReceiver
 ) : UploadKeysTask {
 
     override suspend fun execute(params: UploadKeysTask.Params): KeysUploadResponse {
@@ -49,7 +49,7 @@ internal class DefaultUploadKeysTask @Inject constructor(
 
         Timber.i("## Uploading device keys -> $body")
 
-        return executeRequest(eventBus) {
+        return executeRequest(globalErrorReceiver) {
             apiCall = cryptoApi.uploadKeys(body)
         }
     }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/UploadSignaturesTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/UploadSignaturesTask.kt
index a4e10ddbfcfcb1ccd46eea7af35bb5ceb61f11e5..c50faf37b1fa7e084ce7a51bfea4d55625c1fec5 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/UploadSignaturesTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/UploadSignaturesTask.kt
@@ -18,9 +18,9 @@ package org.matrix.android.sdk.internal.crypto.tasks
 import org.matrix.android.sdk.api.failure.Failure
 import org.matrix.android.sdk.internal.crypto.api.CryptoApi
 import org.matrix.android.sdk.internal.crypto.model.rest.SignatureUploadResponse
+import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import org.matrix.android.sdk.internal.network.executeRequest
 import org.matrix.android.sdk.internal.task.Task
-import org.greenrobot.eventbus.EventBus
 import javax.inject.Inject
 
 internal interface UploadSignaturesTask : Task<UploadSignaturesTask.Params, Unit> {
@@ -31,12 +31,12 @@ internal interface UploadSignaturesTask : Task<UploadSignaturesTask.Params, Unit
 
 internal class DefaultUploadSignaturesTask @Inject constructor(
         private val cryptoApi: CryptoApi,
-        private val eventBus: EventBus
+        private val globalErrorReceiver: GlobalErrorReceiver
 ) : UploadSignaturesTask {
 
     override suspend fun execute(params: UploadSignaturesTask.Params) {
         try {
-            val response = executeRequest<SignatureUploadResponse>(eventBus) {
+            val response = executeRequest<SignatureUploadResponse>(globalErrorReceiver) {
                 this.isRetryable = true
                 this.maxRetryCount = 10
                 this.apiCall = cryptoApi.uploadSignatures(params.signatures)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/UploadSigningKeysTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/UploadSigningKeysTask.kt
index 038ef9dbd3e84a27ba53bedd4d1013d5ac177b51..14fad2ea3899466d36e27507cf288eccdd50d0f0 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/UploadSigningKeysTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/UploadSigningKeysTask.kt
@@ -16,18 +16,16 @@
 
 package org.matrix.android.sdk.internal.crypto.tasks
 
-import org.matrix.android.sdk.api.auth.data.LoginFlowTypes
 import org.matrix.android.sdk.api.failure.Failure
-import org.matrix.android.sdk.api.failure.toRegistrationFlowResponse
 import org.matrix.android.sdk.internal.crypto.api.CryptoApi
 import org.matrix.android.sdk.internal.crypto.model.CryptoCrossSigningKey
 import org.matrix.android.sdk.internal.crypto.model.rest.KeysQueryResponse
+import org.matrix.android.sdk.api.auth.UIABaseAuth
 import org.matrix.android.sdk.internal.crypto.model.rest.UploadSigningKeysBody
-import org.matrix.android.sdk.internal.crypto.model.rest.UserPasswordAuth
 import org.matrix.android.sdk.internal.crypto.model.toRest
+import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import org.matrix.android.sdk.internal.network.executeRequest
 import org.matrix.android.sdk.internal.task.Task
-import org.greenrobot.eventbus.EventBus
 import javax.inject.Inject
 
 internal interface UploadSigningKeysTask : Task<UploadSigningKeysTask.Params, Unit> {
@@ -39,15 +37,9 @@ internal interface UploadSigningKeysTask : Task<UploadSigningKeysTask.Params, Un
             // the SSK
             val selfSignedKey: CryptoCrossSigningKey,
             /**
-             * - If null:
-             *    - no retry will be performed
-             * - If not null, it may or may not contain a sessionId:
-             *    - If sessionId is null:
-             *       - password should not be null: the task will perform a first request to get a sessionId, and then a second one
-             *    - If sessionId is not null:
-             *       - password should not be null as well, and no retry will be performed
+             * Authorisation info (User Interactive flow)
              */
-            val userPasswordAuth: UserPasswordAuth?
+            val userAuthParam: UIABaseAuth?
     )
 }
 
@@ -55,39 +47,21 @@ data class UploadSigningKeys(val failures: Map<String, Any>?) : Failure.FeatureF
 
 internal class DefaultUploadSigningKeysTask @Inject constructor(
         private val cryptoApi: CryptoApi,
-        private val eventBus: EventBus
+        private val globalErrorReceiver: GlobalErrorReceiver
 ) : UploadSigningKeysTask {
 
     override suspend fun execute(params: UploadSigningKeysTask.Params) {
-        val paramsHaveSessionId = params.userPasswordAuth?.session != null
-
         val uploadQuery = UploadSigningKeysBody(
                 masterKey = params.masterKey.toRest(),
                 userSigningKey = params.userKey.toRest(),
                 selfSigningKey = params.selfSignedKey.toRest(),
-                // If sessionId is provided, use the userPasswordAuth
-                auth = params.userPasswordAuth.takeIf { paramsHaveSessionId }
+                auth = params.userAuthParam?.asMap()
         )
-        try {
-            doRequest(uploadQuery)
-        } catch (throwable: Throwable) {
-            val registrationFlowResponse = throwable.toRegistrationFlowResponse()
-            if (registrationFlowResponse != null
-                    && registrationFlowResponse.flows.orEmpty().any { it.stages?.contains(LoginFlowTypes.PASSWORD) == true }
-                    && params.userPasswordAuth?.password != null
-                    && !paramsHaveSessionId
-            ) {
-                // Retry with authentication
-                doRequest(uploadQuery.copy(auth = params.userPasswordAuth.copy(session = registrationFlowResponse.session)))
-            } else {
-                // Other error
-                throw throwable
-            }
-        }
+        doRequest(uploadQuery)
     }
 
     private suspend fun doRequest(uploadQuery: UploadSigningKeysBody) {
-        val keysQueryResponse = executeRequest<KeysQueryResponse>(eventBus) {
+        val keysQueryResponse = executeRequest<KeysQueryResponse>(globalErrorReceiver) {
             apiCall = cryptoApi.uploadSigningKeys(uploadQuery)
         }
         if (keysQueryResponse.failures?.isNotEmpty() == true) {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt
index b970ec60e2ba7d5b853ae59b7f615c2edac50be8..57002b5a600b622eed6a8af4c67567c0f45746f4 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt
@@ -21,6 +21,8 @@ import io.realm.RealmMigration
 import org.matrix.android.sdk.internal.database.model.HomeServerCapabilitiesEntityFields
 import org.matrix.android.sdk.internal.database.model.PendingThreePidEntityFields
 import org.matrix.android.sdk.internal.database.model.PreviewUrlCacheEntityFields
+import org.matrix.android.sdk.internal.database.model.RoomEntityFields
+import org.matrix.android.sdk.internal.database.model.RoomMembersLoadStatusType
 import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields
 import timber.log.Timber
 import javax.inject.Inject
@@ -28,7 +30,7 @@ import javax.inject.Inject
 class RealmSessionStoreMigration @Inject constructor() : RealmMigration {
 
     companion object {
-        const val SESSION_STORE_SCHEMA_VERSION = 6L
+        const val SESSION_STORE_SCHEMA_VERSION = 7L
     }
 
     override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) {
@@ -40,6 +42,7 @@ class RealmSessionStoreMigration @Inject constructor() : RealmMigration {
         if (oldVersion <= 3) migrateTo4(realm)
         if (oldVersion <= 4) migrateTo5(realm)
         if (oldVersion <= 5) migrateTo6(realm)
+        if (oldVersion <= 6) migrateTo7(realm)
     }
 
     private fun migrateTo1(realm: DynamicRealm) {
@@ -105,4 +108,18 @@ class RealmSessionStoreMigration @Inject constructor() : RealmMigration {
                 .addField(PreviewUrlCacheEntityFields.MXC_URL, String::class.java)
                 .addField(PreviewUrlCacheEntityFields.LAST_UPDATED_TIMESTAMP, Long::class.java)
     }
+
+    private fun migrateTo7(realm: DynamicRealm) {
+        Timber.d("Step 6 -> 7")
+        realm.schema.get("RoomEntity")
+                ?.addField(RoomEntityFields.MEMBERS_LOAD_STATUS_STR, String::class.java)
+                ?.transform { obj ->
+                    if (obj.getBoolean("areAllMembersLoaded")) {
+                        obj.setString("membersLoadStatusStr", RoomMembersLoadStatusType.LOADED.name)
+                    } else {
+                        obj.setString("membersLoadStatusStr", RoomMembersLoadStatusType.NONE.name)
+                    }
+                }
+                ?.removeField("areAllMembersLoaded")
+    }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomEntity.kt
index 9af1646a4cd1d1e2e3b4d968ca3776529c9ee17a..3ff2532604803b7d9c0080ec0174e038f55c9be0 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomEntity.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomEntity.kt
@@ -23,8 +23,7 @@ import io.realm.annotations.PrimaryKey
 
 internal open class RoomEntity(@PrimaryKey var roomId: String = "",
                                var chunks: RealmList<ChunkEntity> = RealmList(),
-                               var sendingTimelineEvents: RealmList<TimelineEventEntity> = RealmList(),
-                               var areAllMembersLoaded: Boolean = false
+                               var sendingTimelineEvents: RealmList<TimelineEventEntity> = RealmList()
 ) : RealmObject() {
 
     private var membershipStr: String = Membership.NONE.name
@@ -36,5 +35,14 @@ internal open class RoomEntity(@PrimaryKey var roomId: String = "",
             membershipStr = value.name
         }
 
+    private var membersLoadStatusStr: String = RoomMembersLoadStatusType.NONE.name
+    var membersLoadStatus: RoomMembersLoadStatusType
+        get() {
+            return RoomMembersLoadStatusType.valueOf(membersLoadStatusStr)
+        }
+        set(value) {
+            membersLoadStatusStr = value.name
+        }
+
     companion object
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/eventbus/EventBusTimberLogger.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomMembersLoadStatusType.kt
similarity index 63%
rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/eventbus/EventBusTimberLogger.kt
rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomMembersLoadStatusType.kt
index 2cbd7ba7f0ff1fd14e981c6b6df4494f050a23a4..79fe17253b2f7623535dce246c5a44b3ab686b88 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/eventbus/EventBusTimberLogger.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomMembersLoadStatusType.kt
@@ -14,18 +14,10 @@
  * limitations under the License.
  */
 
-package org.matrix.android.sdk.internal.eventbus
+package org.matrix.android.sdk.internal.database.model
 
-import org.greenrobot.eventbus.Logger
-import timber.log.Timber
-import java.util.logging.Level
-
-class EventBusTimberLogger : Logger {
-    override fun log(level: Level, msg: String) {
-        Timber.d(msg)
-    }
-
-    override fun log(level: Level, msg: String, th: Throwable) {
-        Timber.e(th, msg)
-    }
+internal enum class RoomMembersLoadStatusType {
+    NONE,
+    LOADING,
+    LOADED
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/GlobalErrorHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/GlobalErrorHandler.kt
new file mode 100644
index 0000000000000000000000000000000000000000..9afdb40ed1559cd1aad9d46c2f7afc37ce23f1de
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/GlobalErrorHandler.kt
@@ -0,0 +1,54 @@
+/*
+ * 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.internal.network
+
+import org.matrix.android.sdk.api.failure.GlobalError
+import org.matrix.android.sdk.internal.auth.SessionParamsStore
+import org.matrix.android.sdk.internal.di.SessionId
+import org.matrix.android.sdk.internal.session.SessionScope
+import org.matrix.android.sdk.internal.task.TaskExecutor
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import timber.log.Timber
+import javax.inject.Inject
+
+@SessionScope
+internal class GlobalErrorHandler @Inject constructor(
+        private val taskExecutor: TaskExecutor,
+        private val sessionParamsStore: SessionParamsStore,
+        @SessionId private val sessionId: String
+) : GlobalErrorReceiver {
+
+    var listener: Listener? = null
+
+    override fun handleGlobalError(globalError: GlobalError) {
+        Timber.e("Global error received: $globalError")
+
+        if (globalError is GlobalError.InvalidToken && globalError.softLogout) {
+            // Mark the token has invalid
+            taskExecutor.executorScope.launch(Dispatchers.IO) {
+                sessionParamsStore.setTokenInvalid(sessionId)
+            }
+        }
+
+        listener?.onGlobalError(globalError)
+    }
+
+    internal interface Listener {
+        fun onGlobalError(globalError: GlobalError)
+    }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/GlobalErrorReceiver.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/GlobalErrorReceiver.kt
new file mode 100644
index 0000000000000000000000000000000000000000..47607ba8939cd41cdf1749d62be17b9f5d0ba265
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/GlobalErrorReceiver.kt
@@ -0,0 +1,23 @@
+/*
+ * 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.internal.network
+
+import org.matrix.android.sdk.api.failure.GlobalError
+
+internal interface GlobalErrorReceiver {
+    fun handleGlobalError(globalError: GlobalError)
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/Request.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/Request.kt
index 2535a5347ae3e25ab7badba075b70896d4e4039e..442029127d3202fcc5e35761ba1e234b00668f3b 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/Request.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/Request.kt
@@ -18,7 +18,6 @@ package org.matrix.android.sdk.internal.network
 
 import kotlinx.coroutines.CancellationException
 import kotlinx.coroutines.delay
-import org.greenrobot.eventbus.EventBus
 import org.matrix.android.sdk.api.failure.Failure
 import org.matrix.android.sdk.api.failure.shouldBeRetried
 import org.matrix.android.sdk.internal.network.ssl.CertUtil
@@ -27,10 +26,10 @@ import retrofit2.awaitResponse
 import timber.log.Timber
 import java.io.IOException
 
-internal suspend inline fun <DATA : Any> executeRequest(eventBus: EventBus?,
-                                                        block: Request<DATA>.() -> Unit) = Request<DATA>(eventBus).apply(block).execute()
+internal suspend inline fun <DATA : Any> executeRequest(globalErrorReceiver: GlobalErrorReceiver?,
+                                                        block: Request<DATA>.() -> Unit) = Request<DATA>(globalErrorReceiver).apply(block).execute()
 
-internal class Request<DATA : Any>(private val eventBus: EventBus?) {
+internal class Request<DATA : Any>(private val globalErrorReceiver: GlobalErrorReceiver?) {
 
     var isRetryable = false
     var initialDelay: Long = 100L
@@ -47,7 +46,7 @@ internal class Request<DATA : Any>(private val eventBus: EventBus?) {
                 response.body()
                         ?: throw IllegalStateException("The request returned a null body")
             } else {
-                throw response.toFailure(eventBus)
+                throw response.toFailure(globalErrorReceiver)
             }
         } catch (exception: Throwable) {
             // Log some details about the request which has failed
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/RetrofitExtensions.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/RetrofitExtensions.kt
index c54af571d879a92412e7720b928960734182d3b4..dd5a69dd3c99aa7232ff256fd6ca470204639be9 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/RetrofitExtensions.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/RetrofitExtensions.kt
@@ -25,7 +25,6 @@ import org.matrix.android.sdk.api.failure.MatrixError
 import org.matrix.android.sdk.internal.di.MoshiProvider
 import kotlinx.coroutines.suspendCancellableCoroutine
 import okhttp3.ResponseBody
-import org.greenrobot.eventbus.EventBus
 import retrofit2.Response
 import timber.log.Timber
 import java.io.IOException
@@ -54,18 +53,18 @@ internal suspend fun okhttp3.Call.awaitResponse(): okhttp3.Response {
 /**
  * Convert a retrofit Response to a Failure, and eventually parse errorBody to convert it to a MatrixError
  */
-internal fun <T> Response<T>.toFailure(eventBus: EventBus?): Failure {
-    return toFailure(errorBody(), code(), eventBus)
+internal fun <T> Response<T>.toFailure(globalErrorReceiver: GlobalErrorReceiver?): Failure {
+    return toFailure(errorBody(), code(), globalErrorReceiver)
 }
 
 /**
  * Convert a okhttp3 Response to a Failure, and eventually parse errorBody to convert it to a MatrixError
  */
-internal fun okhttp3.Response.toFailure(eventBus: EventBus?): Failure {
-    return toFailure(body, code, eventBus)
+internal fun okhttp3.Response.toFailure(globalErrorReceiver: GlobalErrorReceiver?): Failure {
+    return toFailure(body, code, globalErrorReceiver)
 }
 
-private fun toFailure(errorBody: ResponseBody?, httpCode: Int, eventBus: EventBus?): Failure {
+private fun toFailure(errorBody: ResponseBody?, httpCode: Int, globalErrorReceiver: GlobalErrorReceiver?): Failure {
     if (errorBody == null) {
         return Failure.Unknown(RuntimeException("errorBody should not be null"))
     }
@@ -79,12 +78,12 @@ private fun toFailure(errorBody: ResponseBody?, httpCode: Int, eventBus: EventBu
 
         if (matrixError != null) {
             if (matrixError.code == MatrixError.M_CONSENT_NOT_GIVEN && !matrixError.consentUri.isNullOrBlank()) {
-                // Also send this error to the bus, for a global management
-                eventBus?.post(GlobalError.ConsentNotGivenError(matrixError.consentUri))
+                // Also send this error to the globalErrorReceiver, for a global management
+                globalErrorReceiver?.handleGlobalError(GlobalError.ConsentNotGivenError(matrixError.consentUri))
             } else if (httpCode == HttpURLConnection.HTTP_UNAUTHORIZED /* 401 */
                     && matrixError.code == MatrixError.M_UNKNOWN_TOKEN) {
-                // Also send this error to the bus, for a global management
-                eventBus?.post(GlobalError.InvalidToken(matrixError.isSoftLogout))
+                // Also send this error to the globalErrorReceiver, for a global management
+                globalErrorReceiver?.handleGlobalError(GlobalError.InvalidToken(matrixError.isSoftLogout))
             }
 
             return Failure.ServerError(matrixError, httpCode)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultSession.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultSession.kt
index c5f3f65a34c5d791caa7eec252c051f00c13beb9..e09c051c81a25d3c1cf2be408ac3662a3daa7305 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultSession.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultSession.kt
@@ -19,13 +19,7 @@ package org.matrix.android.sdk.internal.session
 import androidx.annotation.MainThread
 import dagger.Lazy
 import io.realm.RealmConfiguration
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.launch
 import okhttp3.OkHttpClient
-import org.greenrobot.eventbus.EventBus
-import org.greenrobot.eventbus.Subscribe
-import org.greenrobot.eventbus.ThreadMode
-import org.matrix.android.sdk.api.MatrixCallback
 import org.matrix.android.sdk.api.auth.data.SessionParams
 import org.matrix.android.sdk.api.failure.GlobalError
 import org.matrix.android.sdk.api.pushrules.PushRuleService
@@ -58,6 +52,8 @@ import org.matrix.android.sdk.api.session.terms.TermsService
 import org.matrix.android.sdk.api.session.typing.TypingUsersTracker
 import org.matrix.android.sdk.api.session.user.UserService
 import org.matrix.android.sdk.api.session.widgets.WidgetService
+import org.matrix.android.sdk.api.util.appendParamToUrl
+import org.matrix.android.sdk.internal.auth.SSO_UIA_FALLBACK_PATH
 import org.matrix.android.sdk.internal.auth.SessionParamsStore
 import org.matrix.android.sdk.internal.crypto.DefaultCryptoService
 import org.matrix.android.sdk.internal.database.tools.RealmDebugTools
@@ -65,13 +61,12 @@ import org.matrix.android.sdk.internal.di.SessionDatabase
 import org.matrix.android.sdk.internal.di.SessionId
 import org.matrix.android.sdk.internal.di.UnauthenticatedWithCertificate
 import org.matrix.android.sdk.internal.di.WorkManagerProvider
+import org.matrix.android.sdk.internal.network.GlobalErrorHandler
 import org.matrix.android.sdk.internal.session.identity.DefaultIdentityService
 import org.matrix.android.sdk.internal.session.room.send.queue.EventSenderProcessor
 import org.matrix.android.sdk.internal.session.sync.SyncTokenStore
 import org.matrix.android.sdk.internal.session.sync.job.SyncThread
 import org.matrix.android.sdk.internal.session.sync.job.SyncWorker
-import org.matrix.android.sdk.internal.task.TaskExecutor
-import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers
 import org.matrix.android.sdk.internal.util.createUIHandler
 import timber.log.Timber
 import javax.inject.Inject
@@ -81,7 +76,7 @@ import javax.inject.Provider
 internal class DefaultSession @Inject constructor(
         override val sessionParams: SessionParams,
         private val workManagerProvider: WorkManagerProvider,
-        private val eventBus: EventBus,
+        private val globalErrorHandler: GlobalErrorHandler,
         @SessionId
         override val sessionId: String,
         @SessionDatabase private val realmConfiguration: RealmConfiguration,
@@ -117,10 +112,8 @@ internal class DefaultSession @Inject constructor(
         private val accountDataService: Lazy<AccountDataService>,
         private val _sharedSecretStorageService: Lazy<SharedSecretStorageService>,
         private val accountService: Lazy<AccountService>,
-        private val coroutineDispatchers: MatrixCoroutineDispatchers,
         private val defaultIdentityService: DefaultIdentityService,
         private val integrationManagerService: IntegrationManagerService,
-        private val taskExecutor: TaskExecutor,
         private val callSignalingService: Lazy<CallSignalingService>,
         @UnauthenticatedWithCertificate
         private val unauthenticatedWithCertificateOkHttpClient: Lazy<OkHttpClient>,
@@ -140,7 +133,8 @@ internal class DefaultSession @Inject constructor(
         HomeServerCapabilitiesService by homeServerCapabilitiesService.get(),
         ProfileService by profileService.get(),
         AccountDataService by accountDataService.get(),
-        AccountService by accountService.get() {
+        AccountService by accountService.get(),
+        GlobalErrorHandler.Listener {
 
     override val sharedSecretStorageService: SharedSecretStorageService
         get() = _sharedSecretStorageService.get()
@@ -162,7 +156,7 @@ internal class DefaultSession @Inject constructor(
         uiHandler.post {
             lifecycleObservers.forEach { it.onStart() }
         }
-        eventBus.register(this)
+        globalErrorHandler.listener = this
         eventSenderProcessor.start()
     }
 
@@ -206,7 +200,7 @@ internal class DefaultSession @Inject constructor(
         }
         cryptoService.get().close()
         isOpen = false
-        eventBus.unregister(this)
+        globalErrorHandler.listener = null
         eventSenderProcessor.interrupt()
     }
 
@@ -224,26 +218,17 @@ internal class DefaultSession @Inject constructor(
         }
     }
 
-    override fun clearCache(callback: MatrixCallback<Unit>) {
+    override suspend fun clearCache() {
         stopSync()
         stopAnyBackgroundSync()
         uiHandler.post {
             lifecycleObservers.forEach { it.onClearCache() }
         }
-        cacheService.get().clearCache(callback)
+        cacheService.get().clearCache()
         workManagerProvider.cancelAllWorks()
     }
 
-    @Subscribe(threadMode = ThreadMode.MAIN)
-    fun onGlobalError(globalError: GlobalError) {
-        if (globalError is GlobalError.InvalidToken
-                && globalError.softLogout) {
-            // Mark the token has invalid
-            taskExecutor.executorScope.launch(Dispatchers.IO) {
-                sessionParamsStore.setTokenInvalid(sessionId)
-            }
-        }
-
+    override fun onGlobalError(globalError: GlobalError) {
         sessionListeners.dispatchGlobalError(globalError)
     }
 
@@ -290,6 +275,18 @@ internal class DefaultSession @Inject constructor(
         return "$myUserId - ${sessionParams.deviceId}"
     }
 
+    override fun getUiaSsoFallbackUrl(authenticationSessionId: String): String {
+        val hsBas = sessionParams.homeServerConnectionConfig
+                .homeServerUri
+                .toString()
+                .trim { it == '/' }
+        return buildString {
+            append(hsBas)
+            append(SSO_UIA_FALLBACK_PATH)
+            appendParamToUrl("session", authenticationSessionId)
+        }
+    }
+
     override fun logDbUsageInfo() {
         RealmDebugTools(realmConfiguration).logInfo("Session")
     }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionComponent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionComponent.kt
index 659fcc8f5cc0397be59b4f2c05b37bc66a5a5d30..f5eade1704e06669b5b3fb76181052f56d3a5946 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionComponent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionComponent.kt
@@ -27,7 +27,6 @@ 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.MatrixComponent
-import org.matrix.android.sdk.internal.di.SessionAssistedInjectModule
 import org.matrix.android.sdk.internal.network.NetworkConnectivityChecker
 import org.matrix.android.sdk.internal.session.account.AccountModule
 import org.matrix.android.sdk.internal.session.cache.CacheModule
@@ -86,7 +85,6 @@ import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers
             TermsModule::class,
             AccountDataModule::class,
             ProfileModule::class,
-            SessionAssistedInjectModule::class,
             AccountModule::class,
             CallModule::class,
             SearchModule::class
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt
index 96b44917bd787620d11293605e8fd27bd84be64f..468c193ad3c55649fe0df57a7e0599f987a9d43e 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt
@@ -26,7 +26,6 @@ import dagger.Provides
 import dagger.multibindings.IntoSet
 import io.realm.RealmConfiguration
 import okhttp3.OkHttpClient
-import org.greenrobot.eventbus.EventBus
 import org.matrix.android.sdk.api.MatrixConfiguration
 import org.matrix.android.sdk.api.auth.data.Credentials
 import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
@@ -61,9 +60,10 @@ import org.matrix.android.sdk.internal.di.UnauthenticatedWithCertificate
 import org.matrix.android.sdk.internal.di.UnauthenticatedWithCertificateWithProgress
 import org.matrix.android.sdk.internal.di.UserId
 import org.matrix.android.sdk.internal.di.UserMd5
-import org.matrix.android.sdk.internal.eventbus.EventBusTimberLogger
 import org.matrix.android.sdk.internal.network.DefaultNetworkConnectivityChecker
 import org.matrix.android.sdk.internal.network.FallbackNetworkCallbackStrategy
+import org.matrix.android.sdk.internal.network.GlobalErrorHandler
+import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import org.matrix.android.sdk.internal.network.NetworkCallbackStrategy
 import org.matrix.android.sdk.internal.network.NetworkConnectivityChecker
 import org.matrix.android.sdk.internal.network.PreferredNetworkCallbackStrategy
@@ -256,16 +256,6 @@ internal abstract class SessionModule {
                     .create(okHttpClient, sessionParams.homeServerConnectionConfig.homeServerUri.toString())
         }
 
-        @JvmStatic
-        @Provides
-        @SessionScope
-        fun providesEventBus(): EventBus {
-            return EventBus
-                    .builder()
-                    .logger(EventBusTimberLogger())
-                    .build()
-        }
-
         @JvmStatic
         @Provides
         @SessionScope
@@ -294,6 +284,9 @@ internal abstract class SessionModule {
     @Binds
     abstract fun bindSession(session: DefaultSession): Session
 
+    @Binds
+    abstract fun bindGlobalErrorReceiver(handler: GlobalErrorHandler): GlobalErrorReceiver
+
     @Binds
     abstract fun bindNetworkConnectivityChecker(checker: DefaultNetworkConnectivityChecker): NetworkConnectivityChecker
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/account/ChangePasswordParams.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/account/ChangePasswordParams.kt
index 70cdbda399621e6fc4b6b0e6da1d16526322683d..1b95820918e3d830e4b14e5b96d3b079004e2137 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/account/ChangePasswordParams.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/account/ChangePasswordParams.kt
@@ -18,7 +18,7 @@ package org.matrix.android.sdk.internal.session.account
 
 import com.squareup.moshi.Json
 import com.squareup.moshi.JsonClass
-import org.matrix.android.sdk.internal.crypto.model.rest.UserPasswordAuth
+import org.matrix.android.sdk.api.auth.UserPasswordAuth
 
 /**
  * Class to pass request parameters to update the password.
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/account/ChangePasswordTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/account/ChangePasswordTask.kt
index 869d3f516c1bf834627c43443b054a3ba95a206e..1f043b0a9de61e1c9505a05c6f7804428fbb8116 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/account/ChangePasswordTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/account/ChangePasswordTask.kt
@@ -18,9 +18,9 @@ package org.matrix.android.sdk.internal.session.account
 
 import org.matrix.android.sdk.api.failure.toRegistrationFlowResponse
 import org.matrix.android.sdk.internal.di.UserId
+import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import org.matrix.android.sdk.internal.network.executeRequest
 import org.matrix.android.sdk.internal.task.Task
-import org.greenrobot.eventbus.EventBus
 import javax.inject.Inject
 
 internal interface ChangePasswordTask : Task<ChangePasswordTask.Params, Unit> {
@@ -32,14 +32,14 @@ internal interface ChangePasswordTask : Task<ChangePasswordTask.Params, Unit> {
 
 internal class DefaultChangePasswordTask @Inject constructor(
         private val accountAPI: AccountAPI,
-        private val eventBus: EventBus,
+        private val globalErrorReceiver: GlobalErrorReceiver,
         @UserId private val userId: String
 ) : ChangePasswordTask {
 
     override suspend fun execute(params: ChangePasswordTask.Params) {
         val changePasswordParams = ChangePasswordParams.create(userId, params.password, params.newPassword)
         try {
-            executeRequest<Unit>(eventBus) {
+            executeRequest<Unit>(globalErrorReceiver) {
                 apiCall = accountAPI.changePassword(changePasswordParams)
             }
         } catch (throwable: Throwable) {
@@ -49,7 +49,7 @@ internal class DefaultChangePasswordTask @Inject constructor(
                     /* Avoid infinite loop */
                     && changePasswordParams.auth?.session == null) {
                 // Retry with authentication
-                executeRequest<Unit>(eventBus) {
+                executeRequest<Unit>(globalErrorReceiver) {
                     apiCall = accountAPI.changePassword(
                             changePasswordParams.copy(auth = changePasswordParams.auth?.copy(session = registrationFlowResponse.session))
                     )
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/account/DeactivateAccountParams.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/account/DeactivateAccountParams.kt
index 6c2e8b4a4e2808d511b00721c234cbdc5eca20f9..d9b4c748b9d2569fd1d5d90331747f0db5bdc836 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/account/DeactivateAccountParams.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/account/DeactivateAccountParams.kt
@@ -18,21 +18,21 @@ package org.matrix.android.sdk.internal.session.account
 
 import com.squareup.moshi.Json
 import com.squareup.moshi.JsonClass
-import org.matrix.android.sdk.internal.crypto.model.rest.UserPasswordAuth
+import org.matrix.android.sdk.api.auth.UIABaseAuth
 
 @JsonClass(generateAdapter = true)
 internal data class DeactivateAccountParams(
-        @Json(name = "auth")
-        val auth: UserPasswordAuth? = null,
-
         // Set to true to erase all data of the account
         @Json(name = "erase")
-        val erase: Boolean
+        val erase: Boolean,
+
+        @Json(name = "auth")
+        val auth: Map<String, *>? = null
 ) {
     companion object {
-        fun create(userId: String, password: String, erase: Boolean): DeactivateAccountParams {
+        fun create(auth: UIABaseAuth?, erase: Boolean): DeactivateAccountParams {
             return DeactivateAccountParams(
-                    auth = UserPasswordAuth(user = userId, password = password),
+                    auth = auth?.asMap(),
                     erase = erase
             )
         }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/account/DeactivateAccountTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/account/DeactivateAccountTask.kt
index ac5febcdaee782a102442f9c8fa9080df4d13096..d67b21567e8a752bab63d2e96723b409a7d91b21 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/account/DeactivateAccountTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/account/DeactivateAccountTask.kt
@@ -16,37 +16,50 @@
 
 package org.matrix.android.sdk.internal.session.account
 
+import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
+import org.matrix.android.sdk.internal.auth.registration.handleUIA
+import org.matrix.android.sdk.api.auth.UIABaseAuth
 import org.matrix.android.sdk.internal.di.UserId
+import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import org.matrix.android.sdk.internal.network.executeRequest
 import org.matrix.android.sdk.internal.session.cleanup.CleanupSession
 import org.matrix.android.sdk.internal.session.identity.IdentityDisconnectTask
 import org.matrix.android.sdk.internal.task.Task
-import org.greenrobot.eventbus.EventBus
 import timber.log.Timber
 import javax.inject.Inject
 
 internal interface DeactivateAccountTask : Task<DeactivateAccountTask.Params, Unit> {
     data class Params(
-            val password: String,
-            val eraseAllData: Boolean
+            val userInteractiveAuthInterceptor: UserInteractiveAuthInterceptor,
+            val eraseAllData: Boolean,
+            val userAuthParam: UIABaseAuth? = null
     )
 }
 
 internal class DefaultDeactivateAccountTask @Inject constructor(
         private val accountAPI: AccountAPI,
-        private val eventBus: EventBus,
+        private val globalErrorReceiver: GlobalErrorReceiver,
         @UserId private val userId: String,
         private val identityDisconnectTask: IdentityDisconnectTask,
         private val cleanupSession: CleanupSession
 ) : DeactivateAccountTask {
 
     override suspend fun execute(params: DeactivateAccountTask.Params) {
-        val deactivateAccountParams = DeactivateAccountParams.create(userId, params.password, params.eraseAllData)
+        val deactivateAccountParams = DeactivateAccountParams.create(params.userAuthParam, params.eraseAllData)
 
-        executeRequest<Unit>(eventBus) {
-            apiCall = accountAPI.deactivate(deactivateAccountParams)
+        try {
+            executeRequest<Unit>(globalErrorReceiver) {
+                apiCall = accountAPI.deactivate(deactivateAccountParams)
+            }
+        } catch (throwable: Throwable) {
+            if (!handleUIA(throwable, params.userInteractiveAuthInterceptor) { auth ->
+                        execute(params.copy(userAuthParam = auth))
+                    }
+            ) {
+                Timber.d("## UIA: propagate failure")
+                throw  throwable
+            }
         }
-
         // Logout from identity server if any, ignoring errors
         runCatching { identityDisconnectTask.execute(Unit) }
                 .onFailure { Timber.w(it, "Unable to disconnect identity server") }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/account/DefaultAccountService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/account/DefaultAccountService.kt
index 1165d2116bb5925ec43b967ab8a36b4df268db8f..25b67159a9fa1cf4f61dda94139f61501bacab03 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/account/DefaultAccountService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/account/DefaultAccountService.kt
@@ -16,6 +16,7 @@
 
 package org.matrix.android.sdk.internal.session.account
 
+import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
 import org.matrix.android.sdk.api.session.account.AccountService
 import javax.inject.Inject
 
@@ -26,7 +27,7 @@ internal class DefaultAccountService @Inject constructor(private val changePassw
         changePasswordTask.execute(ChangePasswordTask.Params(password, newPassword))
     }
 
-    override suspend fun deactivateAccount(password: String, eraseAllData: Boolean) {
-        deactivateAccountTask.execute(DeactivateAccountTask.Params(password, eraseAllData))
+    override suspend fun deactivateAccount(userInteractiveAuthInterceptor: UserInteractiveAuthInterceptor, eraseAllData: Boolean) {
+        deactivateAccountTask.execute(DeactivateAccountTask.Params(userInteractiveAuthInterceptor, eraseAllData))
     }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/cache/DefaultCacheService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/cache/DefaultCacheService.kt
index 19365fce0aeb7a03c8cfa07a9270312f4b156c9d..6d0cd37e1fa9c2d60b25edba7ff900967d7a4498 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/cache/DefaultCacheService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/cache/DefaultCacheService.kt
@@ -16,23 +16,18 @@
 
 package org.matrix.android.sdk.internal.session.cache
 
-import org.matrix.android.sdk.api.MatrixCallback
 import org.matrix.android.sdk.api.session.cache.CacheService
 import org.matrix.android.sdk.internal.di.SessionDatabase
 import org.matrix.android.sdk.internal.task.TaskExecutor
-import org.matrix.android.sdk.internal.task.configureWith
 import javax.inject.Inject
 
 internal class DefaultCacheService @Inject constructor(@SessionDatabase
                                                        private val clearCacheTask: ClearCacheTask,
-                                                       private val taskExecutor: TaskExecutor) : CacheService {
+                                                       private val taskExecutor: TaskExecutor
+) : CacheService {
 
-    override fun clearCache(callback: MatrixCallback<Unit>) {
+    override suspend fun clearCache() {
         taskExecutor.cancelAll()
-        clearCacheTask
-                .configureWith {
-                    this.callback = callback
-                }
-                .executeBy(taskExecutor)
+        clearCacheTask.execute(Unit)
     }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/GetTurnServerTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/GetTurnServerTask.kt
index 1cedee3374cb29de489d988de92cac71ca2d6a2e..b21ec1113aafd71299806b60983629b4a2f0437f 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/GetTurnServerTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/GetTurnServerTask.kt
@@ -17,9 +17,9 @@
 package org.matrix.android.sdk.internal.session.call
 
 import org.matrix.android.sdk.api.session.call.TurnServerResponse
+import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import org.matrix.android.sdk.internal.network.executeRequest
 import org.matrix.android.sdk.internal.task.Task
-import org.greenrobot.eventbus.EventBus
 import javax.inject.Inject
 
 internal abstract class GetTurnServerTask : Task<GetTurnServerTask.Params, TurnServerResponse> {
@@ -27,10 +27,10 @@ internal abstract class GetTurnServerTask : Task<GetTurnServerTask.Params, TurnS
 }
 
 internal class DefaultGetTurnServerTask @Inject constructor(private val voipAPI: VoipApi,
-                                                            private val eventBus: EventBus) : GetTurnServerTask() {
+                                                            private val globalErrorReceiver: GlobalErrorReceiver) : GetTurnServerTask() {
 
     override suspend fun execute(params: Params): TurnServerResponse {
-        return executeRequest(eventBus) {
+        return executeRequest(globalErrorReceiver) {
             apiCall = voipAPI.getTurnServer()
         }
     }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/FileUploader.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/FileUploader.kt
index 4fc1d67c050e97a221c6c4c5caba56c535feab73..8fa595db3044188d23787eb055db1ab4a514f5d0 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/FileUploader.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/FileUploader.kt
@@ -30,10 +30,10 @@ import okhttp3.RequestBody
 import okhttp3.RequestBody.Companion.toRequestBody
 import okio.BufferedSink
 import okio.source
-import org.greenrobot.eventbus.EventBus
 import org.matrix.android.sdk.api.extensions.tryOrNull
 import org.matrix.android.sdk.api.session.content.ContentUrlResolver
 import org.matrix.android.sdk.internal.di.Authenticated
+import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import org.matrix.android.sdk.internal.network.ProgressRequestBody
 import org.matrix.android.sdk.internal.network.awaitResponse
 import org.matrix.android.sdk.internal.network.toFailure
@@ -45,7 +45,7 @@ import javax.inject.Inject
 
 internal class FileUploader @Inject constructor(@Authenticated
                                                 private val okHttpClient: OkHttpClient,
-                                                private val eventBus: EventBus,
+                                                private val globalErrorReceiver: GlobalErrorReceiver,
                                                 private val context: Context,
                                                 contentUrlResolver: ContentUrlResolver,
                                                 moshi: Moshi) {
@@ -115,7 +115,7 @@ internal class FileUploader @Inject constructor(@Authenticated
 
         return okHttpClient.newCall(request).awaitResponse().use { response ->
             if (!response.isSuccessful) {
-                throw response.toFailure(eventBus)
+                throw response.toFailure(globalErrorReceiver)
             } else {
                 response.body?.source()?.let {
                     responseAdapter.fromJson(it)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/ImageCompressor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/ImageCompressor.kt
index 6cf65b867c0800c8535bbb5c2db799b137f7932d..1d6cd61060ff6dd7c91d2ab723b3066351f7b4fd 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/ImageCompressor.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/ImageCompressor.kt
@@ -28,9 +28,8 @@ import java.io.File
 import java.util.UUID
 import javax.inject.Inject
 
-internal class ImageCompressor @Inject constructor() {
+internal class ImageCompressor @Inject constructor(private val context: Context) {
     suspend fun compress(
-            context: Context,
             imageFile: File,
             desiredWidth: Int,
             desiredHeight: Int,
@@ -46,7 +45,7 @@ internal class ImageCompressor @Inject constructor() {
                 }
             } ?: return@withContext imageFile
 
-            val destinationFile = createDestinationFile(context)
+            val destinationFile = createDestinationFile()
 
             runCatching {
                 destinationFile.outputStream().use {
@@ -118,7 +117,7 @@ internal class ImageCompressor @Inject constructor() {
         }
     }
 
-    private fun createDestinationFile(context: Context): File {
+    private fun createDestinationFile(): File {
         return File.createTempFile(UUID.randomUUID().toString(), null, context.cacheDir)
     }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/ThumbnailExtractor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/ThumbnailExtractor.kt
index 4b31db59b1a1e15f1125f9f0db0858e7013b9305..c28668a53eb77074f077c62a87d8e826fd4a8ac7 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/ThumbnailExtractor.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/ThumbnailExtractor.kt
@@ -47,22 +47,24 @@ internal object ThumbnailExtractor {
         val mediaMetadataRetriever = MediaMetadataRetriever()
         try {
             mediaMetadataRetriever.setDataSource(context, attachment.queryUri)
-            val thumbnail = mediaMetadataRetriever.frameAtTime
-
-            val outputStream = ByteArrayOutputStream()
-            thumbnail.compress(Bitmap.CompressFormat.JPEG, 100, outputStream)
-            val thumbnailWidth = thumbnail.width
-            val thumbnailHeight = thumbnail.height
-            val thumbnailSize = outputStream.size()
-            thumbnailData = ThumbnailData(
-                    width = thumbnailWidth,
-                    height = thumbnailHeight,
-                    size = thumbnailSize.toLong(),
-                    bytes = outputStream.toByteArray(),
-                    mimeType = MimeTypes.Jpeg
-            )
-            thumbnail.recycle()
-            outputStream.reset()
+            mediaMetadataRetriever.frameAtTime?.let { thumbnail ->
+                val outputStream = ByteArrayOutputStream()
+                thumbnail.compress(Bitmap.CompressFormat.JPEG, 100, outputStream)
+                val thumbnailWidth = thumbnail.width
+                val thumbnailHeight = thumbnail.height
+                val thumbnailSize = outputStream.size()
+                thumbnailData = ThumbnailData(
+                        width = thumbnailWidth,
+                        height = thumbnailHeight,
+                        size = thumbnailSize.toLong(),
+                        bytes = outputStream.toByteArray(),
+                        mimeType = MimeTypes.Jpeg
+                )
+                thumbnail.recycle()
+                outputStream.reset()
+            } ?: run {
+                Timber.e("Cannot extract video thumbnail at %s", attachment.queryUri.toString())
+            }
         } catch (e: Exception) {
             Timber.e(e, "Cannot extract video thumbnail")
         } finally {
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 672d407d251577a8aa6e0b0247b769afd52fb110..3b727690bfddccb54b07fc90c50740c1b90a54b9 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
@@ -156,7 +156,7 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter
                         // Do not compress gif
                         && attachment.mimeType != MimeTypes.Gif
                         && params.compressBeforeSending) {
-                    fileToUpload = imageCompressor.compress(context, workingFile, MAX_IMAGE_SIZE, MAX_IMAGE_SIZE)
+                    fileToUpload = imageCompressor.compress(workingFile, MAX_IMAGE_SIZE, MAX_IMAGE_SIZE)
                             .also { compressedFile ->
                                 // Get new Bitmap size
                                 compressedFile.inputStream().use {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/SaveFilterTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/SaveFilterTask.kt
index 69ced92fe57f1a3045574e7d225e8dce9a83fb19..da747934e2676173cc23d067b41e75d58dbc1853 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/SaveFilterTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/SaveFilterTask.kt
@@ -18,9 +18,9 @@ package org.matrix.android.sdk.internal.session.filter
 
 import org.matrix.android.sdk.api.session.sync.FilterService
 import org.matrix.android.sdk.internal.di.UserId
+import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import org.matrix.android.sdk.internal.network.executeRequest
 import org.matrix.android.sdk.internal.task.Task
-import org.greenrobot.eventbus.EventBus
 import javax.inject.Inject
 
 /**
@@ -37,7 +37,7 @@ internal class DefaultSaveFilterTask @Inject constructor(
         @UserId private val userId: String,
         private val filterAPI: FilterApi,
         private val filterRepository: FilterRepository,
-        private val eventBus: EventBus
+        private val globalErrorReceiver: GlobalErrorReceiver
 ) : SaveFilterTask {
 
     override suspend fun execute(params: SaveFilterTask.Params) {
@@ -59,7 +59,7 @@ internal class DefaultSaveFilterTask @Inject constructor(
         }
         val updated = filterRepository.storeFilter(filterBody, roomFilter)
         if (updated) {
-            val filterResponse = executeRequest<FilterResponse>(eventBus) {
+            val filterResponse = executeRequest<FilterResponse>(globalErrorReceiver) {
                 // TODO auto retry
                 apiCall = filterAPI.uploadFilter(userId, filterBody)
             }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/group/GetGroupDataTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/group/GetGroupDataTask.kt
index dd703a5e93ee30b220a0dd0d540f913c1f22424a..9836164aecaefd84d9e5bba9c18e883846abaa25 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/group/GetGroupDataTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/group/GetGroupDataTask.kt
@@ -23,13 +23,13 @@ import org.matrix.android.sdk.internal.database.model.GroupSummaryEntity
 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.SessionDatabase
+import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import org.matrix.android.sdk.internal.network.executeRequest
 import org.matrix.android.sdk.internal.session.group.model.GroupRooms
 import org.matrix.android.sdk.internal.session.group.model.GroupSummaryResponse
 import org.matrix.android.sdk.internal.session.group.model.GroupUsers
 import org.matrix.android.sdk.internal.task.Task
 import org.matrix.android.sdk.internal.util.awaitTransaction
-import org.greenrobot.eventbus.EventBus
 import timber.log.Timber
 import javax.inject.Inject
 
@@ -43,7 +43,7 @@ internal interface GetGroupDataTask : Task<GetGroupDataTask.Params, Unit> {
 internal class DefaultGetGroupDataTask @Inject constructor(
         private val groupAPI: GroupAPI,
         @SessionDatabase private val monarchy: Monarchy,
-        private val eventBus: EventBus
+        private val globalErrorReceiver: GlobalErrorReceiver
 ) : GetGroupDataTask {
 
     private data class GroupData(
@@ -64,13 +64,13 @@ internal class DefaultGetGroupDataTask @Inject constructor(
         }
         Timber.v("Fetch data for group with ids: ${groupIds.joinToString(";")}")
         val data = groupIds.map { groupId ->
-            val groupSummary = executeRequest<GroupSummaryResponse>(eventBus) {
+            val groupSummary = executeRequest<GroupSummaryResponse>(globalErrorReceiver) {
                 apiCall = groupAPI.getSummary(groupId)
             }
-            val groupRooms = executeRequest<GroupRooms>(eventBus) {
+            val groupRooms = executeRequest<GroupRooms>(globalErrorReceiver) {
                 apiCall = groupAPI.getRooms(groupId)
             }
-            val groupUsers = executeRequest<GroupUsers>(eventBus) {
+            val groupUsers = executeRequest<GroupUsers>(globalErrorReceiver) {
                 apiCall = groupAPI.getUsers(groupId)
             }
             GroupData(groupId, groupSummary, groupRooms, groupUsers)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetHomeServerCapabilitiesTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetHomeServerCapabilitiesTask.kt
index f3686b02d3df0f9994f678aabfec50da2d3e2140..845cfb392e5730b1af63e50e85b30df97cc804a5 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetHomeServerCapabilitiesTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetHomeServerCapabilitiesTask.kt
@@ -17,7 +17,6 @@
 package org.matrix.android.sdk.internal.session.homeserver
 
 import com.zhuinden.monarchy.Monarchy
-import org.greenrobot.eventbus.EventBus
 import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
 import org.matrix.android.sdk.api.auth.wellknown.WellknownResult
 import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities
@@ -27,6 +26,7 @@ import org.matrix.android.sdk.internal.database.model.HomeServerCapabilitiesEnti
 import org.matrix.android.sdk.internal.database.query.getOrCreate
 import org.matrix.android.sdk.internal.di.SessionDatabase
 import org.matrix.android.sdk.internal.di.UserId
+import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import org.matrix.android.sdk.internal.network.executeRequest
 import org.matrix.android.sdk.internal.session.integrationmanager.IntegrationManagerConfigExtractor
 import org.matrix.android.sdk.internal.session.media.GetMediaConfigResult
@@ -44,7 +44,7 @@ internal class DefaultGetHomeServerCapabilitiesTask @Inject constructor(
         private val capabilitiesAPI: CapabilitiesAPI,
         private val mediaAPI: MediaAPI,
         @SessionDatabase private val monarchy: Monarchy,
-        private val eventBus: EventBus,
+        private val globalErrorReceiver: GlobalErrorReceiver,
         private val getWellknownTask: GetWellknownTask,
         private val configExtractor: IntegrationManagerConfigExtractor,
         private val homeServerConnectionConfig: HomeServerConnectionConfig,
@@ -65,13 +65,13 @@ internal class DefaultGetHomeServerCapabilitiesTask @Inject constructor(
         }
 
         val capabilities = runCatching {
-            executeRequest<GetCapabilitiesResult>(eventBus) {
+            executeRequest<GetCapabilitiesResult>(globalErrorReceiver) {
                 apiCall = capabilitiesAPI.getCapabilities()
             }
         }.getOrNull()
 
         val mediaConfig = runCatching {
-            executeRequest<GetMediaConfigResult>(eventBus) {
+            executeRequest<GetMediaConfigResult>(globalErrorReceiver) {
                 apiCall = mediaAPI.getMediaConfig()
             }
         }.getOrNull()
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityBulkLookupTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityBulkLookupTask.kt
index a03bef95010e1697eac2c69e2bcd13273c470f80..67f3b2aa56952c02e80e9d5d17556a0653fa4fad 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityBulkLookupTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityBulkLookupTask.kt
@@ -52,65 +52,60 @@ internal class DefaultIdentityBulkLookupTask @Inject constructor(
         val pepper = identityData.hashLookupPepper
         val hashDetailResponse = if (pepper == null) {
             // We need to fetch the hash details first
-            fetchAndStoreHashDetails(identityAPI)
+            fetchHashDetails(identityAPI)
+                    .also { identityStore.setHashDetails(it) }
         } else {
             IdentityHashDetailResponse(pepper, identityData.hashLookupAlgorithm)
         }
 
-        if (hashDetailResponse.algorithms.contains("sha256").not()) {
+        if (hashDetailResponse.algorithms.contains(IdentityHashDetailResponse.ALGORITHM_SHA256).not()) {
             // TODO We should ask the user if he is ok to send their 3Pid in clear, but for the moment we do not do it
             // Also, what we have in cache could be outdated, the identity server maybe now supports sha256
             throw IdentityServiceError.BulkLookupSha256NotSupported
         }
 
-        val hashedAddresses = withOlmUtility { olmUtility ->
-            params.threePids.map { threePid ->
-                base64ToBase64Url(
-                        olmUtility.sha256(threePid.value.toLowerCase(Locale.ROOT)
-                                + " " + threePid.toMedium() + " " + hashDetailResponse.pepper)
-                )
-            }
-        }
-
-        val identityLookUpV2Response = lookUpInternal(identityAPI, hashedAddresses, hashDetailResponse, true)
+        val lookUpData = lookUpInternal(identityAPI, params.threePids, hashDetailResponse, true)
 
         // Convert back to List<FoundThreePid>
-        return handleSuccess(params.threePids, hashedAddresses, identityLookUpV2Response)
+        return handleSuccess(params.threePids, lookUpData)
     }
 
+    data class LookUpData(
+            val hashedAddresses: List<String>,
+            val identityLookUpResponse: IdentityLookUpResponse
+    )
+
     private suspend fun lookUpInternal(identityAPI: IdentityAPI,
-                                       hashedAddresses: List<String>,
+                                       threePids: List<ThreePid>,
                                        hashDetailResponse: IdentityHashDetailResponse,
-                                       canRetry: Boolean): IdentityLookUpResponse {
+                                       canRetry: Boolean): LookUpData {
+        val hashedAddresses = getHashedAddresses(threePids, hashDetailResponse.pepper)
         return try {
-            executeRequest(null) {
-                apiCall = identityAPI.lookup(IdentityLookUpParams(
-                        hashedAddresses,
-                        IdentityHashDetailResponse.ALGORITHM_SHA256,
-                        hashDetailResponse.pepper
-                ))
-            }
+            LookUpData(hashedAddresses,
+                    executeRequest(null) {
+                        apiCall = identityAPI.lookup(IdentityLookUpParams(
+                                hashedAddresses,
+                                IdentityHashDetailResponse.ALGORITHM_SHA256,
+                                hashDetailResponse.pepper
+                        ))
+                    })
         } catch (failure: Throwable) {
             // Catch invalid hash pepper and retry
             if (canRetry && failure is Failure.ServerError && failure.error.code == MatrixError.M_INVALID_PEPPER) {
                 // This is not documented, but the error can contain the new pepper!
-                if (!failure.error.newLookupPepper.isNullOrEmpty()) {
+                val newHashDetailResponse = if (!failure.error.newLookupPepper.isNullOrEmpty()) {
                     // Store it and use it right now
                     hashDetailResponse.copy(pepper = failure.error.newLookupPepper)
-                            .also { identityStore.setHashDetails(it) }
-                            .let { lookUpInternal(identityAPI, hashedAddresses, it, false /* Avoid infinite loop */) }
                 } else {
                     // Retrieve the new hash details
-                    val newHashDetailResponse = fetchAndStoreHashDetails(identityAPI)
-
-                    if (hashDetailResponse.algorithms.contains(IdentityHashDetailResponse.ALGORITHM_SHA256).not()) {
-                        // TODO We should ask the user if he is ok to send their 3Pid in clear, but for the moment we do not do it
-                        // Also, what we have in cache is maybe outdated, the identity server maybe now support sha256
-                        throw IdentityServiceError.BulkLookupSha256NotSupported
-                    }
-
-                    lookUpInternal(identityAPI, hashedAddresses, newHashDetailResponse, false /* Avoid infinite loop */)
+                    fetchHashDetails(identityAPI)
+                }
+                        .also { identityStore.setHashDetails(it) }
+                if (newHashDetailResponse.algorithms.contains(IdentityHashDetailResponse.ALGORITHM_SHA256).not()) {
+                    // TODO We should ask the user if he is ok to send their 3Pid in clear, but for the moment we do not do it
+                    throw IdentityServiceError.BulkLookupSha256NotSupported
                 }
+                lookUpInternal(identityAPI, threePids, newHashDetailResponse, false /* Avoid infinite loop */)
             } else {
                 // Other error
                 throw failure
@@ -118,16 +113,29 @@ internal class DefaultIdentityBulkLookupTask @Inject constructor(
         }
     }
 
-    private suspend fun fetchAndStoreHashDetails(identityAPI: IdentityAPI): IdentityHashDetailResponse {
-        return executeRequest<IdentityHashDetailResponse>(null) {
+    private fun getHashedAddresses(threePids: List<ThreePid>, pepper: String): List<String> {
+        return withOlmUtility { olmUtility ->
+            threePids.map { threePid ->
+                base64ToBase64Url(
+                        olmUtility.sha256(threePid.value.toLowerCase(Locale.ROOT)
+                                + " " + threePid.toMedium() + " " + pepper)
+                )
+            }
+        }
+    }
+
+    private suspend fun fetchHashDetails(identityAPI: IdentityAPI): IdentityHashDetailResponse {
+        return executeRequest(null) {
             apiCall = identityAPI.hashDetails()
         }
-                .also { identityStore.setHashDetails(it) }
     }
 
-    private fun handleSuccess(threePids: List<ThreePid>, hashedAddresses: List<String>, identityLookUpResponse: IdentityLookUpResponse): List<FoundThreePid> {
-        return identityLookUpResponse.mappings.keys.map { hashedAddress ->
-            FoundThreePid(threePids[hashedAddresses.indexOf(hashedAddress)], identityLookUpResponse.mappings[hashedAddress] ?: error(""))
+    private fun handleSuccess(threePids: List<ThreePid>, lookupData: LookUpData): List<FoundThreePid> {
+        return lookupData.identityLookUpResponse.mappings.keys.map { hashedAddress ->
+            FoundThreePid(
+                    threePids[lookupData.hashedAddresses.indexOf(hashedAddress)],
+                    lookupData.identityLookUpResponse.mappings[hashedAddress] ?: error("")
+            )
         }
     }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/DefaultMediaService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/DefaultMediaService.kt
index 1a400ccfcfaea8a593df3bbfab3099a4173cc4a6..9b807d03de52da671b3e961b08305d0376b320a3 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/DefaultMediaService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/DefaultMediaService.kt
@@ -18,9 +18,10 @@ package org.matrix.android.sdk.internal.session.media
 
 import androidx.collection.LruCache
 import org.matrix.android.sdk.api.cache.CacheStrategy
-import org.matrix.android.sdk.api.session.events.model.Event
 import org.matrix.android.sdk.api.session.media.MediaService
 import org.matrix.android.sdk.api.session.media.PreviewUrlData
+import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
+import org.matrix.android.sdk.api.session.room.timeline.getLatestEventId
 import org.matrix.android.sdk.api.util.JsonDict
 import org.matrix.android.sdk.internal.util.getOrPut
 import javax.inject.Inject
@@ -34,11 +35,12 @@ internal class DefaultMediaService @Inject constructor(
     // Cache of extracted URLs
     private val extractedUrlsCache = LruCache<String, List<String>>(1_000)
 
-    override fun extractUrls(event: Event): List<String> {
+    override fun extractUrls(event: TimelineEvent): List<String> {
         return extractedUrlsCache.getOrPut(event.cacheKey()) { urlsExtractor.extract(event) }
     }
 
-    private fun Event.cacheKey() = "${eventId ?: ""}-${roomId ?: ""}"
+    // Use the id of the latest Event edition
+    private fun TimelineEvent.cacheKey() = "${getLatestEventId()}-${root.roomId ?: ""}"
 
     override suspend fun getRawPreviewUrl(url: String, timestamp: Long?): JsonDict {
         return getRawPreviewUrlTask.execute(GetRawPreviewUrlTask.Params(url, timestamp))
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/GetPreviewUrlTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/GetPreviewUrlTask.kt
index 69cdfa8faad3551e8a3b127014b043e5821d22cf..a218f3f93cd6862061179a9d329ccec90095f5d4 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/GetPreviewUrlTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/GetPreviewUrlTask.kt
@@ -17,7 +17,6 @@
 package org.matrix.android.sdk.internal.session.media
 
 import com.zhuinden.monarchy.Monarchy
-import org.greenrobot.eventbus.EventBus
 import org.matrix.android.sdk.api.cache.CacheStrategy
 import org.matrix.android.sdk.api.session.media.PreviewUrlData
 import org.matrix.android.sdk.api.util.JsonDict
@@ -25,6 +24,7 @@ import org.matrix.android.sdk.internal.database.model.PreviewUrlCacheEntity
 import org.matrix.android.sdk.internal.database.query.get
 import org.matrix.android.sdk.internal.database.query.getOrCreate
 import org.matrix.android.sdk.internal.di.SessionDatabase
+import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import org.matrix.android.sdk.internal.network.executeRequest
 import org.matrix.android.sdk.internal.task.Task
 import org.matrix.android.sdk.internal.util.awaitTransaction
@@ -41,7 +41,7 @@ internal interface GetPreviewUrlTask : Task<GetPreviewUrlTask.Params, PreviewUrl
 
 internal class DefaultGetPreviewUrlTask @Inject constructor(
         private val mediaAPI: MediaAPI,
-        private val eventBus: EventBus,
+        private val globalErrorReceiver: GlobalErrorReceiver,
         @SessionDatabase private val monarchy: Monarchy
 ) : GetPreviewUrlTask {
 
@@ -64,7 +64,7 @@ internal class DefaultGetPreviewUrlTask @Inject constructor(
     }
 
     private suspend fun doRequest(url: String, timestamp: Long?): PreviewUrlData {
-        return executeRequest<JsonDict>(eventBus) {
+        return executeRequest<JsonDict>(globalErrorReceiver) {
             apiCall = mediaAPI.getPreviewUrlData(url, timestamp)
         }
                 .toPreviewUrlData(url)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/GetRawPreviewUrlTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/GetRawPreviewUrlTask.kt
index 6c5dad2422bffaaefc43065e53c78bd0954d1202..32305cd4e405419470c8e1a4d9a392c849d008e9 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/GetRawPreviewUrlTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/GetRawPreviewUrlTask.kt
@@ -16,8 +16,8 @@
 
 package org.matrix.android.sdk.internal.session.media
 
-import org.greenrobot.eventbus.EventBus
 import org.matrix.android.sdk.api.util.JsonDict
+import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import org.matrix.android.sdk.internal.network.executeRequest
 import org.matrix.android.sdk.internal.task.Task
 import javax.inject.Inject
@@ -31,11 +31,11 @@ internal interface GetRawPreviewUrlTask : Task<GetRawPreviewUrlTask.Params, Json
 
 internal class DefaultGetRawPreviewUrlTask @Inject constructor(
         private val mediaAPI: MediaAPI,
-        private val eventBus: EventBus
+        private val globalErrorReceiver: GlobalErrorReceiver
 ) : GetRawPreviewUrlTask {
 
     override suspend fun execute(params: GetRawPreviewUrlTask.Params): JsonDict {
-        return executeRequest(eventBus) {
+        return executeRequest(globalErrorReceiver) {
             apiCall = mediaAPI.getPreviewUrlData(params.url, params.timestamp)
         }
     }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/UrlsExtractor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/UrlsExtractor.kt
index 9d374c342893a04e7c5a9d4e6ad53102d80fe660..6137b4152cb48582d06ae80eac6fdb6aca3b8fdb 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/UrlsExtractor.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/UrlsExtractor.kt
@@ -17,22 +17,24 @@
 package org.matrix.android.sdk.internal.session.media
 
 import android.util.Patterns
-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.toModel
-import org.matrix.android.sdk.api.session.room.model.message.MessageContent
 import org.matrix.android.sdk.api.session.room.model.message.MessageType
+import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
+import org.matrix.android.sdk.api.session.room.timeline.getLastMessageContent
 import javax.inject.Inject
 
 internal class UrlsExtractor @Inject constructor() {
     // Sadly Patterns.WEB_URL_WITH_PROTOCOL is not public so filter the protocol later
     private val urlRegex = Patterns.WEB_URL.toRegex()
 
-    fun extract(event: Event): List<String> {
-        return event.takeIf { it.getClearType() == EventType.MESSAGE }
-                ?.getClearContent()
-                ?.toModel<MessageContent>()
-                ?.takeIf { it.msgType == MessageType.MSGTYPE_TEXT || it.msgType == MessageType.MSGTYPE_EMOTE }
+    fun extract(event: TimelineEvent): List<String> {
+        return event.takeIf { it.root.getClearType() == EventType.MESSAGE }
+                ?.getLastMessageContent()
+                ?.takeIf {
+                    it.msgType == MessageType.MSGTYPE_TEXT
+                            || it.msgType == MessageType.MSGTYPE_NOTICE
+                            || it.msgType == MessageType.MSGTYPE_EMOTE
+                }
                 ?.body
                 ?.let { urlRegex.findAll(it) }
                 ?.map { it.value }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/openid/GetOpenIdTokenTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/openid/GetOpenIdTokenTask.kt
index 5d7cfd1719500a388552c96c619d02777644ee8f..f83c6b770af5fa3883d84f73c6163bfebf8e6cb9 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/openid/GetOpenIdTokenTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/openid/GetOpenIdTokenTask.kt
@@ -17,9 +17,9 @@
 package org.matrix.android.sdk.internal.session.openid
 
 import org.matrix.android.sdk.internal.di.UserId
+import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import org.matrix.android.sdk.internal.network.executeRequest
 import org.matrix.android.sdk.internal.task.Task
-import org.greenrobot.eventbus.EventBus
 import javax.inject.Inject
 
 internal interface GetOpenIdTokenTask : Task<Unit, RequestOpenIdTokenResponse>
@@ -27,10 +27,10 @@ internal interface GetOpenIdTokenTask : Task<Unit, RequestOpenIdTokenResponse>
 internal class DefaultGetOpenIdTokenTask @Inject constructor(
         @UserId private val userId: String,
         private val openIdAPI: OpenIdAPI,
-        private val eventBus: EventBus) : GetOpenIdTokenTask {
+        private val globalErrorReceiver: GlobalErrorReceiver) : GetOpenIdTokenTask {
 
     override suspend fun execute(params: Unit): RequestOpenIdTokenResponse {
-        return executeRequest(eventBus) {
+        return executeRequest(globalErrorReceiver) {
             apiCall = openIdAPI.openIdToken(userId)
         }
     }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/AddThreePidTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/AddThreePidTask.kt
index 2e8a0b3884beab6bab672002e3e381492135fd28..6d6d70bb0d5d51a84200bfee1d6f78bb99cdc57f 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/AddThreePidTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/AddThreePidTask.kt
@@ -18,9 +18,9 @@ package org.matrix.android.sdk.internal.session.profile
 
 import com.google.i18n.phonenumbers.PhoneNumberUtil
 import com.zhuinden.monarchy.Monarchy
-import org.greenrobot.eventbus.EventBus
 import org.matrix.android.sdk.api.session.identity.ThreePid
 import org.matrix.android.sdk.internal.di.SessionDatabase
+import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import org.matrix.android.sdk.internal.network.executeRequest
 import org.matrix.android.sdk.internal.task.Task
 import org.matrix.android.sdk.internal.util.awaitTransaction
@@ -37,7 +37,7 @@ internal class DefaultAddThreePidTask @Inject constructor(
         private val profileAPI: ProfileAPI,
         @SessionDatabase private val monarchy: Monarchy,
         private val pendingThreePidMapper: PendingThreePidMapper,
-        private val eventBus: EventBus) : AddThreePidTask() {
+        private val globalErrorReceiver: GlobalErrorReceiver) : AddThreePidTask() {
 
     override suspend fun execute(params: Params) {
         when (params.threePid) {
@@ -50,7 +50,7 @@ internal class DefaultAddThreePidTask @Inject constructor(
         val clientSecret = UUID.randomUUID().toString()
         val sendAttempt = 1
 
-        val result = executeRequest<AddEmailResponse>(eventBus) {
+        val result = executeRequest<AddEmailResponse>(globalErrorReceiver) {
             val body = AddEmailBody(
                     clientSecret = clientSecret,
                     email = threePid.email,
@@ -84,7 +84,7 @@ internal class DefaultAddThreePidTask @Inject constructor(
         val countryCode = parsedNumber.countryCode
         val country = phoneNumberUtil.getRegionCodeForCountryCode(countryCode)
 
-        val result = executeRequest<AddMsisdnResponse>(eventBus) {
+        val result = executeRequest<AddMsisdnResponse>(globalErrorReceiver) {
             val body = AddMsisdnBody(
                     clientSecret = clientSecret,
                     country = country,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/BindThreePidsTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/BindThreePidsTask.kt
index dbe6bff508e9ca2c1afc8aceeee9f01d6d38aa52..a37e5380bcc9a0cdeb6b8d5159dafd98f18b3401 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/BindThreePidsTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/BindThreePidsTask.kt
@@ -19,12 +19,12 @@ package org.matrix.android.sdk.internal.session.profile
 import org.matrix.android.sdk.api.session.identity.IdentityServiceError
 import org.matrix.android.sdk.api.session.identity.ThreePid
 import org.matrix.android.sdk.internal.di.AuthenticatedIdentity
+import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import org.matrix.android.sdk.internal.network.executeRequest
 import org.matrix.android.sdk.internal.network.token.AccessTokenProvider
 import org.matrix.android.sdk.internal.session.identity.data.IdentityStore
 import org.matrix.android.sdk.internal.session.identity.data.getIdentityServerUrlWithoutProtocol
 import org.matrix.android.sdk.internal.task.Task
-import org.greenrobot.eventbus.EventBus
 import javax.inject.Inject
 
 internal abstract class BindThreePidsTask : Task<BindThreePidsTask.Params, Unit> {
@@ -37,13 +37,13 @@ internal class DefaultBindThreePidsTask @Inject constructor(private val profileA
                                                             private val identityStore: IdentityStore,
                                                             @AuthenticatedIdentity
                                                             private val accessTokenProvider: AccessTokenProvider,
-                                                            private val eventBus: EventBus) : BindThreePidsTask() {
+                                                            private val globalErrorReceiver: GlobalErrorReceiver) : BindThreePidsTask() {
     override suspend fun execute(params: Params) {
         val identityServerUrlWithoutProtocol = identityStore.getIdentityServerUrlWithoutProtocol() ?: throw IdentityServiceError.NoIdentityServerConfigured
         val identityServerAccessToken = accessTokenProvider.getToken() ?: throw IdentityServiceError.NoIdentityServerConfigured
         val identityPendingBinding = identityStore.getPendingBinding(params.threePid) ?: throw IdentityServiceError.NoCurrentBindingError
 
-        executeRequest<Unit>(eventBus) {
+        executeRequest<Unit>(globalErrorReceiver) {
             apiCall = profileAPI.bindThreePid(
                     BindThreePidBody(
                             clientSecret = identityPendingBinding.clientSecret,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/DefaultProfileService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/DefaultProfileService.kt
index 500d43408e25cbf70fc75786b04645ce818a3822..b3216d744d4bbf006cbbd1dc2a4905f9713715b5 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/DefaultProfileService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/DefaultProfileService.kt
@@ -22,6 +22,7 @@ import androidx.lifecycle.LiveData
 import com.zhuinden.monarchy.Monarchy
 import io.realm.kotlin.where
 import org.matrix.android.sdk.api.MatrixCallback
+import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
 import org.matrix.android.sdk.api.session.identity.ThreePid
 import org.matrix.android.sdk.api.session.profile.ProfileService
 import org.matrix.android.sdk.api.util.Cancelable
@@ -170,14 +171,12 @@ internal class DefaultProfileService @Inject constructor(private val taskExecuto
     }
 
     override fun finalizeAddingThreePid(threePid: ThreePid,
-                                        uiaSession: String?,
-                                        accountPassword: String?,
+                                        userInteractiveAuthInterceptor: UserInteractiveAuthInterceptor,
                                         matrixCallback: MatrixCallback<Unit>): Cancelable {
         return finalizeAddingThreePidTask
                 .configureWith(FinalizeAddingThreePidTask.Params(
                         threePid = threePid,
-                        session = uiaSession,
-                        accountPassword = accountPassword,
+                        userInteractiveAuthInterceptor = userInteractiveAuthInterceptor,
                         userWantsToCancel = false
                 )) {
                     callback = alsoRefresh(matrixCallback)
@@ -189,8 +188,7 @@ internal class DefaultProfileService @Inject constructor(private val taskExecuto
         return finalizeAddingThreePidTask
                 .configureWith(FinalizeAddingThreePidTask.Params(
                         threePid = threePid,
-                        session = null,
-                        accountPassword = null,
+                        userInteractiveAuthInterceptor = null,
                         userWantsToCancel = true
                 )) {
                     callback = alsoRefresh(matrixCallback)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/DeleteThreePidTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/DeleteThreePidTask.kt
index 3f43cbe5998ff93c4ebcea6fc2a55640be769f7c..3549f3613f62f35372304162e4d6a25b0714cb23 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/DeleteThreePidTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/DeleteThreePidTask.kt
@@ -16,9 +16,9 @@
 
 package org.matrix.android.sdk.internal.session.profile
 
-import org.greenrobot.eventbus.EventBus
 import org.matrix.android.sdk.api.session.identity.ThreePid
 import org.matrix.android.sdk.api.session.identity.toMedium
+import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import org.matrix.android.sdk.internal.network.executeRequest
 import org.matrix.android.sdk.internal.task.Task
 import javax.inject.Inject
@@ -31,10 +31,10 @@ internal abstract class DeleteThreePidTask : Task<DeleteThreePidTask.Params, Uni
 
 internal class DefaultDeleteThreePidTask @Inject constructor(
         private val profileAPI: ProfileAPI,
-        private val eventBus: EventBus) : DeleteThreePidTask() {
+        private val globalErrorReceiver: GlobalErrorReceiver) : DeleteThreePidTask() {
 
     override suspend fun execute(params: Params) {
-        executeRequest<DeleteThreePidResponse>(eventBus) {
+        executeRequest<DeleteThreePidResponse>(globalErrorReceiver) {
             val body = DeleteThreePidBody(
                     medium = params.threePid.toMedium(),
                     address = params.threePid.value
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/FinalizeAddThreePidBody.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/FinalizeAddThreePidBody.kt
index 4e46dd096d77b88f904deba9b4d258affe87963f..630192954540c71d24761a5b8c7bbf0e91689953 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/FinalizeAddThreePidBody.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/FinalizeAddThreePidBody.kt
@@ -17,7 +17,6 @@ package org.matrix.android.sdk.internal.session.profile
 
 import com.squareup.moshi.Json
 import com.squareup.moshi.JsonClass
-import org.matrix.android.sdk.internal.crypto.model.rest.UserPasswordAuth
 
 @JsonClass(generateAdapter = true)
 internal data class FinalizeAddThreePidBody(
@@ -37,5 +36,5 @@ internal data class FinalizeAddThreePidBody(
          * Additional authentication information for the user-interactive authentication API.
          */
         @Json(name = "auth")
-        val auth: UserPasswordAuth?
+        val auth: Map<String, *>? = null
 )
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/FinalizeAddingThreePidTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/FinalizeAddingThreePidTask.kt
index 0b1bf8828032085091d561adf6b37168a69277e5..916a6029360f7e7503aa15e2e15d08f2842bc747 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/FinalizeAddingThreePidTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/FinalizeAddingThreePidTask.kt
@@ -17,25 +17,28 @@
 package org.matrix.android.sdk.internal.session.profile
 
 import com.zhuinden.monarchy.Monarchy
-import org.greenrobot.eventbus.EventBus
+import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
 import org.matrix.android.sdk.api.failure.Failure
 import org.matrix.android.sdk.api.failure.toRegistrationFlowResponse
 import org.matrix.android.sdk.api.session.identity.ThreePid
-import org.matrix.android.sdk.internal.crypto.model.rest.UserPasswordAuth
+import org.matrix.android.sdk.internal.auth.registration.handleUIA
+import org.matrix.android.sdk.api.auth.UIABaseAuth
 import org.matrix.android.sdk.internal.database.model.PendingThreePidEntity
 import org.matrix.android.sdk.internal.database.model.PendingThreePidEntityFields
 import org.matrix.android.sdk.internal.di.SessionDatabase
 import org.matrix.android.sdk.internal.di.UserId
+import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import org.matrix.android.sdk.internal.network.executeRequest
 import org.matrix.android.sdk.internal.task.Task
 import org.matrix.android.sdk.internal.util.awaitTransaction
+import timber.log.Timber
 import javax.inject.Inject
 
 internal abstract class FinalizeAddingThreePidTask : Task<FinalizeAddingThreePidTask.Params, Unit> {
     data class Params(
             val threePid: ThreePid,
-            val session: String?,
-            val accountPassword: String?,
+            val userInteractiveAuthInterceptor: UserInteractiveAuthInterceptor?,
+            val userAuthParam: UIABaseAuth? = null,
             val userWantsToCancel: Boolean
     )
 }
@@ -45,7 +48,7 @@ internal class DefaultFinalizeAddingThreePidTask @Inject constructor(
         @SessionDatabase private val monarchy: Monarchy,
         private val pendingThreePidMapper: PendingThreePidMapper,
         @UserId private val userId: String,
-        private val eventBus: EventBus) : FinalizeAddingThreePidTask() {
+        private val globalErrorReceiver: GlobalErrorReceiver) : FinalizeAddingThreePidTask() {
 
     override suspend fun execute(params: Params) {
         if (params.userWantsToCancel.not()) {
@@ -58,24 +61,25 @@ internal class DefaultFinalizeAddingThreePidTask @Inject constructor(
                     ?: throw IllegalArgumentException("unknown threepid")
 
             try {
-                executeRequest<Unit>(eventBus) {
+                executeRequest<Unit>(globalErrorReceiver) {
                     val body = FinalizeAddThreePidBody(
                             clientSecret = pendingThreePids.clientSecret,
                             sid = pendingThreePids.sid,
-                            auth = if (params.session != null && params.accountPassword != null) {
-                                UserPasswordAuth(
-                                        session = params.session,
-                                        user = userId,
-                                        password = params.accountPassword
-                                )
-                            } else null
+                            auth = params.userAuthParam?.asMap()
                     )
                     apiCall = profileAPI.finalizeAddThreePid(body)
                 }
             } catch (throwable: Throwable) {
-                throw throwable.toRegistrationFlowResponse()
-                        ?.let { Failure.RegistrationFlowError(it) }
-                        ?: throwable
+                if (params.userInteractiveAuthInterceptor == null
+                        || !handleUIA(throwable, params.userInteractiveAuthInterceptor) { auth ->
+                            execute(params.copy(userAuthParam = auth))
+                        }
+                ) {
+                    Timber.d("## UIA: propagate failure")
+                    throw  throwable.toRegistrationFlowResponse()
+                            ?.let { Failure.RegistrationFlowError(it) }
+                            ?: throwable
+                }
             }
         }
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/GetProfileInfoTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/GetProfileInfoTask.kt
index 5f1f621ddbaf7ab60743d43aee7428a9ab96bcfd..ed60c4a368d5f8acd7668b35adca3e54caf05e49 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/GetProfileInfoTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/GetProfileInfoTask.kt
@@ -18,9 +18,9 @@
 package org.matrix.android.sdk.internal.session.profile
 
 import org.matrix.android.sdk.api.util.JsonDict
+import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import org.matrix.android.sdk.internal.network.executeRequest
 import org.matrix.android.sdk.internal.task.Task
-import org.greenrobot.eventbus.EventBus
 import javax.inject.Inject
 
 internal abstract class GetProfileInfoTask : Task<GetProfileInfoTask.Params, JsonDict> {
@@ -30,10 +30,10 @@ internal abstract class GetProfileInfoTask : Task<GetProfileInfoTask.Params, Jso
 }
 
 internal class DefaultGetProfileInfoTask @Inject constructor(private val profileAPI: ProfileAPI,
-                                                             private val eventBus: EventBus) : GetProfileInfoTask() {
+                                                             private val globalErrorReceiver: GlobalErrorReceiver) : GetProfileInfoTask() {
 
     override suspend fun execute(params: Params): JsonDict {
-        return executeRequest(eventBus) {
+        return executeRequest(globalErrorReceiver) {
             apiCall = profileAPI.getProfile(params.userId)
         }
     }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/RefreshUserThreePidsTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/RefreshUserThreePidsTask.kt
index 5dd092cceba50ad801793fddfe5c6babe43c382f..552ad874eeeba0bea3e88e06589b3fecdb7ddc09 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/RefreshUserThreePidsTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/RefreshUserThreePidsTask.kt
@@ -19,9 +19,9 @@ package org.matrix.android.sdk.internal.session.profile
 import com.zhuinden.monarchy.Monarchy
 import org.matrix.android.sdk.internal.database.model.UserThreePidEntity
 import org.matrix.android.sdk.internal.di.SessionDatabase
+import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import org.matrix.android.sdk.internal.network.executeRequest
 import org.matrix.android.sdk.internal.task.Task
-import org.greenrobot.eventbus.EventBus
 import org.matrix.android.sdk.internal.util.awaitTransaction
 import timber.log.Timber
 import javax.inject.Inject
@@ -30,10 +30,10 @@ internal abstract class RefreshUserThreePidsTask : Task<Unit, Unit>
 
 internal class DefaultRefreshUserThreePidsTask @Inject constructor(private val profileAPI: ProfileAPI,
                                                                    @SessionDatabase private val monarchy: Monarchy,
-                                                                   private val eventBus: EventBus) : RefreshUserThreePidsTask() {
+                                                                   private val globalErrorReceiver: GlobalErrorReceiver) : RefreshUserThreePidsTask() {
 
     override suspend fun execute(params: Unit) {
-        val accountThreePidsResponse = executeRequest<AccountThreePidsResponse>(eventBus) {
+        val accountThreePidsResponse = executeRequest<AccountThreePidsResponse>(globalErrorReceiver) {
             apiCall = profileAPI.getThreePIDs()
         }
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/SetAvatarUrlTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/SetAvatarUrlTask.kt
index 4b863c20983bbdde9ef7f40aaacb0d4310531c26..b29153d665f634d7c529885622022dbd423989f9 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/SetAvatarUrlTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/SetAvatarUrlTask.kt
@@ -16,9 +16,9 @@
 
 package org.matrix.android.sdk.internal.session.profile
 
+import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import org.matrix.android.sdk.internal.network.executeRequest
 import org.matrix.android.sdk.internal.task.Task
-import org.greenrobot.eventbus.EventBus
 import javax.inject.Inject
 
 internal abstract class SetAvatarUrlTask : Task<SetAvatarUrlTask.Params, Unit> {
@@ -30,10 +30,10 @@ internal abstract class SetAvatarUrlTask : Task<SetAvatarUrlTask.Params, Unit> {
 
 internal class DefaultSetAvatarUrlTask @Inject constructor(
         private val profileAPI: ProfileAPI,
-        private val eventBus: EventBus) : SetAvatarUrlTask() {
+        private val globalErrorReceiver: GlobalErrorReceiver) : SetAvatarUrlTask() {
 
     override suspend fun execute(params: Params) {
-        return executeRequest(eventBus) {
+        return executeRequest(globalErrorReceiver) {
             val body = SetAvatarUrlBody(
                     avatarUrl = params.newAvatarUrl
             )
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/SetDisplayNameTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/SetDisplayNameTask.kt
index 1fa84f98c12288bc6e04e65868fdb208fd9fa5ea..3f236bc589ba22750b31a41d55d59acec236fc2e 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/SetDisplayNameTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/SetDisplayNameTask.kt
@@ -16,9 +16,9 @@
 
 package org.matrix.android.sdk.internal.session.profile
 
+import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import org.matrix.android.sdk.internal.network.executeRequest
 import org.matrix.android.sdk.internal.task.Task
-import org.greenrobot.eventbus.EventBus
 import javax.inject.Inject
 
 internal abstract class SetDisplayNameTask : Task<SetDisplayNameTask.Params, Unit> {
@@ -30,10 +30,10 @@ internal abstract class SetDisplayNameTask : Task<SetDisplayNameTask.Params, Uni
 
 internal class DefaultSetDisplayNameTask @Inject constructor(
         private val profileAPI: ProfileAPI,
-        private val eventBus: EventBus) : SetDisplayNameTask() {
+        private val globalErrorReceiver: GlobalErrorReceiver) : SetDisplayNameTask() {
 
     override suspend fun execute(params: Params) {
-        return executeRequest(eventBus) {
+        return executeRequest(globalErrorReceiver) {
             val body = SetDisplayNameBody(
                     displayName = params.newDisplayName
             )
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/UnbindThreePidsTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/UnbindThreePidsTask.kt
index 96b0717edbb5763634a017eb1d41f87dabd73bdf..3439f6f840a980af59d0324327a5dc3e2bc50362 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/UnbindThreePidsTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/UnbindThreePidsTask.kt
@@ -19,11 +19,11 @@ package org.matrix.android.sdk.internal.session.profile
 import org.matrix.android.sdk.api.session.identity.IdentityServiceError
 import org.matrix.android.sdk.api.session.identity.ThreePid
 import org.matrix.android.sdk.api.session.identity.toMedium
+import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import org.matrix.android.sdk.internal.network.executeRequest
 import org.matrix.android.sdk.internal.session.identity.data.IdentityStore
 import org.matrix.android.sdk.internal.session.identity.data.getIdentityServerUrlWithoutProtocol
 import org.matrix.android.sdk.internal.task.Task
-import org.greenrobot.eventbus.EventBus
 import javax.inject.Inject
 
 internal abstract class UnbindThreePidsTask : Task<UnbindThreePidsTask.Params, Boolean> {
@@ -34,12 +34,12 @@ internal abstract class UnbindThreePidsTask : Task<UnbindThreePidsTask.Params, B
 
 internal class DefaultUnbindThreePidsTask @Inject constructor(private val profileAPI: ProfileAPI,
                                                               private val identityStore: IdentityStore,
-                                                              private val eventBus: EventBus) : UnbindThreePidsTask() {
+                                                              private val globalErrorReceiver: GlobalErrorReceiver) : UnbindThreePidsTask() {
     override suspend fun execute(params: Params): Boolean {
         val identityServerUrlWithoutProtocol = identityStore.getIdentityServerUrlWithoutProtocol()
                 ?: throw IdentityServiceError.NoIdentityServerConfigured
 
-        return executeRequest<UnbindThreePidResponse>(eventBus) {
+        return executeRequest<UnbindThreePidResponse>(globalErrorReceiver) {
             apiCall = profileAPI.unbindThreePid(
                     UnbindThreePidBody(
                             identityServerUrlWithoutProtocol,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/ValidateSmsCodeTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/ValidateSmsCodeTask.kt
index 36804e06fef989755f866d584ffa993ea1af7f40..efb6c6e836ca4dabbb7b255e664c4483281d4c67 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/ValidateSmsCodeTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/ValidateSmsCodeTask.kt
@@ -17,13 +17,13 @@
 package org.matrix.android.sdk.internal.session.profile
 
 import com.zhuinden.monarchy.Monarchy
-import org.greenrobot.eventbus.EventBus
 import org.matrix.android.sdk.api.failure.Failure
 import org.matrix.android.sdk.api.session.identity.ThreePid
 import org.matrix.android.sdk.internal.auth.registration.SuccessResult
 import org.matrix.android.sdk.internal.auth.registration.ValidationCodeBody
 import org.matrix.android.sdk.internal.database.model.PendingThreePidEntity
 import org.matrix.android.sdk.internal.di.SessionDatabase
+import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import org.matrix.android.sdk.internal.network.executeRequest
 import org.matrix.android.sdk.internal.task.Task
 import javax.inject.Inject
@@ -40,7 +40,7 @@ internal class DefaultValidateSmsCodeTask @Inject constructor(
         @SessionDatabase
         private val monarchy: Monarchy,
         private val pendingThreePidMapper: PendingThreePidMapper,
-        private val eventBus: EventBus
+        private val globalErrorReceiver: GlobalErrorReceiver
 ) : ValidateSmsCodeTask {
 
     override suspend fun execute(params: ValidateSmsCodeTask.Params) {
@@ -58,7 +58,7 @@ internal class DefaultValidateSmsCodeTask @Inject constructor(
                 sid = pendingThreePids.sid,
                 code = params.code
         )
-        val result = executeRequest<SuccessResult>(eventBus) {
+        val result = executeRequest<SuccessResult>(globalErrorReceiver) {
             apiCall = profileAPI.validateMsisdn(url, body)
         }
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/AddHttpPusherWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/AddHttpPusherWorker.kt
index 31c5cda5ecd57aff28ccd039ace6e7135b0f1325..d0f7cbfca3d0d5b0ec03bead41e847c95932fb54 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/AddHttpPusherWorker.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/AddHttpPusherWorker.kt
@@ -19,13 +19,13 @@ import android.content.Context
 import androidx.work.WorkerParameters
 import com.squareup.moshi.JsonClass
 import com.zhuinden.monarchy.Monarchy
-import org.greenrobot.eventbus.EventBus
 import org.matrix.android.sdk.api.failure.Failure
 import org.matrix.android.sdk.api.session.pushers.PusherState
 import org.matrix.android.sdk.internal.database.mapper.toEntity
 import org.matrix.android.sdk.internal.database.model.PusherEntity
 import org.matrix.android.sdk.internal.database.query.where
 import org.matrix.android.sdk.internal.di.SessionDatabase
+import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import org.matrix.android.sdk.internal.network.executeRequest
 import org.matrix.android.sdk.internal.session.SessionComponent
 import org.matrix.android.sdk.internal.util.awaitTransaction
@@ -45,7 +45,7 @@ internal class AddHttpPusherWorker(context: Context, params: WorkerParameters)
 
     @Inject lateinit var pushersAPI: PushersAPI
     @Inject @SessionDatabase lateinit var monarchy: Monarchy
-    @Inject lateinit var eventBus: EventBus
+    @Inject lateinit var globalErrorReceiver: GlobalErrorReceiver
 
     override fun injectWith(injector: SessionComponent) {
         injector.inject(this)
@@ -81,7 +81,7 @@ internal class AddHttpPusherWorker(context: Context, params: WorkerParameters)
     }
 
     private suspend fun setPusher(pusher: JsonPusher) {
-        executeRequest<Unit>(eventBus) {
+        executeRequest<Unit>(globalErrorReceiver) {
             apiCall = pushersAPI.setPusher(pusher)
         }
         monarchy.awaitTransaction { realm ->
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/AddPushRuleTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/AddPushRuleTask.kt
index 28ac5db52e339854dcb53996013fd38913409580..03748b15283517d9ee03f653a659ed3d73d62cc2 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/AddPushRuleTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/AddPushRuleTask.kt
@@ -17,9 +17,9 @@ package org.matrix.android.sdk.internal.session.pushers
 
 import org.matrix.android.sdk.api.pushrules.RuleKind
 import org.matrix.android.sdk.api.pushrules.rest.PushRule
+import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import org.matrix.android.sdk.internal.network.executeRequest
 import org.matrix.android.sdk.internal.task.Task
-import org.greenrobot.eventbus.EventBus
 import javax.inject.Inject
 
 internal interface AddPushRuleTask : Task<AddPushRuleTask.Params, Unit> {
@@ -31,11 +31,11 @@ internal interface AddPushRuleTask : Task<AddPushRuleTask.Params, Unit> {
 
 internal class DefaultAddPushRuleTask @Inject constructor(
         private val pushRulesApi: PushRulesApi,
-        private val eventBus: EventBus
+        private val globalErrorReceiver: GlobalErrorReceiver
 ) : AddPushRuleTask {
 
     override suspend fun execute(params: AddPushRuleTask.Params) {
-        return executeRequest(eventBus) {
+        return executeRequest(globalErrorReceiver) {
             apiCall = pushRulesApi.addRule(params.kind.value, params.pushRule.ruleId, params.pushRule)
         }
     }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/GetPushRulesTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/GetPushRulesTask.kt
index 0c532cedbc5d4d71f99f2f4dbb09b90dc314f3df..9fb2d51664290bb3fbba12c385efc63990b216cb 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/GetPushRulesTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/GetPushRulesTask.kt
@@ -16,9 +16,9 @@
 package org.matrix.android.sdk.internal.session.pushers
 
 import org.matrix.android.sdk.api.pushrules.rest.GetPushRulesResponse
+import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import org.matrix.android.sdk.internal.network.executeRequest
 import org.matrix.android.sdk.internal.task.Task
-import org.greenrobot.eventbus.EventBus
 import javax.inject.Inject
 
 internal interface GetPushRulesTask : Task<GetPushRulesTask.Params, Unit> {
@@ -31,11 +31,11 @@ internal interface GetPushRulesTask : Task<GetPushRulesTask.Params, Unit> {
 internal class DefaultGetPushRulesTask @Inject constructor(
         private val pushRulesApi: PushRulesApi,
         private val savePushRulesTask: SavePushRulesTask,
-        private val eventBus: EventBus
+        private val globalErrorReceiver: GlobalErrorReceiver
 ) : GetPushRulesTask {
 
     override suspend fun execute(params: GetPushRulesTask.Params) {
-        val response = executeRequest<GetPushRulesResponse>(eventBus) {
+        val response = executeRequest<GetPushRulesResponse>(globalErrorReceiver) {
             apiCall = pushRulesApi.getAllRules()
         }
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/GetPushersTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/GetPushersTask.kt
index 39e970f4a89c2e8e51d86be702de77b9ada38089..4c7d370446ec41ed82efdf4dcefa52b45022993b 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/GetPushersTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/GetPushersTask.kt
@@ -20,10 +20,10 @@ import org.matrix.android.sdk.api.session.pushers.PusherState
 import org.matrix.android.sdk.internal.database.mapper.toEntity
 import org.matrix.android.sdk.internal.database.model.PusherEntity
 import org.matrix.android.sdk.internal.di.SessionDatabase
+import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import org.matrix.android.sdk.internal.network.executeRequest
 import org.matrix.android.sdk.internal.task.Task
 import org.matrix.android.sdk.internal.util.awaitTransaction
-import org.greenrobot.eventbus.EventBus
 import javax.inject.Inject
 
 internal interface GetPushersTask : Task<Unit, Unit>
@@ -31,11 +31,11 @@ internal interface GetPushersTask : Task<Unit, Unit>
 internal class DefaultGetPushersTask @Inject constructor(
         private val pushersAPI: PushersAPI,
         @SessionDatabase private val monarchy: Monarchy,
-        private val eventBus: EventBus
+        private val globalErrorReceiver: GlobalErrorReceiver
 ) : GetPushersTask {
 
     override suspend fun execute(params: Unit) {
-        val response = executeRequest<GetPushersResponse>(eventBus) {
+        val response = executeRequest<GetPushersResponse>(globalErrorReceiver) {
             apiCall = pushersAPI.getPushers()
         }
         monarchy.awaitTransaction { realm ->
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/RemovePushRuleTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/RemovePushRuleTask.kt
index 2fc97cf02303a8d2d7334e328e55ec734a725c07..ff3122f5665de05f80865c81284751cd44e21a95 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/RemovePushRuleTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/RemovePushRuleTask.kt
@@ -17,9 +17,9 @@ package org.matrix.android.sdk.internal.session.pushers
 
 import org.matrix.android.sdk.api.pushrules.RuleKind
 import org.matrix.android.sdk.api.pushrules.rest.PushRule
+import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import org.matrix.android.sdk.internal.network.executeRequest
 import org.matrix.android.sdk.internal.task.Task
-import org.greenrobot.eventbus.EventBus
 import javax.inject.Inject
 
 internal interface RemovePushRuleTask : Task<RemovePushRuleTask.Params, Unit> {
@@ -31,11 +31,11 @@ internal interface RemovePushRuleTask : Task<RemovePushRuleTask.Params, Unit> {
 
 internal class DefaultRemovePushRuleTask @Inject constructor(
         private val pushRulesApi: PushRulesApi,
-        private val eventBus: EventBus
+        private val globalErrorReceiver: GlobalErrorReceiver
 ) : RemovePushRuleTask {
 
     override suspend fun execute(params: RemovePushRuleTask.Params) {
-        return executeRequest(eventBus) {
+        return executeRequest(globalErrorReceiver) {
             apiCall = pushRulesApi.deleteRule(params.kind.value, params.pushRule.ruleId)
         }
     }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/RemovePusherTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/RemovePusherTask.kt
index 1f108637992e8394a9dcd6018b28e7e5cf50587b..e3f4fdb789671ff7aed078be2c4cacf0c4a3056f 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/RemovePusherTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/RemovePusherTask.kt
@@ -26,7 +26,7 @@ import org.matrix.android.sdk.internal.network.executeRequest
 import org.matrix.android.sdk.internal.task.Task
 import org.matrix.android.sdk.internal.util.awaitTransaction
 import io.realm.Realm
-import org.greenrobot.eventbus.EventBus
+import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import javax.inject.Inject
 
 internal interface RemovePusherTask : Task<RemovePusherTask.Params, Unit> {
@@ -37,7 +37,7 @@ internal interface RemovePusherTask : Task<RemovePusherTask.Params, Unit> {
 internal class DefaultRemovePusherTask @Inject constructor(
         private val pushersAPI: PushersAPI,
         @SessionDatabase private val monarchy: Monarchy,
-        private val eventBus: EventBus
+        private val globalErrorReceiver: GlobalErrorReceiver
 ) : RemovePusherTask {
 
     override suspend fun execute(params: RemovePusherTask.Params) {
@@ -62,7 +62,7 @@ internal class DefaultRemovePusherTask @Inject constructor(
                 data = JsonPusherData(existing.data.url, existing.data.format),
                 append = false
         )
-        executeRequest<Unit>(eventBus) {
+        executeRequest<Unit>(globalErrorReceiver) {
             apiCall = pushersAPI.setPusher(deleteBody)
         }
         monarchy.awaitTransaction {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/UpdatePushRuleActionsTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/UpdatePushRuleActionsTask.kt
index c2dca8a9a58f4e2f3b35bf1c38d6e914d94d4b57..a5c220e662583512910b307d1d3b8cb37ece7bd9 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/UpdatePushRuleActionsTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/UpdatePushRuleActionsTask.kt
@@ -17,9 +17,9 @@ package org.matrix.android.sdk.internal.session.pushers
 
 import org.matrix.android.sdk.api.pushrules.RuleKind
 import org.matrix.android.sdk.api.pushrules.rest.PushRule
+import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import org.matrix.android.sdk.internal.network.executeRequest
 import org.matrix.android.sdk.internal.task.Task
-import org.greenrobot.eventbus.EventBus
 import javax.inject.Inject
 
 internal interface UpdatePushRuleActionsTask : Task<UpdatePushRuleActionsTask.Params, Unit> {
@@ -32,13 +32,13 @@ internal interface UpdatePushRuleActionsTask : Task<UpdatePushRuleActionsTask.Pa
 
 internal class DefaultUpdatePushRuleActionsTask @Inject constructor(
         private val pushRulesApi: PushRulesApi,
-        private val eventBus: EventBus
+        private val globalErrorReceiver: GlobalErrorReceiver
 ) : UpdatePushRuleActionsTask {
 
     override suspend fun execute(params: UpdatePushRuleActionsTask.Params) {
         if (params.oldPushRule.enabled != params.newPushRule.enabled) {
             // First change enabled state
-            executeRequest<Unit>(eventBus) {
+            executeRequest<Unit>(globalErrorReceiver) {
                 apiCall = pushRulesApi.updateEnableRuleStatus(params.kind.value, params.newPushRule.ruleId, params.newPushRule.enabled)
             }
         }
@@ -47,7 +47,7 @@ internal class DefaultUpdatePushRuleActionsTask @Inject constructor(
             // Also ensure the actions are up to date
             val body = mapOf("actions" to params.newPushRule.actions)
 
-            executeRequest<Unit>(eventBus) {
+            executeRequest<Unit>(globalErrorReceiver) {
                 apiCall = pushRulesApi.updateRuleActions(params.kind.value, params.newPushRule.ruleId, body)
             }
         }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/UpdatePushRuleEnableStatusTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/UpdatePushRuleEnableStatusTask.kt
index 4100071c902431b36a6dbbef91165a0bf6f42054..f36b5c55fb1933a4cc4a400a4aadd822ce78d2a9 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/UpdatePushRuleEnableStatusTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/UpdatePushRuleEnableStatusTask.kt
@@ -17,9 +17,9 @@ package org.matrix.android.sdk.internal.session.pushers
 
 import org.matrix.android.sdk.api.pushrules.RuleKind
 import org.matrix.android.sdk.api.pushrules.rest.PushRule
+import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import org.matrix.android.sdk.internal.network.executeRequest
 import org.matrix.android.sdk.internal.task.Task
-import org.greenrobot.eventbus.EventBus
 import javax.inject.Inject
 
 internal interface UpdatePushRuleEnableStatusTask : Task<UpdatePushRuleEnableStatusTask.Params, Unit> {
@@ -30,11 +30,11 @@ internal interface UpdatePushRuleEnableStatusTask : Task<UpdatePushRuleEnableSta
 
 internal class DefaultUpdatePushRuleEnableStatusTask @Inject constructor(
         private val pushRulesApi: PushRulesApi,
-        private val eventBus: EventBus
+        private val globalErrorReceiver: GlobalErrorReceiver
 ) : UpdatePushRuleEnableStatusTask {
 
     override suspend fun execute(params: UpdatePushRuleEnableStatusTask.Params) {
-        return executeRequest(eventBus) {
+        return executeRequest(globalErrorReceiver) {
             apiCall = pushRulesApi.updateEnableRuleStatus(params.kind.value, params.pushRule.ruleId, params.enabled)
         }
     }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/AddRoomAliasTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/AddRoomAliasTask.kt
index 9793750fa096d343dd232b535583bc6d182d5e15..9e4ec6f77779258ba3c550ce2f3795af69f68c24 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/AddRoomAliasTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/AddRoomAliasTask.kt
@@ -16,8 +16,8 @@
 
 package org.matrix.android.sdk.internal.session.room.alias
 
-import org.greenrobot.eventbus.EventBus
 import org.matrix.android.sdk.internal.di.UserId
+import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import org.matrix.android.sdk.internal.network.executeRequest
 import org.matrix.android.sdk.internal.session.directory.DirectoryAPI
 import org.matrix.android.sdk.internal.session.room.alias.RoomAliasAvailabilityChecker.Companion.toFullLocalAlias
@@ -39,13 +39,13 @@ internal class DefaultAddRoomAliasTask @Inject constructor(
         @UserId private val userId: String,
         private val directoryAPI: DirectoryAPI,
         private val aliasAvailabilityChecker: RoomAliasAvailabilityChecker,
-        private val eventBus: EventBus
+        private val globalErrorReceiver: GlobalErrorReceiver
 ) : AddRoomAliasTask {
 
     override suspend fun execute(params: AddRoomAliasTask.Params) {
         aliasAvailabilityChecker.check(params.aliasLocalPart)
 
-        executeRequest<Unit>(eventBus) {
+        executeRequest<Unit>(globalErrorReceiver) {
             apiCall = directoryAPI.addRoomAlias(
                     roomAlias = params.aliasLocalPart.toFullLocalAlias(userId),
                     body = AddRoomAliasBody(
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/DefaultAliasService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/DefaultAliasService.kt
index b6c69224e65dc8e16e988d5c2eed67953cc95ea9..8f58094a2af675c7a5a19c7365de194810db5d57 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/DefaultAliasService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/DefaultAliasService.kt
@@ -16,8 +16,9 @@
 
 package org.matrix.android.sdk.internal.session.room.alias
 
-import com.squareup.inject.assisted.Assisted
-import com.squareup.inject.assisted.AssistedInject
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedInject
+import dagger.assisted.AssistedFactory
 import org.matrix.android.sdk.api.session.room.alias.AliasService
 
 internal class DefaultAliasService @AssistedInject constructor(
@@ -26,9 +27,9 @@ internal class DefaultAliasService @AssistedInject constructor(
         private val addRoomAliasTask: AddRoomAliasTask
 ) : AliasService {
 
-    @AssistedInject.Factory
+    @AssistedFactory
     interface Factory {
-        fun create(roomId: String): AliasService
+        fun create(roomId: String): DefaultAliasService
     }
 
     override suspend fun getRoomAliases(): List<String> {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/DeleteRoomAliasTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/DeleteRoomAliasTask.kt
index 3400fd994ca79bc981726c72794b6ffd7db3aa65..6ad3db90a92f42be824ee8c53235a022545bb93f 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/DeleteRoomAliasTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/DeleteRoomAliasTask.kt
@@ -16,7 +16,7 @@
 
 package org.matrix.android.sdk.internal.session.room.alias
 
-import org.greenrobot.eventbus.EventBus
+import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import org.matrix.android.sdk.internal.network.executeRequest
 import org.matrix.android.sdk.internal.session.directory.DirectoryAPI
 import org.matrix.android.sdk.internal.task.Task
@@ -30,11 +30,11 @@ internal interface DeleteRoomAliasTask : Task<DeleteRoomAliasTask.Params, Unit>
 
 internal class DefaultDeleteRoomAliasTask @Inject constructor(
         private val directoryAPI: DirectoryAPI,
-        private val eventBus: EventBus
+        private val globalErrorReceiver: GlobalErrorReceiver
 ) : DeleteRoomAliasTask {
 
     override suspend fun execute(params: DeleteRoomAliasTask.Params) {
-        executeRequest<Unit>(eventBus) {
+        executeRequest<Unit>(globalErrorReceiver) {
             apiCall = directoryAPI.deleteRoomAlias(
                     roomAlias = params.roomAlias
             )
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/GetRoomIdByAliasTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/GetRoomIdByAliasTask.kt
index 543d60570730009cbe5f23a95faef0494024de3d..a53ffc4fcdaba71d05cf0ec0bfb0519bacbd63e0 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/GetRoomIdByAliasTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/GetRoomIdByAliasTask.kt
@@ -18,12 +18,12 @@ package org.matrix.android.sdk.internal.session.room.alias
 
 import com.zhuinden.monarchy.Monarchy
 import io.realm.Realm
-import org.greenrobot.eventbus.EventBus
 import org.matrix.android.sdk.api.extensions.tryOrNull
 import org.matrix.android.sdk.api.util.Optional
 import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity
 import org.matrix.android.sdk.internal.database.query.findByAlias
 import org.matrix.android.sdk.internal.di.SessionDatabase
+import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import org.matrix.android.sdk.internal.network.executeRequest
 import org.matrix.android.sdk.internal.session.directory.DirectoryAPI
 import org.matrix.android.sdk.internal.task.Task
@@ -39,7 +39,7 @@ internal interface GetRoomIdByAliasTask : Task<GetRoomIdByAliasTask.Params, Opti
 internal class DefaultGetRoomIdByAliasTask @Inject constructor(
         @SessionDatabase private val monarchy: Monarchy,
         private val directoryAPI: DirectoryAPI,
-        private val eventBus: EventBus
+        private val globalErrorReceiver: GlobalErrorReceiver
 ) : GetRoomIdByAliasTask {
 
     override suspend fun execute(params: GetRoomIdByAliasTask.Params): Optional<RoomAliasDescription> {
@@ -52,7 +52,7 @@ internal class DefaultGetRoomIdByAliasTask @Inject constructor(
             Optional.from(null)
         } else {
             val description  = tryOrNull("## Failed to get roomId from alias") {
-                executeRequest<RoomAliasDescription>(eventBus) {
+                executeRequest<RoomAliasDescription>(globalErrorReceiver) {
                     apiCall = directoryAPI.getRoomIdByAlias(params.roomAlias)
                 }
             }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/GetRoomLocalAliasesTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/GetRoomLocalAliasesTask.kt
index 7cfce4ecdcd421d42fc7c68cfeda1734c4cb0ffc..202cb1f6de9ea47249e56d0b557d9441ba44dc91 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/GetRoomLocalAliasesTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/GetRoomLocalAliasesTask.kt
@@ -16,7 +16,7 @@
 
 package org.matrix.android.sdk.internal.session.room.alias
 
-import org.greenrobot.eventbus.EventBus
+import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import org.matrix.android.sdk.internal.network.executeRequest
 import org.matrix.android.sdk.internal.session.room.RoomAPI
 import org.matrix.android.sdk.internal.task.Task
@@ -30,12 +30,12 @@ internal interface GetRoomLocalAliasesTask : Task<GetRoomLocalAliasesTask.Params
 
 internal class DefaultGetRoomLocalAliasesTask @Inject constructor(
         private val roomAPI: RoomAPI,
-        private val eventBus: EventBus
+        private val globalErrorReceiver: GlobalErrorReceiver
 ) : GetRoomLocalAliasesTask {
 
     override suspend fun execute(params: GetRoomLocalAliasesTask.Params): List<String> {
         // We do not check for "org.matrix.msc2432", so the API may be missing
-        val response = executeRequest<GetAliasesResponse>(eventBus) {
+        val response = executeRequest<GetAliasesResponse>(globalErrorReceiver) {
             apiCall = roomAPI.getAliases(roomId = params.roomId)
         }
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/RoomAliasAvailabilityChecker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/RoomAliasAvailabilityChecker.kt
index 25ba493891418f86f82b772ce6847ec244ef008f..51a849a35e9f9a190284e09a264cca16ac64da70 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/RoomAliasAvailabilityChecker.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/RoomAliasAvailabilityChecker.kt
@@ -16,10 +16,10 @@
 
 package org.matrix.android.sdk.internal.session.room.alias
 
-import org.greenrobot.eventbus.EventBus
 import org.matrix.android.sdk.api.failure.Failure
 import org.matrix.android.sdk.api.session.room.alias.RoomAliasError
 import org.matrix.android.sdk.internal.di.UserId
+import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import org.matrix.android.sdk.internal.network.executeRequest
 import org.matrix.android.sdk.internal.session.directory.DirectoryAPI
 import javax.inject.Inject
@@ -27,7 +27,7 @@ import javax.inject.Inject
 internal class RoomAliasAvailabilityChecker @Inject constructor(
         @UserId private val userId: String,
         private val directoryAPI: DirectoryAPI,
-        private val eventBus: EventBus
+        private val globalErrorReceiver: GlobalErrorReceiver
 ) {
     /**
      * @param aliasLocalPart the local part of the alias.
@@ -41,7 +41,7 @@ internal class RoomAliasAvailabilityChecker @Inject constructor(
         // Check alias availability
         val fullAlias = aliasLocalPart.toFullLocalAlias(userId)
         try {
-            executeRequest<RoomAliasDescription>(eventBus) {
+            executeRequest<RoomAliasDescription>(globalErrorReceiver) {
                 apiCall = directoryAPI.getRoomIdByAlias(fullAlias)
             }
         } catch (throwable: Throwable) {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/call/DefaultRoomCallService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/call/DefaultRoomCallService.kt
index 205a085df6c27ea4593d2a4c2e879157138bbfde..9bde5054f6c24f65fb21b823322ed42e1df1d5a6 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/call/DefaultRoomCallService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/call/DefaultRoomCallService.kt
@@ -16,8 +16,9 @@
 
 package org.matrix.android.sdk.internal.session.room.call
 
-import com.squareup.inject.assisted.Assisted
-import com.squareup.inject.assisted.AssistedInject
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedInject
+import dagger.assisted.AssistedFactory
 import org.matrix.android.sdk.api.extensions.orFalse
 import org.matrix.android.sdk.api.session.room.call.RoomCallService
 import org.matrix.android.sdk.internal.session.room.RoomGetter
@@ -27,9 +28,9 @@ internal class DefaultRoomCallService @AssistedInject constructor(
         private val roomGetter: RoomGetter
 ) : RoomCallService {
 
-    @AssistedInject.Factory
+    @AssistedFactory
     interface Factory {
-        fun create(roomId: String): RoomCallService
+        fun create(roomId: String): DefaultRoomCallService
     }
 
     override fun canStartCall(): Boolean {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomBodyBuilder.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomBodyBuilder.kt
index fb840b4eb3f23793db212150e034f8e9f4626748..5e823fc87fe015d89c0eadb0f980c64c77a042c5 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomBodyBuilder.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomBodyBuilder.kt
@@ -143,9 +143,11 @@ internal class CreateRoomBodyBuilder @Inject constructor(
     }
 
     private suspend fun canEnableEncryption(params: CreateRoomParams): Boolean {
-        return (params.enableEncryptionIfInvitedUsersSupportIt
-                && crossSigningService.isCrossSigningVerified()
-                && params.invite3pids.isEmpty())
+        return params.enableEncryptionIfInvitedUsersSupportIt
+                // Parity with web, enable if users have encryption ready devices
+                // for now remove checks on cross signing and 3pid invites
+                // && crossSigningService.isCrossSigningVerified()
+                && params.invite3pids.isEmpty()
                 && params.invitedUserIds.isNotEmpty()
                 && params.invitedUserIds.let { userIds ->
             val keys = deviceListManager.downloadKeys(userIds, forceDownload = false)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomTask.kt
index ef792ab98e5e57f721d449f9e382245003b51824..9c16bd1b0f2214ec1d56a9f3eb448145fa6f3b78 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomTask.kt
@@ -19,7 +19,6 @@ package org.matrix.android.sdk.internal.session.room.create
 import com.zhuinden.monarchy.Monarchy
 import io.realm.RealmConfiguration
 import kotlinx.coroutines.TimeoutCancellationException
-import org.greenrobot.eventbus.EventBus
 import org.matrix.android.sdk.api.failure.Failure
 import org.matrix.android.sdk.api.failure.MatrixError
 import org.matrix.android.sdk.api.session.room.alias.RoomAliasError
@@ -32,6 +31,7 @@ import org.matrix.android.sdk.internal.database.model.RoomEntityFields
 import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity
 import org.matrix.android.sdk.internal.database.query.where
 import org.matrix.android.sdk.internal.di.SessionDatabase
+import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import org.matrix.android.sdk.internal.network.executeRequest
 import org.matrix.android.sdk.internal.session.room.RoomAPI
 import org.matrix.android.sdk.internal.session.room.alias.RoomAliasAvailabilityChecker
@@ -55,7 +55,7 @@ internal class DefaultCreateRoomTask @Inject constructor(
         @SessionDatabase
         private val realmConfiguration: RealmConfiguration,
         private val createRoomBodyBuilder: CreateRoomBodyBuilder,
-        private val eventBus: EventBus
+        private val globalErrorReceiver: GlobalErrorReceiver
 ) : CreateRoomTask {
 
     override suspend fun execute(params: CreateRoomParams): String {
@@ -75,7 +75,7 @@ internal class DefaultCreateRoomTask @Inject constructor(
         val createRoomBody = createRoomBodyBuilder.build(params)
 
         val createRoomResponse = try {
-            executeRequest<CreateRoomResponse>(eventBus) {
+            executeRequest<CreateRoomResponse>(globalErrorReceiver) {
                 apiCall = roomAPI.createRoom(createRoomBody)
             }
         } catch (throwable: Throwable) {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/directory/GetPublicRoomTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/directory/GetPublicRoomTask.kt
index f2bd0c5f69c562aaba34a496bad0d027bd83a45e..edd8ae9b0dfe9a0531f5ee8179604502ffb0f1e0 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/directory/GetPublicRoomTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/directory/GetPublicRoomTask.kt
@@ -18,10 +18,10 @@ package org.matrix.android.sdk.internal.session.room.directory
 
 import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoomsParams
 import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoomsResponse
+import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import org.matrix.android.sdk.internal.network.executeRequest
 import org.matrix.android.sdk.internal.session.room.RoomAPI
 import org.matrix.android.sdk.internal.task.Task
-import org.greenrobot.eventbus.EventBus
 import javax.inject.Inject
 
 internal interface GetPublicRoomTask : Task<GetPublicRoomTask.Params, PublicRoomsResponse> {
@@ -33,11 +33,11 @@ internal interface GetPublicRoomTask : Task<GetPublicRoomTask.Params, PublicRoom
 
 internal class DefaultGetPublicRoomTask @Inject constructor(
         private val roomAPI: RoomAPI,
-        private val eventBus: EventBus
+        private val globalErrorReceiver: GlobalErrorReceiver
 ) : GetPublicRoomTask {
 
     override suspend fun execute(params: GetPublicRoomTask.Params): PublicRoomsResponse {
-        return executeRequest(eventBus) {
+        return executeRequest(globalErrorReceiver) {
             apiCall = roomAPI.publicRooms(params.server, params.publicRoomsParams)
         }
     }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/directory/GetRoomDirectoryVisibilityTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/directory/GetRoomDirectoryVisibilityTask.kt
index fbdd6a03ebad946b967304bd9b50af71393e8a67..8d71001ef958345e5e8fe97703a8536111a61bbc 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/directory/GetRoomDirectoryVisibilityTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/directory/GetRoomDirectoryVisibilityTask.kt
@@ -16,8 +16,8 @@
 
 package org.matrix.android.sdk.internal.session.room.directory
 
-import org.greenrobot.eventbus.EventBus
 import org.matrix.android.sdk.api.session.room.model.RoomDirectoryVisibility
+import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import org.matrix.android.sdk.internal.network.executeRequest
 import org.matrix.android.sdk.internal.session.directory.DirectoryAPI
 import org.matrix.android.sdk.internal.session.directory.RoomDirectoryVisibilityJson
@@ -32,11 +32,11 @@ internal interface GetRoomDirectoryVisibilityTask : Task<GetRoomDirectoryVisibil
 
 internal class DefaultGetRoomDirectoryVisibilityTask @Inject constructor(
         private val directoryAPI: DirectoryAPI,
-        private val eventBus: EventBus
+        private val globalErrorReceiver: GlobalErrorReceiver
 ) : GetRoomDirectoryVisibilityTask {
 
     override suspend fun execute(params: GetRoomDirectoryVisibilityTask.Params): RoomDirectoryVisibility {
-        return executeRequest<RoomDirectoryVisibilityJson>(eventBus) {
+        return executeRequest<RoomDirectoryVisibilityJson>(globalErrorReceiver) {
             apiCall = directoryAPI.getRoomDirectoryVisibility(params.roomId)
         }
                 .visibility
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/directory/GetThirdPartyProtocolsTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/directory/GetThirdPartyProtocolsTask.kt
index 5e08284706b4acd596a8ecae061cde7e24b99916..3477aa671ea6ba1283a598e31685e6ca2070a72c 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/directory/GetThirdPartyProtocolsTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/directory/GetThirdPartyProtocolsTask.kt
@@ -17,21 +17,21 @@
 package org.matrix.android.sdk.internal.session.room.directory
 
 import org.matrix.android.sdk.api.session.room.model.thirdparty.ThirdPartyProtocol
+import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import org.matrix.android.sdk.internal.network.executeRequest
 import org.matrix.android.sdk.internal.session.room.RoomAPI
 import org.matrix.android.sdk.internal.task.Task
-import org.greenrobot.eventbus.EventBus
 import javax.inject.Inject
 
 internal interface GetThirdPartyProtocolsTask : Task<Unit, Map<String, ThirdPartyProtocol>>
 
 internal class DefaultGetThirdPartyProtocolsTask @Inject constructor(
         private val roomAPI: RoomAPI,
-        private val eventBus: EventBus
+        private val globalErrorReceiver: GlobalErrorReceiver
 ) : GetThirdPartyProtocolsTask {
 
     override suspend fun execute(params: Unit): Map<String, ThirdPartyProtocol> {
-        return executeRequest(eventBus) {
+        return executeRequest(globalErrorReceiver) {
             apiCall = roomAPI.thirdPartyProtocols()
         }
     }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/directory/SetRoomDirectoryVisibilityTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/directory/SetRoomDirectoryVisibilityTask.kt
index 33b12aa1ca025d6ef8c10566988902e675a6fdcf..cbb0b6d5d1b85259fe308ad6652823fa51933316 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/directory/SetRoomDirectoryVisibilityTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/directory/SetRoomDirectoryVisibilityTask.kt
@@ -16,8 +16,8 @@
 
 package org.matrix.android.sdk.internal.session.room.directory
 
-import org.greenrobot.eventbus.EventBus
 import org.matrix.android.sdk.api.session.room.model.RoomDirectoryVisibility
+import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import org.matrix.android.sdk.internal.network.executeRequest
 import org.matrix.android.sdk.internal.session.directory.DirectoryAPI
 import org.matrix.android.sdk.internal.session.directory.RoomDirectoryVisibilityJson
@@ -33,11 +33,11 @@ internal interface SetRoomDirectoryVisibilityTask : Task<SetRoomDirectoryVisibil
 
 internal class DefaultSetRoomDirectoryVisibilityTask @Inject constructor(
         private val directoryAPI: DirectoryAPI,
-        private val eventBus: EventBus
+        private val globalErrorReceiver: GlobalErrorReceiver
 ) : SetRoomDirectoryVisibilityTask {
 
     override suspend fun execute(params: SetRoomDirectoryVisibilityTask.Params) {
-        executeRequest<Unit>(eventBus) {
+        executeRequest<Unit>(globalErrorReceiver) {
             apiCall = directoryAPI.setRoomDirectoryVisibility(
                     params.roomId,
                     RoomDirectoryVisibilityJson(visibility = params.roomDirectoryVisibility)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/draft/DefaultDraftService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/draft/DefaultDraftService.kt
index 93fbfb4df0c4fc967d8ba1a46dae51f7271ea295..1d4ab6d516c3d7345c27a480c2f5b4345444679d 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/draft/DefaultDraftService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/draft/DefaultDraftService.kt
@@ -17,8 +17,9 @@
 package org.matrix.android.sdk.internal.session.room.draft
 
 import androidx.lifecycle.LiveData
-import com.squareup.inject.assisted.Assisted
-import com.squareup.inject.assisted.AssistedInject
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedInject
+import dagger.assisted.AssistedFactory
 import kotlinx.coroutines.withContext
 import org.matrix.android.sdk.api.session.room.send.DraftService
 import org.matrix.android.sdk.api.session.room.send.UserDraft
@@ -30,9 +31,9 @@ internal class DefaultDraftService @AssistedInject constructor(@Assisted private
                                                                private val coroutineDispatchers: MatrixCoroutineDispatchers
 ) : DraftService {
 
-    @AssistedInject.Factory
+    @AssistedFactory
     interface Factory {
-        fun create(roomId: String): DraftService
+        fun create(roomId: String): DefaultDraftService
     }
 
     /**
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/DefaultMembershipService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/DefaultMembershipService.kt
index 4fc865304b27a2c0c4fd6583b24b33780e0eaa6e..cd1c9bbbdd8e56a7f32ec0eb26cc24956320bc5a 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/DefaultMembershipService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/DefaultMembershipService.kt
@@ -17,8 +17,9 @@
 package org.matrix.android.sdk.internal.session.room.membership
 
 import androidx.lifecycle.LiveData
-import com.squareup.inject.assisted.Assisted
-import com.squareup.inject.assisted.AssistedInject
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedInject
+import dagger.assisted.AssistedFactory
 import com.zhuinden.monarchy.Monarchy
 import org.matrix.android.sdk.api.MatrixCallback
 import org.matrix.android.sdk.api.session.identity.ThreePid
@@ -58,9 +59,9 @@ internal class DefaultMembershipService @AssistedInject constructor(
         private val userId: String
 ) : MembershipService {
 
-    @AssistedInject.Factory
+    @AssistedFactory
     interface Factory {
-        fun create(roomId: String): MembershipService
+        fun create(roomId: String): DefaultMembershipService
     }
 
     override fun loadRoomMembersIfNeeded(matrixCallback: MatrixCallback<Unit>): Cancelable {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/LoadRoomMembersTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/LoadRoomMembersTask.kt
index 627f927ad88e1965f783ab3d4e98146736075ad1..2be90bf8e3aa21618acc985d9e031c9787182343 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/LoadRoomMembersTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/LoadRoomMembersTask.kt
@@ -17,25 +17,30 @@
 package org.matrix.android.sdk.internal.session.room.membership
 
 import com.zhuinden.monarchy.Monarchy
+import io.realm.Realm
+import io.realm.kotlin.createObject
+import kotlinx.coroutines.TimeoutCancellationException
 import org.matrix.android.sdk.api.session.room.model.Membership
 import org.matrix.android.sdk.api.session.room.send.SendState
+import org.matrix.android.sdk.internal.database.awaitNotEmptyResult
 import org.matrix.android.sdk.internal.database.mapper.toEntity
 import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntity
 import org.matrix.android.sdk.internal.database.model.EventInsertType
 import org.matrix.android.sdk.internal.database.model.RoomEntity
+import org.matrix.android.sdk.internal.database.model.RoomEntityFields
+import org.matrix.android.sdk.internal.database.model.RoomMembersLoadStatusType
 import org.matrix.android.sdk.internal.database.query.copyToRealmOrIgnore
 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.SessionDatabase
+import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import org.matrix.android.sdk.internal.network.executeRequest
 import org.matrix.android.sdk.internal.session.room.RoomAPI
 import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryUpdater
 import org.matrix.android.sdk.internal.session.sync.SyncTokenStore
 import org.matrix.android.sdk.internal.task.Task
 import org.matrix.android.sdk.internal.util.awaitTransaction
-import io.realm.Realm
-import io.realm.kotlin.createObject
-import org.greenrobot.eventbus.EventBus
+import java.util.concurrent.TimeUnit
 import javax.inject.Inject
 
 internal interface LoadRoomMembersTask : Task<LoadRoomMembersTask.Params, Unit> {
@@ -52,17 +57,44 @@ internal class DefaultLoadRoomMembersTask @Inject constructor(
         private val syncTokenStore: SyncTokenStore,
         private val roomSummaryUpdater: RoomSummaryUpdater,
         private val roomMemberEventHandler: RoomMemberEventHandler,
-        private val eventBus: EventBus
+        private val globalErrorReceiver: GlobalErrorReceiver
 ) : LoadRoomMembersTask {
 
     override suspend fun execute(params: LoadRoomMembersTask.Params) {
-        if (areAllMembersAlreadyLoaded(params.roomId)) {
-            return
+        when (getRoomMembersLoadStatus(params.roomId)) {
+            RoomMembersLoadStatusType.NONE    -> doRequest(params)
+            RoomMembersLoadStatusType.LOADING -> waitPreviousRequestToFinish(params)
+            RoomMembersLoadStatusType.LOADED  -> Unit
+        }
+    }
+
+    private suspend fun waitPreviousRequestToFinish(params: LoadRoomMembersTask.Params) {
+        try {
+            awaitNotEmptyResult(monarchy.realmConfiguration, TimeUnit.MINUTES.toMillis(1L)) { realm ->
+                realm.where(RoomEntity::class.java)
+                        .equalTo(RoomEntityFields.ROOM_ID, params.roomId)
+                        .equalTo(RoomEntityFields.MEMBERS_LOAD_STATUS_STR, RoomMembersLoadStatusType.LOADED.name)
+            }
+        } catch (exception: TimeoutCancellationException) {
+            // Timeout, do the request anyway (?)
+            doRequest(params)
         }
+    }
+
+    private suspend fun doRequest(params: LoadRoomMembersTask.Params) {
+        setRoomMembersLoadStatus(params.roomId, RoomMembersLoadStatusType.LOADING)
+
         val lastToken = syncTokenStore.getLastToken()
-        val response = executeRequest<RoomMembersResponse>(eventBus) {
-            apiCall = roomAPI.getMembers(params.roomId, lastToken, null, params.excludeMembership?.value)
+        val response = try {
+            executeRequest<RoomMembersResponse>(globalErrorReceiver) {
+                apiCall = roomAPI.getMembers(params.roomId, lastToken, null, params.excludeMembership?.value)
+            }
+        } catch (throwable: Throwable) {
+            // Revert status to NONE
+            setRoomMembersLoadStatus(params.roomId, RoomMembersLoadStatusType.NONE)
+            throw throwable
         }
+        // This will also set the status to LOADED
         insertInDb(response, params.roomId)
     }
 
@@ -84,14 +116,23 @@ internal class DefaultLoadRoomMembersTask @Inject constructor(
                 }
                 roomMemberEventHandler.handle(realm, roomId, roomMemberEvent)
             }
-            roomEntity.areAllMembersLoaded = true
+            roomEntity.membersLoadStatus = RoomMembersLoadStatusType.LOADED
             roomSummaryUpdater.update(realm, roomId, updateMembers = true)
         }
     }
 
-    private fun areAllMembersAlreadyLoaded(roomId: String): Boolean {
-        return Realm.getInstance(monarchy.realmConfiguration).use {
-            RoomEntity.where(it, roomId).findFirst()?.areAllMembersLoaded ?: false
+    private fun getRoomMembersLoadStatus(roomId: String): RoomMembersLoadStatusType {
+        var result: RoomMembersLoadStatusType?
+        Realm.getInstance(monarchy.realmConfiguration).use {
+            result = RoomEntity.where(it, roomId).findFirst()?.membersLoadStatus
+        }
+        return result ?: RoomMembersLoadStatusType.NONE
+    }
+
+    private suspend fun setRoomMembersLoadStatus(roomId: String, status: RoomMembersLoadStatusType) {
+        monarchy.awaitTransaction { realm ->
+            val roomEntity = RoomEntity.where(realm, roomId).findFirst() ?: realm.createObject(roomId)
+            roomEntity.membersLoadStatus = status
         }
     }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/joining/InviteTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/joining/InviteTask.kt
index 854a332679306a273615402b9be92c0e8edc6d0d..05503bd6436742c551a6991463b1fd0b98fc51d5 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/joining/InviteTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/joining/InviteTask.kt
@@ -16,10 +16,10 @@
 
 package org.matrix.android.sdk.internal.session.room.membership.joining
 
+import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import org.matrix.android.sdk.internal.network.executeRequest
 import org.matrix.android.sdk.internal.session.room.RoomAPI
 import org.matrix.android.sdk.internal.task.Task
-import org.greenrobot.eventbus.EventBus
 import javax.inject.Inject
 
 internal interface InviteTask : Task<InviteTask.Params, Unit> {
@@ -32,11 +32,11 @@ internal interface InviteTask : Task<InviteTask.Params, Unit> {
 
 internal class DefaultInviteTask @Inject constructor(
         private val roomAPI: RoomAPI,
-        private val eventBus: EventBus
+        private val globalErrorReceiver: GlobalErrorReceiver
 ) : InviteTask {
 
     override suspend fun execute(params: InviteTask.Params) {
-        return executeRequest(eventBus) {
+        return executeRequest(globalErrorReceiver) {
             val body = InviteBody(params.userId, params.reason)
             apiCall = roomAPI.invite(params.roomId, body)
             isRetryable = true
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/joining/JoinRoomTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/joining/JoinRoomTask.kt
index dd1dc5fa8a6bc5bc60673631d2eb470ca5a0a212..3b7639d42fa28b0fd4f9693451a8e23144c598f8 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/joining/JoinRoomTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/joining/JoinRoomTask.kt
@@ -30,7 +30,7 @@ import org.matrix.android.sdk.internal.session.room.read.SetReadMarkersTask
 import org.matrix.android.sdk.internal.task.Task
 import io.realm.RealmConfiguration
 import kotlinx.coroutines.TimeoutCancellationException
-import org.greenrobot.eventbus.EventBus
+import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import java.util.concurrent.TimeUnit
 import javax.inject.Inject
 
@@ -48,14 +48,18 @@ internal class DefaultJoinRoomTask @Inject constructor(
         @SessionDatabase
         private val realmConfiguration: RealmConfiguration,
         private val roomChangeMembershipStateDataSource: RoomChangeMembershipStateDataSource,
-        private val eventBus: EventBus
+        private val globalErrorReceiver: GlobalErrorReceiver
 ) : JoinRoomTask {
 
     override suspend fun execute(params: JoinRoomTask.Params) {
         roomChangeMembershipStateDataSource.updateState(params.roomIdOrAlias, ChangeMembershipState.Joining)
         val joinRoomResponse = try {
-            executeRequest<JoinRoomResponse>(eventBus) {
-                apiCall = roomAPI.join(params.roomIdOrAlias, params.viaServers, mapOf("reason" to params.reason))
+            executeRequest<JoinRoomResponse>(globalErrorReceiver) {
+                apiCall = roomAPI.join(
+                        roomIdOrAlias = params.roomIdOrAlias,
+                        viaServers = params.viaServers.take(3),
+                        params = mapOf("reason" to params.reason)
+                )
             }
         } catch (failure: Throwable) {
             roomChangeMembershipStateDataSource.updateState(params.roomIdOrAlias, ChangeMembershipState.FailedJoining(failure))
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/leaving/LeaveRoomTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/leaving/LeaveRoomTask.kt
index 58e34a15ecb12009052ed735398f72377faab4ae..37bb7570d17d599ab2e984ed4da88b343a1647c4 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/leaving/LeaveRoomTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/leaving/LeaveRoomTask.kt
@@ -21,13 +21,13 @@ import org.matrix.android.sdk.api.session.events.model.EventType
 import org.matrix.android.sdk.api.session.events.model.toModel
 import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState
 import org.matrix.android.sdk.api.session.room.model.create.RoomCreateContent
+import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import org.matrix.android.sdk.internal.network.executeRequest
 import org.matrix.android.sdk.internal.session.room.RoomAPI
 import org.matrix.android.sdk.internal.session.room.membership.RoomChangeMembershipStateDataSource
 import org.matrix.android.sdk.internal.session.room.state.StateEventDataSource
 import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryDataSource
 import org.matrix.android.sdk.internal.task.Task
-import org.greenrobot.eventbus.EventBus
 import timber.log.Timber
 import javax.inject.Inject
 
@@ -40,7 +40,7 @@ internal interface LeaveRoomTask : Task<LeaveRoomTask.Params, Unit> {
 
 internal class DefaultLeaveRoomTask @Inject constructor(
         private val roomAPI: RoomAPI,
-        private val eventBus: EventBus,
+        private val globalErrorReceiver: GlobalErrorReceiver,
         private val stateEventDataSource: StateEventDataSource,
         private val roomSummaryDataSource: RoomSummaryDataSource,
         private val roomChangeMembershipStateDataSource: RoomChangeMembershipStateDataSource
@@ -68,7 +68,7 @@ internal class DefaultLeaveRoomTask @Inject constructor(
             leaveRoom(predecessorRoomId, reason)
         }
         try {
-            executeRequest<Unit>(eventBus) {
+            executeRequest<Unit>(globalErrorReceiver) {
                 apiCall = roomAPI.leave(roomId, mapOf("reason" to reason))
             }
         } catch (failure: Throwable) {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/threepid/InviteThreePidTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/threepid/InviteThreePidTask.kt
index 80af00fc7863f6b56447b4cb4fa9e8166b0d4e7f..d237ec795e2334b2ad92c8c75e2dbe35ba57bbb6 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/threepid/InviteThreePidTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/threepid/InviteThreePidTask.kt
@@ -16,11 +16,11 @@
 
 package org.matrix.android.sdk.internal.session.room.membership.threepid
 
-import org.greenrobot.eventbus.EventBus
 import org.matrix.android.sdk.api.session.identity.IdentityServiceError
 import org.matrix.android.sdk.api.session.identity.ThreePid
 import org.matrix.android.sdk.api.session.identity.toMedium
 import org.matrix.android.sdk.internal.di.AuthenticatedIdentity
+import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import org.matrix.android.sdk.internal.network.executeRequest
 import org.matrix.android.sdk.internal.network.token.AccessTokenProvider
 import org.matrix.android.sdk.internal.session.identity.EnsureIdentityTokenTask
@@ -39,7 +39,7 @@ internal interface InviteThreePidTask : Task<InviteThreePidTask.Params, Unit> {
 
 internal class DefaultInviteThreePidTask @Inject constructor(
         private val roomAPI: RoomAPI,
-        private val eventBus: EventBus,
+        private val globalErrorReceiver: GlobalErrorReceiver,
         private val identityStore: IdentityStore,
         private val ensureIdentityTokenTask: EnsureIdentityTokenTask,
         @AuthenticatedIdentity
@@ -52,7 +52,7 @@ internal class DefaultInviteThreePidTask @Inject constructor(
         val identityServerUrlWithoutProtocol = identityStore.getIdentityServerUrlWithoutProtocol() ?: throw IdentityServiceError.NoIdentityServerConfigured
         val identityServerAccessToken = accessTokenProvider.getToken() ?: throw IdentityServiceError.NoIdentityServerConfigured
 
-        return executeRequest(eventBus) {
+        return executeRequest(globalErrorReceiver) {
             val body = ThreePidInviteBody(
                     idServer = identityServerUrlWithoutProtocol,
                     idAccessToken = identityServerAccessToken,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/notification/DefaultRoomPushRuleService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/notification/DefaultRoomPushRuleService.kt
index 67ae55c066e28b59d4fff26d8691928f20a0b94f..5486d96e2898f8fe216c9bb5c89d91e4ec4153cc 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/notification/DefaultRoomPushRuleService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/notification/DefaultRoomPushRuleService.kt
@@ -18,8 +18,9 @@ package org.matrix.android.sdk.internal.session.room.notification
 
 import androidx.lifecycle.LiveData
 import androidx.lifecycle.Transformations
-import com.squareup.inject.assisted.Assisted
-import com.squareup.inject.assisted.AssistedInject
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedInject
+import dagger.assisted.AssistedFactory
 import com.zhuinden.monarchy.Monarchy
 import org.matrix.android.sdk.api.pushrules.RuleScope
 import org.matrix.android.sdk.api.session.room.notification.RoomNotificationState
@@ -33,9 +34,9 @@ internal class DefaultRoomPushRuleService @AssistedInject constructor(@Assisted
                                                                       @SessionDatabase private val monarchy: Monarchy)
     : RoomPushRuleService {
 
-    @AssistedInject.Factory
+    @AssistedFactory
     interface Factory {
-        fun create(roomId: String): RoomPushRuleService
+        fun create(roomId: String): DefaultRoomPushRuleService
     }
 
     override fun getLiveRoomNotificationState(): LiveData<RoomNotificationState> {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/peeking/ResolveRoomStateTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/peeking/ResolveRoomStateTask.kt
index 03ea2408f0956782f66d7fb10a76cf18db23482f..dbec6b555c6fa791b43c5c9fbe2417298c9f9f6e 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/peeking/ResolveRoomStateTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/peeking/ResolveRoomStateTask.kt
@@ -16,8 +16,8 @@
 
 package org.matrix.android.sdk.internal.session.room.peeking
 
-import org.greenrobot.eventbus.EventBus
 import org.matrix.android.sdk.api.session.events.model.Event
+import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import org.matrix.android.sdk.internal.network.executeRequest
 import org.matrix.android.sdk.internal.session.room.RoomAPI
 import org.matrix.android.sdk.internal.task.Task
@@ -31,11 +31,11 @@ internal interface ResolveRoomStateTask : Task<ResolveRoomStateTask.Params, List
 
 internal class DefaultResolveRoomStateTask @Inject constructor(
         private val roomAPI: RoomAPI,
-        private val eventBus: EventBus
+        private val globalErrorReceiver: GlobalErrorReceiver
 ) : ResolveRoomStateTask {
 
     override suspend fun execute(params: ResolveRoomStateTask.Params): List<Event> {
-        return executeRequest(eventBus) {
+        return executeRequest(globalErrorReceiver) {
             apiCall = roomAPI.getRoomState(params.roomId)
         }
     }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/read/DefaultReadService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/read/DefaultReadService.kt
index 025bea09f4c69617c6a767b50ecd5979aa40d386..3cf8cfe5eef3d8f181004a51b666cb07347c3448 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/read/DefaultReadService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/read/DefaultReadService.kt
@@ -18,8 +18,9 @@ package org.matrix.android.sdk.internal.session.room.read
 
 import androidx.lifecycle.LiveData
 import androidx.lifecycle.Transformations
-import com.squareup.inject.assisted.Assisted
-import com.squareup.inject.assisted.AssistedInject
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedInject
+import dagger.assisted.AssistedFactory
 import com.zhuinden.monarchy.Monarchy
 import org.matrix.android.sdk.api.MatrixCallback
 import org.matrix.android.sdk.api.session.room.model.ReadReceipt
@@ -46,9 +47,9 @@ internal class DefaultReadService @AssistedInject constructor(
         @UserId private val userId: String
 ) : ReadService {
 
-    @AssistedInject.Factory
+    @AssistedFactory
     interface Factory {
-        fun create(roomId: String): ReadService
+        fun create(roomId: String): DefaultReadService
     }
 
     override fun markAsRead(params: ReadService.MarkAsReadParams, callback: MatrixCallback<Unit>) {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/read/SetReadMarkersTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/read/SetReadMarkersTask.kt
index a98bb02c83a0f18be56bb426aa873c2efb38f78b..c7f962a699349900f6fba87f8d8f0d85470c1914 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/read/SetReadMarkersTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/read/SetReadMarkersTask.kt
@@ -33,7 +33,7 @@ import org.matrix.android.sdk.internal.session.sync.RoomFullyReadHandler
 import org.matrix.android.sdk.internal.task.Task
 import org.matrix.android.sdk.internal.util.awaitTransaction
 import io.realm.Realm
-import org.greenrobot.eventbus.EventBus
+import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import timber.log.Timber
 import javax.inject.Inject
 import kotlin.collections.set
@@ -58,7 +58,7 @@ internal class DefaultSetReadMarkersTask @Inject constructor(
         private val roomFullyReadHandler: RoomFullyReadHandler,
         private val readReceiptHandler: ReadReceiptHandler,
         @UserId private val userId: String,
-        private val eventBus: EventBus
+        private val globalErrorReceiver: GlobalErrorReceiver
 ) : SetReadMarkersTask {
 
     override suspend fun execute(params: SetReadMarkersTask.Params) {
@@ -96,7 +96,7 @@ internal class DefaultSetReadMarkersTask @Inject constructor(
             updateDatabase(params.roomId, markers, shouldUpdateRoomSummary)
         }
         if (markers.isNotEmpty()) {
-            executeRequest<Unit>(eventBus) {
+            executeRequest<Unit>(globalErrorReceiver) {
                 isRetryable = true
                 apiCall = roomAPI.sendReadMarker(params.roomId, markers)
             }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/DefaultRelationService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/DefaultRelationService.kt
index a7f3f839807d13ab924cf64a3803ea7c77dfa354..b7caf62865ab6c26216f9486ae91282766b8a282 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/DefaultRelationService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/DefaultRelationService.kt
@@ -17,8 +17,9 @@ package org.matrix.android.sdk.internal.session.room.relation
 
 import androidx.lifecycle.LiveData
 import androidx.lifecycle.Transformations
-import com.squareup.inject.assisted.Assisted
-import com.squareup.inject.assisted.AssistedInject
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedInject
+import dagger.assisted.AssistedFactory
 import com.zhuinden.monarchy.Monarchy
 import org.matrix.android.sdk.api.MatrixCallback
 import org.matrix.android.sdk.api.session.events.model.Event
@@ -56,9 +57,9 @@ internal class DefaultRelationService @AssistedInject constructor(
         private val taskExecutor: TaskExecutor)
     : RelationService {
 
-    @AssistedInject.Factory
+    @AssistedFactory
     interface Factory {
-        fun create(roomId: String): RelationService
+        fun create(roomId: String): DefaultRelationService
     }
 
     override fun sendReaction(targetEventId: String, reaction: String): Cancelable {
@@ -140,7 +141,7 @@ internal class DefaultRelationService @AssistedInject constructor(
     }
 
     override fun fetchEditHistory(eventId: String, callback: MatrixCallback<List<Event>>) {
-        val params = FetchEditHistoryTask.Params(roomId, cryptoSessionInfoProvider.isRoomEncrypted(roomId), eventId)
+        val params = FetchEditHistoryTask.Params(roomId, eventId)
         fetchEditHistoryTask
                 .configureWith(params) {
                     this.callback = callback
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/FetchEditHistoryTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/FetchEditHistoryTask.kt
index 51eecb8c2a8d604235d2149dc179161c2bcf03ee..854585ca2935654197edb19b83363f75df2b6d56 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/FetchEditHistoryTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/FetchEditHistoryTask.kt
@@ -18,32 +18,35 @@ package org.matrix.android.sdk.internal.session.room.relation
 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.RelationType
+import org.matrix.android.sdk.internal.crypto.CryptoSessionInfoProvider
+import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import org.matrix.android.sdk.internal.network.executeRequest
 import org.matrix.android.sdk.internal.session.room.RoomAPI
 import org.matrix.android.sdk.internal.task.Task
-import org.greenrobot.eventbus.EventBus
 import javax.inject.Inject
 
 internal interface FetchEditHistoryTask : Task<FetchEditHistoryTask.Params, List<Event>> {
-
     data class Params(
             val roomId: String,
-            val isRoomEncrypted: Boolean,
             val eventId: String
     )
 }
 
 internal class DefaultFetchEditHistoryTask @Inject constructor(
         private val roomAPI: RoomAPI,
-        private val eventBus: EventBus
+        private val globalErrorReceiver: GlobalErrorReceiver,
+        private val cryptoSessionInfoProvider: CryptoSessionInfoProvider
 ) : FetchEditHistoryTask {
 
     override suspend fun execute(params: FetchEditHistoryTask.Params): List<Event> {
-        val response = executeRequest<RelationsResponse>(eventBus) {
-            apiCall = roomAPI.getRelations(params.roomId,
-                    params.eventId,
-                    RelationType.REPLACE,
-                    if (params.isRoomEncrypted) EventType.ENCRYPTED else EventType.MESSAGE)
+        val isRoomEncrypted = cryptoSessionInfoProvider.isRoomEncrypted(params.roomId)
+        val response = executeRequest<RelationsResponse>(globalErrorReceiver) {
+            apiCall = roomAPI.getRelations(
+                    roomId = params.roomId,
+                    eventId = params.eventId,
+                    relationType = RelationType.REPLACE,
+                    eventType = if (isRoomEncrypted) EventType.ENCRYPTED else EventType.MESSAGE
+            )
         }
 
         val events = response.chunks.toMutableList()
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/SendRelationWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/SendRelationWorker.kt
index 25dfe32cbb04753a7955e78dd0867a1a9a272105..c12597bea0a44d7efeac40ea7039a787727c8578 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/SendRelationWorker.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/SendRelationWorker.kt
@@ -18,12 +18,12 @@ package org.matrix.android.sdk.internal.session.room.relation
 import android.content.Context
 import androidx.work.WorkerParameters
 import com.squareup.moshi.JsonClass
-import org.greenrobot.eventbus.EventBus
 import org.matrix.android.sdk.api.failure.Failure
 import org.matrix.android.sdk.api.session.events.model.Event
 import org.matrix.android.sdk.api.session.events.model.toModel
 import org.matrix.android.sdk.api.session.room.model.relation.ReactionContent
 import org.matrix.android.sdk.api.session.room.model.relation.ReactionInfo
+import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import org.matrix.android.sdk.internal.network.executeRequest
 import org.matrix.android.sdk.internal.session.SessionComponent
 import org.matrix.android.sdk.internal.session.room.RoomAPI
@@ -47,7 +47,7 @@ internal class SendRelationWorker(context: Context, params: WorkerParameters)
     ) : SessionWorkerParams
 
     @Inject lateinit var roomAPI: RoomAPI
-    @Inject lateinit var eventBus: EventBus
+    @Inject lateinit var globalErrorReceiver: GlobalErrorReceiver
     @Inject lateinit var localEchoRepository: LocalEchoRepository
 
     override fun injectWith(injector: SessionComponent) {
@@ -84,7 +84,7 @@ internal class SendRelationWorker(context: Context, params: WorkerParameters)
     }
 
     private suspend fun sendRelation(roomId: String, relationType: String, relatedEventId: String, localEvent: Event) {
-        executeRequest<SendResponse>(eventBus) {
+        executeRequest<SendResponse>(globalErrorReceiver) {
             apiCall = roomAPI.sendRelation(
                     roomId = roomId,
                     parentId = relatedEventId,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/reporting/DefaultReportingService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/reporting/DefaultReportingService.kt
index cac87a9d30cf3ff2b6b5727582192ff5b45a0ad3..add17a9fa581bfc3b8e33641ff003c9eae421fcc 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/reporting/DefaultReportingService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/reporting/DefaultReportingService.kt
@@ -16,17 +16,18 @@
 
 package org.matrix.android.sdk.internal.session.room.reporting
 
-import com.squareup.inject.assisted.Assisted
-import com.squareup.inject.assisted.AssistedInject
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedInject
+import dagger.assisted.AssistedFactory
 import org.matrix.android.sdk.api.session.room.reporting.ReportingService
 
 internal class DefaultReportingService @AssistedInject constructor(@Assisted private val roomId: String,
                                                                    private val reportContentTask: ReportContentTask
 ) : ReportingService {
 
-    @AssistedInject.Factory
+    @AssistedFactory
     interface Factory {
-        fun create(roomId: String): ReportingService
+        fun create(roomId: String): DefaultReportingService
     }
 
     override suspend fun reportContent(eventId: String, score: Int, reason: String) {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/reporting/ReportContentTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/reporting/ReportContentTask.kt
index bd1193767688e942f7ddeddc154924eac6a6074b..9c6e9907a4119e88435779f6d850fab12e61a540 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/reporting/ReportContentTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/reporting/ReportContentTask.kt
@@ -16,10 +16,10 @@
 
 package org.matrix.android.sdk.internal.session.room.reporting
 
+import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import org.matrix.android.sdk.internal.network.executeRequest
 import org.matrix.android.sdk.internal.session.room.RoomAPI
 import org.matrix.android.sdk.internal.task.Task
-import org.greenrobot.eventbus.EventBus
 import javax.inject.Inject
 
 internal interface ReportContentTask : Task<ReportContentTask.Params, Unit> {
@@ -33,11 +33,11 @@ internal interface ReportContentTask : Task<ReportContentTask.Params, Unit> {
 
 internal class DefaultReportContentTask @Inject constructor(
         private val roomAPI: RoomAPI,
-        private val eventBus: EventBus
+        private val globalErrorReceiver: GlobalErrorReceiver
 ) : ReportContentTask {
 
     override suspend fun execute(params: ReportContentTask.Params) {
-        return executeRequest(eventBus) {
+        return executeRequest(globalErrorReceiver) {
             apiCall = roomAPI.reportContent(params.roomId, params.eventId, ReportContentBody(params.score, params.reason))
         }
     }
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 8828f3dfedd466adc7ab9c977f6583581c45b476..a12962b51ffeda75b2fe8006cd4451ced1b0a03a 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
@@ -21,8 +21,9 @@ import androidx.work.BackoffPolicy
 import androidx.work.ExistingWorkPolicy
 import androidx.work.OneTimeWorkRequest
 import androidx.work.Operation
-import com.squareup.inject.assisted.Assisted
-import com.squareup.inject.assisted.AssistedInject
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedInject
+import dagger.assisted.AssistedFactory
 import kotlinx.coroutines.launch
 import org.matrix.android.sdk.api.session.content.ContentAttachmentData
 import org.matrix.android.sdk.api.session.events.model.Event
@@ -71,9 +72,9 @@ internal class DefaultSendService @AssistedInject constructor(
         private val cancelSendTracker: CancelSendTracker
 ) : SendService {
 
-    @AssistedInject.Factory
+    @AssistedFactory
     interface Factory {
-        fun create(roomId: String): SendService
+        fun create(roomId: String): DefaultSendService
     }
 
     private val workerFutureListenerExecutor = Executors.newSingleThreadExecutor()
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoRepository.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoRepository.kt
index f4871ab35dffa93bac399093b2d51b04281f065e..f742271fa703a20f42f60f7451daf93bdf139691 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoRepository.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoRepository.kt
@@ -18,7 +18,6 @@ package org.matrix.android.sdk.internal.session.room.send
 
 import com.zhuinden.monarchy.Monarchy
 import io.realm.Realm
-import org.greenrobot.eventbus.EventBus
 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.toModel
@@ -42,7 +41,7 @@ import org.matrix.android.sdk.internal.database.query.where
 import org.matrix.android.sdk.internal.di.SessionDatabase
 import org.matrix.android.sdk.internal.session.room.membership.RoomMemberHelper
 import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryUpdater
-import org.matrix.android.sdk.internal.session.room.timeline.DefaultTimeline
+import org.matrix.android.sdk.internal.session.room.timeline.TimelineInput
 import org.matrix.android.sdk.internal.task.TaskExecutor
 import org.matrix.android.sdk.internal.util.awaitTransaction
 import timber.log.Timber
@@ -52,7 +51,7 @@ internal class LocalEchoRepository @Inject constructor(@SessionDatabase private
                                                        private val taskExecutor: TaskExecutor,
                                                        private val realmSessionProvider: RealmSessionProvider,
                                                        private val roomSummaryUpdater: RoomSummaryUpdater,
-                                                       private val eventBus: EventBus,
+                                                       private val timelineInput: TimelineInput,
                                                        private val timelineEventMapper: TimelineEventMapper) {
 
     fun createLocalEcho(event: Event) {
@@ -76,7 +75,7 @@ internal class LocalEchoRepository @Inject constructor(@SessionDatabase private
             }
         }
         val timelineEvent = timelineEventMapper.map(timelineEventEntity)
-        eventBus.post(DefaultTimeline.OnLocalEchoCreated(roomId = roomId, timelineEvent = timelineEvent))
+        timelineInput.onLocalEchoCreated(roomId = roomId, timelineEvent = timelineEvent)
         taskExecutor.executorScope.asyncTransaction(monarchy) { realm ->
             val eventInsertEntity = EventInsertEntity(event.eventId, event.type).apply {
                 this.insertType = EventInsertType.LOCAL_ECHO
@@ -90,7 +89,7 @@ internal class LocalEchoRepository @Inject constructor(@SessionDatabase private
 
     fun updateSendState(eventId: String, roomId: String?, sendState: SendState) {
         Timber.v("## SendEvent: [${System.currentTimeMillis()}] Update local state of $eventId to ${sendState.name}")
-        eventBus.post(DefaultTimeline.OnLocalEchoUpdated(roomId ?: "", eventId, sendState))
+        timelineInput.onLocalEchoUpdated(roomId = roomId ?: "", eventId = eventId, sendState = sendState)
         updateEchoAsync(eventId) { realm, sendingEventEntity ->
             if (sendState == SendState.SENT && sendingEventEntity.sendState == SendState.SYNCED) {
                 // If already synced, do not put as sent
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 682865eaee17b3e834f335dd9db7e07379721c5b..c901c7e18eb0d72c1c95ce56cdcf1f4b29e68d49 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
@@ -18,8 +18,8 @@ package org.matrix.android.sdk.internal.session.room.send
 import android.content.Context
 import androidx.work.WorkerParameters
 import com.squareup.moshi.JsonClass
-import org.greenrobot.eventbus.EventBus
 import org.matrix.android.sdk.api.failure.Failure
+import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import org.matrix.android.sdk.internal.network.executeRequest
 import org.matrix.android.sdk.internal.session.SessionComponent
 import org.matrix.android.sdk.internal.session.room.RoomAPI
@@ -46,7 +46,7 @@ internal class RedactEventWorker(context: Context, params: WorkerParameters)
     ) : SessionWorkerParams
 
     @Inject lateinit var roomAPI: RoomAPI
-    @Inject lateinit var eventBus: EventBus
+    @Inject lateinit var globalErrorReceiver: GlobalErrorReceiver
 
     override fun injectWith(injector: SessionComponent) {
         injector.inject(this)
@@ -55,7 +55,7 @@ internal class RedactEventWorker(context: Context, params: WorkerParameters)
     override suspend fun doSafeWork(params: Params): Result {
         val eventId = params.eventId
         return runCatching {
-            executeRequest<SendResponse>(eventBus) {
+            executeRequest<SendResponse>(globalErrorReceiver) {
                 apiCall = roomAPI.redactEvent(
                         params.txID,
                         params.roomId,
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 37a429d2429f0db91778fc904da111eaded95fb7..c1fc2fd9fee405d1f0936a8b44bb8477b8f377a4 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
@@ -20,7 +20,6 @@ import android.content.Context
 import androidx.work.WorkerParameters
 import com.squareup.moshi.JsonClass
 import io.realm.RealmConfiguration
-import org.greenrobot.eventbus.EventBus
 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
@@ -54,7 +53,6 @@ internal class SendEventWorker(context: Context,
     @Inject lateinit var localEchoRepository: LocalEchoRepository
     @Inject lateinit var sendEventTask: SendEventTask
     @Inject lateinit var cryptoService: CryptoService
-    @Inject lateinit var eventBus: EventBus
     @Inject lateinit var cancelSendTracker: CancelSendTracker
     @SessionDatabase @Inject lateinit var realmConfiguration: RealmConfiguration
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/DefaultStateService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/DefaultStateService.kt
index b546584450796b14def12b0be59ed122481a50eb..f2640fd1e733d21629f505d8a3b3a4434403273a 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/DefaultStateService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/DefaultStateService.kt
@@ -18,8 +18,9 @@ package org.matrix.android.sdk.internal.session.room.state
 
 import android.net.Uri
 import androidx.lifecycle.LiveData
-import com.squareup.inject.assisted.Assisted
-import com.squareup.inject.assisted.AssistedInject
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedInject
+import dagger.assisted.AssistedFactory
 import org.matrix.android.sdk.api.query.QueryStringValue
 import org.matrix.android.sdk.api.session.events.model.Event
 import org.matrix.android.sdk.api.session.events.model.EventType
@@ -35,18 +36,16 @@ import org.matrix.android.sdk.api.util.JsonDict
 import org.matrix.android.sdk.api.util.MimeTypes
 import org.matrix.android.sdk.api.util.Optional
 import org.matrix.android.sdk.internal.session.content.FileUploader
-import org.matrix.android.sdk.internal.session.room.alias.AddRoomAliasTask
 
 internal class DefaultStateService @AssistedInject constructor(@Assisted private val roomId: String,
                                                                private val stateEventDataSource: StateEventDataSource,
                                                                private val sendStateTask: SendStateTask,
-                                                               private val fileUploader: FileUploader,
-                                                               private val addRoomAliasTask: AddRoomAliasTask
+                                                               private val fileUploader: FileUploader
 ) : StateService {
 
-    @AssistedInject.Factory
+    @AssistedFactory
     interface Factory {
-        fun create(roomId: String): StateService
+        fun create(roomId: String): DefaultStateService
     }
 
     override fun getStateEvent(eventType: String, stateKey: QueryStringValue): Event? {
@@ -74,11 +73,19 @@ internal class DefaultStateService @AssistedInject constructor(@Assisted private
                 roomId = roomId,
                 stateKey = stateKey,
                 eventType = eventType,
-                body = body
+                body = body.toSafeJson(eventType)
         )
         sendStateTask.execute(params)
     }
 
+    private fun JsonDict.toSafeJson(eventType: String): JsonDict {
+        // Safe treatment for PowerLevelContent
+        return when (eventType) {
+            EventType.STATE_ROOM_POWER_LEVELS -> toSafePowerLevelsContentDict()
+            else                              -> this
+        }
+    }
+
     override suspend fun updateTopic(topic: String) {
         sendStateEvent(
                 eventType = EventType.STATE_ROOM_TOPIC,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/SafePowerLevelContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/SafePowerLevelContent.kt
new file mode 100644
index 0000000000000000000000000000000000000000..a97709e38ba2d1d3e0b83a99503ab93c0e99cb4b
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/SafePowerLevelContent.kt
@@ -0,0 +1,60 @@
+/*
+ * 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.
+ * 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.session.room.state
+
+import com.squareup.moshi.Json
+import com.squareup.moshi.JsonClass
+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.PowerLevelsContent
+import org.matrix.android.sdk.api.session.room.powerlevels.Role
+import org.matrix.android.sdk.api.util.JsonDict
+
+@JsonClass(generateAdapter = true)
+internal data class SerializablePowerLevelsContent(
+        @Json(name = "ban") val ban: Int = Role.Moderator.value,
+        @Json(name = "kick") val kick: Int = Role.Moderator.value,
+        @Json(name = "invite") val invite: Int = Role.Moderator.value,
+        @Json(name = "redact") val redact: Int = Role.Moderator.value,
+        @Json(name = "events_default") val eventsDefault: Int = Role.Default.value,
+        @Json(name = "events") val events: Map<String, Int> = emptyMap(),
+        @Json(name = "users_default") val usersDefault: Int = Role.Default.value,
+        @Json(name = "users") val users: Map<String, Int> = emptyMap(),
+        @Json(name = "state_default") val stateDefault: Int = Role.Moderator.value,
+        // `Int` is the diff here (instead of `Any`)
+        @Json(name = "notifications") val notifications: Map<String, Int> = emptyMap()
+)
+
+internal fun JsonDict.toSafePowerLevelsContentDict(): JsonDict {
+    return toModel<PowerLevelsContent>()
+            ?.let { content ->
+                SerializablePowerLevelsContent(
+                        ban = content.ban,
+                        kick = content.kick,
+                        invite = content.invite,
+                        redact = content.redact,
+                        eventsDefault = content.eventsDefault,
+                        events = content.events,
+                        usersDefault = content.usersDefault,
+                        users = content.users,
+                        stateDefault = content.stateDefault,
+                        notifications = content.notifications.mapValues { content.notificationLevel(it.key)  }
+                )
+            }
+            ?.toContent()
+            ?: emptyMap()
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/SendStateTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/SendStateTask.kt
index 642f68c15b61a6ff41670a86e09c7306ce3b3639..63691d9207e816109f0f3a8fdc24dd8a0f91d77c 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/SendStateTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/SendStateTask.kt
@@ -17,10 +17,10 @@
 package org.matrix.android.sdk.internal.session.room.state
 
 import org.matrix.android.sdk.api.util.JsonDict
+import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import org.matrix.android.sdk.internal.network.executeRequest
 import org.matrix.android.sdk.internal.session.room.RoomAPI
 import org.matrix.android.sdk.internal.task.Task
-import org.greenrobot.eventbus.EventBus
 import javax.inject.Inject
 
 internal interface SendStateTask : Task<SendStateTask.Params, Unit> {
@@ -34,11 +34,11 @@ internal interface SendStateTask : Task<SendStateTask.Params, Unit> {
 
 internal class DefaultSendStateTask @Inject constructor(
         private val roomAPI: RoomAPI,
-        private val eventBus: EventBus
+        private val globalErrorReceiver: GlobalErrorReceiver
 ) : SendStateTask {
 
     override suspend fun execute(params: SendStateTask.Params) {
-        return executeRequest(eventBus) {
+        return executeRequest(globalErrorReceiver) {
             apiCall = if (params.stateKey == null) {
                 roomAPI.sendStateEvent(
                         roomId = params.roomId,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt
index 8c71604183f234f79497ab8be66a8c96299ac96f..fff780fb0c65ce5d063d502c087797424c700394 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt
@@ -140,14 +140,13 @@ internal class RoomSummaryUpdater @Inject constructor(
                     .queryActiveRoomMembersEvent()
                     .notEqualTo(RoomMemberSummaryEntityFields.USER_ID, userId)
                     .findAll()
-                    .asSequence()
                     .map { it.userId }
 
             roomSummaryEntity.otherMemberIds.clear()
             roomSummaryEntity.otherMemberIds.addAll(otherRoomMembers)
             if (roomSummaryEntity.isEncrypted) {
                 // mmm maybe we could only refresh shield instead of checking trust also?
-                crossSigningService.onUsersDeviceUpdate(roomSummaryEntity.otherMemberIds.toList())
+                crossSigningService.onUsersDeviceUpdate(otherRoomMembers)
             }
         }
     }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/tags/AddTagToRoomTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/tags/AddTagToRoomTask.kt
index 013fc86d5c9409d2ba09e9d825c0c3bceecfd91b..c3b5c3f78fa2448e6099b7167efb77562ad89323 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/tags/AddTagToRoomTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/tags/AddTagToRoomTask.kt
@@ -17,10 +17,10 @@
 package org.matrix.android.sdk.internal.session.room.tags
 
 import org.matrix.android.sdk.internal.di.UserId
+import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import org.matrix.android.sdk.internal.network.executeRequest
 import org.matrix.android.sdk.internal.session.room.RoomAPI
 import org.matrix.android.sdk.internal.task.Task
-import org.greenrobot.eventbus.EventBus
 import javax.inject.Inject
 
 internal interface AddTagToRoomTask : Task<AddTagToRoomTask.Params, Unit> {
@@ -35,11 +35,11 @@ internal interface AddTagToRoomTask : Task<AddTagToRoomTask.Params, Unit> {
 internal class DefaultAddTagToRoomTask @Inject constructor(
         private val roomAPI: RoomAPI,
         @UserId private val userId: String,
-        private val eventBus: EventBus
+        private val globalErrorReceiver: GlobalErrorReceiver
 ) : AddTagToRoomTask {
 
     override suspend fun execute(params: AddTagToRoomTask.Params) {
-        executeRequest<Unit>(eventBus) {
+        executeRequest<Unit>(globalErrorReceiver) {
             apiCall = roomAPI.putTag(
                     userId = userId,
                     roomId = params.roomId,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/tags/DefaultTagsService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/tags/DefaultTagsService.kt
index d6c02f0a49b4d3297b4dcc95fac9412e2030800f..02acaa057044908dce1df587f0c30f5d5bb42c8a 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/tags/DefaultTagsService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/tags/DefaultTagsService.kt
@@ -16,8 +16,9 @@
 
 package org.matrix.android.sdk.internal.session.room.tags
 
-import com.squareup.inject.assisted.Assisted
-import com.squareup.inject.assisted.AssistedInject
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedInject
+import dagger.assisted.AssistedFactory
 import org.matrix.android.sdk.api.session.room.tags.TagsService
 
 internal class DefaultTagsService @AssistedInject constructor(
@@ -26,9 +27,9 @@ internal class DefaultTagsService @AssistedInject constructor(
         private val deleteTagFromRoomTask: DeleteTagFromRoomTask
 ) : TagsService {
 
-    @AssistedInject.Factory
+    @AssistedFactory
     interface Factory {
-        fun create(roomId: String): TagsService
+        fun create(roomId: String): DefaultTagsService
     }
 
     override suspend fun addTag(tag: String, order: Double?) {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/tags/DeleteTagFromRoomTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/tags/DeleteTagFromRoomTask.kt
index b22355d4319471faad78de681afce587ca6c2a22..d578d21fde13c558aa488a3cd5b89232f28d2b86 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/tags/DeleteTagFromRoomTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/tags/DeleteTagFromRoomTask.kt
@@ -17,10 +17,10 @@
 package org.matrix.android.sdk.internal.session.room.tags
 
 import org.matrix.android.sdk.internal.di.UserId
+import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import org.matrix.android.sdk.internal.network.executeRequest
 import org.matrix.android.sdk.internal.session.room.RoomAPI
 import org.matrix.android.sdk.internal.task.Task
-import org.greenrobot.eventbus.EventBus
 import javax.inject.Inject
 
 internal interface DeleteTagFromRoomTask : Task<DeleteTagFromRoomTask.Params, Unit> {
@@ -34,11 +34,11 @@ internal interface DeleteTagFromRoomTask : Task<DeleteTagFromRoomTask.Params, Un
 internal class DefaultDeleteTagFromRoomTask @Inject constructor(
         private val roomAPI: RoomAPI,
         @UserId private val userId: String,
-        private val eventBus: EventBus
+        private val globalErrorReceiver: GlobalErrorReceiver
 ) : DeleteTagFromRoomTask {
 
     override suspend fun execute(params: DeleteTagFromRoomTask.Params) {
-        executeRequest<Unit>(eventBus) {
+        executeRequest<Unit>(globalErrorReceiver) {
             apiCall = roomAPI.deleteTag(
                     userId = userId,
                     roomId = params.roomId,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt
index 86b0497bd0e78c867a865e18ae921a4aa38cc83b..ae90282d52d2e49dad52cf82021c3b9c6bac8ea8 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt
@@ -23,10 +23,8 @@ import io.realm.RealmConfiguration
 import io.realm.RealmQuery
 import io.realm.RealmResults
 import io.realm.Sort
-import org.greenrobot.eventbus.EventBus
-import org.greenrobot.eventbus.Subscribe
-import org.greenrobot.eventbus.ThreadMode
 import org.matrix.android.sdk.api.MatrixCallback
+import org.matrix.android.sdk.api.NoOpMatrixCallback
 import org.matrix.android.sdk.api.extensions.orFalse
 import org.matrix.android.sdk.api.extensions.tryOrNull
 import org.matrix.android.sdk.api.session.events.model.EventType
@@ -53,6 +51,7 @@ import org.matrix.android.sdk.internal.database.query.filterEvents
 import org.matrix.android.sdk.internal.database.query.findAllInRoomWithSendStates
 import org.matrix.android.sdk.internal.database.query.where
 import org.matrix.android.sdk.internal.database.query.whereRoomId
+import org.matrix.android.sdk.internal.session.room.membership.LoadRoomMembersTask
 import org.matrix.android.sdk.internal.task.TaskExecutor
 import org.matrix.android.sdk.internal.task.configureWith
 import org.matrix.android.sdk.internal.util.Debouncer
@@ -79,14 +78,13 @@ internal class DefaultTimeline(
         private val timelineEventMapper: TimelineEventMapper,
         private val settings: TimelineSettings,
         private val hiddenReadReceipts: TimelineHiddenReadReceipts,
-        private val eventBus: EventBus,
+        private val timelineInput: TimelineInput,
         private val eventDecryptor: TimelineEventDecryptor,
-        private val realmSessionProvider: RealmSessionProvider
-) : Timeline, TimelineHiddenReadReceipts.Delegate {
-
-    data class OnNewTimelineEvents(val roomId: String, val eventIds: List<String>)
-    data class OnLocalEchoCreated(val roomId: String, val timelineEvent: TimelineEvent)
-    data class OnLocalEchoUpdated(val roomId: String, val eventId: String, val sendState: SendState)
+        private val realmSessionProvider: RealmSessionProvider,
+        private val loadRoomMembersTask: LoadRoomMembersTask
+) : Timeline,
+        TimelineHiddenReadReceipts.Delegate,
+        TimelineInput.Listener {
 
     companion object {
         val BACKGROUND_HANDLER = createBackgroundHandler("TIMELINE_DB_THREAD")
@@ -158,7 +156,7 @@ internal class DefaultTimeline(
     override fun start() {
         if (isStarted.compareAndSet(false, true)) {
             Timber.v("Start timeline for roomId: $roomId and eventId: $initialEventId")
-            eventBus.register(this)
+            timelineInput.listeners.add(this)
             BACKGROUND_HANDLER.post {
                 eventDecryptor.start()
                 val realm = Realm.getInstance(realmConfiguration)
@@ -184,6 +182,13 @@ internal class DefaultTimeline(
                 if (settings.shouldHandleHiddenReadReceipts()) {
                     hiddenReadReceipts.start(realm, filteredEvents, nonFilteredEvents, this)
                 }
+
+                loadRoomMembersTask
+                        .configureWith(LoadRoomMembersTask.Params(roomId)) {
+                            this.callback = NoOpMatrixCallback()
+                        }
+                        .executeBy(taskExecutor)
+
                 isReady.set(true)
             }
         }
@@ -196,7 +201,7 @@ internal class DefaultTimeline(
     override fun dispose() {
         if (isStarted.compareAndSet(true, false)) {
             isReady.set(false)
-            eventBus.unregister(this)
+            timelineInput.listeners.remove(this)
             Timber.v("Dispose timeline for roomId: $roomId and eventId: $initialEventId")
             cancelableBag.cancel()
             BACKGROUND_HANDLER.removeCallbacksAndMessages(null)
@@ -313,25 +318,22 @@ internal class DefaultTimeline(
         postSnapshot()
     }
 
-    @Subscribe(threadMode = ThreadMode.MAIN)
-    fun onNewTimelineEvents(onNewTimelineEvents: OnNewTimelineEvents) {
-        if (isLive && onNewTimelineEvents.roomId == roomId) {
+    override fun onNewTimelineEvents(roomId: String, eventIds: List<String>) {
+        if (isLive && this.roomId == roomId) {
             listeners.forEach {
-                it.onNewTimelineEvents(onNewTimelineEvents.eventIds)
+                it.onNewTimelineEvents(eventIds)
             }
         }
     }
 
-    @Subscribe(threadMode = ThreadMode.MAIN)
-    fun onLocalEchoCreated(onLocalEchoCreated: OnLocalEchoCreated) {
-        if (uiEchoManager.onLocalEchoCreated(onLocalEchoCreated)) {
+    override fun onLocalEchoCreated(roomId: String, timelineEvent: TimelineEvent) {
+        if (uiEchoManager.onLocalEchoCreated(roomId, timelineEvent)) {
             postSnapshot()
         }
     }
 
-    @Subscribe(threadMode = ThreadMode.MAIN)
-    fun onLocalEchoUpdated(onLocalEchoUpdated: OnLocalEchoUpdated) {
-        if (uiEchoManager.onLocalEchoUpdated(onLocalEchoUpdated)) {
+    override fun onLocalEchoUpdated(roomId: String, eventId: String, sendState: SendState) {
+        if (uiEchoManager.onLocalEchoUpdated(roomId, eventId, sendState)) {
             postSnapshot()
         }
     }
@@ -848,11 +850,11 @@ internal class DefaultTimeline(
             }
         }
 
-        fun onLocalEchoUpdated(onLocalEchoUpdated: OnLocalEchoUpdated): Boolean {
-            if (isLive && onLocalEchoUpdated.roomId == roomId) {
-                val existingState = inMemorySendingStates[onLocalEchoUpdated.eventId]
-                inMemorySendingStates[onLocalEchoUpdated.eventId] = onLocalEchoUpdated.sendState
-                if (existingState != onLocalEchoUpdated.sendState) {
+        fun onLocalEchoUpdated(roomId: String, eventId: String, sendState: SendState): Boolean {
+            if (isLive && roomId == this@DefaultTimeline.roomId) {
+                val existingState = inMemorySendingStates[eventId]
+                inMemorySendingStates[eventId] = sendState
+                if (existingState != sendState) {
                     return true
                 }
             }
@@ -860,22 +862,22 @@ internal class DefaultTimeline(
         }
 
         // return true if should update
-        fun onLocalEchoCreated(onLocalEchoCreated: OnLocalEchoCreated): Boolean {
+        fun onLocalEchoCreated(roomId: String, timelineEvent: TimelineEvent): Boolean {
             var postSnapshot = false
-            if (isLive && onLocalEchoCreated.roomId == roomId) {
+            if (isLive && roomId == this@DefaultTimeline.roomId) {
                 // Manage some ui echos (do it before filter because actual event could be filtered out)
-                when (onLocalEchoCreated.timelineEvent.root.getClearType()) {
+                when (timelineEvent.root.getClearType()) {
                     EventType.REDACTION -> {
                     }
                     EventType.REACTION  -> {
-                        val content = onLocalEchoCreated.timelineEvent.root.content?.toModel<ReactionContent>()
+                        val content = timelineEvent.root.content?.toModel<ReactionContent>()
                         if (RelationType.ANNOTATION == content?.relatesTo?.type) {
                             val reaction = content.relatesTo.key
                             val relatedEventID = content.relatesTo.eventId
                             inMemoryReactions.getOrPut(relatedEventID) { mutableListOf() }
                                     .add(
                                             ReactionUiEchoData(
-                                                    localEchoId = onLocalEchoCreated.timelineEvent.eventId,
+                                                    localEchoId = timelineEvent.eventId,
                                                     reactedOnEventId = relatedEventID,
                                                     reaction = reaction
                                             )
@@ -888,12 +890,12 @@ internal class DefaultTimeline(
                 }
 
                 // do not add events that would have been filtered
-                if (listOf(onLocalEchoCreated.timelineEvent).filterEventsWithSettings().isNotEmpty()) {
+                if (listOf(timelineEvent).filterEventsWithSettings().isNotEmpty()) {
                     listeners.forEach {
-                        it.onNewTimelineEvents(listOf(onLocalEchoCreated.timelineEvent.eventId))
+                        it.onNewTimelineEvents(listOf(timelineEvent.eventId))
                     }
-                    Timber.v("On local echo created: ${onLocalEchoCreated.timelineEvent.eventId}")
-                    inMemorySendingEvents.add(0, onLocalEchoCreated.timelineEvent)
+                    Timber.v("On local echo created: ${timelineEvent.eventId}")
+                    inMemorySendingEvents.add(0, timelineEvent)
                     postSnapshot = true
                 }
             }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimelineService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimelineService.kt
index 783aa53ddf725469e62e2bbf2f0c451b3f6dc50b..ef890db79e75947032a4208da4096ff0a7d24ed0 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimelineService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimelineService.kt
@@ -18,12 +18,12 @@ package org.matrix.android.sdk.internal.session.room.timeline
 
 import androidx.lifecycle.LiveData
 import androidx.lifecycle.Transformations
-import com.squareup.inject.assisted.Assisted
-import com.squareup.inject.assisted.AssistedInject
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedInject
+import dagger.assisted.AssistedFactory
 import com.zhuinden.monarchy.Monarchy
 import io.realm.Sort
 import io.realm.kotlin.where
-import org.greenrobot.eventbus.EventBus
 import org.matrix.android.sdk.api.session.events.model.isImageMessage
 import org.matrix.android.sdk.api.session.events.model.isVideoMessage
 import org.matrix.android.sdk.api.session.room.timeline.Timeline
@@ -39,24 +39,26 @@ import org.matrix.android.sdk.internal.database.model.TimelineEventEntity
 import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields
 import org.matrix.android.sdk.internal.database.query.where
 import org.matrix.android.sdk.internal.di.SessionDatabase
+import org.matrix.android.sdk.internal.session.room.membership.LoadRoomMembersTask
 import org.matrix.android.sdk.internal.task.TaskExecutor
 
 internal class DefaultTimelineService @AssistedInject constructor(@Assisted private val roomId: String,
                                                                   @SessionDatabase private val monarchy: Monarchy,
                                                                   private val realmSessionProvider: RealmSessionProvider,
-                                                                  private val eventBus: EventBus,
+                                                                  private val timelineInput: TimelineInput,
                                                                   private val taskExecutor: TaskExecutor,
                                                                   private val contextOfEventTask: GetContextOfEventTask,
                                                                   private val eventDecryptor: TimelineEventDecryptor,
                                                                   private val paginationTask: PaginationTask,
                                                                   private val fetchTokenAndPaginateTask: FetchTokenAndPaginateTask,
                                                                   private val timelineEventMapper: TimelineEventMapper,
-                                                                  private val readReceiptsSummaryMapper: ReadReceiptsSummaryMapper
+                                                                  private val readReceiptsSummaryMapper: ReadReceiptsSummaryMapper,
+                                                                  private val loadRoomMembersTask: LoadRoomMembersTask
 ) : TimelineService {
 
-    @AssistedInject.Factory
+    @AssistedFactory
     interface Factory {
-        fun create(roomId: String): TimelineService
+        fun create(roomId: String): DefaultTimelineService
     }
 
     override fun createTimeline(eventId: String?, settings: TimelineSettings): Timeline {
@@ -70,10 +72,11 @@ internal class DefaultTimelineService @AssistedInject constructor(@Assisted priv
                 timelineEventMapper = timelineEventMapper,
                 settings = settings,
                 hiddenReadReceipts = TimelineHiddenReadReceipts(readReceiptsSummaryMapper, roomId, settings),
-                eventBus = eventBus,
+                timelineInput = timelineInput,
                 eventDecryptor = eventDecryptor,
                 fetchTokenAndPaginateTask = fetchTokenAndPaginateTask,
-                realmSessionProvider = realmSessionProvider
+                realmSessionProvider = realmSessionProvider,
+                loadRoomMembersTask = loadRoomMembersTask
         )
     }
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/EventContextResponse.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/EventContextResponse.kt
index bce03354d75105a83243515d8b5d8891c713dedc..654cf0fb7416c9eb67d97310d723859c16455b5f 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/EventContextResponse.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/EventContextResponse.kt
@@ -21,16 +21,34 @@ import com.squareup.moshi.JsonClass
 import org.matrix.android.sdk.api.session.events.model.Event
 
 @JsonClass(generateAdapter = true)
-data class EventContextResponse(
+internal data class EventContextResponse(
+        /**
+         * Details of the requested event.
+         */
         @Json(name = "event") val event: Event,
+        /**
+         * A token that can be used to paginate backwards with.
+         */
         @Json(name = "start") override val start: String? = null,
-        @Json(name = "events_before") val eventsBefore: List<Event> = emptyList(),
-        @Json(name = "events_after") val eventsAfter: List<Event> = emptyList(),
+        /**
+         * A list of room events that happened just before the requested event, in reverse-chronological order.
+         */
+        @Json(name = "events_before") val eventsBefore: List<Event>? = null,
+        /**
+         * A list of room events that happened just after the requested event, in chronological order.
+         */
+        @Json(name = "events_after") val eventsAfter: List<Event>? = null,
+        /**
+         * A token that can be used to paginate forwards with.
+         */
         @Json(name = "end") override val end: String? = null,
-        @Json(name = "state") override val stateEvents: List<Event> = emptyList()
+        /**
+         * The state of the room at the last event returned.
+         */
+        @Json(name = "state") override val stateEvents: List<Event>? = null
 ) : TokenChunkEvent {
 
     override val events: List<Event> by lazy {
-        eventsAfter.reversed() + listOf(event) + eventsBefore
+        eventsAfter.orEmpty().reversed() + event + eventsBefore.orEmpty()
     }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/FetchTokenAndPaginateTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/FetchTokenAndPaginateTask.kt
index d1bfa1adcb5dc137437f1ac4dbf8ed73cdbfb06c..76c4b3812c59f8b281090a72423d28b8820d743b 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/FetchTokenAndPaginateTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/FetchTokenAndPaginateTask.kt
@@ -20,12 +20,12 @@ import com.zhuinden.monarchy.Monarchy
 import org.matrix.android.sdk.internal.database.model.ChunkEntity
 import org.matrix.android.sdk.internal.database.query.findIncludingEvent
 import org.matrix.android.sdk.internal.di.SessionDatabase
+import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import org.matrix.android.sdk.internal.network.executeRequest
 import org.matrix.android.sdk.internal.session.filter.FilterRepository
 import org.matrix.android.sdk.internal.session.room.RoomAPI
 import org.matrix.android.sdk.internal.task.Task
 import org.matrix.android.sdk.internal.util.awaitTransaction
-import org.greenrobot.eventbus.EventBus
 import javax.inject.Inject
 
 internal interface FetchTokenAndPaginateTask : Task<FetchTokenAndPaginateTask.Params, TokenChunkEventPersistor.Result> {
@@ -43,12 +43,12 @@ internal class DefaultFetchTokenAndPaginateTask @Inject constructor(
         @SessionDatabase private val monarchy: Monarchy,
         private val filterRepository: FilterRepository,
         private val paginationTask: PaginationTask,
-        private val eventBus: EventBus
+        private val globalErrorReceiver: GlobalErrorReceiver
 ) : FetchTokenAndPaginateTask {
 
     override suspend fun execute(params: FetchTokenAndPaginateTask.Params): TokenChunkEventPersistor.Result {
         val filter = filterRepository.getRoomFilter()
-        val response = executeRequest<EventContextResponse>(eventBus) {
+        val response = executeRequest<EventContextResponse>(globalErrorReceiver) {
             apiCall = roomAPI.getContextOfEvent(params.roomId, params.lastKnownEventId, 0, filter)
         }
         val fromToken = if (params.direction == PaginationDirection.FORWARDS) {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/GetContextOfEventTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/GetContextOfEventTask.kt
index 7a611dd350f77f6bde74ef40fc33f0651bbbb0a5..d02a7bafe9a99dcf893ce56e623015479ef5b2e0 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/GetContextOfEventTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/GetContextOfEventTask.kt
@@ -16,11 +16,11 @@
 
 package org.matrix.android.sdk.internal.session.room.timeline
 
+import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import org.matrix.android.sdk.internal.network.executeRequest
 import org.matrix.android.sdk.internal.session.filter.FilterRepository
 import org.matrix.android.sdk.internal.session.room.RoomAPI
 import org.matrix.android.sdk.internal.task.Task
-import org.greenrobot.eventbus.EventBus
 import javax.inject.Inject
 
 internal interface GetContextOfEventTask : Task<GetContextOfEventTask.Params, TokenChunkEventPersistor.Result> {
@@ -35,12 +35,12 @@ internal class DefaultGetContextOfEventTask @Inject constructor(
         private val roomAPI: RoomAPI,
         private val filterRepository: FilterRepository,
         private val tokenChunkEventPersistor: TokenChunkEventPersistor,
-        private val eventBus: EventBus
+        private val globalErrorReceiver: GlobalErrorReceiver
 ) : GetContextOfEventTask {
 
     override suspend fun execute(params: GetContextOfEventTask.Params): TokenChunkEventPersistor.Result {
         val filter = filterRepository.getRoomFilter()
-        val response = executeRequest<EventContextResponse>(eventBus) {
+        val response = executeRequest<EventContextResponse>(globalErrorReceiver) {
             // We are limiting the response to the event with eventId to be sure we don't have any issue with potential merging process.
             apiCall = roomAPI.getContextOfEvent(params.roomId, params.eventId, 0, filter)
         }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/GetEventTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/GetEventTask.kt
index acac3929ae7ddea543630f1511e1e58cd44983bc..b8585b1e747979135b7be0a7aef059b324e7c4d0 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/GetEventTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/GetEventTask.kt
@@ -17,17 +17,17 @@
 package org.matrix.android.sdk.internal.session.room.timeline
 
 import org.matrix.android.sdk.api.session.events.model.Event
+import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import org.matrix.android.sdk.internal.network.executeRequest
 import org.matrix.android.sdk.internal.session.room.RoomAPI
 import org.matrix.android.sdk.internal.task.Task
-import org.greenrobot.eventbus.EventBus
 import javax.inject.Inject
 
 // TODO Add parent task
 
 internal class GetEventTask @Inject constructor(
         private val roomAPI: RoomAPI,
-        private val eventBus: EventBus
+        private val globalErrorReceiver: GlobalErrorReceiver
 ) : Task<GetEventTask.Params, Event> {
 
     internal data class Params(
@@ -36,7 +36,7 @@ internal class GetEventTask @Inject constructor(
     )
 
     override suspend fun execute(params: Params): Event {
-        return executeRequest(eventBus) {
+        return executeRequest(globalErrorReceiver) {
             apiCall = roomAPI.getEvent(params.roomId, params.eventId)
         }
     }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/PaginationResponse.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/PaginationResponse.kt
index ed384d3b3c7edd1c7b0b40bf878620a8e3d11736..2f61b1cce8ad7718d9fb20b518775a9785bac349 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/PaginationResponse.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/PaginationResponse.kt
@@ -22,8 +22,28 @@ import org.matrix.android.sdk.api.session.events.model.Event
 
 @JsonClass(generateAdapter = true)
 internal data class PaginationResponse(
+        /**
+         * The token the pagination starts from. If dir=b this will be the token supplied in from.
+         */
         @Json(name = "start") override val start: String? = null,
+        /**
+         * The token the pagination ends at. If dir=b this token should be used again to request even earlier events.
+         */
         @Json(name = "end") override val end: String? = null,
-        @Json(name = "chunk") override val events: List<Event> = emptyList(),
-        @Json(name = "state") override val stateEvents: List<Event> = emptyList()
-) : TokenChunkEvent
+        /**
+         * A list of room events. The order depends on the dir parameter. For dir=b events will be in
+         * reverse-chronological order, for dir=f in chronological order, so that events start at the from point.
+         */
+        @Json(name = "chunk") val chunk: List<Event>? = null,
+        /**
+         * A list of state events relevant to showing the chunk. For example, if lazy_load_members is enabled
+         * in the filter then this may contain the membership events for the senders of events in the chunk.
+         *
+         * Unless include_redundant_members is true, the server may remove membership events which would have
+         * already been sent to the client in prior calls to this endpoint, assuming the membership of those members has not changed.
+         */
+        @Json(name = "state") override val stateEvents: List<Event>? = null
+) : TokenChunkEvent {
+    override val events: List<Event>
+        get() = chunk.orEmpty()
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/PaginationTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/PaginationTask.kt
index b663d03bd7b504d983af0b12ed3e6829f517dae4..1f99893e17142c0373343a569c2bcca8c64d259e 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/PaginationTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/PaginationTask.kt
@@ -16,11 +16,11 @@
 
 package org.matrix.android.sdk.internal.session.room.timeline
 
+import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import org.matrix.android.sdk.internal.network.executeRequest
 import org.matrix.android.sdk.internal.session.filter.FilterRepository
 import org.matrix.android.sdk.internal.session.room.RoomAPI
 import org.matrix.android.sdk.internal.task.Task
-import org.greenrobot.eventbus.EventBus
 import javax.inject.Inject
 
 internal interface PaginationTask : Task<PaginationTask.Params, TokenChunkEventPersistor.Result> {
@@ -37,12 +37,12 @@ internal class DefaultPaginationTask @Inject constructor(
         private val roomAPI: RoomAPI,
         private val filterRepository: FilterRepository,
         private val tokenChunkEventPersistor: TokenChunkEventPersistor,
-        private val eventBus: EventBus
+        private val globalErrorReceiver: GlobalErrorReceiver
 ) : PaginationTask {
 
     override suspend fun execute(params: PaginationTask.Params): TokenChunkEventPersistor.Result {
         val filter = filterRepository.getRoomFilter()
-        val chunk = executeRequest<PaginationResponse>(eventBus) {
+        val chunk = executeRequest<PaginationResponse>(globalErrorReceiver) {
             isRetryable = true
             apiCall = roomAPI.getRoomMessagesFrom(params.roomId, params.from, params.direction.value, params.limit, filter)
         }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineInput.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineInput.kt
new file mode 100644
index 0000000000000000000000000000000000000000..002ab1dd8a17fe41aec312fcf90c3a8f88725bea
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineInput.kt
@@ -0,0 +1,45 @@
+/*
+ * 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.
+ * 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.session.room.timeline
+
+import org.matrix.android.sdk.api.session.room.send.SendState
+import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
+import org.matrix.android.sdk.internal.session.SessionScope
+import javax.inject.Inject
+
+@SessionScope
+internal class TimelineInput @Inject constructor() {
+    fun onLocalEchoCreated(roomId: String, timelineEvent: TimelineEvent) {
+        listeners.toSet().forEach { it.onLocalEchoCreated(roomId, timelineEvent) }
+    }
+
+    fun onLocalEchoUpdated(roomId: String, eventId: String, sendState: SendState) {
+        listeners.toSet().forEach { it.onLocalEchoUpdated(roomId, eventId, sendState) }
+    }
+
+    fun onNewTimelineEvents(roomId: String, eventIds: List<String>) {
+        listeners.toSet().forEach { it.onNewTimelineEvents(roomId, eventIds) }
+    }
+
+    val listeners = mutableSetOf<Listener>()
+
+    internal interface Listener {
+        fun onLocalEchoCreated(roomId: String, timelineEvent: TimelineEvent)
+        fun onLocalEchoUpdated(roomId: String, eventId: String, sendState: SendState)
+        fun onNewTimelineEvents(roomId: String, eventIds: List<String>)
+    }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TokenChunkEvent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TokenChunkEvent.kt
index 08b20f1701b5228cdf4049114a0ebac89ed1795c..465b0faac8058f2c72b023192cdb07c69e1ece37 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TokenChunkEvent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TokenChunkEvent.kt
@@ -22,7 +22,7 @@ internal interface TokenChunkEvent {
     val start: String?
     val end: String?
     val events: List<Event>
-    val stateEvents: List<Event>
+    val stateEvents: List<Event>?
 
     fun hasMore() = start != end
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TokenChunkEventPersistor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TokenChunkEventPersistor.kt
index 2a532c6bf55a7a1f25eae4e6d4b57b364df15690..1a497b883555a75d24759a9a6a037d8753b3cf5c 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TokenChunkEventPersistor.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TokenChunkEventPersistor.kt
@@ -156,7 +156,7 @@ internal class TokenChunkEventPersistor @Inject constructor(@SessionDatabase pri
                     }
                 }
         return if (receivedChunk.events.isEmpty()) {
-            if (receivedChunk.start != receivedChunk.end) {
+            if (receivedChunk.hasMore()) {
                 Result.SHOULD_FETCH_MORE
             } else {
                 Result.REACHED_END
@@ -196,7 +196,7 @@ internal class TokenChunkEventPersistor @Inject constructor(@SessionDatabase pri
 
         val now = System.currentTimeMillis()
 
-        for (stateEvent in stateEvents) {
+        stateEvents?.forEach { stateEvent ->
             val ageLocalTs = stateEvent.unsignedData?.age?.let { now - it }
             val stateEventEntity = stateEvent.toEntity(roomId, SendState.SYNCED, ageLocalTs).copyToRealmOrIgnore(realm, EventInsertType.PAGINATION)
             currentChunk.addStateEvent(roomId, stateEventEntity, direction)
@@ -205,9 +205,9 @@ internal class TokenChunkEventPersistor @Inject constructor(@SessionDatabase pri
             }
         }
         val eventIds = ArrayList<String>(eventList.size)
-        for (event in eventList) {
+        eventList.forEach { event ->
             if (event.eventId == null || event.senderId == null) {
-                continue
+                return@forEach
             }
             val ageLocalTs = event.unsignedData?.age?.let { now - it }
             eventIds.add(event.eventId)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/typing/DefaultTypingService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/typing/DefaultTypingService.kt
index 5dcf3fcdd664bce700306a5fbc780d66b04e08f1..39b7967bc1de69314497a86d90e8d0cbeaa03e55 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/typing/DefaultTypingService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/typing/DefaultTypingService.kt
@@ -17,8 +17,9 @@
 package org.matrix.android.sdk.internal.session.room.typing
 
 import android.os.SystemClock
-import com.squareup.inject.assisted.Assisted
-import com.squareup.inject.assisted.AssistedInject
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedInject
+import dagger.assisted.AssistedFactory
 import org.matrix.android.sdk.api.MatrixCallback
 import org.matrix.android.sdk.api.session.room.typing.TypingService
 import org.matrix.android.sdk.api.util.Cancelable
@@ -38,9 +39,9 @@ internal class DefaultTypingService @AssistedInject constructor(
         private val sendTypingTask: SendTypingTask
 ) : TypingService {
 
-    @AssistedInject.Factory
+    @AssistedFactory
     interface Factory {
-        fun create(roomId: String): TypingService
+        fun create(roomId: String): DefaultTypingService
     }
 
     private var currentTask: Cancelable? = null
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/typing/SendTypingTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/typing/SendTypingTask.kt
index c8cbb08e2cc03ef68fe6873e7fb33153b9166079..3b56d04872820b454fa234c56e030069dcc26727 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/typing/SendTypingTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/typing/SendTypingTask.kt
@@ -21,7 +21,7 @@ import org.matrix.android.sdk.internal.network.executeRequest
 import org.matrix.android.sdk.internal.session.room.RoomAPI
 import org.matrix.android.sdk.internal.task.Task
 import kotlinx.coroutines.delay
-import org.greenrobot.eventbus.EventBus
+import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import javax.inject.Inject
 
 internal interface SendTypingTask : Task<SendTypingTask.Params, Unit> {
@@ -38,13 +38,13 @@ internal interface SendTypingTask : Task<SendTypingTask.Params, Unit> {
 internal class DefaultSendTypingTask @Inject constructor(
         private val roomAPI: RoomAPI,
         @UserId private val userId: String,
-        private val eventBus: EventBus
+        private val globalErrorReceiver: GlobalErrorReceiver
 ) : SendTypingTask {
 
     override suspend fun execute(params: SendTypingTask.Params) {
         delay(params.delay ?: -1)
 
-        executeRequest<Unit>(eventBus) {
+        executeRequest<Unit>(globalErrorReceiver) {
             apiCall = roomAPI.sendTypingState(
                     params.roomId,
                     userId,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/uploads/DefaultUploadsService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/uploads/DefaultUploadsService.kt
index 895f1cf50d73982962c462077e115d4a0c4776f1..6d841644dc85bea423edfbea762954aa9c68aafa 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/uploads/DefaultUploadsService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/uploads/DefaultUploadsService.kt
@@ -16,8 +16,9 @@
 
 package org.matrix.android.sdk.internal.session.room.uploads
 
-import com.squareup.inject.assisted.Assisted
-import com.squareup.inject.assisted.AssistedInject
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedInject
+import dagger.assisted.AssistedFactory
 import org.matrix.android.sdk.api.session.crypto.CryptoService
 import org.matrix.android.sdk.api.session.room.uploads.GetUploadsResult
 import org.matrix.android.sdk.api.session.room.uploads.UploadsService
@@ -28,9 +29,9 @@ internal class DefaultUploadsService @AssistedInject constructor(
         private val cryptoService: CryptoService
 ) : UploadsService {
 
-    @AssistedInject.Factory
+    @AssistedFactory
     interface Factory {
-        fun create(roomId: String): UploadsService
+        fun create(roomId: String): DefaultUploadsService
     }
 
     override suspend fun getUploads(numberOfEvents: Int, since: String?): GetUploadsResult {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/uploads/GetUploadsTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/uploads/GetUploadsTask.kt
index d0439ce7f9ab08153a15cd842720d1f4386dfffa..b3e4a5aa0597cdcc6353a19b9429ef64530cc392 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/uploads/GetUploadsTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/uploads/GetUploadsTask.kt
@@ -31,6 +31,7 @@ import org.matrix.android.sdk.internal.database.model.EventEntityFields
 import org.matrix.android.sdk.internal.database.query.TimelineEventFilter
 import org.matrix.android.sdk.internal.database.query.whereType
 import org.matrix.android.sdk.internal.di.SessionDatabase
+import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import org.matrix.android.sdk.internal.network.executeRequest
 import org.matrix.android.sdk.internal.session.filter.FilterFactory
 import org.matrix.android.sdk.internal.session.room.RoomAPI
@@ -39,7 +40,6 @@ import org.matrix.android.sdk.internal.session.room.timeline.PaginationDirection
 import org.matrix.android.sdk.internal.session.room.timeline.PaginationResponse
 import org.matrix.android.sdk.internal.session.sync.SyncTokenStore
 import org.matrix.android.sdk.internal.task.Task
-import org.greenrobot.eventbus.EventBus
 import javax.inject.Inject
 
 internal interface GetUploadsTask : Task<GetUploadsTask.Params, GetUploadsResult> {
@@ -56,8 +56,8 @@ internal class DefaultGetUploadsTask @Inject constructor(
         private val roomAPI: RoomAPI,
         private val tokenStore: SyncTokenStore,
         @SessionDatabase private val monarchy: Monarchy,
-        private val eventBus: EventBus)
-    : GetUploadsTask {
+        private val globalErrorReceiver: GlobalErrorReceiver
+) : GetUploadsTask {
 
     override suspend fun execute(params: GetUploadsTask.Params): GetUploadsResult {
         val result: GetUploadsResult
@@ -86,7 +86,7 @@ internal class DefaultGetUploadsTask @Inject constructor(
             val since = params.since ?: tokenStore.getLastToken() ?: throw IllegalStateException("No token available")
 
             val filter = FilterFactory.createUploadsFilter(params.numberOfEvents).toJSONString()
-            val chunk = executeRequest<PaginationResponse>(eventBus) {
+            val chunk = executeRequest<PaginationResponse>(globalErrorReceiver) {
                 apiCall = roomAPI.getRoomMessagesFrom(params.roomId, since, PaginationDirection.BACKWARDS.value, params.numberOfEvents, filter)
             }
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/search/SearchTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/search/SearchTask.kt
index 4f574e5ead078f3749d0787b5ec3ece389579e45..402602e4d5488382a8c29f375b2ae76b2ea1543b 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/search/SearchTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/search/SearchTask.kt
@@ -16,10 +16,10 @@
 
 package org.matrix.android.sdk.internal.session.search
 
-import org.greenrobot.eventbus.EventBus
 import org.matrix.android.sdk.api.session.search.EventAndSender
 import org.matrix.android.sdk.api.session.search.SearchResult
 import org.matrix.android.sdk.api.util.MatrixItem
+import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import org.matrix.android.sdk.internal.network.executeRequest
 import org.matrix.android.sdk.internal.session.search.request.SearchRequestBody
 import org.matrix.android.sdk.internal.session.search.request.SearchRequestCategories
@@ -47,11 +47,11 @@ internal interface SearchTask : Task<SearchTask.Params, SearchResult> {
 
 internal class DefaultSearchTask @Inject constructor(
         private val searchAPI: SearchAPI,
-        private val eventBus: EventBus
+        private val globalErrorReceiver: GlobalErrorReceiver
 ) : SearchTask {
 
     override suspend fun execute(params: SearchTask.Params): SearchResult {
-        return executeRequest<SearchResponse>(eventBus) {
+        return executeRequest<SearchResponse>(globalErrorReceiver) {
             val searchRequestBody = SearchRequestBody(
                     searchCategories = SearchRequestCategories(
                             roomEvents = SearchRequestRoomEvents(
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/signout/DefaultSignOutService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/signout/DefaultSignOutService.kt
index ea3730b195351dc9308880e31f0d30d0c6106628..e7b20f905bb9b6e5efa3a54ee6f5c6724eb2425d 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/signout/DefaultSignOutService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/signout/DefaultSignOutService.kt
@@ -16,45 +16,25 @@
 
 package org.matrix.android.sdk.internal.session.signout
 
-import org.matrix.android.sdk.api.MatrixCallback
 import org.matrix.android.sdk.api.auth.data.Credentials
 import org.matrix.android.sdk.api.session.signout.SignOutService
-import org.matrix.android.sdk.api.util.Cancelable
 import org.matrix.android.sdk.internal.auth.SessionParamsStore
-import org.matrix.android.sdk.internal.task.TaskExecutor
-import org.matrix.android.sdk.internal.task.configureWith
-import org.matrix.android.sdk.internal.task.launchToCallback
-import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers
 import javax.inject.Inject
 
 internal class DefaultSignOutService @Inject constructor(private val signOutTask: SignOutTask,
                                                          private val signInAgainTask: SignInAgainTask,
-                                                         private val sessionParamsStore: SessionParamsStore,
-                                                         private val coroutineDispatchers: MatrixCoroutineDispatchers,
-                                                         private val taskExecutor: TaskExecutor) : SignOutService {
+                                                         private val sessionParamsStore: SessionParamsStore
+) : SignOutService {
 
-    override fun signInAgain(password: String,
-                             callback: MatrixCallback<Unit>): Cancelable {
-        return signInAgainTask
-                .configureWith(SignInAgainTask.Params(password)) {
-                    this.callback = callback
-                }
-                .executeBy(taskExecutor)
+    override suspend fun signInAgain(password: String) {
+        signInAgainTask.execute(SignInAgainTask.Params(password))
     }
 
-    override fun updateCredentials(credentials: Credentials,
-                                   callback: MatrixCallback<Unit>): Cancelable {
-        return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, callback) {
-            sessionParamsStore.updateCredentials(credentials)
-        }
+    override suspend fun updateCredentials(credentials: Credentials) {
+        sessionParamsStore.updateCredentials(credentials)
     }
 
-    override fun signOut(signOutFromHomeserver: Boolean,
-                         callback: MatrixCallback<Unit>): Cancelable {
-        return signOutTask
-                .configureWith(SignOutTask.Params(signOutFromHomeserver)) {
-                    this.callback = callback
-                }
-                .executeBy(taskExecutor)
+    override suspend fun signOut(signOutFromHomeserver: Boolean) {
+        return signOutTask.execute(SignOutTask.Params(signOutFromHomeserver))
     }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/signout/SignInAgainTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/signout/SignInAgainTask.kt
index 3bed0bdbff1192b4d26e3d3512f46b06d0228930..2c3cd5d2703fc9423c6073bf08a1604f57b51475 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/signout/SignInAgainTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/signout/SignInAgainTask.kt
@@ -20,9 +20,9 @@ import org.matrix.android.sdk.api.auth.data.Credentials
 import org.matrix.android.sdk.api.auth.data.SessionParams
 import org.matrix.android.sdk.internal.auth.SessionParamsStore
 import org.matrix.android.sdk.internal.auth.data.PasswordLoginParams
+import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import org.matrix.android.sdk.internal.network.executeRequest
 import org.matrix.android.sdk.internal.task.Task
-import org.greenrobot.eventbus.EventBus
 import javax.inject.Inject
 
 internal interface SignInAgainTask : Task<SignInAgainTask.Params, Unit> {
@@ -35,11 +35,11 @@ internal class DefaultSignInAgainTask @Inject constructor(
         private val signOutAPI: SignOutAPI,
         private val sessionParams: SessionParams,
         private val sessionParamsStore: SessionParamsStore,
-        private val eventBus: EventBus
+        private val globalErrorReceiver: GlobalErrorReceiver
 ) : SignInAgainTask {
 
     override suspend fun execute(params: SignInAgainTask.Params) {
-        val newCredentials = executeRequest<Credentials>(eventBus) {
+        val newCredentials = executeRequest<Credentials>(globalErrorReceiver) {
             apiCall = signOutAPI.loginAgain(
                     PasswordLoginParams.userIdentifier(
                             // Reuse the same userId
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/signout/SignOutTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/signout/SignOutTask.kt
index 153ea5a6fde57e7aa0f14e8f4ea66194935d3711..0cb8704782fa329d354de51da3952bd93553196e 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/signout/SignOutTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/signout/SignOutTask.kt
@@ -18,11 +18,11 @@ package org.matrix.android.sdk.internal.session.signout
 
 import org.matrix.android.sdk.api.failure.Failure
 import org.matrix.android.sdk.api.failure.MatrixError
+import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import org.matrix.android.sdk.internal.network.executeRequest
 import org.matrix.android.sdk.internal.session.cleanup.CleanupSession
 import org.matrix.android.sdk.internal.session.identity.IdentityDisconnectTask
 import org.matrix.android.sdk.internal.task.Task
-import org.greenrobot.eventbus.EventBus
 import timber.log.Timber
 import java.net.HttpURLConnection
 import javax.inject.Inject
@@ -35,7 +35,7 @@ internal interface SignOutTask : Task<SignOutTask.Params, Unit> {
 
 internal class DefaultSignOutTask @Inject constructor(
         private val signOutAPI: SignOutAPI,
-        private val eventBus: EventBus,
+        private val globalErrorReceiver: GlobalErrorReceiver,
         private val identityDisconnectTask: IdentityDisconnectTask,
         private val cleanupSession: CleanupSession
 ) : SignOutTask {
@@ -45,7 +45,7 @@ internal class DefaultSignOutTask @Inject constructor(
         if (params.signOutFromHomeserver) {
             Timber.d("SignOut: send request...")
             try {
-                executeRequest<Unit>(eventBus) {
+                executeRequest<Unit>(globalErrorReceiver) {
                     apiCall = signOutAPI.signOut()
                 }
             } catch (throwable: Throwable) {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomSyncHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomSyncHandler.kt
index b1b2f65dc2fdde7ee6fff658d95a4bdb1fa383ac..456b0f9c2625224be559f61e3675967889e99347 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomSyncHandler.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomSyncHandler.kt
@@ -18,7 +18,6 @@ package org.matrix.android.sdk.internal.session.sync
 
 import io.realm.Realm
 import io.realm.kotlin.createObject
-import org.greenrobot.eventbus.EventBus
 import org.matrix.android.sdk.R
 import org.matrix.android.sdk.api.session.crypto.MXCryptoError
 import org.matrix.android.sdk.api.session.events.model.Event
@@ -55,8 +54,8 @@ import org.matrix.android.sdk.internal.session.room.membership.RoomChangeMembers
 import org.matrix.android.sdk.internal.session.room.membership.RoomMemberEventHandler
 import org.matrix.android.sdk.internal.session.room.read.FullyReadContent
 import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryUpdater
-import org.matrix.android.sdk.internal.session.room.timeline.DefaultTimeline
 import org.matrix.android.sdk.internal.session.room.timeline.PaginationDirection
+import org.matrix.android.sdk.internal.session.room.timeline.TimelineInput
 import org.matrix.android.sdk.internal.session.room.typing.TypingEventContent
 import org.matrix.android.sdk.internal.session.sync.model.InvitedRoomSync
 import org.matrix.android.sdk.internal.session.sync.model.RoomSync
@@ -75,7 +74,7 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
                                                    private val roomTypingUsersHandler: RoomTypingUsersHandler,
                                                    private val roomChangeMembershipStateDataSource: RoomChangeMembershipStateDataSource,
                                                    @UserId private val userId: String,
-                                                   private val eventBus: EventBus) {
+                                                   private val timelineInput: TimelineInput) {
 
     sealed class HandlingStrategy {
         data class JOINED(val data: Map<String, RoomSync>) : HandlingStrategy()
@@ -348,7 +347,7 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
             }
         }
         // posting new events to timeline if any is registered
-        eventBus.post(DefaultTimeline.OnNewTimelineEvents(roomId = roomId, eventIds = eventIds))
+        timelineInput.onNewTimelineEvents(roomId = roomId, eventIds = eventIds)
         return chunkEntity
     }
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncTask.kt
index b4fd6e7386ac6c0d0a803e0678ed7509513f20b9..7c38230065ff8bb5c6ff3e5001bb0e95b0a5a64e 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncTask.kt
@@ -16,9 +16,9 @@
 
 package org.matrix.android.sdk.internal.session.sync
 
-import org.greenrobot.eventbus.EventBus
 import org.matrix.android.sdk.R
 import org.matrix.android.sdk.internal.di.UserId
+import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import org.matrix.android.sdk.internal.network.TimeOutInterceptor
 import org.matrix.android.sdk.internal.network.executeRequest
 import org.matrix.android.sdk.internal.session.DefaultInitialSyncProgressService
@@ -48,7 +48,7 @@ internal class DefaultSyncTask @Inject constructor(
         private val getHomeServerCapabilitiesTask: GetHomeServerCapabilitiesTask,
         private val userStore: UserStore,
         private val syncTaskSequencer: SyncTaskSequencer,
-        private val eventBus: EventBus
+        private val globalErrorReceiver: GlobalErrorReceiver
 ) : SyncTask {
 
     override suspend fun execute(params: SyncTask.Params) = syncTaskSequencer.post {
@@ -81,7 +81,7 @@ internal class DefaultSyncTask @Inject constructor(
 
         val readTimeOut = (params.timeout + TIMEOUT_MARGIN).coerceAtLeast(TimeOutInterceptor.DEFAULT_LONG_TIMEOUT)
 
-        val syncResponse = executeRequest<SyncResponse>(eventBus) {
+        val syncResponse = executeRequest<SyncResponse>(globalErrorReceiver) {
             apiCall = syncAPI.sync(
                     params = requestParams,
                     readTimeOut = readTimeOut
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncService.kt
index 6d100a71f90210e4253de08e7af88e7d1833c01a..cce169c2464af123907e3c25f942b08692313746 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncService.kt
@@ -36,6 +36,7 @@ import org.matrix.android.sdk.internal.task.TaskExecutor
 import org.matrix.android.sdk.internal.util.BackgroundDetectionObserver
 import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers
 import timber.log.Timber
+import java.net.SocketTimeoutException
 import java.util.concurrent.atomic.AtomicBoolean
 
 /**
@@ -49,8 +50,9 @@ abstract class SyncService : Service() {
     private var sessionId: String? = null
     private var mIsSelfDestroyed: Boolean = false
 
-    private var syncTimeoutSeconds: Int = 6
-    private var syncDelaySeconds: Int = 60
+    private var syncTimeoutSeconds: Int = getDefaultSyncTimeoutSeconds()
+    private var syncDelaySeconds: Int = getDefaultSyncDelaySeconds()
+
     private var periodic: Boolean = false
     private var preventReschedule: Boolean = false
 
@@ -68,14 +70,12 @@ abstract class SyncService : Service() {
 
     override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
         Timber.i("## Sync: onStartCommand [$this] $intent with action: ${intent?.action}")
-
-        // We should start we have to ensure we fulfill contract to show notification
-        // for foreground service (as per design for this service)
-        // TODO can we check if it's really in foreground
-        onStart(isInitialSync)
         when (intent?.action) {
             ACTION_STOP -> {
                 Timber.i("## Sync: stop command received")
+                // We should start we have to ensure we fulfill contract to show notification
+                // for foreground service (as per design for this service)
+                onStart(isInitialSync)
                 // If it was periodic we ensure that it will not reschedule itself
                 preventReschedule = true
                 // we don't want to cancel initial syncs, let it finish
@@ -85,11 +85,12 @@ abstract class SyncService : Service() {
             }
             else        -> {
                 val isInit = initialize(intent)
+                onStart(isInitialSync)
                 if (isInit) {
                     periodic = intent?.getBooleanExtra(EXTRA_PERIODIC, false) ?: false
                     val onNetworkBack = intent?.getBooleanExtra(EXTRA_NETWORK_BACK_RESTART, false) ?: false
                     Timber.d("## Sync: command received, periodic: $periodic  networkBack: $onNetworkBack")
-                    if (onNetworkBack && !backgroundDetectionObserver.isInBackground) {
+                    if (!isInitialSync && onNetworkBack && !backgroundDetectionObserver.isInBackground) {
                         // the restart after network occurs while the app is in foreground
                         // so just stop. It will be restarted when entering background
                         preventReschedule = true
@@ -119,7 +120,11 @@ abstract class SyncService : Service() {
         serviceScope.coroutineContext.cancelChildren()
         if (!preventReschedule && periodic && sessionId != null && backgroundDetectionObserver.isInBackground) {
             Timber.d("## Sync: Reschedule service in $syncDelaySeconds sec")
-            onRescheduleAsked(sessionId ?: "", false, syncTimeoutSeconds, syncDelaySeconds)
+            onRescheduleAsked(
+                    sessionId = sessionId ?: "",
+                    syncTimeoutSeconds = syncTimeoutSeconds,
+                    syncDelaySeconds = syncDelaySeconds
+            )
         }
         super.onDestroy()
     }
@@ -165,10 +170,23 @@ abstract class SyncService : Service() {
                 preventReschedule = true
             }
             if (throwable is Failure.NetworkConnection) {
-                // Network is off, no need to reschedule endless alarms :/
+                // Timeout is not critical, so retry as soon as possible.
+                if (throwable.cause is SocketTimeoutException) {
+                    // For big accounts, computing sync response can take time, but Synapse will cache the
+                    // result for the next request. So keep retrying in loop
+                    Timber.w("Timeout during sync, retry in loop")
+                    doSync()
+                    return
+                }
+                // Network might be off, no need to reschedule endless alarms :/
                 preventReschedule = true
-                // Instead start a work to restart background sync when network is back
-                onNetworkError(sessionId ?: "", isInitialSync, syncTimeoutSeconds, syncDelaySeconds)
+                // Instead start a work to restart background sync when network is on
+                onNetworkError(
+                        sessionId = sessionId ?: "",
+                        syncTimeoutSeconds = syncTimeoutSeconds,
+                        syncDelaySeconds = syncDelaySeconds,
+                        isPeriodic = periodic
+                )
             }
             // JobCancellation could be caught here when onDestroy cancels the coroutine context
             if (isRunning.get()) stopMe()
@@ -182,8 +200,8 @@ abstract class SyncService : Service() {
         }
         val matrix = Matrix.getInstance(applicationContext)
         val safeSessionId = intent.getStringExtra(EXTRA_SESSION_ID) ?: return false
-        syncTimeoutSeconds = intent.getIntExtra(EXTRA_TIMEOUT_SECONDS, 6)
-        syncDelaySeconds = intent.getIntExtra(EXTRA_DELAY_SECONDS, 60)
+        syncTimeoutSeconds = intent.getIntExtra(EXTRA_TIMEOUT_SECONDS, getDefaultSyncTimeoutSeconds())
+        syncDelaySeconds = intent.getIntExtra(EXTRA_DELAY_SECONDS, getDefaultSyncDelaySeconds())
         try {
             val sessionComponent = matrix.sessionManager.getSessionComponent(safeSessionId)
                     ?: throw IllegalStateException("## Sync: You should have a session to make it work")
@@ -202,11 +220,15 @@ abstract class SyncService : Service() {
         }
     }
 
+    abstract fun getDefaultSyncTimeoutSeconds(): Int
+
+    abstract fun getDefaultSyncDelaySeconds(): Int
+
     abstract fun onStart(isInitialSync: Boolean)
 
-    abstract fun onRescheduleAsked(sessionId: String, isInitialSync: Boolean, timeout: Int, delay: Int)
+    abstract fun onRescheduleAsked(sessionId: String, syncTimeoutSeconds: Int, syncDelaySeconds: Int)
 
-    abstract fun onNetworkError(sessionId: String, isInitialSync: Boolean, timeout: Int, delay: Int)
+    abstract fun onNetworkError(sessionId: String, syncTimeoutSeconds: Int, syncDelaySeconds: Int, isPeriodic: Boolean)
 
     override fun onBind(intent: Intent?): IBinder? {
         return null
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/UpdateIgnoredUserIdsTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/UpdateIgnoredUserIdsTask.kt
index d1393c8b3706d673bf3085baa62480172865b570..26e8d3380a04cf8f2dff84e73cf6ffbcd9f633c2 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/UpdateIgnoredUserIdsTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/UpdateIgnoredUserIdsTask.kt
@@ -17,11 +17,11 @@
 package org.matrix.android.sdk.internal.session.user.accountdata
 
 import com.zhuinden.monarchy.Monarchy
-import org.greenrobot.eventbus.EventBus
 import org.matrix.android.sdk.api.session.accountdata.UserAccountDataTypes
 import org.matrix.android.sdk.internal.database.model.IgnoredUserEntity
 import org.matrix.android.sdk.internal.di.SessionDatabase
 import org.matrix.android.sdk.internal.di.UserId
+import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import org.matrix.android.sdk.internal.network.executeRequest
 import org.matrix.android.sdk.internal.session.sync.model.accountdata.IgnoredUsersContent
 import org.matrix.android.sdk.internal.task.Task
@@ -40,7 +40,7 @@ internal class DefaultUpdateIgnoredUserIdsTask @Inject constructor(
         @SessionDatabase private val monarchy: Monarchy,
         private val saveIgnoredUsersTask: SaveIgnoredUsersTask,
         @UserId private val userId: String,
-        private val eventBus: EventBus
+        private val globalErrorReceiver: GlobalErrorReceiver
 ) : UpdateIgnoredUserIdsTask {
 
     override suspend fun execute(params: UpdateIgnoredUserIdsTask.Params) {
@@ -63,7 +63,7 @@ internal class DefaultUpdateIgnoredUserIdsTask @Inject constructor(
         val list = ignoredUserIds.toList()
         val body = IgnoredUsersContent.createWithUserIds(list)
 
-        executeRequest<Unit>(eventBus) {
+        executeRequest<Unit>(globalErrorReceiver) {
             apiCall = accountDataApi.setAccountData(userId, UserAccountDataTypes.TYPE_IGNORED_USER_LIST, body)
         }
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/UpdateUserAccountDataTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/UpdateUserAccountDataTask.kt
index 80ab79b22800115f46a94e065bffc8c08236df00..dba28253a7440fc1358c6605a86792634068d39a 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/UpdateUserAccountDataTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/UpdateUserAccountDataTask.kt
@@ -24,8 +24,8 @@ import org.matrix.android.sdk.internal.session.sync.model.accountdata.AcceptedTe
 import org.matrix.android.sdk.internal.session.sync.model.accountdata.BreadcrumbsContent
 import org.matrix.android.sdk.internal.session.sync.model.accountdata.IdentityServerContent
 import org.matrix.android.sdk.api.session.accountdata.UserAccountDataTypes
+import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import org.matrix.android.sdk.internal.task.Task
-import org.greenrobot.eventbus.EventBus
 import javax.inject.Inject
 
 internal interface UpdateUserAccountDataTask : Task<UpdateUserAccountDataTask.Params, Unit> {
@@ -100,11 +100,11 @@ internal interface UpdateUserAccountDataTask : Task<UpdateUserAccountDataTask.Pa
 internal class DefaultUpdateUserAccountDataTask @Inject constructor(
         private val accountDataApi: AccountDataAPI,
         @UserId private val userId: String,
-        private val eventBus: EventBus
+        private val globalErrorReceiver: GlobalErrorReceiver
 ) : UpdateUserAccountDataTask {
 
     override suspend fun execute(params: UpdateUserAccountDataTask.Params) {
-        return executeRequest(eventBus) {
+        return executeRequest(globalErrorReceiver) {
             apiCall = accountDataApi.setAccountData(userId, params.type, params.getData())
         }
     }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/model/SearchUserTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/model/SearchUserTask.kt
index cd9be0e7ddee1c819f63e0d2404e6174ed94d915..380fa6e209cdb47293c1ee9df2c63bc79356036d 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/model/SearchUserTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/model/SearchUserTask.kt
@@ -17,10 +17,10 @@
 package org.matrix.android.sdk.internal.session.user.model
 
 import org.matrix.android.sdk.api.session.user.model.User
+import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import org.matrix.android.sdk.internal.network.executeRequest
 import org.matrix.android.sdk.internal.session.user.SearchUserAPI
 import org.matrix.android.sdk.internal.task.Task
-import org.greenrobot.eventbus.EventBus
 import javax.inject.Inject
 
 internal interface SearchUserTask : Task<SearchUserTask.Params, List<User>> {
@@ -34,11 +34,11 @@ internal interface SearchUserTask : Task<SearchUserTask.Params, List<User>> {
 
 internal class DefaultSearchUserTask @Inject constructor(
         private val searchUserAPI: SearchUserAPI,
-        private val eventBus: EventBus
+        private val globalErrorReceiver: GlobalErrorReceiver
 ) : SearchUserTask {
 
     override suspend fun execute(params: SearchUserTask.Params): List<User> {
-        val response = executeRequest<SearchUsersResponse>(eventBus) {
+        val response = executeRequest<SearchUsersResponse>(globalErrorReceiver) {
             apiCall = searchUserAPI.searchUsers(SearchUsersParams(params.search, params.limit))
         }
         return response.users.map {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/CreateWidgetTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/CreateWidgetTask.kt
index 422615af2d35c4dc362a31930fa6deb1afdb6d90..ae807ce30fefed2f472b4101b386c611d7105699 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/CreateWidgetTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/CreateWidgetTask.kt
@@ -25,10 +25,10 @@ import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntityFie
 import org.matrix.android.sdk.internal.database.query.whereStateKey
 import org.matrix.android.sdk.internal.di.SessionDatabase
 import org.matrix.android.sdk.internal.di.UserId
+import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import org.matrix.android.sdk.internal.network.executeRequest
 import org.matrix.android.sdk.internal.session.room.RoomAPI
 import org.matrix.android.sdk.internal.task.Task
-import org.greenrobot.eventbus.EventBus
 import javax.inject.Inject
 
 internal interface CreateWidgetTask : Task<CreateWidgetTask.Params, Unit> {
@@ -43,10 +43,10 @@ internal interface CreateWidgetTask : Task<CreateWidgetTask.Params, Unit> {
 internal class DefaultCreateWidgetTask @Inject constructor(@SessionDatabase private val monarchy: Monarchy,
                                                            private val roomAPI: RoomAPI,
                                                            @UserId private val userId: String,
-                                                           private val eventBus: EventBus) : CreateWidgetTask {
+                                                           private val globalErrorReceiver: GlobalErrorReceiver) : CreateWidgetTask {
 
     override suspend fun execute(params: CreateWidgetTask.Params) {
-        executeRequest<Unit>(eventBus) {
+        executeRequest<Unit>(globalErrorReceiver) {
             apiCall = roomAPI.sendStateEvent(
                     roomId = params.roomId,
                     stateEventType = EventType.STATE_ROOM_WIDGET_LEGACY,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/helper/WidgetFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/helper/WidgetFactory.kt
index c41f1df0de82b6ec20d3ea2dceb35264d41c6bf3..000b9e38b9e8027774fb33329f48cffd4eafe5c5 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/helper/WidgetFactory.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/helper/WidgetFactory.kt
@@ -53,7 +53,7 @@ internal class WidgetFactory @Inject constructor(private val userDataSource: Use
             }
         }
         val isAddedByMe = widgetEvent.senderId == userId
-        val computedUrl = widgetContent.computeURL(widgetEvent.roomId)
+        val computedUrl = widgetContent.computeURL(widgetEvent.roomId, widgetId)
         return Widget(
                 widgetContent = widgetContent,
                 event = widgetEvent,
@@ -65,13 +65,14 @@ internal class WidgetFactory @Inject constructor(private val userDataSource: Use
         )
     }
 
-    private fun WidgetContent.computeURL(roomId: String?): String? {
+    private fun WidgetContent.computeURL(roomId: String?, widgetId: String): String? {
         var computedUrl = url ?: return null
         val myUser = userDataSource.getUser(userId)
         computedUrl = computedUrl
                 .replace("\$matrix_user_id", userId)
                 .replace("\$matrix_display_name", myUser?.displayName ?: userId)
                 .replace("\$matrix_avatar_url", myUser?.avatarUrl ?: "")
+                .replace("\$matrix_widget_id", widgetId)
 
         if (roomId != null) {
             computedUrl = computedUrl.replace("\$matrix_room_id", roomId)
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 c6647f757238a3335d23e3c17e02aa374ef6cf68..b58cab99b5d4fbcf6ab5f524965483880d8e175e 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
@@ -20,6 +20,7 @@ import android.content.Context
 import androidx.work.ListenableWorker
 import androidx.work.WorkerFactory
 import androidx.work.WorkerParameters
+import timber.log.Timber
 import javax.inject.Inject
 import javax.inject.Provider
 
@@ -32,6 +33,8 @@ class MatrixWorkerFactory @Inject constructor(
             workerClassName: String,
             workerParameters: WorkerParameters
     ): ListenableWorker? {
+        Timber.d("MatrixWorkerFactory.createWorker for $workerClassName")
+
         val foundEntry =
                 workerFactories.entries.find { Class.forName(workerClassName).isAssignableFrom(it.key) }
         val factoryProvider = foundEntry?.value
diff --git a/matrix-sdk-android/src/main/res/values-cs/strings.xml b/matrix-sdk-android/src/main/res/values-cs/strings.xml
index 50dea12b09c799d1eaf76f81d3a08e399eb8eac3..adb35179279190c52c2b3c74d90b5591b0b414d5 100644
--- a/matrix-sdk-android/src/main/res/values-cs/strings.xml
+++ b/matrix-sdk-android/src/main/res/values-cs/strings.xml
@@ -242,4 +242,30 @@
     <string name="notice_room_server_acl_set_banned">• Server shodující se s %s je zakázán.</string>
     <string name="notice_room_server_acl_set_title_by_you">Nastavili jste ACL serveru pro tuto místnost.</string>
     <string name="notice_room_server_acl_set_title">%s nastavili ACL serveru pro tuto místnost.</string>
+    <string name="notice_room_canonical_alias_no_change_by_you">Změnili jste adresy pro tuto místnost.</string>
+    <string name="notice_room_canonical_alias_no_change">%1$s změnili adresy pro tuto místnost.</string>
+    <string name="notice_room_canonical_alias_main_and_alternative_changed_by_you">Změnili jste hlavní a alternativní adresu pro tuto místnost.</string>
+    <string name="notice_room_canonical_alias_main_and_alternative_changed">%1$s změnili hlavní a alternativní adresu pro tuto místnost.</string>
+    <string name="notice_room_canonical_alias_alternative_changed_by_you">Změnili jste alternativní adresu pro tuto místnost.</string>
+    <string name="notice_room_canonical_alias_alternative_changed">%1$s změnili alternativní adresu pro tuto místnost.</string>
+    <plurals name="notice_room_canonical_alias_alternative_removed_by_you">
+        <item quantity="one">Odstranili jste alternativní adresu %1$s pro tuto místnost.</item>
+        <item quantity="few">Odstranili jste alternativní adresy %1$s pro tuto místnost.</item>
+        <item quantity="other">Odstranili jste alternativní adresy %1$s pro tuto místnost.</item>
+    </plurals>
+    <plurals name="notice_room_canonical_alias_alternative_removed">
+        <item quantity="one">%1$s odstranili alternativní adresu %2$s pro tuto místnost.</item>
+        <item quantity="few">%1$s odstranili alternativní adresy %2$s pro tuto místnost.</item>
+        <item quantity="other">%1$s odstranili alternativní adresy %2$s pro tuto místnost.</item>
+    </plurals>
+    <plurals name="notice_room_canonical_alias_alternative_added_by_you">
+        <item quantity="one">Přidali jste alternativní adresu %1$s pro tuto místnost.</item>
+        <item quantity="few">Přidali jste alternativní adresy %1$s pro tuto místnost.</item>
+        <item quantity="other">Přidali jste alternativní adresy %1$s pro tuto místnost.</item>
+    </plurals>
+    <plurals name="notice_room_canonical_alias_alternative_added">
+        <item quantity="one">%1$s přidali alternativní adresu %2$s pro tuto místnost.</item>
+        <item quantity="few">%1$s přidali alternativní adresy %2$s pro tuto místnost.</item>
+        <item quantity="other">%1$s přidali alternativní adresy %2$s pro tuto místnost.</item>
+    </plurals>
 </resources>
\ No newline at end of file
diff --git a/matrix-sdk-android/src/main/res/values-et/strings.xml b/matrix-sdk-android/src/main/res/values-et/strings.xml
index 957c0b9955310acee50291aec2ac1e1048f71b4a..af2cc33b99c9e41952f1d92a6e5ae821413717f8 100644
--- a/matrix-sdk-android/src/main/res/values-et/strings.xml
+++ b/matrix-sdk-android/src/main/res/values-et/strings.xml
@@ -47,7 +47,7 @@
     <string name="notice_crypto_error_unkwown_inbound_session_id">Sõnumi saatja seade ei ole selle sõnumi jaoks saatnud dekrüptimisvõtmeid.</string>
     <string name="could_not_redact">Ei saanud muuta sõnumit</string>
     <string name="unable_to_send_message">Sõnumi saatmine ei õnnestunud</string>
-    <string name="message_failed_to_upload">Faili üles laadimine ei õnnestunud</string>
+    <string name="message_failed_to_upload">Pildi üleslaadimine ei õnnestunud</string>
     <string name="network_error">Võrguühenduse viga</string>
     <string name="matrix_error">Matrix\'i viga</string>
     <string name="room_error_join_failed_empty_room">Hetkel ei ole võimalik uuesti liituda tühja jututoaga.</string>
@@ -236,4 +236,26 @@
     <string name="notice_room_server_acl_set_ip_literals_allowed">• Lubatud on serverid, mille ip-aadress vastab mustrile.</string>
     <string name="notice_room_server_acl_set_allowed">• Lubatud on serverid, mille nimes leidub %s.</string>
     <string name="notice_room_server_acl_set_banned">• Keelatud on serverid, mille nimes leidub %s.</string>
+    <string name="notice_room_canonical_alias_no_change">%1$s muutis selle jututoa aadresse.</string>
+    <string name="notice_room_canonical_alias_main_and_alternative_changed_by_you">Sa muutsid selle jututoa põhiaadressi ja täiendavaid aadresse.</string>
+    <string name="notice_room_canonical_alias_alternative_changed">%1$s muutis selle jututoa täiendavaid aadresse.</string>
+    <string name="notice_room_canonical_alias_alternative_changed_by_you">Sa muutsid selle jututoa täiendavaid aadresse.</string>
+    <string name="notice_room_canonical_alias_main_and_alternative_changed">%1$s muutis selle jututoa põhiaadressi ja täiendavaid aadresse.</string>
+    <plurals name="notice_room_canonical_alias_alternative_removed_by_you">
+        <item quantity="one">Sa eemaldasid selle jututoa täiendava aadressi %1$s.</item>
+        <item quantity="other">Sa eemaldasid selle jututoa täiendavad aadressid %1$s.</item>
+    </plurals>
+    <plurals name="notice_room_canonical_alias_alternative_removed">
+        <item quantity="one">%1$s eemaldas selle jututoa täiendava aadressi %2$s.</item>
+        <item quantity="other">%1$s eemaldas selle jututoa täiendavad aadressid %2$s.</item>
+    </plurals>
+    <plurals name="notice_room_canonical_alias_alternative_added_by_you">
+        <item quantity="one">Sa lisasid sellele jututoale täiendava aadressi %1$s.</item>
+        <item quantity="other">Sa lisasid sellele jututoale täiendavad aadressid %1$s.</item>
+    </plurals>
+    <plurals name="notice_room_canonical_alias_alternative_added">
+        <item quantity="one">%1$s lisas sellele jututoale täiendava aadressi %2$s.</item>
+        <item quantity="other">%1$s lisas sellele jututoale täiendavad aadressid %2$s.</item>
+    </plurals>
+    <string name="notice_room_canonical_alias_no_change_by_you">Sa muutsid selle jututoa aadresse.</string>
 </resources>
\ No newline at end of file
diff --git a/matrix-sdk-android/src/main/res/values-fa/strings.xml b/matrix-sdk-android/src/main/res/values-fa/strings.xml
index 8f8059067e554707e2a2d4467b4d86607422da6b..50446b9708c5d79de412300207fa5c720ba25dc0 100644
--- a/matrix-sdk-android/src/main/res/values-fa/strings.xml
+++ b/matrix-sdk-android/src/main/res/values-fa/strings.xml
@@ -218,4 +218,5 @@
         <item quantity="other">%1$s، %2$s، %3$s و %4$d نفر دیگر</item>
     </plurals>
     <string name="room_displayname_3_members">%1$s، %2$s و %3$s</string>
+    <string name="room_displayname_4_members">%1$s، %2$s، %3$s و %4$s</string>
 </resources>
\ No newline at end of file
diff --git a/matrix-sdk-android/src/main/res/values-it/strings.xml b/matrix-sdk-android/src/main/res/values-it/strings.xml
index c1a5cf85cbcc0557261feb87743ffe00ad20c622..ec19cd5c17e072ae7c7add030363b3135bd92210 100644
--- a/matrix-sdk-android/src/main/res/values-it/strings.xml
+++ b/matrix-sdk-android/src/main/res/values-it/strings.xml
@@ -102,7 +102,7 @@
     </plurals>
     <plurals name="notice_room_aliases_removed">
         <item quantity="one">%1$s ha rimosso %2$s come indirizzo per questa stanza.</item>
-        <item quantity="other">%1$s ha rimosso %3$s come indirizzi per questa stanza.</item>
+        <item quantity="other">%1$s ha rimosso %2$s come indirizzi per questa stanza.</item>
     </plurals>
     <string name="notice_room_aliases_added_and_removed">%1$s ha aggiunto %2$s e rimosso %3$s come indirizzi per questa stanza.</string>
     <string name="notice_room_canonical_alias_set">%1$s ha impostato l\'indirizzo principale per questa stanza a %2$s.</string>
@@ -237,4 +237,26 @@
     <string name="notice_room_server_acl_set_banned">• I server che corrispondono a %s sono banditi.</string>
     <string name="notice_room_server_acl_set_title_by_you">Hai impostato le ACL del server per questa stanza.</string>
     <string name="notice_room_server_acl_set_title">%s ha impostato le ACL del server per questa stanza.</string>
+    <string name="notice_room_canonical_alias_no_change_by_you">Hai cambiato gli indirizzi per questa stanza.</string>
+    <string name="notice_room_canonical_alias_no_change">%1$s ha cambiato gli indirizzi per questa stanza.</string>
+    <string name="notice_room_canonical_alias_main_and_alternative_changed_by_you">Hai cambiato gli indirizzi principali ed alternativi per questa stanza.</string>
+    <string name="notice_room_canonical_alias_main_and_alternative_changed">%1$s ha cambiato gli indirizzi principali ed alternativi per questa stanza.</string>
+    <string name="notice_room_canonical_alias_alternative_changed_by_you">Hai cambiato gli indirizzi alternativi per questa stanza.</string>
+    <string name="notice_room_canonical_alias_alternative_changed">%1$s ha cambiato gli indirizzi alternativi per questa stanza.</string>
+    <plurals name="notice_room_canonical_alias_alternative_removed_by_you">
+        <item quantity="one">Hai rimosso l\'indirizzo alternativo %1$s per questa stanza.</item>
+        <item quantity="other">Hai rimosso gli indirizzi alternativi %1$s per questa stanza.</item>
+    </plurals>
+    <plurals name="notice_room_canonical_alias_alternative_removed">
+        <item quantity="one">%1$s ha rimosso l\'indirizzo alternativo %2$s per questa stanza.</item>
+        <item quantity="other">%1$s ha rimosso gli indirizzi alternativi %2$s per questa stanza.</item>
+    </plurals>
+    <plurals name="notice_room_canonical_alias_alternative_added_by_you">
+        <item quantity="one">Hai aggiunto l\'indirizzo alternativo %1$s per questa stanza.</item>
+        <item quantity="other">Hai aggiunto gli indirizzi alternativi %1$s per questa stanza.</item>
+    </plurals>
+    <plurals name="notice_room_canonical_alias_alternative_added">
+        <item quantity="one">%1$s ha aggiunto l\'indirizzo alternativo %2$s per questa stanza.</item>
+        <item quantity="other">%1$s ha aggiunto gli indirizzi alternativi %2$s per questa stanza.</item>
+    </plurals>
 </resources>
\ No newline at end of file
diff --git a/matrix-sdk-android/src/main/res/values-lv/strings.xml b/matrix-sdk-android/src/main/res/values-lv/strings.xml
index b14cbb4b000a9a44a952e6400a3efe1a9c897e14..ec107b47d6bd385cd63704e78d060e8e3f302f8c 100644
--- a/matrix-sdk-android/src/main/res/values-lv/strings.xml
+++ b/matrix-sdk-android/src/main/res/values-lv/strings.xml
@@ -1,8 +1,7 @@
-<?xml version='1.0' encoding='UTF-8'?>
+<?xml version="1.0" encoding="utf-8"?>
 <resources>
     <string name="summary_message">%1$s: %2$s</string>
     <string name="summary_user_sent_image">%1$s nosūtīja attēlu.</string>
-
     <string name="notice_room_invite_no_invitee">%s\'s uzaicinājums</string>
     <string name="notice_room_invite">%1$s uzaicināja %2$s</string>
     <string name="notice_room_invite_you">%1$s uzaicināja tevi</string>
@@ -30,46 +29,56 @@
     <string name="notice_room_visibility_world_readable">ikviens.</string>
     <string name="notice_room_visibility_unknown">nezināms (%s).</string>
     <string name="notice_end_to_end">%1$s ieslēdza ierīce-ierīce šifrēšanu (%2$s)</string>
-
     <string name="notice_requested_voip_conference">%1$s vēlas VoIP konferenci</string>
     <string name="notice_voip_started">VoIP konference sākusies</string>
     <string name="notice_voip_finished">VoIP konference ir beigusies</string>
-
     <string name="notice_avatar_changed_too">(arī profila attēls mainījās)</string>
     <string name="notice_room_name_removed">%1$s dzēsa istabas nosaukumu</string>
     <string name="notice_room_topic_removed">%1$s dzēsa istabas tēmas nosaukumu</string>
     <string name="notice_profile_change_redacted">%1$s atjaunoja profila informāciju %2$s</string>
     <string name="notice_room_third_party_invite">%1$s nosūtīja uzaicinājumu %2$s pievienoties istabai</string>
     <string name="notice_room_third_party_registered_invite">%1$s apstiprināja uzaicinājumu priekš %2$s</string>
-
     <string name="notice_crypto_unable_to_decrypt">** Nav iespējams atkodēt: %s **</string>
     <string name="notice_crypto_error_unkwown_inbound_session_id">Sūtītāja ierīce mums nenosūtīja atslēgas priekš šīs ziņas.</string>
-
     <string name="could_not_redact">Nevarēja rediģēt</string>
     <string name="unable_to_send_message">Nav iespējams nosūtīt ziņu</string>
-
     <string name="message_failed_to_upload">Neizdevās augšuplādēt attēlu</string>
-
     <string name="network_error">Tīkla kļūda</string>
     <string name="matrix_error">Matrix kļūda</string>
-
     <string name="room_error_join_failed_empty_room">Šobrīd nav iespējams atkārtoti pievienoties tukšai istabai.</string>
-
     <string name="encrypted_message">Šifrēta ziņa</string>
-
     <string name="medium_email">Epasta adrese</string>
     <string name="medium_phone_number">Telefona numurs</string>
-
     <string name="room_displayname_invite_from">Uzaicinājums no %s</string>
     <string name="room_displayname_room_invite">Uzaicinājums uz istabu</string>
     <string name="room_displayname_two_members">%1$s un %2$s</string>
     <string name="room_displayname_empty_room">Tukša istaba</string>
-
     <plurals name="room_displayname_three_and_more_members">
         <item quantity="zero">%1$s un 1 cits</item>
         <item quantity="one">%1$s un %2$d citi</item>
         <item quantity="other">%1$s un %2$d citu</item>
     </plurals>
-
-
-</resources>
+    <string name="notice_display_name_changed_from_by_you">Tu nomainīji savu attēlojamo vārdu no %1$s uz %2$s</string>
+    <string name="notice_display_name_set_by_you">Tu nomainījis savu attēlojamo vārdu uz %1$s</string>
+    <string name="notice_avatar_url_changed_by_you">Tu nomainīji savu avataru</string>
+    <string name="notice_room_withdraw_by_you">Tu atsauci %1$s uzaicinājumu</string>
+    <string name="notice_room_ban_by_you">Tu nobanoji %1$s</string>
+    <string name="notice_room_unban_by_you">Tu atbanoji %1$s</string>
+    <string name="notice_room_kick_by_you">Tu izspēri %1$s</string>
+    <string name="notice_room_reject_by_you">Tu noraidīji uzaicinājumu</string>
+    <string name="notice_direct_room_leave_by_you">Tu pameti telpu</string>
+    <string name="notice_direct_room_leave">%1$s atstāja telpu</string>
+    <string name="notice_room_leave_by_you">Tu atstāji telpu</string>
+    <string name="notice_direct_room_join_by_you">Tu pievienojies</string>
+    <string name="notice_direct_room_join">%1$s pievienojās telpai</string>
+    <string name="notice_room_join_by_you">Tu pievienojies telpai</string>
+    <string name="notice_room_invite_by_you">Tu uzaicināji %1$s</string>
+    <string name="notice_direct_room_created_by_you">Tu izveidoji apspriedi (diskusiju)</string>
+    <string name="notice_direct_room_created">%1$s izveidoja apspriedi (diskusiju)</string>
+    <string name="notice_room_created_by_you">Tu izveidoji istabu</string>
+    <string name="notice_room_created">%1$s izveidoja telpu</string>
+    <string name="notice_room_invite_no_invitee_by_you">Tavs uzaicinājums</string>
+    <string name="summary_you_sent_sticker">Tu nosūtīji uzlīmi/lipekli.</string>
+    <string name="summary_user_sent_sticker">%1$s nosūtīja uzlīmi/lipekli.</string>
+    <string name="summary_you_sent_image">Tu nosūtīji attēlu.</string>
+</resources>
\ No newline at end of file
diff --git a/matrix-sdk-android/src/main/res/values-nb/strings.xml b/matrix-sdk-android/src/main/res/values-nb/strings.xml
new file mode 100644
index 0000000000000000000000000000000000000000..07cf4226e02eb7338bca42a647227c62c0fc1d4d
--- /dev/null
+++ b/matrix-sdk-android/src/main/res/values-nb/strings.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <string name="notice_direct_room_created_by_you">Du opprettet diskusjonen</string>
+    <string name="notice_room_created_by_you">Du opprettet rommet</string>
+    <string name="notice_room_invite_no_invitee_by_you">Invitasjonen din</string>
+    <string name="summary_you_sent_sticker">Du sendte et klistremerke.</string>
+    <string name="summary_user_sent_sticker">%1$s sendte et klistremerke.</string>
+    <string name="summary_you_sent_image">Du sendte et bilde.</string>
+    <string name="summary_user_sent_image">%1$s sendte et bilde.</string>
+    <string name="summary_message">%1$s: %2$s</string>
+</resources>
\ No newline at end of file
diff --git a/matrix-sdk-android/src/main/res/values-nn/strings.xml b/matrix-sdk-android/src/main/res/values-nn/strings.xml
index d986e697ad6c981e3c345b369f95dba593f52ce1..441d568fc3c92a39c9486007e79bddd81b675c0e 100644
--- a/matrix-sdk-android/src/main/res/values-nn/strings.xml
+++ b/matrix-sdk-android/src/main/res/values-nn/strings.xml
@@ -1,12 +1,9 @@
-<?xml version='1.0' encoding='UTF-8'?>
+<?xml version="1.0" encoding="utf-8"?>
 <resources>
     <string name="encrypted_message">Kryptert melding</string>
-
-
     <string name="summary_message">%1$s: %2$s</string>
     <string name="summary_user_sent_image">%1$s sende eit bilæte.</string>
     <string name="summary_user_sent_sticker">%1$s sende eit klistremerke.</string>
-
     <string name="notice_room_invite_no_invitee">%s si innbjoding</string>
     <string name="notice_room_invite">%1$s inviterte %2$s</string>
     <string name="notice_room_invite_you">%1$s inviterte deg</string>
@@ -34,53 +31,38 @@
     <string name="notice_room_visibility_world_readable">kven som heldst.</string>
     <string name="notice_room_visibility_unknown">uvisst (%s).</string>
     <string name="notice_end_to_end">%1$s skrudde ende-til-ende-kryptering på (%2$s)</string>
-
     <string name="notice_requested_voip_conference">%1$s bad um ei VoIP-gruppasamtala</string>
     <string name="notice_voip_started">VoIP-gruppasamtala er starta</string>
     <string name="notice_voip_finished">VoIP-gruppasamtala er ferdug</string>
-
     <string name="notice_avatar_changed_too">(avataren vart au byta)</string>
     <string name="notice_room_name_removed">%1$s tok burt romnamnet</string>
     <string name="notice_room_topic_removed">%1$s tok burt romemnet</string>
     <string name="notice_profile_change_redacted">%1$s gjorde um på skildringi si %2$s</string>
     <string name="notice_room_third_party_invite">%1$s inviterte %2$s til rommet</string>
     <string name="notice_room_third_party_registered_invite">%1$s sa ja til innbjodingi til %2$s</string>
-
     <string name="notice_crypto_unable_to_decrypt">** Fekk ikkje til å dekryptera: %s **</string>
     <string name="notice_crypto_error_unkwown_inbound_session_id">Avsendareiningi hev ikkje sendt oss nyklane fyr denna meldingi.</string>
-
     <string name="could_not_redact">Kunde ikkje gjera um</string>
-    <string name="unable_to_send_message">Fekk ikkje å senda meldingi</string>
-
+    <string name="unable_to_send_message">Fekk ikkje til å senda meldingi</string>
     <string name="message_failed_to_upload">Fekk ikkje til å lasta biletet upp</string>
-
     <string name="network_error">Noko gjekk gale med netverket</string>
     <string name="matrix_error">Noko gjekk gale med Matrix</string>
-
     <string name="room_error_join_failed_empty_room">Det lèt seg fyrebils ikkje gjera å fara inn att i eit tomt rom.</string>
-
     <string name="medium_email">Epostadresse</string>
     <string name="medium_phone_number">Telefonnummer</string>
-
     <string name="room_displayname_invite_from">Innbjoding frå %s</string>
     <string name="room_displayname_room_invite">Rominnbjoding</string>
     <string name="room_displayname_two_members">%1$s og %2$s</string>
-
     <plurals name="room_displayname_three_and_more_members">
         <item quantity="one">%1$s og 1 til</item>
         <item quantity="other">%1$s og %2$d til</item>
     </plurals>
-
     <string name="room_displayname_empty_room">Tomt rom</string>
-
     <string name="notice_event_redacted">Ei melding vart stroki</string>
     <string name="notice_event_redacted_by">%1$s strauk meldingi</string>
     <string name="notice_event_redacted_with_reason">Meldingi vart stroki [av di: %1$s]</string>
     <string name="notice_event_redacted_by_with_reason">%1$s strauk meldingi [av di: %2$s]</string>
-
     <string name="notice_room_update">%s oppgraderte rommet.</string>
-
     <string name="clear_timeline_send_queue">Nullstill sendingskø</string>
-
     <string name="notice_room_leave_with_reason">%1$s forlot rommet. Grunn: %2$s</string>
-</resources>
+</resources>
\ No newline at end of file
diff --git a/matrix-sdk-android/src/main/res/values-pt-rBR/strings.xml b/matrix-sdk-android/src/main/res/values-pt-rBR/strings.xml
index e6c93cb55ce78bf97ed28f3425c65eb3297d3280..5e3282a3052560a48085f1b9cb126fc4415abb02 100644
--- a/matrix-sdk-android/src/main/res/values-pt-rBR/strings.xml
+++ b/matrix-sdk-android/src/main/res/values-pt-rBR/strings.xml
@@ -178,7 +178,7 @@
     </plurals>
     <plurals name="notice_room_aliases_removed">
         <item quantity="one">%1$s removeu %2$s como um endereço desta sala.</item>
-        <item quantity="other">%1$s removeu %3$s como endereços desta sala.</item>
+        <item quantity="other">%1$s removeu %2$s como endereços desta sala.</item>
     </plurals>
     <plurals name="notice_room_aliases_removed_by_you">
         <item quantity="one">Você removeu %1$s como um endereço desta sala.</item>
@@ -244,4 +244,26 @@
     <string name="notice_room_server_acl_set_banned">• Servidores correspondentes à %s estão banidos.</string>
     <string name="notice_room_server_acl_set_title_by_you">Você definiu a lista de controle de acesso (ACL) do servidor para esta sala.</string>
     <string name="notice_room_server_acl_set_title">%s definiu a lista de controle de acesso (ACL) do servidor para esta sala.</string>
+    <string name="notice_room_canonical_alias_alternative_changed_by_you">Você alterou os endereços alternativos desta sala.</string>
+    <string name="notice_room_canonical_alias_alternative_changed">%1$s alterou os endereços alternativos desta sala.</string>
+    <plurals name="notice_room_canonical_alias_alternative_removed_by_you">
+        <item quantity="one">Você removeu o endereço alternativo %1$s para esta sala.</item>
+        <item quantity="other">Você removeu os endereços alternativos %1$s para esta sala.</item>
+    </plurals>
+    <plurals name="notice_room_canonical_alias_alternative_removed">
+        <item quantity="one">%1$s removeu o endereço alternativo %2$s para esta sala.</item>
+        <item quantity="other">%1$s removeu os endereços alternativos %2$s para esta sala.</item>
+    </plurals>
+    <plurals name="notice_room_canonical_alias_alternative_added_by_you">
+        <item quantity="one">Você adicionou o endereço alternativo %1$s para esta sala.</item>
+        <item quantity="other">Você adicionou os endereços alternativos %1$s para esta sala.</item>
+    </plurals>
+    <plurals name="notice_room_canonical_alias_alternative_added">
+        <item quantity="one">%1$s adicionou o endereço alternativo %2$s para esta sala.</item>
+        <item quantity="other">%1$s adicionou os endereços alternativos %2$s para esta sala.</item>
+    </plurals>
+    <string name="notice_room_canonical_alias_no_change_by_you">Você alterou os endereços desta sala.</string>
+    <string name="notice_room_canonical_alias_no_change">%1$s alterou os endereços desta sala.</string>
+    <string name="notice_room_canonical_alias_main_and_alternative_changed_by_you">Você alterou os endereços principal e alternativos desta sala.</string>
+    <string name="notice_room_canonical_alias_main_and_alternative_changed">%1$s alterou os endereços principal e alternativos desta sala.</string>
 </resources>
\ No newline at end of file
diff --git a/matrix-sdk-android/src/main/res/values-ru/strings.xml b/matrix-sdk-android/src/main/res/values-ru/strings.xml
index 5ef5a4f447d64e58a1b78027bc739c95df7d2ad2..f2e0bd668f6b5c85c33099ace140ff8e21896dff 100644
--- a/matrix-sdk-android/src/main/res/values-ru/strings.xml
+++ b/matrix-sdk-android/src/main/res/values-ru/strings.xml
@@ -252,4 +252,34 @@
     <string name="notice_room_server_acl_updated_title">%s изменил права доступа сервера (ACL) для этой комнаты.</string>
     <string name="notice_room_server_acl_set_title_by_you">Вы настроили права доступа сервера (ACL) для этой комнаты.</string>
     <string name="notice_room_server_acl_set_title">%s устанавливает права доступа сервера (ACL) для этой комнаты.</string>
+    <string name="notice_room_canonical_alias_no_change_by_you">Вы изменили адреса этой комнаты.</string>
+    <string name="notice_room_canonical_alias_no_change">%1$s изменил(а) адреса этой комнаты.</string>
+    <string name="notice_room_canonical_alias_main_and_alternative_changed_by_you">Вы изменили основной и альтернативный адреса этой комнаты.</string>
+    <string name="notice_room_canonical_alias_main_and_alternative_changed">%1$s изменил(а) основной и альтернативный адреса этой комнаты.</string>
+    <string name="notice_room_canonical_alias_alternative_changed_by_you">Вы изменили альтернативные адреса для этой комнаты.</string>
+    <string name="notice_room_canonical_alias_alternative_changed">%1$s изменил(а) альтернативные адреса для этой комнаты.</string>
+    <plurals name="notice_room_canonical_alias_alternative_removed_by_you">
+        <item quantity="one">Вы удалили альтернативный адрес %1$s для этой комнаты.</item>
+        <item quantity="few">Вы удалили альтернативные адреса %1$s для этой комнаты.</item>
+        <item quantity="many">Вы удалили альтернативные адреса %1$s для этой комнаты.</item>
+        <item quantity="other">Вы удалили альтернативные адреса %1$s для этой комнаты.</item>
+    </plurals>
+    <plurals name="notice_room_canonical_alias_alternative_removed">
+        <item quantity="one">%1$s удалил(а) альтернативный адрес %2$s для этой комнаты.</item>
+        <item quantity="few">%1$s удалил(а) альтернативные адреса %2$s для этой комнаты.</item>
+        <item quantity="many">%1$s удалил(а) альтернативные адреса %2$s для этой комнаты.</item>
+        <item quantity="other">%1$s удалил(а) альтернативные адреса %2$s для этой комнаты.</item>
+    </plurals>
+    <plurals name="notice_room_canonical_alias_alternative_added_by_you">
+        <item quantity="one">Вы добавили альтернативный адрес %1$s для этой комнаты.</item>
+        <item quantity="few">Вы добавили альтернативные адреса %1$s для этой комнаты.</item>
+        <item quantity="many">Вы добавили альтернативные адреса %1$s для этой комнаты.</item>
+        <item quantity="other">Вы добавили альтернативные адреса %1$s для этой комнаты.</item>
+    </plurals>
+    <plurals name="notice_room_canonical_alias_alternative_added">
+        <item quantity="one">%1$s добавил(а) альтернативный адрес %2$s для этой комнаты.</item>
+        <item quantity="few">%1$s добавил(а) альтернативные адреса %2$s для этой комнаты.</item>
+        <item quantity="many">%1$s добавил(а) альтернативные адреса %2$s для этой комнаты.</item>
+        <item quantity="other">%1$s добавил(а) альтернативные адреса %2$s для этой комнаты.</item>
+    </plurals>
 </resources>
\ No newline at end of file
diff --git a/matrix-sdk-android/src/main/res/values-sk/strings.xml b/matrix-sdk-android/src/main/res/values-sk/strings.xml
index 15924d02e1063c3430f39f26458e7492188f4748..a40654f7bf39cd922af9a677645eb42509bb3536 100644
--- a/matrix-sdk-android/src/main/res/values-sk/strings.xml
+++ b/matrix-sdk-android/src/main/res/values-sk/strings.xml
@@ -197,4 +197,16 @@
     <string name="notice_end_to_end_unknown_algorithm">%1$s povolil/a E2E šifrovanie (Nerozpoznaný algorytmus %2$s).</string>
     <string name="notice_end_to_end_unknown_algorithm_by_you">Povolili ste E2E šifrovanie (Nerozpoznaný algorytmus %1$s).</string>
     <string name="key_verification_request_fallback_message">%s požaduje overenie vašich šifrovacích kľúčov, ale váš klient nepodporuje overenie kľúčov v konverzácii. Budete musieť použiť zastaralú metódu overenia.</string>
+    <string name="notice_room_server_acl_set_title_by_you">nastavili ste na servery pravidlá ACL tejto miestnosti.</string>
+    <string name="notice_room_server_acl_set_title">%s nastavil(a) na servery pravidlá ACL tejto miestnosti.</string>
+    <string name="notice_direct_room_update_by_you">Aktualizovali ste sem.</string>
+    <string name="notice_direct_room_update">%s aktualizoval(a) sem.</string>
+    <string name="notice_made_future_direct_room_visibility">%1$s sprístupnil(a) budúce správy %2$s</string>
+    <string name="notice_made_future_direct_room_visibility_by_you">Sprístupnili ste budúce správy %1$s</string>
+    <string name="notice_direct_room_leave_by_you">Opustili ste miestnosť</string>
+    <string name="notice_direct_room_leave">%1$s opustil(a) miestnosť</string>
+    <string name="notice_direct_room_join_by_you">Vstúpili ste</string>
+    <string name="notice_direct_room_join">%1$s vstúpil(a)</string>
+    <string name="notice_direct_room_created_by_you">Vytvorili ste konverzáciu</string>
+    <string name="notice_direct_room_created">%1$s vytvoril(a) konverzáciu</string>
 </resources>
\ No newline at end of file
diff --git a/matrix-sdk-android/src/main/res/values-sq/strings.xml b/matrix-sdk-android/src/main/res/values-sq/strings.xml
index 58ba8877bb719cbaeee5e6b75ea65ca367180681..0d4b2888ba016cbdd6628621c05b346308699320 100644
--- a/matrix-sdk-android/src/main/res/values-sq/strings.xml
+++ b/matrix-sdk-android/src/main/res/values-sq/strings.xml
@@ -101,7 +101,7 @@
     </plurals>
     <plurals name="notice_room_aliases_removed">
         <item quantity="one">%1$s hoqi %2$s si adresë për këtë dhomë.</item>
-        <item quantity="other">%1$s hoqi %3$s si adresa për këtë dhomë.</item>
+        <item quantity="other">%1$s hoqi %2$s si adresa për këtë dhomë.</item>
     </plurals>
     <string name="notice_room_aliases_added_and_removed">%1$s shtoi %2$s dhe hoqi %3$s si adresa për këtë dhomë.</string>
     <string name="notice_room_canonical_alias_set">%1$s caktoi %2$s si adresë kryesore për këtë dhomë.</string>
@@ -232,4 +232,26 @@
     <string name="notice_room_server_acl_updated_banned">• Shërbyesit që kanë përputhje me %s tani janë të ndaluar.</string>
     <string name="notice_room_server_acl_set_allowed">• Shërbyesit që kanë përputhje me %s janë të ndaluar.</string>
     <string name="notice_room_server_acl_set_banned">• Shërbyesit që kanë përputhje me %s janë të ndaluar.</string>
+    <string name="notice_room_canonical_alias_no_change_by_you">Ndryshuat adresat për këtë dhomë.</string>
+    <string name="notice_room_canonical_alias_no_change">%1$s ndryshoi adresat për këtë dhomë.</string>
+    <string name="notice_room_canonical_alias_main_and_alternative_changed_by_you">Ndryshuat adresat kryesore dhe alternative për këtë dhomë.</string>
+    <string name="notice_room_canonical_alias_main_and_alternative_changed">%1$s ndryshoi adresat kryesore dhe alternative për këtë dhomë.</string>
+    <string name="notice_room_canonical_alias_alternative_changed_by_you">Ndryshuat adresat alternative për këtë dhomë.</string>
+    <string name="notice_room_canonical_alias_alternative_changed">%1$s ndryshoi adresat alternative për këtë dhomë.</string>
+    <plurals name="notice_room_canonical_alias_alternative_removed_by_you">
+        <item quantity="one">Hoqët adresën alternative %1$s për këtë dhomë.</item>
+        <item quantity="other">Hoqët adresat alternative %1$s për këtë dhomë.</item>
+    </plurals>
+    <plurals name="notice_room_canonical_alias_alternative_removed">
+        <item quantity="one">%1$s hoqët adresën alternative %2$s për këtë dhomë.</item>
+        <item quantity="other">%1$s hoqët adresat alternative %2$s për këtë dhomë.</item>
+    </plurals>
+    <plurals name="notice_room_canonical_alias_alternative_added_by_you">
+        <item quantity="one">Shtuat adresën alternative %1$s për këtë dhomë.</item>
+        <item quantity="other">Shtuat adresat alternative %1$s për këtë dhomë.</item>
+    </plurals>
+    <plurals name="notice_room_canonical_alias_alternative_added">
+        <item quantity="one">%1$s shtoi adresën alternative %2$s për këtë dhomë.</item>
+        <item quantity="other">%1$s shtoi adresat alternative %2$s për këtë dhomë.</item>
+    </plurals>
 </resources>
\ No newline at end of file
diff --git a/matrix-sdk-android/src/main/res/values-sv/strings.xml b/matrix-sdk-android/src/main/res/values-sv/strings.xml
index d42c6ba2cae6e9960dbb7fbf2bcd7ed07458b8ff..91d874591f5e033d41c115c3799798ccd0f48e97 100644
--- a/matrix-sdk-android/src/main/res/values-sv/strings.xml
+++ b/matrix-sdk-android/src/main/res/values-sv/strings.xml
@@ -213,4 +213,49 @@
     <string name="notice_direct_room_join">%1$s gick med</string>
     <string name="notice_direct_room_created_by_you">Du skapade diskussionen</string>
     <string name="notice_direct_room_created">%1$s skapade diskussionen</string>
+    <string name="notice_room_canonical_alias_no_change_by_you">Du ändrade adresserna för det här rummet.</string>
+    <string name="notice_room_canonical_alias_no_change">%1$s ändrade adresserna för det här rummet.</string>
+    <string name="notice_room_canonical_alias_main_and_alternative_changed_by_you">Du ändrade huvudadressen och de alternativa adresserna för det här rummet.</string>
+    <string name="notice_room_canonical_alias_main_and_alternative_changed">%1$s ändrade huvudadressen och de alternativa adresserna för det här rummet.</string>
+    <string name="notice_room_canonical_alias_alternative_changed_by_you">Du ändrade de alternativa adresserna för det här rummet.</string>
+    <string name="notice_room_canonical_alias_alternative_changed">%1$s ändrade de alternativa adresserna för det här rummet.</string>
+    <plurals name="notice_room_canonical_alias_alternative_removed_by_you">
+        <item quantity="one">Du tog bort den alternativa adressen %1$s för det här rummet.</item>
+        <item quantity="other">Du tog bort de alternativa adresserna %1$s för det här rummet.</item>
+    </plurals>
+    <plurals name="notice_room_canonical_alias_alternative_removed">
+        <item quantity="one">%1$s tog bort den alternativa adressen %2$s för det här rummet.</item>
+        <item quantity="other">%1$s tog bort de alternativa adresserna %2$s för det här rummet.</item>
+    </plurals>
+    <plurals name="notice_room_canonical_alias_alternative_added_by_you">
+        <item quantity="one">Du lade till den alternativa adressen %1$s för det här rummet.</item>
+        <item quantity="other">Du lade till de alternativa adresserna %1$s för det här rummet.</item>
+    </plurals>
+    <plurals name="notice_room_canonical_alias_alternative_added">
+        <item quantity="one">%1$s lade till den alternativa adressen %2$s för det här rummet.</item>
+        <item quantity="other">%1$s lade till de alternativa adresserna %2$s för det här rummet.</item>
+    </plurals>
+    <string name="room_displayname_empty_room_was">Tomt rum (var %s)</string>
+    <plurals name="room_displayname_four_and_more_members">
+        <item quantity="one">%1$s, %2$s, %3$s och %4$d till</item>
+        <item quantity="other">%1$s, %2$s, %3$s och %4$d till</item>
+    </plurals>
+    <string name="room_displayname_4_members">%1$s, %2$s, %3$s och %4$s</string>
+    <string name="room_displayname_3_members">%1$s, %2$s och %3$s</string>
+    <string name="notice_room_server_acl_allow_is_empty">🎉 Alla servrar har bannats från att delta! Det här rummet kan inte användas längre.</string>
+    <string name="notice_room_server_acl_updated_no_change">Ingen ändring.</string>
+    <string name="notice_room_server_acl_updated_ip_literals_not_allowed">• Servrar som matchar IP-adresser är nu bannade.</string>
+    <string name="notice_room_server_acl_updated_ip_literals_allowed">• Servrar som matchar IP-adresser är nu tillåtna.</string>
+    <string name="notice_room_server_acl_updated_was_allowed">• Servrar som matchar %s togs bort från tillåtelselistan.</string>
+    <string name="notice_room_server_acl_updated_allowed">• Servrar som matchar %s är nu tillåtna.</string>
+    <string name="notice_room_server_acl_updated_was_banned">• Servrar som matchar %s togs bort från bannlistan.</string>
+    <string name="notice_room_server_acl_updated_banned">• Servrar som matchar %s är nu bannade.</string>
+    <string name="notice_room_server_acl_updated_title_by_you">Du ändrade server-ACLer för det här rummet.</string>
+    <string name="notice_room_server_acl_updated_title">%s ändrade server-ACLer för det här rummet.</string>
+    <string name="notice_room_server_acl_set_ip_literals_allowed">• Servrar som matchar IP-adresser är tillåtna.</string>
+    <string name="notice_room_server_acl_set_ip_literals_not_allowed">• Servrar som matchar IP-adresser är bannade.</string>
+    <string name="notice_room_server_acl_set_allowed">• Servrar som matchar %s är tillåtna.</string>
+    <string name="notice_room_server_acl_set_banned">• Servrar som matchar %s är bannade.</string>
+    <string name="notice_room_server_acl_set_title_by_you">Du satte server-ACLer för det här rummet.</string>
+    <string name="notice_room_server_acl_set_title">%s satte server-ACLer för det här rummet.</string>
 </resources>
\ No newline at end of file
diff --git a/matrix-sdk-android/src/main/res/values-szl/strings.xml b/matrix-sdk-android/src/main/res/values-szl/strings.xml
new file mode 100644
index 0000000000000000000000000000000000000000..a6b3daec9354f9ae75cdf8d94a67446c6227dd96
--- /dev/null
+++ b/matrix-sdk-android/src/main/res/values-szl/strings.xml
@@ -0,0 +1,2 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources></resources>
\ No newline at end of file
diff --git a/matrix-sdk-android/src/main/res/values-uk/strings.xml b/matrix-sdk-android/src/main/res/values-uk/strings.xml
index 24774873796d4decea113943e39569ba49c5bdb1..0f45a7182ca6d16111ad3e07db35fa1197d6857e 100644
--- a/matrix-sdk-android/src/main/res/values-uk/strings.xml
+++ b/matrix-sdk-android/src/main/res/values-uk/strings.xml
@@ -13,7 +13,7 @@
     <string name="summary_user_sent_sticker">%1$s надіслав(ла) наліпку.</string>
     <string name="notice_room_invite_you">%1$s запросив(ла) Вас</string>
     <string name="notice_room_join">%1$s приєднується</string>
-    <string name="notice_room_leave">%1$s покинув(ла)</string>
+    <string name="notice_room_leave">%1$s покидає кімнату</string>
     <string name="notice_room_reject">%1$s відхилив(ла) запрошення</string>
     <string name="notice_room_kick">%1$s копнув(ла) %2$s</string>
     <string name="notice_room_unban">%1$s розблокував(ла) %2$s</string>
@@ -65,4 +65,23 @@
     <string name="notice_event_redacted">Повідомлення видалено</string>
     <string name="notice_event_redacted_by">%1$s видалили повідомлення</string>
     <string name="notice_event_redacted_with_reason">Повідомлення видалено [причина: %1$s]</string>
+    <string name="initial_sync_start_importing_account_data">Початкове налаштування:
+\nІмпортування даних облікового запису</string>
+    <string name="notice_direct_room_leave_with_reason_by_you">Ви покинули. Причина: %1$s</string>
+    <string name="notice_direct_room_leave_with_reason">%1$s покидає. Причина: %2$s</string>
+    <string name="notice_room_leave_with_reason_by_you">Ви покинули кімнату. Причина: %1$s</string>
+    <string name="notice_room_leave_with_reason">%1$s покидає кімнату. Причина: %2$s</string>
+    <string name="notice_direct_room_leave">%1$s покидає кімнату</string>
+    <string name="notice_direct_room_leave_by_you">Ви покинули кімнату</string>
+    <string name="notice_room_leave_by_you">Ви покинули кімнату</string>
+    <string name="notice_room_canonical_alias_no_change_by_you">Ви змінили адреси цієї кімнати.</string>
+    <string name="notice_room_canonical_alias_main_and_alternative_changed_by_you">Ви змінили основну та альтернативну адреси цієї кімнати.</string>
+    <string name="notice_room_canonical_alias_alternative_changed_by_you">Ви змінили альтернативні адреси для цієї кімнати.</string>
+    <string name="notice_power_level_changed_by_you">Ви змінили рівень доступу на %1$s.</string>
+    <string name="notice_room_server_acl_updated_title_by_you">Ви змінили серверні списки контролю доступу для цієї кімнати.</string>
+    <string name="notice_room_name_changed_by_you">Ви змінили назву кімнати на: %1$s</string>
+    <string name="notice_room_avatar_changed_by_you">Ви змінили світлину кімнати</string>
+    <string name="notice_room_topic_changed_by_you">Ви змінили тему на: %1$s</string>
+    <string name="notice_display_name_changed_from_by_you">Ви змінили показуване ім\'я з %1$s на %2$s</string>
+    <string name="notice_avatar_url_changed_by_you">Ви змінили світлину профілю</string>
 </resources>
\ No newline at end of file
diff --git a/matrix-sdk-android/src/main/res/values-zh-rTW/strings.xml b/matrix-sdk-android/src/main/res/values-zh-rTW/strings.xml
index 08050b400d3f2a0095826cd3a03e976bab24fc4d..5038e8aab2a12395019c11c03c7130c709450108 100644
--- a/matrix-sdk-android/src/main/res/values-zh-rTW/strings.xml
+++ b/matrix-sdk-android/src/main/res/values-zh-rTW/strings.xml
@@ -98,7 +98,7 @@
         <item quantity="other">%1$s 新增了 %2$s 為此聊天室的地址。</item>
     </plurals>
     <plurals name="notice_room_aliases_removed">
-        <item quantity="other">%1$s 移除了此聊天室的 %3$s 地址。</item>
+        <item quantity="other">%1$s 移除了此聊天室的 %2$s 地址。</item>
     </plurals>
     <string name="notice_room_aliases_added_and_removed">%1$s 為此聊天室新增 %2$s 並移除 %3$s 地址。</string>
     <string name="notice_room_canonical_alias_set">%1$s 為此聊天室設定了 %2$s 為主地址。</string>
@@ -230,4 +230,22 @@
     <string name="notice_room_server_acl_set_banned">• 已禁止伺服器符合 %s。</string>
     <string name="notice_room_server_acl_set_title_by_you">您為此聊天是設定了伺服器 ACL。</string>
     <string name="notice_room_server_acl_set_title">%s 為此聊天是設定了伺服器 ACL。</string>
+    <string name="notice_room_canonical_alias_no_change_by_you">您變更了此聊天室的地址。</string>
+    <string name="notice_room_canonical_alias_no_change">%1$s 變更了此聊天室的地址。</string>
+    <string name="notice_room_canonical_alias_main_and_alternative_changed_by_you">您為此聊天室變更了主要及備用地址。</string>
+    <string name="notice_room_canonical_alias_main_and_alternative_changed">%1$s 為此聊天室變更了主要及備用地址。</string>
+    <string name="notice_room_canonical_alias_alternative_changed_by_you">您為此聊天室變更了備用地址。</string>
+    <string name="notice_room_canonical_alias_alternative_changed">%1$s 變更了此聊天室的備用地址。</string>
+    <plurals name="notice_room_canonical_alias_alternative_removed_by_you">
+        <item quantity="other">您為此聊天室移除了備用地址 %1$s。</item>
+    </plurals>
+    <plurals name="notice_room_canonical_alias_alternative_removed">
+        <item quantity="other">%1$s 已為此聊天室移除備用地址 %2$s。</item>
+    </plurals>
+    <plurals name="notice_room_canonical_alias_alternative_added_by_you">
+        <item quantity="other">您為此聊天室新增了備用地址 %1$s。</item>
+    </plurals>
+    <plurals name="notice_room_canonical_alias_alternative_added">
+        <item quantity="other">%1$s 已為此聊天室新增了備用地址 %2$s。</item>
+    </plurals>
 </resources>
\ No newline at end of file
diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/task/CoroutineSequencersTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/task/CoroutineSequencersTest.kt
index 667e0b2471f08a735fbc91fa8f874cebeeee9e81..74b6c03d93b15844e19b5d430b34a0baa987ae36 100644
--- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/task/CoroutineSequencersTest.kt
+++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/task/CoroutineSequencersTest.kt
@@ -38,19 +38,16 @@ class CoroutineSequencersTest: MatrixTest {
 
         val jobs = listOf(
                 GlobalScope.launch(dispatcher) {
-                    sequencer.post { suspendingMethod("#1") }.also {
-                        results.add(it)
-                    }
+                    sequencer.post { suspendingMethod("#1") }
+                            .also { results.add(it) }
                 },
                 GlobalScope.launch(dispatcher) {
-                    sequencer.post { suspendingMethod("#2") }.also {
-                        results.add(it)
-                    }
+                    sequencer.post { suspendingMethod("#2") }
+                            .also { results.add(it) }
                 },
                 GlobalScope.launch(dispatcher) {
-                    sequencer.post { suspendingMethod("#3") }.also {
-                        results.add(it)
-                    }
+                    sequencer.post { suspendingMethod("#3") }
+                            .also { results.add(it) }
                 }
         )
         runBlocking {
@@ -70,19 +67,16 @@ class CoroutineSequencersTest: MatrixTest {
         val results = ArrayList<String>()
         val jobs = listOf(
                 GlobalScope.launch(dispatcher) {
-                    sequencer1.post { suspendingMethod("#1") }.also {
-                        results.add(it)
-                    }
+                    sequencer1.post { suspendingMethod("#1") }
+                            .also { results.add(it) }
                 },
                 GlobalScope.launch(dispatcher) {
-                    sequencer2.post { suspendingMethod("#2") }.also {
-                        results.add(it)
-                    }
+                    sequencer2.post { suspendingMethod("#2") }
+                            .also { results.add(it) }
                 },
                 GlobalScope.launch(dispatcher) {
-                    sequencer3.post { suspendingMethod("#3") }.also {
-                        results.add(it)
-                    }
+                    sequencer3.post { suspendingMethod("#3") }
+                            .also { results.add(it) }
                 }
         )
         runBlocking {
@@ -97,20 +91,17 @@ class CoroutineSequencersTest: MatrixTest {
         val results = ArrayList<String>()
         val jobs = listOf(
                 GlobalScope.launch(dispatcher) {
-                    sequencer.post { suspendingMethod("#1") }.also {
-                        results.add(it)
-                    }
+                    sequencer.post { suspendingMethod("#1") }
+                            .also { results.add(it) }
                 },
                 GlobalScope.launch(dispatcher) {
-                    val result = sequencer.post { suspendingMethod("#2") }.also {
-                        results.add(it)
-                    }
-                    println("Result: $result")
+                    sequencer.post { suspendingMethod("#2") }
+                            .also { results.add(it) }
+                            .also { println("Result: $it") }
                 },
                 GlobalScope.launch(dispatcher) {
-                    sequencer.post { suspendingMethod("#3") }.also {
-                        results.add(it)
-                    }
+                    sequencer.post { suspendingMethod("#3") }
+                            .also { results.add(it) }
                 }
         )
         // We are canceling the second job