diff --git a/CHANGES.md b/CHANGES.md
index d442df706c80afc1e4da5ca66d07741de89f93a6..8d3820ea8f39d2695d53ce8db7597cd885d8a543 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,5 +1,15 @@
 Please also refer to the Changelog of Element Android: https://github.com/vector-im/element-android/blob/master/CHANGES.md
 
+Changes in Matrix-SDK 1.1.4 (2021-04-09)
+===================================================
+
+Imported from Element 1.1.4. (https://github.com/vector-im/element-android/releases/tag/v1.1.4)
+
+Changes in Matrix-SDK 1.1.1 (2021-03-10)
+===================================================
+
+Imported from Element 1.1.1. (https://github.com/vector-im/element-android/releases/tag/v1.1.1)
+
 Changes in Matrix-SDK 1.0.16 (2021-02-08)
 ===================================================
 
diff --git a/build.gradle b/build.gradle
index f8730fb59ed71055c2b27e3faf8a8f2a1651d04b..affee31588a15c069a98159df4f174244dd32f73 100644
--- a/build.gradle
+++ b/build.gradle
@@ -2,7 +2,7 @@
 
 buildscript {
     // Ref: https://kotlinlang.org/releases.html
-    ext.kotlin_version = '1.4.31'
+    ext.kotlin_version = '1.4.32'
     ext.kotlin_coroutines_version = "1.4.2"
     repositories {
         google()
@@ -12,7 +12,7 @@ buildscript {
         }
     }
     dependencies {
-        classpath 'com.android.tools.build:gradle:4.1.2'
+        classpath 'com.android.tools.build:gradle:4.1.3'
         classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
 
         // NOTE: Do not place your application dependencies here; they belong
diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle
index 7053e7f1baa7e65eccd1fa323a44cd6ce63407f7..883f83783da485766e2ff3d10ce567a9e1b75ff6 100644
--- a/matrix-sdk-android/build.gradle
+++ b/matrix-sdk-android/build.gradle
@@ -6,10 +6,10 @@ apply plugin: 'realm-android'
 
 buildscript {
     repositories {
-        jcenter()
+        mavenCentral()
     }
     dependencies {
-        classpath "io.realm:realm-gradle-plugin:10.3.1"
+        classpath "io.realm:realm-gradle-plugin:10.4.0"
     }
 }
 
@@ -21,7 +21,7 @@ android {
         minSdkVersion 21
         targetSdkVersion 30
         versionCode 1
-        versionName "1.1.1"
+        versionName "1.1.4"
         // Multidex is useful for tests
         multiDexEnabled true
         testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
@@ -108,7 +108,7 @@ static def gitRevisionDate() {
 dependencies {
 
     def arrow_version = "0.8.2"
-    def moshi_version = '1.11.0'
+    def moshi_version = '1.12.0'
     def lifecycle_version = '2.2.0'
     def arch_version = '2.1.0'
     def markwon_version = '3.1.0'
@@ -163,16 +163,16 @@ dependencies {
 
     // Logging
     implementation 'com.jakewharton.timber:timber:4.7.1'
-    implementation 'com.facebook.stetho:stetho-okhttp3:1.5.1'
+    implementation 'com.facebook.stetho:stetho-okhttp3:1.6.0'
 
     // Phone number https://github.com/google/libphonenumber
-    implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.19'
+    implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.21'
 
     testImplementation 'junit:junit:4.13.2'
     testImplementation 'org.robolectric:robolectric:4.5.1'
     //testImplementation 'org.robolectric:shadows-support-v4:3.0'
     // Note: version sticks to 1.9.2 due to https://github.com/mockk/mockk/issues/281
-    testImplementation 'io.mockk:mockk:1.10.6'
+    testImplementation 'io.mockk:mockk:1.11.0'
     testImplementation 'org.amshove.kluent:kluent-android:1.65'
     testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_version"
     // Plant Timber tree for test
@@ -185,8 +185,7 @@ dependencies {
     androidTestImplementation 'androidx.test.ext:junit:1.1.2'
     androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
     androidTestImplementation 'org.amshove.kluent:kluent-android:1.61'
-    // Note: version sticks to 1.9.2 due to https://github.com/mockk/mockk/issues/281
-    androidTestImplementation 'io.mockk:mockk-android:1.10.6'
+    androidTestImplementation 'io.mockk:mockk-android:1.11.0'
     androidTestImplementation "androidx.arch.core:core-testing:$arch_version"
     androidTestImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_version"
     // Plant Timber tree for test
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/InstrumentedTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/InstrumentedTest.kt
index b78488436337da26256cff8c93ffd711bd8e44de..583406346eb519353ab57ed93d729e252b18bb82 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/InstrumentedTest.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/InstrumentedTest.kt
@@ -20,7 +20,6 @@ import android.content.Context
 import androidx.test.core.app.ApplicationProvider
 import org.matrix.android.sdk.test.shared.createTimberTestRule
 import org.junit.Rule
-import java.io.File
 
 interface InstrumentedTest {
 
@@ -30,8 +29,4 @@ interface InstrumentedTest {
     fun context(): Context {
         return ApplicationProvider.getApplicationContext()
     }
-
-    fun cacheDir(): File {
-        return context().cacheDir
-    }
 }
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/api/Matrix.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/api/Matrix.kt
index 03943cea1448db7ec867c3a7a8c5426bea7371a8..c439da8407bcde21a93c78b2d78ff4272c2a234c 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/api/Matrix.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/api/Matrix.kt
@@ -27,9 +27,12 @@ import org.matrix.android.sdk.BuildConfig
 import org.matrix.android.sdk.api.auth.AuthenticationService
 import org.matrix.android.sdk.api.auth.HomeServerHistoryService
 import org.matrix.android.sdk.api.legacy.LegacySessionImporter
+import org.matrix.android.sdk.api.network.ApiInterceptorListener
+import org.matrix.android.sdk.api.network.ApiPath
 import org.matrix.android.sdk.api.raw.RawService
 import org.matrix.android.sdk.common.DaggerTestMatrixComponent
 import org.matrix.android.sdk.internal.SessionManager
+import org.matrix.android.sdk.internal.network.ApiInterceptor
 import org.matrix.android.sdk.internal.network.UserAgentHolder
 import org.matrix.android.sdk.internal.util.BackgroundDetectionObserver
 import org.matrix.olm.OlmManager
@@ -51,6 +54,7 @@ class Matrix private constructor(context: Context, matrixConfiguration: MatrixCo
     @Inject internal lateinit var olmManager: OlmManager
     @Inject internal lateinit var sessionManager: SessionManager
     @Inject internal lateinit var homeServerHistoryService: HomeServerHistoryService
+    @Inject internal lateinit var apiInterceptor: ApiInterceptor
 
     private val uiHandler = Handler(Looper.getMainLooper())
 
@@ -79,6 +83,14 @@ class Matrix private constructor(context: Context, matrixConfiguration: MatrixCo
         return legacySessionImporter
     }
 
+    fun registerApiInterceptorListener(path: ApiPath, listener: ApiInterceptorListener) {
+        apiInterceptor.addListener(path, listener)
+    }
+
+    fun unregisterApiInterceptorListener(path: ApiPath, listener: ApiInterceptorListener) {
+        apiInterceptor.removeListener(path, listener)
+    }
+
     companion object {
 
         private lateinit var instance: Matrix
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/api/network/ApiInterceptorTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/api/network/ApiInterceptorTest.kt
new file mode 100644
index 0000000000000000000000000000000000000000..9371154aaf0aee016ab47dfccadc11e126aab846
--- /dev/null
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/api/network/ApiInterceptorTest.kt
@@ -0,0 +1,58 @@
+/*
+ * 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.api.network
+
+import org.amshove.kluent.shouldBeEqualTo
+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.common.CommonTestHelper
+import org.matrix.android.sdk.common.SessionTestParams
+import org.matrix.android.sdk.common.TestConstants
+import timber.log.Timber
+
+@RunWith(JUnit4::class)
+@FixMethodOrder(MethodSorters.JVM)
+class ApiInterceptorTest : InstrumentedTest {
+
+    private val commonTestHelper = CommonTestHelper(context())
+
+    @Test
+    fun apiInterceptorTest() {
+        val responses = mutableListOf<String>()
+
+        val listener = object : ApiInterceptorListener {
+            override fun onApiResponse(path: ApiPath, response: String) {
+                Timber.w("onApiResponse($path): $response")
+                responses.add(response)
+            }
+        }
+
+        commonTestHelper.matrix.registerApiInterceptorListener(ApiPath.REGISTER, listener)
+
+        val session = commonTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(withInitialSync = true))
+
+        commonTestHelper.signOutAndClose(session)
+
+        commonTestHelper.matrix.unregisterApiInterceptorListener(ApiPath.REGISTER, listener)
+
+        responses.size shouldBeEqualTo 2
+    }
+}
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 eb7e4a9fbec2c3a0f68b242b6f98f2a7cb01f71f..5815b23c0642867848b77f76efd6ca65e1f54e81 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
@@ -112,8 +112,8 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) {
             bobRoomSummariesLive.observeForever(newRoomObserver)
         }
 
-        mTestHelper.doSync<Unit> {
-            aliceRoom.invite(bobSession.myUserId, callback = it)
+        mTestHelper.runBlockingTest {
+            aliceRoom.invite(bobSession.myUserId)
         }
 
         mTestHelper.await(lock1)
@@ -172,8 +172,8 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) {
     fun createSamAccountAndInviteToTheRoom(room: Room): Session {
         val samSession = mTestHelper.createAccount(TestConstants.USER_SAM, defaultSessionParams)
 
-        mTestHelper.doSync<Unit> {
-            room.invite(samSession.myUserId, null, it)
+        mTestHelper.runBlockingTest {
+            room.invite(samSession.myUserId, null)
         }
 
         mTestHelper.doSync<Unit> {
@@ -337,8 +337,7 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) {
                 requestID,
                 roomId,
                 bob.myUserId,
-                bob.sessionParams.credentials.deviceId!!,
-                null)
+                bob.sessionParams.credentials.deviceId!!)
 
         // we should reach SHOW SAS on both
         var alicePovTx: OutgoingSasVerificationTransaction? = null
@@ -411,7 +410,7 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) {
         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) }
+            mTestHelper.runBlockingTest(timeout = 600_000) { room.invite(session.myUserId, null) }
             println("TEST -> " + session.myUserId + " invited")
             mTestHelper.doSync<Unit> { session.joinRoom(room.roomId, null, emptyList(), it) }
             println("TEST -> " + session.myUserId + " joined")
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/TestRoomDisplayNameFallbackProvider.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/TestRoomDisplayNameFallbackProvider.kt
index 7a1d4604f05e4bdfc6006137b93ff3e7af79d5bf..af2d57f9ce2e6d136a3e6470f1d92e2523ec4eeb 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/TestRoomDisplayNameFallbackProvider.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/TestRoomDisplayNameFallbackProvider.kt
@@ -18,23 +18,26 @@ package org.matrix.android.sdk.common
 
 import org.matrix.android.sdk.api.RoomDisplayNameFallbackProvider
 
-class TestRoomDisplayNameFallbackProvider() : RoomDisplayNameFallbackProvider {
+class TestRoomDisplayNameFallbackProvider : RoomDisplayNameFallbackProvider {
 
     override fun getNameForRoomInvite() =
             "Room invite"
 
-    override fun getNameForEmptyRoom() =
+    override fun getNameForEmptyRoom(isDirect: Boolean, leftMemberNames: List<String>) =
             "Empty room"
 
-    override fun getNameFor2members(name1: String?, name2: String?) =
+    override fun getNameFor1member(name: String) =
+            name
+
+    override fun getNameFor2members(name1: String, name2: String) =
             "$name1 and $name2"
 
-    override fun getNameFor3members(name1: String?, name2: String?, name3: String?) =
+    override fun getNameFor3members(name1: String, name2: String, name3: String) =
             "$name1, $name2 and $name3"
 
-    override fun getNameFor4members(name1: String?, name2: String?, name3: String?, name4: String?) =
+    override fun getNameFor4members(name1: String, name2: String, name3: String, name4: String) =
             "$name1, $name2, $name3 and $name4"
 
-    override fun getNameFor4membersAndMore(name1: String?, name2: String?, name3: String?, remainingCount: Int) =
+    override fun getNameFor4membersAndMore(name1: String, name2: String, name3: String, remainingCount: Int) =
             "$name1, $name2, $name3 and $remainingCount others"
 }
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 8c3917adc131e32027c75be629c3790de2c9582f..e6b364f3fb3d9cf0017963f5ec7b3344c9253114 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
@@ -367,8 +367,8 @@ class KeyShareTests : InstrumentedTest {
         }
 
         // Let alice invite bob
-        mTestHelper.doSync<Unit> {
-            roomAlicePov.invite(bobSession.myUserId, null, it)
+        mTestHelper.runBlockingTest {
+            roomAlicePov.invite(bobSession.myUserId, null)
         }
 
         mTestHelper.doSync<Unit> {
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/ssss/QuadSTests.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/ssss/QuadSTests.kt
index 0489ee179f52c6fe48d3ae1095c20b451e42d355..eb4773f3c86f1b13ef45e920d64949d8d66af30d 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/ssss/QuadSTests.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/ssss/QuadSTests.kt
@@ -19,7 +19,6 @@ package org.matrix.android.sdk.internal.crypto.ssss
 import androidx.lifecycle.Observer
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import org.matrix.android.sdk.InstrumentedTest
-import org.matrix.android.sdk.api.MatrixCallback
 import org.matrix.android.sdk.api.session.Session
 import org.matrix.android.sdk.api.session.securestorage.EncryptedSecretContent
 import org.matrix.android.sdk.api.session.securestorage.KeySigner
@@ -31,7 +30,6 @@ import org.matrix.android.sdk.api.util.Optional
 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.matrix.android.sdk.internal.crypto.SSSS_ALGORITHM_AES_HMAC_SHA2
 import org.matrix.android.sdk.internal.crypto.crosssigning.toBase64NoPadding
 import org.matrix.android.sdk.internal.crypto.secrets.DefaultSharedSecretStorageService
@@ -40,7 +38,6 @@ import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.GlobalScope
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.runBlocking
-import org.amshove.kluent.shouldBe
 import org.junit.Assert.assertEquals
 import org.junit.Assert.assertNotNull
 import org.junit.Assert.assertNull
@@ -70,8 +67,8 @@ class QuadSTests : InstrumentedTest {
 
         val TEST_KEY_ID = "my.test.Key"
 
-        mTestHelper.doSync<SsssKeyCreationInfo> {
-            quadS.generateKey(TEST_KEY_ID, null, "Test Key", emptyKeySigner, it)
+        mTestHelper.runBlockingTest {
+            quadS.generateKey(TEST_KEY_ID, null, "Test Key", emptyKeySigner)
         }
 
         // Assert Account data is updated
@@ -99,7 +96,9 @@ class QuadSTests : InstrumentedTest {
         assertNull("Key was not generated from passphrase", parsed.passphrase)
 
         // Set as default key
-        quadS.setDefaultKey(TEST_KEY_ID, object : MatrixCallback<Unit> {})
+        GlobalScope.launch {
+            quadS.setDefaultKey(TEST_KEY_ID)
+        }
 
         var defaultKeyAccountData: UserAccountDataEvent? = null
         val defaultDataLock = CountDownLatch(1)
@@ -133,12 +132,11 @@ class QuadSTests : InstrumentedTest {
 
         // Store a secret
         val clearSecret = "42".toByteArray().toBase64NoPadding()
-        mTestHelper.doSync<Unit> {
+        mTestHelper.runBlockingTest {
             aliceSession.sharedSecretStorageService.storeSecret(
                     "secret.of.life",
                     clearSecret,
-                    listOf(SharedSecretStorageService.KeyRef(null, keySpec)), // default key
-                    it
+                    listOf(SharedSecretStorageService.KeyRef(null, keySpec)) // default key
             )
         }
 
@@ -155,12 +153,11 @@ class QuadSTests : InstrumentedTest {
 
         // Try to decrypt??
 
-        val decryptedSecret = mTestHelper.doSync<String> {
+        val decryptedSecret = mTestHelper.runBlockingTest {
             aliceSession.sharedSecretStorageService.getSecret(
                     "secret.of.life",
                     null, // default key
-                    keySpec!!,
-                    it
+                    keySpec!!
             )
         }
 
@@ -176,13 +173,13 @@ class QuadSTests : InstrumentedTest {
 
         val TEST_KEY_ID = "my.test.Key"
 
-        mTestHelper.doSync<SsssKeyCreationInfo> {
-            quadS.generateKey(TEST_KEY_ID, null, "Test Key", emptyKeySigner, it)
+        mTestHelper.runBlockingTest {
+            quadS.generateKey(TEST_KEY_ID, null, "Test Key", emptyKeySigner)
         }
 
         // Test that we don't need to wait for an account data sync to access directly the keyid from DB
-        mTestHelper.doSync<Unit> {
-            quadS.setDefaultKey(TEST_KEY_ID, it)
+        mTestHelper.runBlockingTest {
+            quadS.setDefaultKey(TEST_KEY_ID)
         }
 
         mTestHelper.signOutAndClose(aliceSession)
@@ -198,15 +195,14 @@ class QuadSTests : InstrumentedTest {
 
         val mySecretText = "Lorem ipsum dolor sit amet, consectetur adipiscing elit"
 
-        mTestHelper.doSync<Unit> {
+        mTestHelper.runBlockingTest {
             aliceSession.sharedSecretStorageService.storeSecret(
                     "my.secret",
                     mySecretText.toByteArray().toBase64NoPadding(),
                     listOf(
                             SharedSecretStorageService.KeyRef(keyId1, RawBytesKeySpec.fromRecoveryKey(key1Info.recoveryKey)),
                             SharedSecretStorageService.KeyRef(keyId2, RawBytesKeySpec.fromRecoveryKey(key2Info.recoveryKey))
-                    ),
-                    it
+                    )
             )
         }
 
@@ -219,19 +215,17 @@ class QuadSTests : InstrumentedTest {
         assertNotNull(encryptedContent?.get(keyId2))
 
         // Assert that can decrypt with both keys
-        mTestHelper.doSync<String> {
+        mTestHelper.runBlockingTest {
             aliceSession.sharedSecretStorageService.getSecret("my.secret",
                     keyId1,
-                    RawBytesKeySpec.fromRecoveryKey(key1Info.recoveryKey)!!,
-                    it
+                    RawBytesKeySpec.fromRecoveryKey(key1Info.recoveryKey)!!
             )
         }
 
-        mTestHelper.doSync<String> {
+        mTestHelper.runBlockingTest {
             aliceSession.sharedSecretStorageService.getSecret("my.secret",
                     keyId2,
-                    RawBytesKeySpec.fromRecoveryKey(key2Info.recoveryKey)!!,
-                    it
+                    RawBytesKeySpec.fromRecoveryKey(key2Info.recoveryKey)!!
             )
         }
 
@@ -247,50 +241,34 @@ class QuadSTests : InstrumentedTest {
 
         val mySecretText = "Lorem ipsum dolor sit amet, consectetur adipiscing elit"
 
-        mTestHelper.doSync<Unit> {
+        mTestHelper.runBlockingTest {
             aliceSession.sharedSecretStorageService.storeSecret(
                     "my.secret",
                     mySecretText.toByteArray().toBase64NoPadding(),
-                    listOf(SharedSecretStorageService.KeyRef(keyId1, RawBytesKeySpec.fromRecoveryKey(key1Info.recoveryKey))),
-                    it
+                    listOf(SharedSecretStorageService.KeyRef(keyId1, RawBytesKeySpec.fromRecoveryKey(key1Info.recoveryKey)))
             )
         }
 
-        val decryptCountDownLatch = CountDownLatch(1)
-        var error = false
-        aliceSession.sharedSecretStorageService.getSecret("my.secret",
-                keyId1,
-                RawBytesKeySpec.fromPassphrase(
-                        "A bad passphrase",
-                        key1Info.content?.passphrase?.salt ?: "",
-                        key1Info.content?.passphrase?.iterations ?: 0,
-                        null),
-                object : MatrixCallback<String> {
-                    override fun onSuccess(data: String) {
-                        decryptCountDownLatch.countDown()
-                    }
-
-                    override fun onFailure(failure: Throwable) {
-                        error = true
-                        decryptCountDownLatch.countDown()
-                    }
-                }
-        )
-
-        mTestHelper.await(decryptCountDownLatch)
-
-        error shouldBe true
+        mTestHelper.runBlockingTest {
+            aliceSession.sharedSecretStorageService.getSecret("my.secret",
+                    keyId1,
+                    RawBytesKeySpec.fromPassphrase(
+                            "A bad passphrase",
+                            key1Info.content?.passphrase?.salt ?: "",
+                            key1Info.content?.passphrase?.iterations ?: 0,
+                            null)
+            )
+        }
 
         // Now try with correct key
-        mTestHelper.doSync<String> {
+        mTestHelper.runBlockingTest {
             aliceSession.sharedSecretStorageService.getSecret("my.secret",
                     keyId1,
                     RawBytesKeySpec.fromPassphrase(
                             passphrase,
                             key1Info.content?.passphrase?.salt ?: "",
                             key1Info.content?.passphrase?.iterations ?: 0,
-                            null),
-                    it
+                            null)
             )
         }
 
@@ -321,15 +299,15 @@ class QuadSTests : InstrumentedTest {
     private fun generatedSecret(session: Session, keyId: String, asDefault: Boolean = true): SsssKeyCreationInfo {
         val quadS = session.sharedSecretStorageService
 
-        val creationInfo = mTestHelper.doSync<SsssKeyCreationInfo> {
-            quadS.generateKey(keyId, null, keyId, emptyKeySigner, it)
+        val creationInfo = mTestHelper.runBlockingTest {
+            quadS.generateKey(keyId, null, keyId, emptyKeySigner)
         }
 
         assertAccountData(session, "${DefaultSharedSecretStorageService.KEY_ID_BASE}.$keyId")
 
         if (asDefault) {
-            mTestHelper.doSync<Unit> {
-                quadS.setDefaultKey(keyId, it)
+            mTestHelper.runBlockingTest {
+                quadS.setDefaultKey(keyId)
             }
             assertAccountData(session, DefaultSharedSecretStorageService.DEFAULT_KEY_ID)
         }
@@ -340,21 +318,20 @@ class QuadSTests : InstrumentedTest {
     private fun generatedSecretFromPassphrase(session: Session, passphrase: String, keyId: String, asDefault: Boolean = true): SsssKeyCreationInfo {
         val quadS = session.sharedSecretStorageService
 
-        val creationInfo = mTestHelper.doSync<SsssKeyCreationInfo> {
+        val creationInfo = mTestHelper.runBlockingTest {
             quadS.generateKeyWithPassphrase(
                     keyId,
                     keyId,
                     passphrase,
                     emptyKeySigner,
-                    null,
-                    it)
+                    null)
         }
 
         assertAccountData(session, "${DefaultSharedSecretStorageService.KEY_ID_BASE}.$keyId")
         if (asDefault) {
-            val setDefaultLatch = CountDownLatch(1)
-            quadS.setDefaultKey(keyId, TestMatrixCallback(setDefaultLatch))
-            mTestHelper.await(setDefaultLatch)
+            mTestHelper.runBlockingTest {
+                quadS.setDefaultKey(keyId)
+            }
             assertAccountData(session, DefaultSharedSecretStorageService.DEFAULT_KEY_ID)
         }
 
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/SASTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/SASTest.kt
index a81f503e774e38c5467b7ae4f0e4662c616525d9..4ea8cdc074449fb7bba792feda5b8d8f13a1c034 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/SASTest.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/SASTest.kt
@@ -593,16 +593,14 @@ class SASTest : InstrumentedTest {
                 requestID!!,
                 cryptoTestData.roomId,
                 bobSession.myUserId,
-                bobSession.sessionParams.deviceId!!,
-                null)
+                bobSession.sessionParams.deviceId!!)
 
         bobVerificationService.beginKeyVerificationInDMs(
                 VerificationMethod.SAS,
                 requestID!!,
                 cryptoTestData.roomId,
                 aliceSession.myUserId,
-                aliceSession.sessionParams.deviceId!!,
-                null)
+                aliceSession.sessionParams.deviceId!!)
 
         // we should reach SHOW SAS on both
         var alicePovTx: SasVerificationTransaction?
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/search/SearchMessagesTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/search/SearchMessagesTest.kt
index cadb83ca00bc05fbf53ebc0b389e4e8b59eca4af..1baf490dfc70852ebe41085f4697799e8e542795 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/search/SearchMessagesTest.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/search/SearchMessagesTest.kt
@@ -17,149 +17,104 @@
 package org.matrix.android.sdk.session.search
 
 import org.junit.Assert.assertTrue
-import org.junit.Assert.fail
 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.MatrixCallback
 import org.matrix.android.sdk.api.extensions.orFalse
 import org.matrix.android.sdk.api.session.events.model.toModel
 import org.matrix.android.sdk.api.session.room.model.message.MessageContent
 import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings
 import org.matrix.android.sdk.api.session.search.SearchResult
 import org.matrix.android.sdk.common.CommonTestHelper
+import org.matrix.android.sdk.common.CryptoTestData
 import org.matrix.android.sdk.common.CryptoTestHelper
-import org.matrix.android.sdk.common.TestConstants
 import java.util.concurrent.CountDownLatch
-import java.util.concurrent.TimeUnit
 
 @RunWith(JUnit4::class)
 @FixMethodOrder(MethodSorters.JVM)
 class SearchMessagesTest : InstrumentedTest {
 
-    private val MESSAGE = "Lorem ipsum dolor sit amet"
+    companion object {
+        private const val MESSAGE = "Lorem ipsum dolor sit amet"
+    }
 
     private val commonTestHelper = CommonTestHelper(context())
     private val cryptoTestHelper = CryptoTestHelper(commonTestHelper)
 
     @Test
     fun sendTextMessageAndSearchPartOfItUsingSession() {
-        val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(false)
-        val aliceSession = cryptoTestData.firstSession
-        val aliceRoomId = cryptoTestData.roomId
-        aliceSession.cryptoService().setWarnOnUnknownDevices(false)
-        val roomFromAlicePOV = aliceSession.getRoom(aliceRoomId)!!
-        val aliceTimeline = roomFromAlicePOV.createTimeline(null, TimelineSettings(10))
-        aliceTimeline.start()
-
-        commonTestHelper.sendTextMessage(
-                roomFromAlicePOV,
-                MESSAGE,
-                2)
-
-        run {
-            val lock = CountDownLatch(1)
-
-            val eventListener = commonTestHelper.createEventListener(lock) { snapshot ->
-                snapshot.count { it.root.content.toModel<MessageContent>()?.body?.startsWith(MESSAGE).orFalse() } == 2
-            }
-
-            aliceTimeline.addListener(eventListener)
-            commonTestHelper.await(lock)
-
-            val data = commonTestHelper.runBlockingTest {
-                aliceSession
-                        .searchService()
-                        .search(
-                                searchTerm = "lore",
-                                limit = 10,
-                                includeProfile = true,
-                                afterLimit = 0,
-                                beforeLimit = 10,
-                                orderByRecent = true,
-                                nextBatch = null,
-                                roomId = aliceRoomId
-                        )
-            }
-            assertTrue(data.results?.size == 2)
-            assertTrue(
-                    data.results
-                            ?.all {
-                                (it.event.content?.get("body") as? String)?.startsWith(MESSAGE).orFalse()
-                            }.orFalse()
-            )
-
-            aliceTimeline.removeAllListeners()
-            cryptoTestData.cleanUp(commonTestHelper)
+        doTest { cryptoTestData ->
+            cryptoTestData.firstSession
+                    .searchService()
+                    .search(
+                            searchTerm = "lore",
+                            limit = 10,
+                            includeProfile = true,
+                            afterLimit = 0,
+                            beforeLimit = 10,
+                            orderByRecent = true,
+                            nextBatch = null,
+                            roomId = cryptoTestData.roomId
+                    )
         }
-
-        aliceSession.startSync(true)
     }
 
     @Test
     fun sendTextMessageAndSearchPartOfItUsingRoom() {
-        val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(false)
+        doTest { cryptoTestData ->
+            cryptoTestData.firstSession
+                    .getRoom(cryptoTestData.roomId)!!
+                    .search(
+                            searchTerm = "lore",
+                            limit = 10,
+                            includeProfile = true,
+                            afterLimit = 0,
+                            beforeLimit = 10,
+                            orderByRecent = true,
+                            nextBatch = null
+                    )
+        }
+    }
+
+    private fun doTest(block: suspend (CryptoTestData) -> SearchResult) {
+        val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceInARoom(false)
         val aliceSession = cryptoTestData.firstSession
         val aliceRoomId = cryptoTestData.roomId
-        aliceSession.cryptoService().setWarnOnUnknownDevices(false)
         val roomFromAlicePOV = aliceSession.getRoom(aliceRoomId)!!
         val aliceTimeline = roomFromAlicePOV.createTimeline(null, TimelineSettings(10))
         aliceTimeline.start()
 
+        val lock = CountDownLatch(1)
+
+        val eventListener = commonTestHelper.createEventListener(lock) { snapshot ->
+            snapshot.count { it.root.content.toModel<MessageContent>()?.body?.startsWith(MESSAGE).orFalse() } == 2
+        }
+
+        aliceTimeline.addListener(eventListener)
+
         commonTestHelper.sendTextMessage(
                 roomFromAlicePOV,
                 MESSAGE,
                 2)
 
-        run {
-            var lock = CountDownLatch(1)
-
-            val eventListener = commonTestHelper.createEventListener(lock) { snapshot ->
-                snapshot.count { it.root.content.toModel<MessageContent>()?.body?.startsWith(MESSAGE).orFalse() } == 2
-            }
+        commonTestHelper.await(lock)
 
-            aliceTimeline.addListener(eventListener)
-            commonTestHelper.await(lock)
-
-            lock = CountDownLatch(1)
-            roomFromAlicePOV
-                    .search(
-                            searchTerm = "lore",
-                            limit = 10,
-                            includeProfile = true,
-                            afterLimit = 0,
-                            beforeLimit = 10,
-                            orderByRecent = true,
-                            nextBatch = null,
-                            callback = object : MatrixCallback<SearchResult> {
-                                override fun onSuccess(data: SearchResult) {
-                                    super.onSuccess(data)
-                                    assertTrue(data.results?.size == 2)
-                                    assertTrue(
-                                            data.results
-                                                    ?.all {
-                                                        (it.event.content?.get("body") as? String)?.startsWith(MESSAGE).orFalse()
-                                                    }.orFalse()
-                                    )
-                                    lock.countDown()
-                                }
-
-                                override fun onFailure(failure: Throwable) {
-                                    super.onFailure(failure)
-                                    fail(failure.localizedMessage)
-                                    lock.countDown()
-                                }
-                            }
-                    )
-            lock.await(TestConstants.timeOutMillis, TimeUnit.MILLISECONDS)
-
-            aliceTimeline.removeAllListeners()
-            cryptoTestData.cleanUp(commonTestHelper)
+        val data = commonTestHelper.runBlockingTest {
+            block.invoke(cryptoTestData)
         }
 
-        aliceSession.startSync(true)
+        assertTrue(data.results?.size == 2)
+        assertTrue(
+                data.results
+                        ?.all {
+                            (it.event.content?.get("body") as? String)?.startsWith(MESSAGE).orFalse()
+                        }.orFalse()
+        )
+
+        aliceTimeline.removeAllListeners()
+        cryptoTestData.cleanUp(commonTestHelper)
     }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/Matrix.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/Matrix.kt
index a5d457222f6d9371824d08253f9d27748f77557a..99802592667e956951a946cae612d5bb2e28d662 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/Matrix.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/Matrix.kt
@@ -25,9 +25,12 @@ import org.matrix.android.sdk.BuildConfig
 import org.matrix.android.sdk.api.auth.AuthenticationService
 import org.matrix.android.sdk.api.auth.HomeServerHistoryService
 import org.matrix.android.sdk.api.legacy.LegacySessionImporter
+import org.matrix.android.sdk.api.network.ApiInterceptorListener
+import org.matrix.android.sdk.api.network.ApiPath
 import org.matrix.android.sdk.api.raw.RawService
 import org.matrix.android.sdk.internal.SessionManager
 import org.matrix.android.sdk.internal.di.DaggerMatrixComponent
+import org.matrix.android.sdk.internal.network.ApiInterceptor
 import org.matrix.android.sdk.internal.network.UserAgentHolder
 import org.matrix.android.sdk.internal.util.BackgroundDetectionObserver
 import org.matrix.olm.OlmManager
@@ -49,6 +52,7 @@ class Matrix private constructor(context: Context, matrixConfiguration: MatrixCo
     @Inject internal lateinit var olmManager: OlmManager
     @Inject internal lateinit var sessionManager: SessionManager
     @Inject internal lateinit var homeServerHistoryService: HomeServerHistoryService
+    @Inject internal lateinit var apiInterceptor: ApiInterceptor
 
     init {
         Monarchy.init(context)
@@ -73,6 +77,14 @@ class Matrix private constructor(context: Context, matrixConfiguration: MatrixCo
         return legacySessionImporter
     }
 
+    fun registerApiInterceptorListener(path: ApiPath, listener: ApiInterceptorListener) {
+        apiInterceptor.addListener(path, listener)
+    }
+
+    fun unregisterApiInterceptorListener(path: ApiPath, listener: ApiInterceptorListener) {
+        apiInterceptor.removeListener(path, listener)
+    }
+
     companion object {
 
         private lateinit var instance: Matrix
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/RoomDisplayNameFallbackProvider.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/RoomDisplayNameFallbackProvider.kt
index 4ac14d5f63afb92d1d552c108db7f25317df7b51..a34dbcc1965a08741ed6d66bf0d2b8e61ca732f8 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/RoomDisplayNameFallbackProvider.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/RoomDisplayNameFallbackProvider.kt
@@ -18,9 +18,10 @@ package org.matrix.android.sdk.api
 
 interface RoomDisplayNameFallbackProvider {
     fun getNameForRoomInvite(): String
-    fun getNameForEmptyRoom(): String
-    fun getNameFor2members(name1: String?, name2: String?): String
-    fun getNameFor3members(name1: String?, name2: String?, name3: String?): String
-    fun getNameFor4members(name1: String?, name2: String?, name3: String?, name4: String?): String
-    fun getNameFor4membersAndMore(name1: String?, name2: String?, name3: String?, remainingCount: Int): String
+    fun getNameForEmptyRoom(isDirect: Boolean, leftMemberNames: List<String>): String
+    fun getNameFor1member(name: String): String
+    fun getNameFor2members(name1: String, name2: String): String
+    fun getNameFor3members(name1: String, name2: String, name3: String): String
+    fun getNameFor4members(name1: String, name2: String, name3: String, name4: String): String
+    fun getNameFor4membersAndMore(name1: String, name2: String, name3: String, remainingCount: Int): String
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/MatrixCallbackDelegate.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/registration/RegistrationAvailability.kt
similarity index 62%
rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/MatrixCallbackDelegate.kt
rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/registration/RegistrationAvailability.kt
index 63d37f409f12c4e9ca13edebaecc03dadce127f6..f9a7ace7ba1218b8eab27bb326db1d0b21e0569c 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/MatrixCallbackDelegate.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/registration/RegistrationAvailability.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright 2020 The Matrix.org Foundation C.I.C.
+ * Copyright 2021 The Matrix.org Foundation C.I.C.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,11 +14,11 @@
  * limitations under the License.
  */
 
-package org.matrix.android.sdk.api.util
+package org.matrix.android.sdk.api.auth.registration
 
-import org.matrix.android.sdk.api.MatrixCallback
+import org.matrix.android.sdk.api.failure.Failure
 
-/**
- * Simple MatrixCallback implementation which delegate its calls to another callback
- */
-open class MatrixCallbackDelegate<T>(private val callback: MatrixCallback<T>) : MatrixCallback<T> by callback
+sealed class RegistrationAvailability {
+    object Available : RegistrationAvailability()
+    data class NotAvailable(val failure: Failure.ServerError) : RegistrationAvailability()
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/registration/RegistrationWizard.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/registration/RegistrationWizard.kt
index d00c9a0c8211c01b1dafbbdbf47d97db8f731681..38a5a77291c1e8688b0cd6981ea54cda8350915f 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/registration/RegistrationWizard.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/registration/RegistrationWizard.kt
@@ -36,6 +36,8 @@ interface RegistrationWizard {
 
     suspend fun checkIfEmailHasBeenValidated(delayMillis: Long): RegistrationResult
 
+    suspend fun registrationAvailable(userName: String): RegistrationAvailability
+
     val currentThreePid: String?
 
     // True when login and password has been sent with success to the homeserver
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 e0ee9f36ba8f9b9387afa7dd523e2609abf8e16e..0ba61e5890ea38fecab67e156b684206e32e8769 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
@@ -37,6 +37,18 @@ fun Throwable.shouldBeRetried(): Boolean {
             || (this is Failure.ServerError && error.code == MatrixError.M_LIMIT_EXCEEDED)
 }
 
+/**
+ * Get the retry delay in case of rate limit exceeded error, adding 100 ms, of defaultValue otherwise
+ */
+fun Throwable.getRetryDelay(defaultValue: Long): Long {
+    return (this as? Failure.ServerError)
+            ?.error
+            ?.takeIf { it.code == MatrixError.M_LIMIT_EXCEEDED }
+            ?.retryAfterMillis
+            ?.plus(100L)
+            ?: defaultValue
+}
+
 fun Throwable.isInvalidPassword(): Boolean {
     return this is Failure.ServerError
             && error.code == MatrixError.M_FORBIDDEN
@@ -53,13 +65,16 @@ fun Throwable.isInvalidUIAAuth(): Boolean {
  * Try to convert to a RegistrationFlowResponse. Return null in the cases it's not possible
  */
 fun Throwable.toRegistrationFlowResponse(): RegistrationFlowResponse? {
-    return if (this is Failure.OtherServerError && httpCode == 401) {
+    return if (this is Failure.OtherServerError
+            && httpCode == HttpsURLConnection.HTTP_UNAUTHORIZED /* 401 */) {
         tryOrNull {
             MoshiProvider.providesMoshi()
                     .adapter(RegistrationFlowResponse::class.java)
                     .fromJson(errorBody)
         }
-    } else if (this is Failure.ServerError && httpCode == 401 && error.code == MatrixError.M_FORBIDDEN) {
+    } else if (this is Failure.ServerError
+            && httpCode == HttpsURLConnection.HTTP_UNAUTHORIZED /* 401 */
+            && error.code == MatrixError.M_FORBIDDEN) {
         // This happens when the submission for this stage was bad (like bad password)
         if (error.session != null && error.flows != null) {
             RegistrationFlowResponse(
@@ -75,3 +90,11 @@ fun Throwable.toRegistrationFlowResponse(): RegistrationFlowResponse? {
         null
     }
 }
+
+fun Throwable.isRegistrationAvailabilityError(): Boolean {
+    return this is Failure.ServerError
+            && httpCode == HttpsURLConnection.HTTP_BAD_REQUEST /* 400 */
+            && (error.code == MatrixError.M_USER_IN_USE
+            || error.code == MatrixError.M_INVALID_USERNAME
+            || error.code == MatrixError.M_EXCLUSIVE)
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/network/ApiInterceptorListener.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/network/ApiInterceptorListener.kt
new file mode 100644
index 0000000000000000000000000000000000000000..ad21da8fdff174400957e42ef343c1ea13d60d6a
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/network/ApiInterceptorListener.kt
@@ -0,0 +1,21 @@
+/*
+ * 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.api.network
+
+interface ApiInterceptorListener {
+    fun onApiResponse(path: ApiPath, response: String)
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/network/ApiPath.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/network/ApiPath.kt
new file mode 100644
index 0000000000000000000000000000000000000000..db112a30b26d5e7bbdc98b6592354c0d98419069
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/network/ApiPath.kt
@@ -0,0 +1,183 @@
+/*
+ * 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.api.network
+
+import org.matrix.android.sdk.internal.network.NetworkConstants
+
+enum class ApiPath(val path: String, val method: String) {
+    // AuthApi
+    VERSIONS(NetworkConstants.URI_API_PREFIX_PATH_R0 + "versions", "GET"),
+    REGISTER(NetworkConstants.URI_API_PREFIX_PATH_R0 + "register", "POST"),
+    ADD_3PID(NetworkConstants.URI_API_PREFIX_PATH_R0 + "register/{threePid}/requestToken", "POST"),
+    LOGIN_FLOWS(NetworkConstants.URI_API_PREFIX_PATH_R0 + "login", "GET"),
+    LOGIN(NetworkConstants.URI_API_PREFIX_PATH_R0 + "login", "POST"),
+    RESET_PASSWORD(NetworkConstants.URI_API_PREFIX_PATH_R0 + "account/password/email/requestToken", "POST"),
+    RESET_PASSWORD_MAIL_CONFIRMED(NetworkConstants.URI_API_PREFIX_PATH_R0 + "account/password", "POST"),
+
+    // DirectoryApi
+    ROOM_ID_BY_ALIAS(NetworkConstants.URI_API_PREFIX_PATH_R0 + "directory/room/{roomAlias}", "GET"),
+    ROOM_DIRECTORY_VISIBILITY(NetworkConstants.URI_API_PREFIX_PATH_R0 + "directory/list/room/{roomId}", "GET"),
+    SET_ROOM_DIRECTORY_VISIBILITY(NetworkConstants.URI_API_PREFIX_PATH_R0 + "directory/list/room/{roomId}", "PUT"),
+    ADD_ROOM_ALIAS(NetworkConstants.URI_API_PREFIX_PATH_R0 + "directory/room/{roomAlias}", "PUT"),
+    DELETE_ROOM_ALIAS(NetworkConstants.URI_API_PREFIX_PATH_R0 + "directory/room/{roomAlias}", "DELETE"),
+
+    // CryptoApi
+    GET_DEVICES(NetworkConstants.URI_API_PREFIX_PATH_R0 + "devices", "GET"),
+    GET_DEVICE_INFO(NetworkConstants.URI_API_PREFIX_PATH_R0 + "devices/{deviceId}", "GET"),
+    UPLOAD_KEYS(NetworkConstants.URI_API_PREFIX_PATH_R0 + "keys/upload", "POST"),
+    DOWNLOAD_KEYS_FOR_USERS(NetworkConstants.URI_API_PREFIX_PATH_R0 + "keys/query", "POST"),
+    UPLOAD_SIGNING_KEYS(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "keys/device_signing/upload", "POST"),
+    UPLOAD_SIGNATURES(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "keys/signatures/upload", "POST"),
+    CLAIM_ONE_TIME_KEYS_FOR_USERS_DEVICES(NetworkConstants.URI_API_PREFIX_PATH_R0 + "keys/claim", "POST"),
+    SEND_TO_DEVICE(NetworkConstants.URI_API_PREFIX_PATH_R0 + "sendToDevice/{eventType}/{txnId}", "PUT"),
+    DELETE_DEVICE(NetworkConstants.URI_API_PREFIX_PATH_R0 + "devices/{device_id}", "DELETE"),
+    UPDATE_DEVICE_INFO(NetworkConstants.URI_API_PREFIX_PATH_R0 + "devices/{device_id}", "PUT"),
+    GET_KEY_CHANGES(NetworkConstants.URI_API_PREFIX_PATH_R0 + "keys/changes", "GET"),
+
+    // RoomKeysApi
+    CREATE_KEYS_BACKUP_VERSION(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "room_keys/version", "POST"),
+    GET_KEYS_BACKUP_LAST_VERSION(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "room_keys/version", "GET"),
+    GET_KEYS_BACKUP_VERSION(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "room_keys/version/{version}", "GET"),
+    UPDATE_KEYS_BACKUP_VERSION(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "room_keys/version/{version}", "PUT"),
+    STORE_ROOM_SESSION_DATA(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "room_keys/keys/{roomId}/{sessionId}", "PUT"),
+    STORE_ROOM_SESSIONS_DATA(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "room_keys/keys/{roomId}", "PUT"),
+    STORE_SESSIONS_DATA(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "room_keys/keys", "PUT"),
+    GET_ROOM_SESSION_DATA(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "room_keys/keys/{roomId}/{sessionId}", "GET"),
+    GET_ROOM_SESSIONS_DATA(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "room_keys/keys/{roomId}", "GET"),
+    GET_SESSIONS_DATA(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "room_keys/keys", "GET"),
+    DELETE_ROOM_SESSION_DATA(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "room_keys/keys/{roomId}/{sessionId}", "DELETE"),
+    DELETE_ROOM_SESSIONS_DATA(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "room_keys/keys/{roomId}", "DELETE"),
+    DELETE_SESSIONS_DATA(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "room_keys/keys", "DELETE"),
+    DELETE_BACKUP(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "room_keys/version/{version}", "DELETE"),
+
+    // AccountApi
+    CHANGE_PASSWORD(NetworkConstants.URI_API_PREFIX_PATH_R0 + "account/password", "POST"),
+    DEACTIVATE_ACCOUNT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "account/deactivate", "POST"),
+
+    // SearchApi
+    SEARCH(NetworkConstants.URI_API_PREFIX_PATH_R0 + "search", "POST"),
+
+    // FederationApi
+    GET_FEDERATION_VERSION(NetworkConstants.URI_FEDERATION_PATH + "version", "GET"),
+
+    // VoipApi
+    GET_TURN_SERVER(NetworkConstants.URI_API_PREFIX_PATH_R0 + "voip/turnServer", "GET"),
+
+    // PushGatewayApi
+    NOTIFY_PUSH_GATEWAY(NetworkConstants.URI_PUSH_GATEWAY_PREFIX_PATH + "notify", "POST"),
+
+    // GroupApi
+    GET_GROUP_SUMMARY(NetworkConstants.URI_API_PREFIX_PATH_R0 + "groups/{groupId}/summary", "GET"),
+    GET_GROUP_ROOMS(NetworkConstants.URI_API_PREFIX_PATH_R0 + "groups/{groupId}/rooms", "GET"),
+    GET_GROUP_USERS(NetworkConstants.URI_API_PREFIX_PATH_R0 + "groups/{groupId}/users", "GET"),
+
+    // CapabilitiesApi
+    GET_CAPABILITIES(NetworkConstants.URI_API_PREFIX_PATH_R0 + "capabilities", "GET"),
+    GET_VERSIONS(NetworkConstants.URI_API_PREFIX_PATH_ + "versions", "GET"),
+    PING(NetworkConstants.URI_API_PREFIX_PATH_ + "versions", "GET"),
+
+    // IdentityApi
+    GET_ACCOUNT(NetworkConstants.URI_IDENTITY_PATH_V2 + "account", "GET"),
+    LOGOUT(NetworkConstants.URI_IDENTITY_PATH_V2 + "account/logout", "POST"),
+    IDENTITY_HAS_DETAILS(NetworkConstants.URI_IDENTITY_PATH_V2 + "hash_details", "GET"),
+    LOOKUP(NetworkConstants.URI_IDENTITY_PATH_V2 + "lookup", "POST"),
+    REQUEST_TOKEN_TO_BIND_EMAIL(NetworkConstants.URI_IDENTITY_PATH_V2 + "validate/email/requestToken", "POST"),
+    REQUEST_TOKEN_TO_BIND_MSISDN(NetworkConstants.URI_IDENTITY_PATH_V2 + "validate/msisdn/requestToken", "POST"),
+    SUBMIT_TOKEN(NetworkConstants.URI_IDENTITY_PATH_V2 + "validate/{medium}/submitToken", "POST"),
+
+    // FilterApi
+    UPLOAD_FILTER(NetworkConstants.URI_API_PREFIX_PATH_R0 + "user/{userId}/filter", "POST"),
+    GET_FILTER_BY_ID(NetworkConstants.URI_API_PREFIX_PATH_R0 + "user/{userId}/filter/{filterId}", "GET"),
+
+    // IndentityAuthApi
+    IDENTITY_REGISTER(NetworkConstants.URI_IDENTITY_PATH_V2 + "account/register", "POST"),
+
+    // MediaApi
+    GET_MEDIA_CONFIG(NetworkConstants.URI_API_MEDIA_PREFIX_PATH_R0 + "config", "GET"),
+    GET_PREVIEW_URL_DATA(NetworkConstants.URI_API_MEDIA_PREFIX_PATH_R0 + "preview_url", "GET"),
+
+    // OpenIdApi
+    OPEN_ID_TOKEN(NetworkConstants.URI_API_PREFIX_PATH_R0 + "user/{userId}/openid/request_token", "POST"),
+
+    // ProfileApi
+    GET_PROFILE(NetworkConstants.URI_API_PREFIX_PATH_R0 + "profile/{userId}", "GET"),
+    GET_THREE_PIDS(NetworkConstants.URI_API_PREFIX_PATH_R0 + "account/3pid", "GET"),
+    SET_DISPLAY_NAME(NetworkConstants.URI_API_PREFIX_PATH_R0 + "profile/{userId}/displayname", "PUT"),
+    SET_AVATAR_URL(NetworkConstants.URI_API_PREFIX_PATH_R0 + "profile/{userId}/avatar_url", "PUT"),
+    BIND_THREE_PID(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "account/3pid/bind", "POST"),
+    UNBIND_THREE_PID(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "account/3pid/unbind", "POST"),
+    ADD_EMAIL(NetworkConstants.URI_API_PREFIX_PATH_R0 + "account/3pid/email/requestToken", "POST"),
+    ADD_MSISDN(NetworkConstants.URI_API_PREFIX_PATH_R0 + "account/3pid/msisdn/requestToken", "POST"),
+    FINALIZE_ADD_THREE_PID(NetworkConstants.URI_API_PREFIX_PATH_R0 + "account/3pid/add", "POST"),
+    DELETE_THREE_PID(NetworkConstants.URI_API_PREFIX_PATH_R0 + "account/3pid/delete", "POST"),
+
+    // PusherRulesApi
+    GET_ALL_PUSHER_RULES(NetworkConstants.URI_API_PREFIX_PATH_R0 + "pushrules/", "GET"),
+    UPDATE_ENABLE_PUSH_RULE_STATUS(NetworkConstants.URI_API_PREFIX_PATH_R0 + "pushrules/global/{kind}/{ruleId}/enabled", "PUT"),
+    UPDATE_PUSH_RULE_ACTIONS(NetworkConstants.URI_API_PREFIX_PATH_R0 + "pushrules/global/{kind}/{ruleId}/actions", "PUT"),
+    DELETE_PUSH_RULE(NetworkConstants.URI_API_PREFIX_PATH_R0 + "pushrules/global/{kind}/{ruleId}", "DELETE"),
+    ADD_PUSH_RULE(NetworkConstants.URI_API_PREFIX_PATH_R0 + "pushrules/global/{kind}/{ruleId}", "PUT"),
+
+    // PusherApi
+    GET_PUSHERS(NetworkConstants.URI_API_PREFIX_PATH_R0 + "pushers", "GET"),
+    SET_PUSHER(NetworkConstants.URI_API_PREFIX_PATH_R0 + "pushers/set", "POST"),
+
+    // SignOutApi
+    LOGIN_AGAIN(NetworkConstants.URI_API_PREFIX_PATH_R0 + "login", "POST"),
+    SIGN_OUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "logout", "POST"),
+
+    // RoomApi
+    GET_PUBLIC_ROOMS(NetworkConstants.URI_API_PREFIX_PATH_R0 + "publicRooms", "POST"),
+    CREATE_ROOM(NetworkConstants.URI_API_PREFIX_PATH_R0 + "createRoom", "POST"),
+    GET_ROOM_MESSAGES_FROM(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/messages", "GET"),
+    GET_MEMBERS(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/members", "GET"),
+    SEND_EVENT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/send/{eventType}/{txId}", "PUT"),
+    GET_CONTEXT_OF_EVENT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/context/{eventId}", "GET"),
+    GET_EVENT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/event/{eventId}", "GET"),
+    SEND_READ_MARKER(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/read_markers", "POST"),
+    INVITE(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/invite", "POST"),
+    INVITE_USING_THREE_PID(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/invite", "POST"),
+    SEND_STATE_EVENT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/state/{state_event_type}", "PUT"),
+    SEND_STATE_EVENT_WITH_STATE_KEY(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/state/{state_event_type}/{state_key}", "PUT"),
+    GET_ROOM_STATE(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/state", "GET"),
+    SEND_RELATION(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/send_relation/{parent_id}/{relation_type}/{event_type}", "POST"),
+    GET_RELATIONS(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "rooms/{roomId}/relations/{eventId}/{relationType}/{eventType}", "GET"),
+    JOIN_ROOM(NetworkConstants.URI_API_PREFIX_PATH_R0 + "join/{roomIdOrAlias}", "POST"),
+    LEAVE_ROOM(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/leave", "POST"),
+    BAN_USER(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/ban", "POST"),
+    UNBAN_USER(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/unban", "POST"),
+    KICK_USER(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/kick", "POST"),
+    REDACT_EVENT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/redact/{eventId}/{txnId}", "PUT"),
+    REPORT_CONTENT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/report/{eventId}", "POST"),
+    GET_ALIASES(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "org.matrix.msc2432/rooms/{roomId}/aliases", "GET"),
+    SEND_TYPING_STATE(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/typing/{userId}", "PUT"),
+    PUT_TAG(NetworkConstants.URI_API_PREFIX_PATH_R0 + "user/{userId}/rooms/{roomId}/tags/{tag}", "PUT"),
+    DELETE_TAG(NetworkConstants.URI_API_PREFIX_PATH_R0 + "user/{userId}/rooms/{roomId}/tags/{tag}", "DELETE"),
+
+    // SyncApi
+    SYNC(NetworkConstants.URI_API_PREFIX_PATH_R0 + "sync", "GET"),
+
+    // ThirdPartyApi
+    THIRD_PARTY_PROTOCOLS(NetworkConstants.URI_API_PREFIX_PATH_R0 + "thirdparty/protocols", "GET"),
+    THIRD_PARTY_USER(NetworkConstants.URI_API_PREFIX_PATH_R0 + "thirdparty/protocols/user/{protocol}", "GET"),
+
+    // SearchUserApi
+    SEARCH_USERS(NetworkConstants.URI_API_PREFIX_PATH_R0 + "user_directory/search", "POST"),
+
+    // AccountDataApi
+    SET_ACCOUNT_DATA(NetworkConstants.URI_API_PREFIX_PATH_R0 + "user/{userId}/account_data/{type}", "PUT")
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/PushRuleService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/PushRuleService.kt
index 4da1662681e08c5dd6a13ffdca049509796adc63..d9bf5cfd13b2b0bb3123df5943b82d5d1f30f46a 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/PushRuleService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/PushRuleService.kt
@@ -39,6 +39,8 @@ interface PushRuleService {
 
     fun removePushRuleListener(listener: PushRuleListener)
 
+    fun getActions(event: Event): List<Action>
+
 //    fun fulfilledBingRule(event: Event, rules: List<PushRule>): PushRule?
 
     interface PushRuleListener {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/query/RoomCategoryFilter.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/query/RoomCategoryFilter.kt
new file mode 100644
index 0000000000000000000000000000000000000000..c8ccc4c8a3ec3d8e9abce5ffee7591675028120f
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/query/RoomCategoryFilter.kt
@@ -0,0 +1,24 @@
+/*
+ * 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.api.query
+
+enum class RoomCategoryFilter {
+    ONLY_DM,
+    ONLY_ROOMS,
+    ONLY_WITH_NOTIFICATIONS,
+    ALL
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/query/RoomTagQueryFilter.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/query/RoomTagQueryFilter.kt
new file mode 100644
index 0000000000000000000000000000000000000000..613916bc18a3a4b0d569595bc7a444a40bde45c7
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/query/RoomTagQueryFilter.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.api.query
+
+data class RoomTagQueryFilter(
+        val isFavorite: Boolean?,
+        val isLowPriority: Boolean?,
+        val isServerNotice: Boolean?
+)
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 7a24ccac111224013585540df912b5f6bb4324dc..a15799d862ff4a601deb57ea1fb0df2e4e291228 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
@@ -30,6 +30,7 @@ import org.matrix.android.sdk.api.session.call.CallSignalingService
 import org.matrix.android.sdk.api.session.content.ContentUploadStateTracker
 import org.matrix.android.sdk.api.session.content.ContentUrlResolver
 import org.matrix.android.sdk.api.session.crypto.CryptoService
+import org.matrix.android.sdk.api.session.events.EventService
 import org.matrix.android.sdk.api.session.file.ContentDownloadStateTracker
 import org.matrix.android.sdk.api.session.file.FileService
 import org.matrix.android.sdk.api.session.group.GroupService
@@ -68,6 +69,7 @@ interface Session :
         SignOutService,
         FilterService,
         TermsService,
+        EventService,
         ProfileService,
         PushRuleService,
         PushersService,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/accountdata/AccountDataService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/accountdata/AccountDataService.kt
index f5d2a7df3e7e973c1594fa8fa924ed658fad5107..5ebeaad3de6fb8a1819024b154973df3a80e9968 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/accountdata/AccountDataService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/accountdata/AccountDataService.kt
@@ -17,9 +17,7 @@
 package org.matrix.android.sdk.api.session.accountdata
 
 import androidx.lifecycle.LiveData
-import org.matrix.android.sdk.api.MatrixCallback
 import org.matrix.android.sdk.api.session.events.model.Content
-import org.matrix.android.sdk.api.util.Cancelable
 import org.matrix.android.sdk.api.util.Optional
 
 interface AccountDataService {
@@ -48,5 +46,5 @@ interface AccountDataService {
     /**
      * Update the account data with the provided type and the provided account data content
      */
-    fun updateAccountData(type: String, content: Content, callback: MatrixCallback<Unit>? = null): Cancelable
+    suspend fun updateAccountData(type: String, content: Content)
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/verification/VerificationService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/verification/VerificationService.kt
index 2413786ea90cd85f7d0fef382670c9dbbfcf4e5c..54a1e896ae04088bfc4c638f3f53dddcde3e1af3 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/verification/VerificationService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/verification/VerificationService.kt
@@ -16,7 +16,6 @@
 
 package org.matrix.android.sdk.api.session.crypto.verification
 
-import org.matrix.android.sdk.api.MatrixCallback
 import org.matrix.android.sdk.api.session.events.model.Event
 import org.matrix.android.sdk.api.session.events.model.LocalEcho
 
@@ -79,8 +78,7 @@ interface VerificationService {
                                   transactionId: String,
                                   roomId: String,
                                   otherUserId: String,
-                                  otherDeviceId: String,
-                                  callback: MatrixCallback<String>?): String?
+                                  otherDeviceId: String): String
 
     /**
      * Returns false if the request is unknown
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/MXQueuedEncryption.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/EventService.kt
old mode 100755
new mode 100644
similarity index 56%
rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/MXQueuedEncryption.kt
rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/EventService.kt
index fe6b3a74bb0f73f50fcbfed790ac3749c7ffc5b7..297f277497f13e48f59392b00a08c55eaaab9b98
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/MXQueuedEncryption.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/EventService.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright 2020 The Matrix.org Foundation C.I.C.
+ * Copyright (c) 2021 The Matrix.org Foundation C.I.C.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,21 +14,16 @@
  * limitations under the License.
  */
 
-package org.matrix.android.sdk.internal.crypto.model
+package org.matrix.android.sdk.api.session.events
 
-import org.matrix.android.sdk.api.MatrixCallback
-import org.matrix.android.sdk.api.session.events.model.Content
+import org.matrix.android.sdk.api.session.events.model.Event
 
-class MXQueuedEncryption {
+interface EventService {
 
     /**
-     * The data to encrypt.
+     * Ask the homeserver for an event content. The SDK will try to decrypt it if it is possible
+     * The result will not be stored into cache
      */
-    var eventContent: Content? = null
-    var eventType: String? = null
-
-    /**
-     * the asynchronous callback
-     */
-    var apiCallback: MatrixCallback<Content>? = null
+    suspend fun getEvent(roomId: String,
+                         eventId: String): Event
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt
index 844e8dbbab575abfde48aa878390234f65b1087d..89b873febbb6d593d93fe470acf11a7e1a2b4ee0 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt
@@ -289,3 +289,7 @@ fun Event.getRelationContent(): RelationDefaultContent? {
 fun Event.isReply(): Boolean {
     return getRelationContent()?.inReplyTo?.eventId != null
 }
+
+fun Event.isEdition(): Boolean {
+    return getRelationContent()?.takeIf { it.type == RelationType.REPLACE }?.eventId != null
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/file/FileService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/file/FileService.kt
index bcdb5ea2575bd132e70f8329acecbe5033078056..adfdc2498e40c45e4b05d2e1ce0f86224b4ca97d 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/file/FileService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/file/FileService.kt
@@ -17,11 +17,9 @@
 package org.matrix.android.sdk.api.session.file
 
 import android.net.Uri
-import org.matrix.android.sdk.api.MatrixCallback
 import org.matrix.android.sdk.api.session.room.model.message.MessageWithAttachmentContent
 import org.matrix.android.sdk.api.session.room.model.message.getFileName
 import org.matrix.android.sdk.api.session.room.model.message.getFileUrl
-import org.matrix.android.sdk.api.util.Cancelable
 import org.matrix.android.sdk.internal.crypto.attachments.ElementToDecrypt
 import org.matrix.android.sdk.internal.crypto.attachments.toElementToDecrypt
 import java.io.File
@@ -41,20 +39,17 @@ interface FileService {
      * Download a file.
      * Result will be a decrypted file, stored in the cache folder. url parameter will be used to create unique filename to avoid name collision.
      */
-    fun downloadFile(fileName: String,
+    suspend fun downloadFile(fileName: String,
                      mimeType: String?,
                      url: String?,
-                     elementToDecrypt: ElementToDecrypt?,
-                     callback: MatrixCallback<File>): Cancelable
+                     elementToDecrypt: ElementToDecrypt?): File
 
-    fun downloadFile(messageContent: MessageWithAttachmentContent,
-                     callback: MatrixCallback<File>): Cancelable =
+    suspend fun downloadFile(messageContent: MessageWithAttachmentContent): File =
             downloadFile(
                     fileName = messageContent.getFileName(),
                     mimeType = messageContent.mimeType,
                     url = messageContent.getFileUrl(),
-                    elementToDecrypt = messageContent.encryptedFileInfo?.toElementToDecrypt(),
-                    callback = callback
+                    elementToDecrypt = messageContent.encryptedFileInfo?.toElementToDecrypt()
             )
 
     fun isFileInCache(mxcUrl: String?,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/identity/IdentityService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/identity/IdentityService.kt
index aedb81373585412377705b40aec3ee1d115311fd..8f8967e8fb25994d35339f50b09fe348e8662d39 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/identity/IdentityService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/identity/IdentityService.kt
@@ -16,9 +16,6 @@
 
 package org.matrix.android.sdk.api.session.identity
 
-import org.matrix.android.sdk.api.MatrixCallback
-import org.matrix.android.sdk.api.util.Cancelable
-
 /**
  * Provides access to the identity server configuration and services identity server can provide
  */
@@ -40,55 +37,55 @@ interface IdentityService {
      * See https://matrix.org/docs/spec/identity_service/latest#status-check
      * RiotX SDK only supports identity server API v2
      */
-    fun isValidIdentityServer(url: String, callback: MatrixCallback<Unit>): Cancelable
+    suspend fun isValidIdentityServer(url: String)
 
     /**
      * Update the identity server url.
      * If successful, any previous identity server will be disconnected.
      * In case of error, any previous identity server will remain configured.
      * @param url the new url.
-     * @param callback will notify the user if change is successful. The String will be the final url of the identity server.
+     * @return The String will be the final url of the identity server.
      * The SDK can prepend "https://" for instance.
      */
-    fun setNewIdentityServer(url: String, callback: MatrixCallback<String>): Cancelable
+    suspend fun setNewIdentityServer(url: String): String
 
     /**
      * Disconnect (logout) from the current identity server
      */
-    fun disconnect(callback: MatrixCallback<Unit>): Cancelable
+    suspend fun disconnect()
 
     /**
      * This will ask the identity server to send an email or an SMS to let the user confirm he owns the ThreePid
      */
-    fun startBindThreePid(threePid: ThreePid, callback: MatrixCallback<Unit>): Cancelable
+    suspend fun startBindThreePid(threePid: ThreePid)
 
     /**
      * This will cancel a pending binding of threePid.
      */
-    fun cancelBindThreePid(threePid: ThreePid, callback: MatrixCallback<Unit>): Cancelable
+    suspend fun cancelBindThreePid(threePid: ThreePid)
 
     /**
      * This will ask the identity server to send an new email or a new SMS to let the user confirm he owns the ThreePid
      */
-    fun sendAgainValidationCode(threePid: ThreePid, callback: MatrixCallback<Unit>): Cancelable
+    suspend fun sendAgainValidationCode(threePid: ThreePid)
 
     /**
      * Submit the code that the identity server has sent to the user (in email or SMS)
      * Once successful, you will have to call [finalizeBindThreePid]
      * @param code the code sent to the user
      */
-    fun submitValidationToken(threePid: ThreePid, code: String, callback: MatrixCallback<Unit>): Cancelable
+    suspend fun submitValidationToken(threePid: ThreePid, code: String)
 
     /**
      * This will perform the actual association of ThreePid and Matrix account
      */
-    fun finalizeBindThreePid(threePid: ThreePid, callback: MatrixCallback<Unit>): Cancelable
+    suspend fun finalizeBindThreePid(threePid: ThreePid)
 
     /**
      * Unbind a threePid
      * The request will actually be done on the homeserver
      */
-    fun unbindThreePid(threePid: ThreePid, callback: MatrixCallback<Unit>): Cancelable
+    suspend fun unbindThreePid(threePid: ThreePid)
 
     /**
      * Search MatrixId of users providing email and phone numbers
@@ -96,7 +93,7 @@ interface IdentityService {
      * Application has to explicitly ask for the user consent, and the answer can be stored using [setUserConsent]
      * Please see https://support.google.com/googleplay/android-developer/answer/9888076?hl=en for more details.
      */
-    fun lookUp(threePids: List<ThreePid>, callback: MatrixCallback<List<FoundThreePid>>): Cancelable
+    suspend fun lookUp(threePids: List<ThreePid>): List<FoundThreePid>
 
     /**
      * Return the current user consent for the current identity server, which has been stored using [setUserConsent].
@@ -120,9 +117,9 @@ interface IdentityService {
      * A lookup will be performed, but also pending binding state will be restored
      *
      * @param threePids the list of threePid the user owns (retrieved form the homeserver)
-     * @param callback onSuccess will be called with a map of ThreePid -> SharedState
+     * @return a map of ThreePid -> SharedState
      */
-    fun getShareStatus(threePids: List<ThreePid>, callback: MatrixCallback<Map<ThreePid, SharedState>>): Cancelable
+    suspend fun getShareStatus(threePids: List<ThreePid>): Map<ThreePid, SharedState>
 
     fun addListener(listener: IdentityServiceListener)
     fun removeListener(listener: IdentityServiceListener)
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 a4d5b665c648f453056e68623db2408e817a1050..e493adeaf273ad5cb4421a659d48de1764094e5e 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
@@ -19,10 +19,8 @@ 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
 import org.matrix.android.sdk.api.util.Optional
 
@@ -41,14 +39,14 @@ interface ProfileService {
      * @param userId the userId param to look for
      *
      */
-    fun getDisplayName(userId: String, matrixCallback: MatrixCallback<Optional<String>>): Cancelable
+    suspend fun getDisplayName(userId: String): Optional<String>
 
     /**
      * Update the display name for this user
      * @param userId the userId to update the display name of
      * @param newDisplayName the new display name of the user
      */
-    fun setDisplayName(userId: String, newDisplayName: String, matrixCallback: MatrixCallback<Unit>): Cancelable
+    suspend fun setDisplayName(userId: String, newDisplayName: String)
 
     /**
      * Update the avatar for this user
@@ -56,14 +54,14 @@ interface ProfileService {
      * @param newAvatarUri the new avatar uri of the user
      * @param fileName the fileName of selected image
      */
-    fun updateAvatar(userId: String, newAvatarUri: Uri, fileName: String, matrixCallback: MatrixCallback<Unit>): Cancelable
+    suspend fun updateAvatar(userId: String, newAvatarUri: Uri, fileName: String)
 
     /**
      * Return the current avatarUrl for this user.
      * @param userId the userId param to look for
      *
      */
-    fun getAvatarUrl(userId: String, matrixCallback: MatrixCallback<Optional<String>>): Cancelable
+    suspend fun getAvatarUrl(userId: String): Optional<String>
 
     /**
      * Get the combined profile information for this user.
@@ -71,7 +69,7 @@ interface ProfileService {
      * @param userId the userId param to look for
      *
      */
-    fun getProfile(userId: String, matrixCallback: MatrixCallback<JsonDict>): Cancelable
+    suspend fun getProfile(userId: String): JsonDict
 
     /**
      * Get the current user 3Pids
@@ -97,28 +95,26 @@ interface ProfileService {
     /**
      * Add a 3Pids. This is the first step to add a ThreePid to an account. Then the threePid will be added to the pending threePid list.
      */
-    fun addThreePid(threePid: ThreePid, matrixCallback: MatrixCallback<Unit>): Cancelable
+    suspend fun addThreePid(threePid: ThreePid)
 
     /**
      * Validate a code received by text message
      */
-    fun submitSmsCode(threePid: ThreePid.Msisdn, code: String, matrixCallback: MatrixCallback<Unit>): Cancelable
+    suspend fun submitSmsCode(threePid: ThreePid.Msisdn, code: String)
 
     /**
      * Finalize adding a 3Pids. Call this method once the user has validated that he owns the ThreePid
      */
-    fun finalizeAddingThreePid(threePid: ThreePid,
-                               userInteractiveAuthInterceptor: UserInteractiveAuthInterceptor,
-                               matrixCallback: MatrixCallback<Unit>): Cancelable
+    suspend fun finalizeAddingThreePid(threePid: ThreePid,
+                                       userInteractiveAuthInterceptor: UserInteractiveAuthInterceptor)
 
     /**
      * Cancel adding a threepid. It will remove locally stored data about this ThreePid
      */
-    fun cancelAddingThreePid(threePid: ThreePid,
-                             matrixCallback: MatrixCallback<Unit>): Cancelable
+    suspend fun cancelAddingThreePid(threePid: ThreePid)
 
     /**
      * Remove a 3Pid from the Matrix account.
      */
-    fun deleteThreePid(threePid: ThreePid, matrixCallback: MatrixCallback<Unit>): Cancelable
+    suspend fun deleteThreePid(threePid: ThreePid)
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushers/PushersService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushers/PushersService.kt
index 3993422b1d1d27e471ea4478c225995b4842da8a..9ea820f5b3d80280d19833976e01dbbf32e09d2c 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushers/PushersService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushers/PushersService.kt
@@ -16,8 +16,6 @@
 package org.matrix.android.sdk.api.session.pushers
 
 import androidx.lifecycle.LiveData
-import org.matrix.android.sdk.api.MatrixCallback
-import org.matrix.android.sdk.api.util.Cancelable
 import java.util.UUID
 
 interface PushersService {
@@ -75,16 +73,15 @@ interface PushersService {
      * @param callback callback to know if the push gateway has accepted the request. In this case, the app should receive a Push with the provided eventId.
      *                 In case of error, PusherRejected failure can happen. In this case it means that the pushkey is not valid.
      */
-    fun testPush(url: String,
-                 appId: String,
-                 pushkey: String,
-                 eventId: String,
-                 callback: MatrixCallback<Unit>): Cancelable
+    suspend fun testPush(url: String,
+                         appId: String,
+                         pushkey: String,
+                         eventId: String)
 
     /**
      * Remove the http pusher
      */
-    fun removeHttpPusher(pushkey: String, appId: String, callback: MatrixCallback<Unit>): Cancelable
+    suspend fun removeHttpPusher(pushkey: String, appId: String)
 
     /**
      * Get the current pushers, as a LiveData
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/Room.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/Room.kt
index cb6690b5c5eb56cfe8ac6dc40df035cc329895c8..257c83564e0c2df663e485b5947a9899b4cbcd5d 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/Room.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/Room.kt
@@ -17,7 +17,6 @@
 package org.matrix.android.sdk.api.session.room
 
 import androidx.lifecycle.LiveData
-import org.matrix.android.sdk.api.MatrixCallback
 import org.matrix.android.sdk.api.session.room.alias.AliasService
 import org.matrix.android.sdk.api.session.room.call.RoomCallService
 import org.matrix.android.sdk.api.session.room.crypto.RoomCryptoService
@@ -35,7 +34,6 @@ import org.matrix.android.sdk.api.session.room.timeline.TimelineService
 import org.matrix.android.sdk.api.session.room.typing.TypingService
 import org.matrix.android.sdk.api.session.room.uploads.UploadsService
 import org.matrix.android.sdk.api.session.search.SearchResult
-import org.matrix.android.sdk.api.util.Cancelable
 import org.matrix.android.sdk.api.util.Optional
 
 /**
@@ -86,12 +84,11 @@ interface Room :
      * @param includeProfile requests that the server returns the historic profile information for the users that sent the events that were returned.
      * @param callback Callback to get the search result
      */
-    fun search(searchTerm: String,
+    suspend fun search(searchTerm: String,
                nextBatch: String?,
                orderByRecent: Boolean,
                limit: Int,
                beforeLimit: Int,
                afterLimit: Int,
-               includeProfile: Boolean,
-               callback: MatrixCallback<SearchResult>): Cancelable
+               includeProfile: Boolean): SearchResult
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomService.kt
index 5f02b77a1e43c56dfa48b2e6e2c16d9fff2f9e93..8c833644ee51af5b04e541c905855018d5e5ee11 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomService.kt
@@ -17,6 +17,7 @@
 package org.matrix.android.sdk.api.session.room
 
 import androidx.lifecycle.LiveData
+import androidx.paging.PagedList
 import org.matrix.android.sdk.api.MatrixCallback
 import org.matrix.android.sdk.api.session.events.model.Event
 import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState
@@ -24,6 +25,7 @@ import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary
 import org.matrix.android.sdk.api.session.room.model.RoomSummary
 import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams
 import org.matrix.android.sdk.api.session.room.peeking.PeekResult
+import org.matrix.android.sdk.api.session.room.summary.RoomAggregateNotificationCount
 import org.matrix.android.sdk.api.util.Cancelable
 import org.matrix.android.sdk.api.util.Optional
 import org.matrix.android.sdk.internal.session.room.alias.RoomAliasDescription
@@ -178,4 +180,29 @@ interface RoomService {
      * This call will try to gather some information on this room, but it could fail and get nothing more
      */
     fun peekRoom(roomIdOrAlias: String, callback: MatrixCallback<PeekResult>)
+
+    /**
+     * TODO Doc
+     */
+    fun getPagedRoomSummariesLive(queryParams: RoomSummaryQueryParams,
+                                  pagedListConfig: PagedList.Config = defaultPagedListConfig): LiveData<PagedList<RoomSummary>>
+
+    /**
+     * TODO Doc
+     */
+    fun getFilteredPagedRoomSummariesLive(queryParams: RoomSummaryQueryParams,
+                                          pagedListConfig: PagedList.Config = defaultPagedListConfig): UpdatableFilterLivePageResult
+
+    /**
+     * TODO Doc
+     */
+    fun getNotificationCountForRooms(queryParams: RoomSummaryQueryParams): RoomAggregateNotificationCount
+
+    private val defaultPagedListConfig
+        get() = PagedList.Config.Builder()
+                .setPageSize(10)
+                .setInitialLoadSizeHint(20)
+                .setEnablePlaceholders(false)
+                .setPrefetchDistance(10)
+                .build()
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomSummaryQueryParams.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomSummaryQueryParams.kt
index f859d74a6f3d02919c625af9c66103e1fda932a1..7e04ebb5f295fbed1ec39a22f444a63fdae81fc7 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomSummaryQueryParams.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomSummaryQueryParams.kt
@@ -17,6 +17,8 @@
 package org.matrix.android.sdk.api.session.room
 
 import org.matrix.android.sdk.api.query.QueryStringValue
+import org.matrix.android.sdk.api.query.RoomCategoryFilter
+import org.matrix.android.sdk.api.query.RoomTagQueryFilter
 import org.matrix.android.sdk.api.session.room.model.Membership
 
 fun roomSummaryQueryParams(init: (RoomSummaryQueryParams.Builder.() -> Unit) = {}): RoomSummaryQueryParams {
@@ -31,7 +33,9 @@ data class RoomSummaryQueryParams(
         val roomId: QueryStringValue,
         val displayName: QueryStringValue,
         val canonicalAlias: QueryStringValue,
-        val memberships: List<Membership>
+        val memberships: List<Membership>,
+        val roomCategoryFilter: RoomCategoryFilter?,
+        val roomTagQueryFilter: RoomTagQueryFilter?
 ) {
 
     class Builder {
@@ -40,12 +44,16 @@ data class RoomSummaryQueryParams(
         var displayName: QueryStringValue = QueryStringValue.IsNotEmpty
         var canonicalAlias: QueryStringValue = QueryStringValue.NoCondition
         var memberships: List<Membership> = Membership.all()
+        var roomCategoryFilter: RoomCategoryFilter? = RoomCategoryFilter.ALL
+        var roomTagQueryFilter: RoomTagQueryFilter? = null
 
         fun build() = RoomSummaryQueryParams(
                 roomId = roomId,
                 displayName = displayName,
                 canonicalAlias = canonicalAlias,
-                memberships = memberships
+                memberships = memberships,
+                roomCategoryFilter = roomCategoryFilter,
+                roomTagQueryFilter = roomTagQueryFilter
         )
     }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/UpdatableFilterLivePageResult.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/UpdatableFilterLivePageResult.kt
new file mode 100644
index 0000000000000000000000000000000000000000..71b3c665e709b2bbff8c4b41db0e91f9af016402
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/UpdatableFilterLivePageResult.kt
@@ -0,0 +1,27 @@
+/*
+ * 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.api.session.room
+
+import androidx.lifecycle.LiveData
+import androidx.paging.PagedList
+import org.matrix.android.sdk.api.session.room.model.RoomSummary
+
+interface UpdatableFilterLivePageResult {
+    val livePagedList: LiveData<PagedList<RoomSummary>>
+
+    fun updateQuery(queryParams: RoomSummaryQueryParams)
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/members/MembershipService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/members/MembershipService.kt
index 2c3ffac6875e899871f1693524a4910ee9924d80..198d6677a0bb319e764fd48ccb953616ac1ce015 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/members/MembershipService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/members/MembershipService.kt
@@ -17,10 +17,8 @@
 package org.matrix.android.sdk.api.session.room.members
 
 import androidx.lifecycle.LiveData
-import org.matrix.android.sdk.api.MatrixCallback
 import org.matrix.android.sdk.api.session.identity.ThreePid
 import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary
-import org.matrix.android.sdk.api.util.Cancelable
 
 /**
  * This interface defines methods to handling membership. It's implemented at the room level.
@@ -29,9 +27,8 @@ interface MembershipService {
 
     /**
      * This methods load all room members if it was done yet.
-     * @return a [Cancelable]
      */
-    fun loadRoomMembersIfNeeded(matrixCallback: MatrixCallback<Unit>): Cancelable
+    suspend fun loadRoomMembersIfNeeded()
 
     /**
      * Return the roomMember with userId or null.
@@ -60,47 +57,35 @@ interface MembershipService {
     /**
      * Invite a user in the room
      */
-    fun invite(userId: String,
-               reason: String? = null,
-               callback: MatrixCallback<Unit>): Cancelable
+    suspend fun invite(userId: String, reason: String? = null)
 
     /**
      * Invite a user with email or phone number in the room
      */
-    fun invite3pid(threePid: ThreePid,
-                   callback: MatrixCallback<Unit>): Cancelable
+    suspend fun invite3pid(threePid: ThreePid)
 
     /**
      * Ban a user from the room
      */
-    fun ban(userId: String,
-            reason: String? = null,
-            callback: MatrixCallback<Unit>): Cancelable
+    suspend fun ban(userId: String, reason: String? = null)
 
     /**
      * Unban a user from the room
      */
-    fun unban(userId: String,
-              reason: String? = null,
-              callback: MatrixCallback<Unit>): Cancelable
+    suspend fun unban(userId: String, reason: String? = null)
 
     /**
      * Kick a user from the room
      */
-    fun kick(userId: String,
-             reason: String? = null,
-             callback: MatrixCallback<Unit>): Cancelable
+    suspend fun kick(userId: String, reason: String? = null)
 
     /**
      * Join the room, or accept an invitation.
      */
-    fun join(reason: String? = null,
-             viaServers: List<String> = emptyList(),
-             callback: MatrixCallback<Unit>): Cancelable
+    suspend fun join(reason: String? = null, viaServers: List<String> = emptyList())
 
     /**
      * Leave the room, or reject an invitation.
      */
-    fun leave(reason: String? = null,
-              callback: MatrixCallback<Unit>): Cancelable
+    suspend fun leave(reason: String? = null)
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/Membership.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/Membership.kt
index 5844aead8d23d05db0f3f52c2aeccc1683dac4f9..a5d0f63722c43dc1f110f4de549f76fc0f838dce 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/Membership.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/Membership.kt
@@ -23,24 +23,13 @@ import com.squareup.moshi.JsonClass
  * Represents the membership of a user on a room
  */
 @JsonClass(generateAdapter = false)
-enum class Membership(val value: String) {
-
-    NONE("none"),
-
-    @Json(name = "invite")
-    INVITE("invite"),
-
-    @Json(name = "join")
-    JOIN("join"),
-
-    @Json(name = "knock")
-    KNOCK("knock"),
-
-    @Json(name = "leave")
-    LEAVE("leave"),
-
-    @Json(name = "ban")
-    BAN("ban");
+enum class Membership {
+    NONE,
+    @Json(name = "invite") INVITE,
+    @Json(name = "join") JOIN,
+    @Json(name = "knock") KNOCK,
+    @Json(name = "leave") LEAVE,
+    @Json(name = "ban") BAN;
 
     fun isLeft(): Boolean {
         return this == KNOCK || this == LEAVE || this == BAN
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomGuestAccessContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomGuestAccessContent.kt
index 99b035d30e593a996affe7c6542556318b034509..0760c6f1b42900df128467e6e0795b266b5de284 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomGuestAccessContent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomGuestAccessContent.kt
@@ -18,6 +18,7 @@ package org.matrix.android.sdk.api.session.room.model
 
 import com.squareup.moshi.Json
 import com.squareup.moshi.JsonClass
+import timber.log.Timber
 
 /**
  * Class representing the EventType.STATE_ROOM_GUEST_ACCESS state event content
@@ -26,14 +27,20 @@ import com.squareup.moshi.JsonClass
 @JsonClass(generateAdapter = true)
 data class RoomGuestAccessContent(
         // Required. Whether guests can join the room. One of: ["can_join", "forbidden"]
-        @Json(name = "guest_access") val guestAccess: GuestAccess? = null
-)
+        @Json(name = "guest_access") val _guestAccess: String? = null
+) {
+    val guestAccess: GuestAccess? = when (_guestAccess) {
+        "can_join"  -> GuestAccess.CanJoin
+        "forbidden" -> GuestAccess.Forbidden
+        else        -> {
+            Timber.w("Invalid value for GuestAccess: `$_guestAccess`")
+            null
+        }
+    }
+}
 
 @JsonClass(generateAdapter = false)
-enum class GuestAccess(val value: String) {
-    @Json(name = "can_join")
-    CanJoin("can_join"),
-
-    @Json(name = "forbidden")
-    Forbidden("forbidden")
+enum class GuestAccess {
+    @Json(name = "can_join") CanJoin,
+    @Json(name = "forbidden") Forbidden
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomHistoryVisibilityContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomHistoryVisibilityContent.kt
index 31493be7eacd6799bf59b322c013ed0ac7d52260..3ac14e48de77e592e05fcb75ed80b6ce371e4423 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomHistoryVisibilityContent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomHistoryVisibilityContent.kt
@@ -18,8 +18,20 @@ package org.matrix.android.sdk.api.session.room.model
 
 import com.squareup.moshi.Json
 import com.squareup.moshi.JsonClass
+import timber.log.Timber
 
 @JsonClass(generateAdapter = true)
 data class RoomHistoryVisibilityContent(
-        @Json(name = "history_visibility") val historyVisibility: RoomHistoryVisibility? = null
-)
+        @Json(name = "history_visibility") val _historyVisibility: String? = null
+) {
+    val historyVisibility: RoomHistoryVisibility? = when (_historyVisibility) {
+        "world_readable" -> RoomHistoryVisibility.WORLD_READABLE
+        "shared"         -> RoomHistoryVisibility.SHARED
+        "invited"        -> RoomHistoryVisibility.INVITED
+        "joined"         -> RoomHistoryVisibility.JOINED
+        else             -> {
+            Timber.w("Invalid value for RoomHistoryVisibility: `$_historyVisibility`")
+            null
+        }
+    }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomJoinRules.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomJoinRules.kt
index 09aacfabbe74d92f2002aa2eceacf78ba0c288f7..f3e8d357f3149f6990ba9a259d0e57a8f34c9da8 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomJoinRules.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomJoinRules.kt
@@ -24,17 +24,9 @@ import com.squareup.moshi.JsonClass
  * Enum for [RoomJoinRulesContent] : https://matrix.org/docs/spec/client_server/r0.4.0#m-room-join-rules
  */
 @JsonClass(generateAdapter = false)
-enum class RoomJoinRules(val value: String) {
-
-    @Json(name = "public")
-    PUBLIC("public"),
-
-    @Json(name = "invite")
-    INVITE("invite"),
-
-    @Json(name = "knock")
-    KNOCK("knock"),
-
-    @Json(name = "private")
-    PRIVATE("private")
+enum class RoomJoinRules {
+    @Json(name = "public") PUBLIC,
+    @Json(name = "invite") INVITE,
+    @Json(name = "knock") KNOCK,
+    @Json(name = "private") PRIVATE
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomJoinRulesContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomJoinRulesContent.kt
index 3be2d38be76903f79f696f452ab5742614c1325c..8082486b22f1c8174704eee4c50d715b037e851b 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomJoinRulesContent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomJoinRulesContent.kt
@@ -19,11 +19,23 @@ package org.matrix.android.sdk.api.session.room.model
 
 import com.squareup.moshi.Json
 import com.squareup.moshi.JsonClass
+import timber.log.Timber
 
 /**
  * Class representing the EventType.STATE_ROOM_JOIN_RULES state event content
  */
 @JsonClass(generateAdapter = true)
 data class RoomJoinRulesContent(
-        @Json(name = "join_rule") val joinRules: RoomJoinRules? = null
-)
+        @Json(name = "join_rule") val _joinRules: String? = null
+) {
+    val joinRules: RoomJoinRules? = when (_joinRules) {
+        "public"  -> RoomJoinRules.PUBLIC
+        "invite"  -> RoomJoinRules.INVITE
+        "knock"   -> RoomJoinRules.KNOCK
+        "private" -> RoomJoinRules.PRIVATE
+        else      -> {
+            Timber.w("Invalid value for RoomJoinRules: `$_joinRules`")
+            null
+        }
+    }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageType.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageType.kt
index a2b4e135d122d7f5822d205ac84eef023ff0adea..c96a800ee586b32895bbd6bd9ebf3fcab8bb6890 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageType.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageType.kt
@@ -35,5 +35,5 @@ object MessageType {
     const val MSGTYPE_STICKER_LOCAL = "org.matrix.android.sdk.sticker"
 
     const val MSGTYPE_CONFETTI = "nic.custom.confetti"
-    const val MSGTYPE_SNOW = "nic.custom.snow"
+    const val MSGTYPE_SNOW = "io.element.effect.snowfall"
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/read/ReadService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/read/ReadService.kt
index 4f44c9a912630fe818c9fd2c4bfe75213eee5c81..b037a3f3668846b4b6f54e9bbe99b2c9fe573cee 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/read/ReadService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/read/ReadService.kt
@@ -17,7 +17,6 @@
 package org.matrix.android.sdk.api.session.room.read
 
 import androidx.lifecycle.LiveData
-import org.matrix.android.sdk.api.MatrixCallback
 import org.matrix.android.sdk.api.session.room.model.ReadReceipt
 import org.matrix.android.sdk.api.util.Optional
 
@@ -35,17 +34,17 @@ interface ReadService {
     /**
      * Force the read marker to be set on the latest event.
      */
-    fun markAsRead(params: MarkAsReadParams = MarkAsReadParams.BOTH, callback: MatrixCallback<Unit>)
+    suspend fun markAsRead(params: MarkAsReadParams = MarkAsReadParams.BOTH)
 
     /**
      * Set the read receipt on the event with provided eventId.
      */
-    fun setReadReceipt(eventId: String, callback: MatrixCallback<Unit>)
+    suspend fun setReadReceipt(eventId: String)
 
     /**
      * Set the read marker on the event with provided eventId.
      */
-    fun setReadMarker(fullyReadEventId: String, callback: MatrixCallback<Unit>)
+    suspend fun setReadMarker(fullyReadEventId: String)
 
     /**
      * Check if an event is already read, ie. your read receipt is set on a more recent event.
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/summary/RoomAggregateNotificationCount.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/summary/RoomAggregateNotificationCount.kt
new file mode 100644
index 0000000000000000000000000000000000000000..066178b1ecfc5f36ea25dbf6f5b876e7455cf584
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/summary/RoomAggregateNotificationCount.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2021 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.api.session.room.summary
+
+data class RoomAggregateNotificationCount(
+        val notificationCount: Int,
+        val highlightCount: Int
+) {
+    val totalCount = notificationCount + highlightCount
+    val isHighlight = highlightCount > 0
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/Timeline.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/Timeline.kt
index 8932d0734e5570e4e5fe36c6fd543162eb48e1f4..06c88db83162afe5a50ce55fa45c721484ea7df1 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/Timeline.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/Timeline.kt
@@ -95,12 +95,6 @@ interface Timeline {
      */
     fun getTimelineEventWithId(eventId: String?): TimelineEvent?
 
-    /**
-     * Returns the first displayable events starting from eventId.
-     * It does depend on the provided [TimelineSettings].
-     */
-    fun getFirstDisplayableEventId(eventId: String): String?
-
     interface Listener {
         /**
          * Call when the timeline has been updated through pagination or sync.
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineSettings.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineSettings.kt
index 25c63d6fbc38da81d406dd05b0f575295cf3ba62..ceffedb234775e286efa12d53e5bf5e4e1e3caee 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineSettings.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineSettings.kt
@@ -24,10 +24,6 @@ data class TimelineSettings(
          * The initial number of events to retrieve from cache. You might get less events if you don't have loaded enough yet.
          */
         val initialSize: Int,
-        /**
-         * Filters for timeline event
-         */
-        val filters: TimelineEventFilters = TimelineEventFilters(),
         /**
          * If true, will build read receipts for each event.
          */
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/securestorage/SharedSecretStorageService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/securestorage/SharedSecretStorageService.kt
index 37ecf99f9ad67fe92a8459f0069d1746cf5ccb28..721a2bc8af11f03577ac931014bdf1f869fbcb8c 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/securestorage/SharedSecretStorageService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/securestorage/SharedSecretStorageService.kt
@@ -16,7 +16,6 @@
 
 package org.matrix.android.sdk.api.session.securestorage
 
-import org.matrix.android.sdk.api.MatrixCallback
 import org.matrix.android.sdk.api.listeners.ProgressListener
 import org.matrix.android.sdk.api.session.crypto.crosssigning.KEYBACKUP_SECRET_SSSS_NAME
 import org.matrix.android.sdk.api.session.crypto.crosssigning.MASTER_KEY_SSSS_NAME
@@ -43,13 +42,12 @@ interface SharedSecretStorageService {
      * @param keyName a human readable name
      * @param keySigner Used to add a signature to the key (client should check key signature before storing secret)
      *
-     * @param callback Get key creation info
+     * @return key creation info
      */
-    fun generateKey(keyId: String,
-                    key: SsssKeySpec?,
-                    keyName: String,
-                    keySigner: KeySigner?,
-                    callback: MatrixCallback<SsssKeyCreationInfo>)
+    suspend fun generateKey(keyId: String,
+                            key: SsssKeySpec?,
+                            keyName: String,
+                            keySigner: KeySigner?): SsssKeyCreationInfo
 
     /**
      * Generates a SSSS key using the given passphrase.
@@ -61,14 +59,13 @@ interface SharedSecretStorageService {
      * @param keySigner Used to add a signature to the key (client should check key signature before retrieving secret)
      * @param progressListener The derivation of the passphrase may take long depending on the device, use this to report progress
      *
-     * @param callback Get key creation info
+     * @return key creation info
      */
-    fun generateKeyWithPassphrase(keyId: String,
-                                  keyName: String,
-                                  passphrase: String,
-                                  keySigner: KeySigner,
-                                  progressListener: ProgressListener?,
-                                  callback: MatrixCallback<SsssKeyCreationInfo>)
+    suspend fun generateKeyWithPassphrase(keyId: String,
+                                          keyName: String,
+                                          passphrase: String,
+                                          keySigner: KeySigner,
+                                          progressListener: ProgressListener?): SsssKeyCreationInfo
 
     fun getKey(keyId: String): KeyInfoResult
 
@@ -80,7 +77,7 @@ interface SharedSecretStorageService {
      */
     fun getDefaultKey(): KeyInfoResult
 
-    fun setDefaultKey(keyId: String, callback: MatrixCallback<Unit>)
+    suspend fun setDefaultKey(keyId: String)
 
     /**
      * Check whether we have a key with a given ID.
@@ -98,7 +95,7 @@ interface SharedSecretStorageService {
      * @param secret The secret contents.
      * @param keys The list of (ID,privateKey) of the keys to use to encrypt the secret.
      */
-    fun storeSecret(name: String, secretBase64: String, keys: List<KeyRef>, callback: MatrixCallback<Unit>)
+    suspend fun storeSecret(name: String, secretBase64: String, keys: List<KeyRef>)
 
     /**
      * Use this call to determine which SSSSKeySpec to use for requesting secret
@@ -113,7 +110,7 @@ interface SharedSecretStorageService {
      * @param secretKey the secret key to use (@see #RawBytesKeySpec)
      *
      */
-    fun getSecret(name: String, keyId: String?, secretKey: SsssKeySpec, callback: MatrixCallback<String>)
+    suspend fun getSecret(name: String, keyId: String?, secretKey: SsssKeySpec): String
 
     /**
      * Return true if SSSS is configured
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/user/UserService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/user/UserService.kt
index ab85f979bf6ca591a31031ea1b5b91b2103074ff..cd4fb216d327331b943f46aee28e960b2c096ef1 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/user/UserService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/user/UserService.kt
@@ -18,9 +18,7 @@ package org.matrix.android.sdk.api.session.user
 
 import androidx.lifecycle.LiveData
 import androidx.paging.PagedList
-import org.matrix.android.sdk.api.MatrixCallback
 import org.matrix.android.sdk.api.session.user.model.User
-import org.matrix.android.sdk.api.util.Cancelable
 import org.matrix.android.sdk.api.util.Optional
 
 /**
@@ -38,17 +36,16 @@ interface UserService {
     /**
      * Try to resolve user from known users, or using profile api
      */
-    fun resolveUser(userId: String, callback: MatrixCallback<User>)
+    suspend fun resolveUser(userId: String): User
 
     /**
      * Search list of users on server directory.
      * @param search the searched term
      * @param limit the max number of users to return
      * @param excludedUserIds the user ids to filter from the search
-     * @param callback the async callback
      * @return Cancelable
      */
-    fun searchUsersDirectory(search: String, limit: Int, excludedUserIds: Set<String>, callback: MatrixCallback<List<User>>): Cancelable
+    suspend fun searchUsersDirectory(search: String, limit: Int, excludedUserIds: Set<String>): List<User>
 
     /**
      * Observe a live user from a userId
@@ -79,10 +76,10 @@ interface UserService {
     /**
      * Ignore users
      */
-    fun ignoreUserIds(userIds: List<String>, callback: MatrixCallback<Unit>): Cancelable
+    suspend fun ignoreUserIds(userIds: List<String>)
 
     /**
      * Un-ignore some users
      */
-    fun unIgnoreUserIds(userIds: List<String>, callback: MatrixCallback<Unit>): Cancelable
+    suspend fun unIgnoreUserIds(userIds: List<String>)
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/widgets/WidgetService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/widgets/WidgetService.kt
index bf3ff8959dc21d5e99fe39d9c21ea08d8a9557ff..8f35ff0e4a4532c8efe5da900feb02e66e969664 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/widgets/WidgetService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/widgets/WidgetService.kt
@@ -17,10 +17,8 @@
 package org.matrix.android.sdk.api.session.widgets
 
 import androidx.lifecycle.LiveData
-import org.matrix.android.sdk.api.MatrixCallback
 import org.matrix.android.sdk.api.query.QueryStringValue
 import org.matrix.android.sdk.api.session.events.model.Content
-import org.matrix.android.sdk.api.util.Cancelable
 import org.matrix.android.sdk.api.session.widgets.model.Widget
 
 /**
@@ -107,20 +105,16 @@ interface WidgetService {
      * @param roomId the room where you want to create the widget.
      * @param widgetId the widget to create.
      * @param content the content of the widget
-     * @param callback the matrix callback to listen for result.
-     * @return Cancelable
      */
-    fun createRoomWidget(roomId: String, widgetId: String, content: Content, callback: MatrixCallback<Widget>): Cancelable
+    suspend fun createRoomWidget(roomId: String, widgetId: String, content: Content): Widget
 
     /**
      * Deactivate a widget in a room. It makes sure you have the rights to handle this.
      *
      * @param roomId: the room where you want to deactivate the widget.
      * @param widgetId: the widget to deactivate.
-     * @param callback the matrix callback to listen for result.
-     * @return Cancelable
      */
-    fun destroyRoomWidget(roomId: String, widgetId: String, callback: MatrixCallback<Unit>): Cancelable
+    suspend fun destroyRoomWidget(roomId: String, widgetId: String)
 
     /**
      * Returns true if you can add/remove widgets. It goes through
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/AuthAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/AuthAPI.kt
index f92ae7e0eec972c28d6363a6476640285fb99621..f93f285c6eaca6e4a730bcb69d8a52aba30fb444 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/AuthAPI.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/AuthAPI.kt
@@ -17,6 +17,7 @@
 package org.matrix.android.sdk.internal.auth
 
 import org.matrix.android.sdk.api.auth.data.Credentials
+import org.matrix.android.sdk.internal.auth.data.Availability
 import org.matrix.android.sdk.internal.auth.data.LoginFlowResponse
 import org.matrix.android.sdk.internal.auth.data.PasswordLoginParams
 import org.matrix.android.sdk.internal.auth.data.RiotConfig
@@ -29,12 +30,12 @@ import org.matrix.android.sdk.internal.auth.registration.SuccessResult
 import org.matrix.android.sdk.internal.auth.registration.ValidationCodeBody
 import org.matrix.android.sdk.internal.auth.version.Versions
 import org.matrix.android.sdk.internal.network.NetworkConstants
-import retrofit2.Call
 import retrofit2.http.Body
 import retrofit2.http.GET
 import retrofit2.http.Headers
 import retrofit2.http.POST
 import retrofit2.http.Path
+import retrofit2.http.Query
 import retrofit2.http.Url
 
 /**
@@ -45,26 +46,32 @@ internal interface AuthAPI {
      * Get a Riot config file, using the name including the domain
      */
     @GET("config.{domain}.json")
-    fun getRiotConfigDomain(@Path("domain") domain: String): Call<RiotConfig>
+    suspend fun getRiotConfigDomain(@Path("domain") domain: String): RiotConfig
 
     /**
      * Get a Riot config file
      */
     @GET("config.json")
-    fun getRiotConfig(): Call<RiotConfig>
+    suspend fun getRiotConfig(): RiotConfig
 
     /**
      * Get the version information of the homeserver
      */
     @GET(NetworkConstants.URI_API_PREFIX_PATH_ + "versions")
-    fun versions(): Call<Versions>
+    suspend fun versions(): Versions
 
     /**
      * Register to the homeserver, or get error 401 with a RegistrationFlowResponse object if registration is incomplete
      * Ref: https://matrix.org/docs/spec/client_server/latest#account-registration-and-management
      */
     @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "register")
-    fun register(@Body registrationParams: RegistrationParams): Call<Credentials>
+    suspend fun register(@Body registrationParams: RegistrationParams): Credentials
+
+    /**
+     * Checks to see if a username is available, and valid, for the server.
+     */
+    @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "register/available")
+    suspend fun registerAvailable(@Query("username") username: String): Availability
 
     /**
      * Add 3Pid during registration
@@ -72,22 +79,22 @@ internal interface AuthAPI {
      * https://github.com/matrix-org/matrix-doc/pull/2290
      */
     @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "register/{threePid}/requestToken")
-    fun add3Pid(@Path("threePid") threePid: String,
-                @Body params: AddThreePidRegistrationParams): Call<AddThreePidRegistrationResponse>
+    suspend fun add3Pid(@Path("threePid") threePid: String,
+                        @Body params: AddThreePidRegistrationParams): AddThreePidRegistrationResponse
 
     /**
      * Validate 3pid
      */
     @POST
-    fun validate3Pid(@Url url: String,
-                     @Body params: ValidationCodeBody): Call<SuccessResult>
+    suspend fun validate3Pid(@Url url: String,
+                             @Body params: ValidationCodeBody): SuccessResult
 
     /**
      * Get the supported login flow
      * Ref: https://matrix.org/docs/spec/client_server/latest#get-matrix-client-r0-login
      */
     @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "login")
-    fun getLoginFlows(): Call<LoginFlowResponse>
+    suspend fun getLoginFlows(): LoginFlowResponse
 
     /**
      * Pass params to the server for the current login phase.
@@ -97,22 +104,22 @@ internal interface AuthAPI {
      */
     @Headers("CONNECT_TIMEOUT:60000", "READ_TIMEOUT:60000", "WRITE_TIMEOUT:60000")
     @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "login")
-    fun login(@Body loginParams: PasswordLoginParams): Call<Credentials>
+    suspend fun login(@Body loginParams: PasswordLoginParams): Credentials
 
     // Unfortunately we cannot use interface for @Body parameter, so I duplicate the method for the type TokenLoginParams
     @Headers("CONNECT_TIMEOUT:60000", "READ_TIMEOUT:60000", "WRITE_TIMEOUT:60000")
     @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "login")
-    fun login(@Body loginParams: TokenLoginParams): Call<Credentials>
+    suspend fun login(@Body loginParams: TokenLoginParams): Credentials
 
     /**
      * Ask the homeserver to reset the password associated with the provided email.
      */
     @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "account/password/email/requestToken")
-    fun resetPassword(@Body params: AddThreePidRegistrationParams): Call<AddThreePidRegistrationResponse>
+    suspend fun resetPassword(@Body params: AddThreePidRegistrationParams): AddThreePidRegistrationResponse
 
     /**
      * Ask the homeserver to reset the password with the provided new password once the email is validated.
      */
     @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "account/password")
-    fun resetPasswordMailConfirmed(@Body params: ResetPasswordMailConfirmed): Call<Unit>
+    suspend fun resetPasswordMailConfirmed(@Body params: ResetPasswordMailConfirmed)
 }
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 4f3451cf307dc9ce85c1b03a29ae3055665c28a7..e26286ad2f5b64bca88b469a69ddd4e6cd9a4196 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
@@ -31,7 +31,6 @@ import org.matrix.android.sdk.api.failure.Failure
 import org.matrix.android.sdk.api.session.Session
 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
 import org.matrix.android.sdk.internal.auth.db.PendingSessionData
 import org.matrix.android.sdk.internal.auth.login.DefaultLoginWizard
@@ -172,8 +171,8 @@ internal class DefaultAuthenticationService @Inject constructor(
 
         // First check the homeserver version
         return runCatching {
-            executeRequest<Versions>(null) {
-                apiCall = authAPI.versions()
+            executeRequest(null) {
+                authAPI.versions()
             }
         }
                 .map { versions ->
@@ -204,8 +203,8 @@ internal class DefaultAuthenticationService @Inject constructor(
 
         // Ok, try to get the config.domain.json file of a RiotWeb client
         return runCatching {
-            executeRequest<RiotConfig>(null) {
-                apiCall = authAPI.getRiotConfigDomain(domain)
+            executeRequest(null) {
+                authAPI.getRiotConfigDomain(domain)
             }
         }
                 .map { riotConfig ->
@@ -232,8 +231,8 @@ internal class DefaultAuthenticationService @Inject constructor(
 
         // Ok, try to get the config.json file of a RiotWeb client
         return runCatching {
-            executeRequest<RiotConfig>(null) {
-                apiCall = authAPI.getRiotConfig()
+            executeRequest(null) {
+                authAPI.getRiotConfig()
             }
         }
                 .map { riotConfig ->
@@ -265,8 +264,8 @@ internal class DefaultAuthenticationService @Inject constructor(
 
             val newAuthAPI = buildAuthAPI(newHomeServerConnectionConfig)
 
-            val versions = executeRequest<Versions>(null) {
-                apiCall = newAuthAPI.versions()
+            val versions = executeRequest(null) {
+                newAuthAPI.versions()
             }
 
             return getLoginFlowResult(newAuthAPI, versions, defaultHomeServerUrl)
@@ -293,8 +292,8 @@ internal class DefaultAuthenticationService @Inject constructor(
 
                 val newAuthAPI = buildAuthAPI(newHomeServerConnectionConfig)
 
-                val versions = executeRequest<Versions>(null) {
-                    apiCall = newAuthAPI.versions()
+                val versions = executeRequest(null) {
+                    newAuthAPI.versions()
                 }
 
                 getLoginFlowResult(newAuthAPI, versions, wellknownResult.homeServerUrl)
@@ -305,8 +304,8 @@ internal class DefaultAuthenticationService @Inject constructor(
 
     private suspend fun getLoginFlowResult(authAPI: AuthAPI, versions: Versions, homeServerUrl: String): LoginFlowResult {
         // Get the login flow
-        val loginFlowResponse = executeRequest<LoginFlowResponse>(null) {
-            apiCall = authAPI.getLoginFlows()
+        val loginFlowResponse = executeRequest(null) {
+            authAPI.getLoginFlows()
         }
         return LoginFlowResult.Success(
                 loginFlowResponse.flows.orEmpty().mapNotNull { it.type },
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/IsValidClientServerApiTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/IsValidClientServerApiTask.kt
index b8416d69bfef0c1fa87f3c268392f940a0553f77..867cf46b8dbef8fd47e4601da74345c3bda21ad8 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/IsValidClientServerApiTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/IsValidClientServerApiTask.kt
@@ -20,7 +20,6 @@ import dagger.Lazy
 import okhttp3.OkHttpClient
 import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
 import org.matrix.android.sdk.api.failure.Failure
-import org.matrix.android.sdk.internal.auth.data.LoginFlowResponse
 import org.matrix.android.sdk.internal.di.Unauthenticated
 import org.matrix.android.sdk.internal.network.RetrofitFactory
 import org.matrix.android.sdk.internal.network.executeRequest
@@ -49,8 +48,8 @@ internal class DefaultIsValidClientServerApiTask @Inject constructor(
                 .create(AuthAPI::class.java)
 
         return try {
-            executeRequest<LoginFlowResponse>(null) {
-                apiCall = authAPI.getLoginFlows()
+            executeRequest(null) {
+                authAPI.getLoginFlows()
             }
             // We get a response, so the API is valid
             true
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/Availability.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/Availability.kt
new file mode 100644
index 0000000000000000000000000000000000000000..5ef3c0d06a1f4bc5b1be555db09798cb3e409e11
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/Availability.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2021 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.internal.auth.data
+
+import com.squareup.moshi.Json
+import com.squareup.moshi.JsonClass
+
+@JsonClass(generateAdapter = true)
+internal data class Availability(
+        /**
+         * A flag to indicate that the username is available. This should always be true when the server replies with 200 OK.
+         */
+        @Json(name = "available")
+        val available: Boolean? = null
+)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/login/DefaultLoginWizard.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/login/DefaultLoginWizard.kt
index 41678758497352821957812b5397c1a35254dbd9..8b81f42e03873f47c2108c90039141fe5c90f1fb 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/login/DefaultLoginWizard.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/login/DefaultLoginWizard.kt
@@ -17,7 +17,6 @@
 package org.matrix.android.sdk.internal.auth.login
 
 import android.util.Patterns
-import org.matrix.android.sdk.api.auth.data.Credentials
 import org.matrix.android.sdk.api.auth.login.LoginWizard
 import org.matrix.android.sdk.api.auth.registration.RegisterThreePid
 import org.matrix.android.sdk.api.session.Session
@@ -29,7 +28,6 @@ import org.matrix.android.sdk.internal.auth.data.ThreePidMedium
 import org.matrix.android.sdk.internal.auth.data.TokenLoginParams
 import org.matrix.android.sdk.internal.auth.db.PendingSessionData
 import org.matrix.android.sdk.internal.auth.registration.AddThreePidRegistrationParams
-import org.matrix.android.sdk.internal.auth.registration.AddThreePidRegistrationResponse
 import org.matrix.android.sdk.internal.auth.registration.RegisterAddThreePidTask
 import org.matrix.android.sdk.internal.network.executeRequest
 
@@ -49,8 +47,8 @@ internal class DefaultLoginWizard(
         } else {
             PasswordLoginParams.userIdentifier(login, password, deviceName)
         }
-        val credentials = executeRequest<Credentials>(null) {
-            apiCall = authAPI.login(loginParams)
+        val credentials = executeRequest(null) {
+            authAPI.login(loginParams)
         }
 
         return sessionCreator.createSession(credentials, pendingSessionData.homeServerConnectionConfig)
@@ -63,8 +61,8 @@ internal class DefaultLoginWizard(
         val loginParams = TokenLoginParams(
                 token = loginToken
         )
-        val credentials = executeRequest<Credentials>(null) {
-            apiCall = authAPI.login(loginParams)
+        val credentials = executeRequest(null) {
+            authAPI.login(loginParams)
         }
 
         return sessionCreator.createSession(credentials, pendingSessionData.homeServerConnectionConfig)
@@ -80,8 +78,8 @@ internal class DefaultLoginWizard(
         pendingSessionData = pendingSessionData.copy(sendAttempt = pendingSessionData.sendAttempt + 1)
                 .also { pendingSessionStore.savePendingSessionData(it) }
 
-        val result = executeRequest<AddThreePidRegistrationResponse>(null) {
-            apiCall = authAPI.resetPassword(AddThreePidRegistrationParams.from(param))
+        val result = executeRequest(null) {
+            authAPI.resetPassword(AddThreePidRegistrationParams.from(param))
         }
 
         pendingSessionData = pendingSessionData.copy(resetPasswordData = ResetPasswordData(newPassword, result))
@@ -98,8 +96,8 @@ internal class DefaultLoginWizard(
                 safeResetPasswordData.newPassword
         )
 
-        executeRequest<Unit>(null) {
-            apiCall = authAPI.resetPasswordMailConfirmed(param)
+        executeRequest(null) {
+            authAPI.resetPasswordMailConfirmed(param)
         }
 
         // Set to null?
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/login/DirectLoginTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/login/DirectLoginTask.kt
index be6ff389312d74072776dd8d5418db7d23ad95f2..77bbb8096fe943c1a5e7b4cbb8f5bf5f1beb638c 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/login/DirectLoginTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/login/DirectLoginTask.kt
@@ -17,7 +17,6 @@
 package org.matrix.android.sdk.internal.auth.login
 
 import dagger.Lazy
-import org.matrix.android.sdk.api.auth.data.Credentials
 import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
 import org.matrix.android.sdk.api.failure.Failure
 import org.matrix.android.sdk.api.session.Session
@@ -59,19 +58,16 @@ internal class DefaultDirectLoginTask @Inject constructor(
         val loginParams = PasswordLoginParams.userIdentifier(params.userId, params.password, params.deviceName)
 
         val credentials = try {
-            executeRequest<Credentials>(null) {
-                apiCall = authAPI.login(loginParams)
+            executeRequest(null) {
+                authAPI.login(loginParams)
             }
         } catch (throwable: Throwable) {
-            when (throwable) {
-                is UnrecognizedCertificateException -> {
-                    throw Failure.UnrecognizedCertificateFailure(
-                            homeServerUrl,
-                            throwable.fingerprint
-                    )
-                }
-                else                                ->
-                    throw throwable
+            throw when (throwable) {
+                is UnrecognizedCertificateException -> Failure.UnrecognizedCertificateFailure(
+                        homeServerUrl,
+                        throwable.fingerprint
+                )
+                else                                -> throwable
             }
         }
 
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 91e414e689771e8f6939eb60d817ada4a82bcb55..4a3d53a8fc9328b80da39bdc83ee6a2781fcd5b9 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
@@ -19,6 +19,7 @@ package org.matrix.android.sdk.internal.auth.registration
 import kotlinx.coroutines.delay
 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.RegistrationAvailability
 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
@@ -40,9 +41,10 @@ internal class DefaultRegistrationWizard(
 
     private var pendingSessionData: PendingSessionData = pendingSessionStore.getPendingSessionData() ?: error("Pending session data should exist here")
 
-    private val registerTask = DefaultRegisterTask(authAPI)
-    private val registerAddThreePidTask = DefaultRegisterAddThreePidTask(authAPI)
-    private val validateCodeTask = DefaultValidateCodeTask(authAPI)
+    private val registerTask: RegisterTask = DefaultRegisterTask(authAPI)
+    private val registerAvailableTask: RegisterAvailableTask = DefaultRegisterAvailableTask(authAPI)
+    private val registerAddThreePidTask: RegisterAddThreePidTask = DefaultRegisterAddThreePidTask(authAPI)
+    private val validateCodeTask: ValidateCodeTask = DefaultValidateCodeTask(authAPI)
 
     override val currentThreePid: String?
         get() {
@@ -203,4 +205,8 @@ internal class DefaultRegistrationWizard(
         val session = sessionCreator.createSession(credentials, pendingSessionData.homeServerConnectionConfig)
         return RegistrationResult.Success(session)
     }
+
+    override suspend fun registrationAvailable(userName: String): RegistrationAvailability {
+        return registerAvailableTask.execute(RegisterAvailableTask.Params(userName))
+    }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/RegisterAddThreePidTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/RegisterAddThreePidTask.kt
index 57c4b72b8a23e8268d81722b825b6c4728b06071..54a8ba0e6ce94786fd7e61f9cae3ee9da1c1ed20 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/RegisterAddThreePidTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/RegisterAddThreePidTask.kt
@@ -35,7 +35,7 @@ internal class DefaultRegisterAddThreePidTask(
 
     override suspend fun execute(params: RegisterAddThreePidTask.Params): AddThreePidRegistrationResponse {
         return executeRequest(null) {
-            apiCall = authAPI.add3Pid(params.threePid.toPath(), AddThreePidRegistrationParams.from(params))
+            authAPI.add3Pid(params.threePid.toPath(), AddThreePidRegistrationParams.from(params))
         }
     }
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/RegisterAvailableTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/RegisterAvailableTask.kt
new file mode 100644
index 0000000000000000000000000000000000000000..314a24dad4cb23e7d0c15e192ea20f0985af068b
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/RegisterAvailableTask.kt
@@ -0,0 +1,47 @@
+/*
+ * 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.auth.registration
+
+import org.matrix.android.sdk.api.auth.registration.RegistrationAvailability
+import org.matrix.android.sdk.api.failure.Failure
+import org.matrix.android.sdk.api.failure.isRegistrationAvailabilityError
+import org.matrix.android.sdk.internal.auth.AuthAPI
+import org.matrix.android.sdk.internal.network.executeRequest
+import org.matrix.android.sdk.internal.task.Task
+
+internal interface RegisterAvailableTask : Task<RegisterAvailableTask.Params, RegistrationAvailability> {
+    data class Params(
+            val userName: String
+    )
+}
+
+internal class DefaultRegisterAvailableTask(private val authAPI: AuthAPI) : RegisterAvailableTask {
+    override suspend fun execute(params: RegisterAvailableTask.Params): RegistrationAvailability {
+        return try {
+            executeRequest(null) {
+                authAPI.registerAvailable(params.userName)
+            }
+            RegistrationAvailability.Available
+        } catch (exception: Throwable) {
+            if (exception.isRegistrationAvailabilityError()) {
+                RegistrationAvailability.NotAvailable(exception as Failure.ServerError)
+            } else {
+                throw exception
+            }
+        }
+    }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/RegisterTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/RegisterTask.kt
index bf5d899276933bbb2b086191d4ea7a38fd86ff84..45668cb8ad0d2a7f44974c5642912b84d42d29d1 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/RegisterTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/RegisterTask.kt
@@ -36,7 +36,7 @@ internal class DefaultRegisterTask(
     override suspend fun execute(params: RegisterTask.Params): Credentials {
         try {
             return executeRequest(null) {
-                apiCall = authAPI.register(params.registrationParams)
+                authAPI.register(params.registrationParams)
             }
         } catch (throwable: Throwable) {
             throw throwable.toRegistrationFlowResponse()
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/ValidateCodeTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/ValidateCodeTask.kt
index b297c9849d0fcf3d2790f4993f1f62c10c3d64bb..d68b7cd9eb68484968b2ee50148417c2d82eaaee 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/ValidateCodeTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/ValidateCodeTask.kt
@@ -33,7 +33,7 @@ internal class DefaultValidateCodeTask(
 
     override suspend fun execute(params: ValidateCodeTask.Params): SuccessResult {
         return executeRequest(null) {
-            apiCall = authAPI.validate3Pid(params.url, params.body)
+            authAPI.validate3Pid(params.url, params.body)
         }
     }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoSessionInfoProvider.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoSessionInfoProvider.kt
index e8a70615e1497acebf65bce12701d33938227ac6..5338e7e92f537bca9d63113311ec1b844ef05eef 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoSessionInfoProvider.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoSessionInfoProvider.kt
@@ -38,7 +38,7 @@ internal class CryptoSessionInfoProvider @Inject constructor(
         val encryptionEvent = monarchy.fetchCopied { realm ->
             EventEntity.whereType(realm, roomId = roomId, type = EventType.STATE_ROOM_ENCRYPTION)
                     .contains(EventEntityFields.CONTENT, "\"algorithm\":\"$MXCRYPTO_ALGORITHM_MEGOLM\"")
-                    .isNotNull(EventEntityFields.STATE_KEY) // should be an empty key
+                    .isEmpty(EventEntityFields.STATE_KEY)
                     .findFirst()
         }
         return encryptionEvent != null
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 17d25736ebd52b105cd70295eab5dc5f8a64a7d5..2163b2a5e0276046d414bb1e4da8ff4d5d112284 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
@@ -856,15 +856,8 @@ internal class DefaultCryptoService @Inject constructor(
             return
         }
         cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
-            val params = LoadRoomMembersTask.Params(roomId)
-            try {
-                loadRoomMembersTask.execute(params)
-            } catch (throwable: Throwable) {
-                Timber.e(throwable, "## CRYPTO | onRoomEncryptionEvent ERROR FAILED TO SETUP CRYPTO ")
-            } finally {
-                val userIds = getRoomUserIds(roomId)
-                setEncryptionInRoom(roomId, event.content?.get("algorithm")?.toString(), true, userIds)
-            }
+            val userIds = getRoomUserIds(roomId)
+            setEncryptionInRoom(roomId, event.content?.get("algorithm")?.toString(), true, userIds)
         }
     }
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt
index 42df6b354b53aec8bc3b0e86efd12b3eeca1ff66..e5f1c011f8c2c349ca9d4bac26eb67c8c7beb9b8 100755
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt
@@ -16,6 +16,7 @@
 
 package org.matrix.android.sdk.internal.crypto
 
+import kotlinx.coroutines.launch
 import org.matrix.android.sdk.api.MatrixPatterns
 import org.matrix.android.sdk.api.auth.data.Credentials
 import org.matrix.android.sdk.internal.crypto.crosssigning.DeviceTrustLevel
@@ -28,7 +29,7 @@ import org.matrix.android.sdk.internal.session.SessionScope
 import org.matrix.android.sdk.internal.session.sync.SyncTokenStore
 import org.matrix.android.sdk.internal.task.TaskExecutor
 import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers
-import kotlinx.coroutines.launch
+import org.matrix.android.sdk.internal.util.logLimit
 import timber.log.Timber
 import javax.inject.Inject
 
@@ -39,8 +40,9 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
                                                      private val syncTokenStore: SyncTokenStore,
                                                      private val credentials: Credentials,
                                                      private val downloadKeysForUsersTask: DownloadKeysForUsersTask,
+                                                     private val cryptoSessionInfoProvider: CryptoSessionInfoProvider,
                                                      coroutineDispatchers: MatrixCoroutineDispatchers,
-                                                     taskExecutor: TaskExecutor) {
+                                                     private val taskExecutor: TaskExecutor) {
 
     interface UserDevicesUpdateListener {
         fun onUsersDeviceUpdate(userIds: List<String>)
@@ -75,8 +77,10 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
     // HS not ready for retry
     private val notReadyToRetryHS = mutableSetOf<String>()
 
+    private val cryptoCoroutineContext = coroutineDispatchers.crypto
+
     init {
-        taskExecutor.executorScope.launch(coroutineDispatchers.crypto) {
+        taskExecutor.executorScope.launch(cryptoCoroutineContext) {
             var isUpdated = false
             val deviceTrackingStatuses = cryptoStore.getDeviceTrackingStatuses().toMutableMap()
             for ((userId, status) in deviceTrackingStatuses) {
@@ -104,7 +108,7 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
         if (':' in userId) {
             try {
                 synchronized(notReadyToRetryHS) {
-                    res = !notReadyToRetryHS.contains(userId.substringAfterLast(':'))
+                    res = !notReadyToRetryHS.contains(userId.substringAfter(':'))
                 }
             } catch (e: Exception) {
                 Timber.e(e, "## CRYPTO | canRetryKeysDownload() failed")
@@ -123,28 +127,37 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
         }
     }
 
+    fun onRoomMembersLoadedFor(roomId: String) {
+        taskExecutor.executorScope.launch(cryptoCoroutineContext) {
+            if (cryptoSessionInfoProvider.isRoomEncrypted(roomId)) {
+                // It's OK to track also device for invited users
+                val userIds = cryptoSessionInfoProvider.getRoomUserIds(roomId, true)
+                startTrackingDeviceList(userIds)
+                refreshOutdatedDeviceLists()
+            }
+        }
+    }
+
     /**
      * Mark the cached device list for the given user outdated
      * flag the given user for device-list tracking, if they are not already.
      *
      * @param userIds the user ids list
      */
-    fun startTrackingDeviceList(userIds: List<String>?) {
-        if (null != userIds) {
-            var isUpdated = false
-            val deviceTrackingStatuses = cryptoStore.getDeviceTrackingStatuses().toMutableMap()
+    fun startTrackingDeviceList(userIds: List<String>) {
+        var isUpdated = false
+        val deviceTrackingStatuses = cryptoStore.getDeviceTrackingStatuses().toMutableMap()
 
-            for (userId in userIds) {
-                if (!deviceTrackingStatuses.containsKey(userId) || TRACKING_STATUS_NOT_TRACKED == deviceTrackingStatuses[userId]) {
-                    Timber.v("## CRYPTO | startTrackingDeviceList() : Now tracking device list for $userId")
-                    deviceTrackingStatuses[userId] = TRACKING_STATUS_PENDING_DOWNLOAD
-                    isUpdated = true
-                }
+        for (userId in userIds) {
+            if (!deviceTrackingStatuses.containsKey(userId) || TRACKING_STATUS_NOT_TRACKED == deviceTrackingStatuses[userId]) {
+                Timber.v("## CRYPTO | startTrackingDeviceList() : Now tracking device list for $userId")
+                deviceTrackingStatuses[userId] = TRACKING_STATUS_PENDING_DOWNLOAD
+                isUpdated = true
             }
+        }
 
-            if (isUpdated) {
-                cryptoStore.saveDeviceTrackingStatuses(deviceTrackingStatuses)
-            }
+        if (isUpdated) {
+            cryptoStore.saveDeviceTrackingStatuses(deviceTrackingStatuses)
         }
     }
 
@@ -155,13 +168,17 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
      * @param left    the user ids list which left a room
      */
     fun handleDeviceListsChanges(changed: Collection<String>, left: Collection<String>) {
-        Timber.v("## CRYPTO: handleDeviceListsChanges changed:$changed / left:$left")
+        Timber.v("## CRYPTO: handleDeviceListsChanges changed: ${changed.logLimit()} / left: ${left.logLimit()}")
         var isUpdated = false
         val deviceTrackingStatuses = cryptoStore.getDeviceTrackingStatuses().toMutableMap()
 
+        if (changed.isNotEmpty() || left.isNotEmpty()) {
+            clearUnavailableServersList()
+        }
+
         for (userId in changed) {
             if (deviceTrackingStatuses.containsKey(userId)) {
-                Timber.v("## CRYPTO | invalidateUserDeviceList() : Marking device list outdated for $userId")
+                Timber.v("## CRYPTO | handleDeviceListsChanges() : Marking device list outdated for $userId")
                 deviceTrackingStatuses[userId] = TRACKING_STATUS_PENDING_DOWNLOAD
                 isUpdated = true
             }
@@ -169,7 +186,7 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
 
         for (userId in left) {
             if (deviceTrackingStatuses.containsKey(userId)) {
-                Timber.v("## CRYPTO | invalidateUserDeviceList() : No longer tracking device list for $userId")
+                Timber.v("## CRYPTO | handleDeviceListsChanges() : No longer tracking device list for $userId")
                 deviceTrackingStatuses[userId] = TRACKING_STATUS_NOT_TRACKED
                 isUpdated = true
             }
@@ -307,7 +324,7 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
      * @param downloadUsers the user ids list
      */
     private suspend fun doKeyDownloadForUsers(downloadUsers: List<String>): MXUsersDevicesMap<CryptoDeviceInfo> {
-        Timber.v("## CRYPTO | doKeyDownloadForUsers() : doKeyDownloadForUsers $downloadUsers")
+        Timber.v("## CRYPTO | doKeyDownloadForUsers() : doKeyDownloadForUsers ${downloadUsers.logLimit()}")
         // get the user ids which did not already trigger a keys download
         val filteredUsers = downloadUsers.filter { MatrixPatterns.isUserId(it) }
         if (filteredUsers.isEmpty()) {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/GossipingWorkManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/GossipingWorkManager.kt
index c9451aa0028afcbad2b62334992399677b86de54..0013c31eead3319a5977e72d9dad4275bc944328 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/GossipingWorkManager.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/GossipingWorkManager.kt
@@ -39,7 +39,7 @@ internal class GossipingWorkManager @Inject constructor(
                 .setConstraints(WorkManagerProvider.workConstraints)
                 .startChain(startChain)
                 .setInputData(data)
-                .setBackoffCriteria(BackoffPolicy.LINEAR, 10_000L, TimeUnit.MILLISECONDS)
+                .setBackoffCriteria(BackoffPolicy.LINEAR, WorkManagerProvider.BACKOFF_DELAY_MILLIS, TimeUnit.MILLISECONDS)
                 .build()
     }
 
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 b1e91e8d50760b84915b63237ed1773051035291..b8f1a9abea84cfc0f6c39f9cfb5a051851f249c9 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
@@ -312,7 +312,7 @@ internal class MXOlmDevice @Inject constructor(
      * @param theirDeviceIdentityKey the Curve25519 identity key for the remote device.
      * @return a list of known session ids for the device.
      */
-    fun getSessionIds(theirDeviceIdentityKey: String): Set<String>? {
+    fun getSessionIds(theirDeviceIdentityKey: String): List<String>? {
         return store.getDeviceSessionIds(theirDeviceIdentityKey)
     }
 
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 6b91c0b859adf52242d2b77713f7fa49d934bbea..697711d05161d8440b14edaa9b9dac78c119654e 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
@@ -67,8 +67,9 @@ internal class MXMegolmEncryption(
 
     init {
         // restore existing outbound session if any
-       outboundSession = olmDevice.restoreOutboundGroupSessionForRoom(roomId)
+        outboundSession = olmDevice.restoreOutboundGroupSessionForRoom(roomId)
     }
+
     // Default rotation periods
     // TODO: Make it configurable via parameters
     // Session rotation periods
@@ -125,6 +126,7 @@ internal class MXMegolmEncryption(
 
         Timber.v("## CRYPTO | preshareKey ${System.currentTimeMillis() - ts} millis")
     }
+
     /**
      * Prepare a new session.
      *
@@ -240,6 +242,7 @@ internal class MXMegolmEncryption(
         val contentMap = MXUsersDevicesMap<Any>()
         var haveTargets = false
         val userIds = results.userIds
+        val noOlmToNotify = mutableListOf<UserDevice>()
         for (userId in userIds) {
             val devicesToShareWith = devicesByUser[userId]
             for ((deviceID) in devicesToShareWith!!) {
@@ -251,13 +254,7 @@ internal class MXMegolmEncryption(
                     // MSC 2399
                     // send withheld m.no_olm: an olm session could not be established.
                     // This may happen, for example, if the sender was unable to obtain a one-time key from the recipient.
-                    notifyKeyWithHeld(
-                            listOf(UserDevice(userId, deviceID)),
-                            session.sessionId,
-                            olmDevice.deviceCurve25519Key,
-                            WithHeldCode.NO_OLM
-                    )
-
+                    noOlmToNotify.add(UserDevice(userId, deviceID))
                     continue
                 }
                 Timber.i("## CRYPTO | shareUserDevicesKey() : Add to share keys contentMap for $userId:$deviceID")
@@ -277,14 +274,14 @@ internal class MXMegolmEncryption(
                 session.sharedWithHelper.markedSessionAsShared(userId, deviceId, chainIndex)
                 gossipingEventBuffer.add(
                         Event(
-                        type = EventType.ROOM_KEY,
-                        senderId = this.userId,
-                        content = submap.apply {
-                            this["session_key"] = ""
-                            // we add a fake key for trail
-                            this["_dest"] = "$userId|$deviceId"
-                        }
-                ))
+                                type = EventType.ROOM_KEY,
+                                senderId = this.userId,
+                                content = submap.apply {
+                                    this["session_key"] = ""
+                                    // we add a fake key for trail
+                                    this["_dest"] = "$userId|$deviceId"
+                                }
+                        ))
             }
         }
 
@@ -304,6 +301,16 @@ internal class MXMegolmEncryption(
         } else {
             Timber.i("## CRYPTO | shareUserDevicesKey() : no need to sharekey")
         }
+
+        if (noOlmToNotify.isNotEmpty()) {
+            // XXX offload?, as they won't read the message anyhow?
+            notifyKeyWithHeld(
+                    noOlmToNotify,
+                    session.sessionId,
+                    olmDevice.deviceCurve25519Key,
+                    WithHeldCode.NO_OLM
+            )
+        }
     }
 
     private suspend fun notifyKeyWithHeld(targets: List<UserDevice>,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/olm/MXOlmDecryption.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/olm/MXOlmDecryption.kt
index 541f62de2c322a8d708de7800b23ae7673120a8f..082b86c9da67226003ef1431c7b23ccb02612dd8 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/olm/MXOlmDecryption.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/olm/MXOlmDecryption.kt
@@ -154,7 +154,7 @@ internal class MXOlmDecryption(
      * @return payload, if decrypted successfully.
      */
     private fun decryptMessage(message: JsonDict, theirDeviceIdentityKey: String): String? {
-        val sessionIds = olmDevice.getSessionIds(theirDeviceIdentityKey) ?: emptySet()
+        val sessionIds = olmDevice.getSessionIds(theirDeviceIdentityKey).orEmpty()
 
         val messageBody = message["body"] as? String ?: return null
         val messageType = when (val typeAsVoid = message["type"]) {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/api/CryptoApi.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/api/CryptoApi.kt
index 5604e97152981128dce6753fdfc90888de5da740..cef86e8b5ec03f1fde51e4ca1f5d909f9187072a 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/api/CryptoApi.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/api/CryptoApi.kt
@@ -30,7 +30,6 @@ import org.matrix.android.sdk.internal.crypto.model.rest.SignatureUploadResponse
 import org.matrix.android.sdk.internal.crypto.model.rest.UpdateDeviceInfoBody
 import org.matrix.android.sdk.internal.crypto.model.rest.UploadSigningKeysBody
 import org.matrix.android.sdk.internal.network.NetworkConstants
-import retrofit2.Call
 import retrofit2.http.Body
 import retrofit2.http.GET
 import retrofit2.http.HTTP
@@ -46,14 +45,14 @@ internal interface CryptoApi {
      * Doc: https://matrix.org/docs/spec/client_server/latest#get-matrix-client-r0-devices
      */
     @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "devices")
-    fun getDevices(): Call<DevicesListResponse>
+    suspend fun getDevices(): DevicesListResponse
 
     /**
      * Get the device info by id
      * Doc: https://matrix.org/docs/spec/client_server/latest#get-matrix-client-r0-devices-deviceid
      */
     @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "devices/{deviceId}")
-    fun getDeviceInfo(@Path("deviceId") deviceId: String): Call<DeviceInfo>
+    suspend fun getDeviceInfo(@Path("deviceId") deviceId: String): DeviceInfo
 
     /**
      * Upload device and/or one-time keys.
@@ -62,7 +61,7 @@ internal interface CryptoApi {
      * @param body the keys to be sent.
      */
     @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "keys/upload")
-    fun uploadKeys(@Body body: KeysUploadBody): Call<KeysUploadResponse>
+    suspend fun uploadKeys(@Body body: KeysUploadBody): KeysUploadResponse
 
     /**
      * Download device keys.
@@ -71,7 +70,7 @@ internal interface CryptoApi {
      * @param params the params.
      */
     @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "keys/query")
-    fun downloadKeysForUsers(@Body params: KeysQueryBody): Call<KeysQueryResponse>
+    suspend fun downloadKeysForUsers(@Body params: KeysQueryBody): KeysQueryResponse
 
     /**
      * CrossSigning - Uploading signing keys
@@ -79,7 +78,7 @@ internal interface CryptoApi {
      * This endpoint requires UI Auth.
      */
     @POST(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "keys/device_signing/upload")
-    fun uploadSigningKeys(@Body params: UploadSigningKeysBody): Call<KeysQueryResponse>
+    suspend fun uploadSigningKeys(@Body params: UploadSigningKeysBody): KeysQueryResponse
 
     /**
      *  CrossSigning - Uploading signatures
@@ -98,7 +97,7 @@ internal interface CryptoApi {
      * However, signatures made for other users' keys, made by her user-signing key, will not be included.
      */
     @POST(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "keys/signatures/upload")
-    fun uploadSignatures(@Body params: Map<String, @JvmSuppressWildcards Any>?): Call<SignatureUploadResponse>
+    suspend fun uploadSignatures(@Body params: Map<String, @JvmSuppressWildcards Any>?): SignatureUploadResponse
 
     /**
      * Claim one-time keys.
@@ -107,7 +106,7 @@ internal interface CryptoApi {
      * @param params the params.
      */
     @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "keys/claim")
-    fun claimOneTimeKeysForUsersDevices(@Body body: KeysClaimBody): Call<KeysClaimResponse>
+    suspend fun claimOneTimeKeysForUsersDevices(@Body body: KeysClaimBody): KeysClaimResponse
 
     /**
      * Send an event to a specific list of devices
@@ -118,9 +117,9 @@ internal interface CryptoApi {
      * @param body          the body
      */
     @PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "sendToDevice/{eventType}/{txnId}")
-    fun sendToDevice(@Path("eventType") eventType: String,
-                     @Path("txnId") transactionId: String,
-                     @Body body: SendToDeviceBody): Call<Unit>
+    suspend fun sendToDevice(@Path("eventType") eventType: String,
+                             @Path("txnId") transactionId: String,
+                             @Body body: SendToDeviceBody)
 
     /**
      * Delete a device.
@@ -130,8 +129,8 @@ internal interface CryptoApi {
      * @param params   the deletion parameters
      */
     @HTTP(path = NetworkConstants.URI_API_PREFIX_PATH_R0 + "devices/{device_id}", method = "DELETE", hasBody = true)
-    fun deleteDevice(@Path("device_id") deviceId: String,
-                     @Body params: DeleteDeviceParams): Call<Unit>
+    suspend fun deleteDevice(@Path("device_id") deviceId: String,
+                             @Body params: DeleteDeviceParams)
 
     /**
      * Update the device information.
@@ -141,8 +140,8 @@ internal interface CryptoApi {
      * @param params   the params
      */
     @PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "devices/{device_id}")
-    fun updateDeviceInfo(@Path("device_id") deviceId: String,
-                         @Body params: UpdateDeviceInfoBody): Call<Unit>
+    suspend fun updateDeviceInfo(@Path("device_id") deviceId: String,
+                                 @Body params: UpdateDeviceInfoBody)
 
     /**
      * Get the update devices list from two sync token.
@@ -152,6 +151,6 @@ internal interface CryptoApi {
      * @param newToken the up-to token.
      */
     @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "keys/changes")
-    fun getKeyChanges(@Query("from") oldToken: String,
-                      @Query("to") newToken: String): Call<KeyChangesResponse>
+    suspend fun getKeyChanges(@Query("from") oldToken: String,
+                              @Query("to") newToken: String): KeyChangesResponse
 }
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 9b282f0a84bd2235b15bc9415a3d5503891dd533..0289fadbd8f8fa980c9bff80da6e86d4c0a6d683 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
@@ -43,6 +43,7 @@ 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 org.matrix.android.sdk.internal.util.logLimit
 import org.matrix.android.sdk.internal.worker.WorkerParamsFactory
 import org.matrix.olm.OlmPkSigning
 import org.matrix.olm.OlmUtility
@@ -750,7 +751,7 @@ internal class DefaultCrossSigningService @Inject constructor(
     }
 
     override fun onUsersDeviceUpdate(userIds: List<String>) {
-        Timber.d("## CrossSigning - onUsersDeviceUpdate for ${userIds.size} users: $userIds")
+        Timber.d("## CrossSigning - onUsersDeviceUpdate for users: ${userIds.logLimit()}")
         val workerParams = UpdateTrustWorker.Params(
                 sessionId = sessionId,
                 filename = updateTrustWorkerDataRepository.createParam(userIds)
@@ -759,7 +760,7 @@ internal class DefaultCrossSigningService @Inject constructor(
 
         val workRequest = workManagerProvider.matrixOneTimeWorkRequestBuilder<UpdateTrustWorker>()
                 .setInputData(workerData)
-                .setBackoffCriteria(BackoffPolicy.LINEAR, 2_000L, TimeUnit.MILLISECONDS)
+                .setBackoffCriteria(BackoffPolicy.LINEAR, WorkManagerProvider.BACKOFF_DELAY_MILLIS, TimeUnit.MILLISECONDS)
                 .build()
 
         workManagerProvider.workManager
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 1660bae0b7f1bd5635943bf5705c2240713c1ed0..76b63b7798e9e66675761255ed0fd0a5e8fc9591 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
@@ -33,15 +33,18 @@ import org.matrix.android.sdk.internal.crypto.store.db.model.CryptoMapper
 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.UserEntityFields
+import org.matrix.android.sdk.internal.database.awaitTransaction
 import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntity
 import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntityFields
 import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity
+import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields
 import org.matrix.android.sdk.internal.database.query.where
 import org.matrix.android.sdk.internal.di.CryptoDatabase
 import org.matrix.android.sdk.internal.di.SessionDatabase
 import org.matrix.android.sdk.internal.di.UserId
 import org.matrix.android.sdk.internal.session.SessionComponent
 import org.matrix.android.sdk.internal.session.room.membership.RoomMemberHelper
+import org.matrix.android.sdk.internal.util.logLimit
 import org.matrix.android.sdk.internal.worker.SessionSafeCoroutineWorker
 import org.matrix.android.sdk.internal.worker.SessionWorkerParams
 import timber.log.Timber
@@ -65,11 +68,16 @@ internal class UpdateTrustWorker(context: Context,
     @Inject lateinit var crossSigningService: DefaultCrossSigningService
 
     // It breaks the crypto store contract, but we need to batch things :/
-    @CryptoDatabase @Inject lateinit var realmConfiguration: RealmConfiguration
-    @UserId @Inject lateinit var myUserId: String
+    @CryptoDatabase
+    @Inject lateinit var cryptoRealmConfiguration: RealmConfiguration
+
+    @SessionDatabase
+    @Inject lateinit var sessionRealmConfiguration: 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
     @Inject lateinit var cryptoStore: IMXCryptoStore
@@ -79,118 +87,109 @@ internal class UpdateTrustWorker(context: Context,
     }
 
     override suspend fun doSafeWork(params: Params): Result {
-        var userList = params.filename
+        val 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()
+        // List should not be empty, but let's avoid go further in case of empty list
+        if (userList.isNotEmpty()) {
+            // 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 :/
+            Timber.d("## CrossSigning - Updating trust for users: ${userList.logLimit()}")
+            updateTrust(userList)
         }
 
-        // 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 :/
+        cleanup(params)
+        return Result.success()
+    }
 
-        Timber.d("## CrossSigning - Updating trust for $userList")
+    private suspend fun updateTrust(userListParam: List<String>) {
+        var userList = userListParam
+        var myCrossSigningInfo: MXCrossSigningInfo? = null
 
         // First we check that the users MSK are trusted by mine
         // After that we check the trust chain for each devices of each users
-        Realm.getInstance(realmConfiguration).use { realm ->
-            realm.executeTransaction {
-                // By mapping here to model, this object is not live
-                // I should update it if needed
-                var myCrossSigningInfo = realm.where(CrossSigningInfoEntity::class.java)
-                        .equalTo(CrossSigningInfoEntityFields.USER_ID, myUserId)
-                        .findFirst()?.let { mapCrossSigningInfoEntity(it) }
-
-                var myTrustResult: UserTrustResult? = null
-
-                if (userList.contains(myUserId)) {
-                    Timber.d("## CrossSigning - Clear all trust as a change on my user was detected")
-                    // i am in the list.. but i don't know exactly the delta of change :/
-                    // If it's my cross signing keys we should refresh all trust
-                    // do it anyway ?
-                    userList = realm.where(CrossSigningInfoEntity::class.java)
-                            .findAll().mapNotNull { it.userId }
-                    Timber.d("## CrossSigning - Updating trust for all $userList")
-
-                    // check right now my keys and mark it as trusted as other trust depends on it
-                    val myDevices = realm.where<UserEntity>()
-                            .equalTo(UserEntityFields.USER_ID, myUserId)
-                            .findFirst()
-                            ?.devices
-                            ?.map { deviceInfo ->
-                                CryptoMapper.mapToModel(deviceInfo)
-                            }
-                    myTrustResult = crossSigningService.checkSelfTrust(myCrossSigningInfo, myDevices).also {
-                        updateCrossSigningKeysTrust(realm, myUserId, it.isVerified())
-                        // update model reference
-                        myCrossSigningInfo = realm.where(CrossSigningInfoEntity::class.java)
-                                .equalTo(CrossSigningInfoEntityFields.USER_ID, myUserId)
-                                .findFirst()?.let { mapCrossSigningInfoEntity(it) }
-                    }
-                }
+        awaitTransaction(cryptoRealmConfiguration) { cryptoRealm ->
+            // By mapping here to model, this object is not live
+            // I should update it if needed
+            myCrossSigningInfo = getCrossSigningInfo(cryptoRealm, myUserId)
+
+            var myTrustResult: UserTrustResult? = null
+
+            if (userList.contains(myUserId)) {
+                Timber.d("## CrossSigning - Clear all trust as a change on my user was detected")
+                // i am in the list.. but i don't know exactly the delta of change :/
+                // If it's my cross signing keys we should refresh all trust
+                // do it anyway ?
+                userList = cryptoRealm.where(CrossSigningInfoEntity::class.java)
+                        .findAll()
+                        .mapNotNull { it.userId }
+
+                // check right now my keys and mark it as trusted as other trust depends on it
+                val myDevices = cryptoRealm.where<UserEntity>()
+                        .equalTo(UserEntityFields.USER_ID, myUserId)
+                        .findFirst()
+                        ?.devices
+                        ?.map { CryptoMapper.mapToModel(it) }
+
+                myTrustResult = crossSigningService.checkSelfTrust(myCrossSigningInfo, myDevices)
+                updateCrossSigningKeysTrust(cryptoRealm, myUserId, myTrustResult.isVerified())
+                // update model reference
+                myCrossSigningInfo = getCrossSigningInfo(cryptoRealm, myUserId)
+            }
 
-                val otherInfos = userList.map {
-                    it to realm.where(CrossSigningInfoEntity::class.java)
-                            .equalTo(CrossSigningInfoEntityFields.USER_ID, it)
-                            .findFirst()?.let { mapCrossSigningInfoEntity(it) }
-                }
-                        .toMap()
-
-                val trusts = otherInfos.map { infoEntry ->
-                    infoEntry.key to when (infoEntry.key) {
-                        myUserId -> myTrustResult
-                        else     -> {
-                            crossSigningService.checkOtherMSKTrusted(myCrossSigningInfo, infoEntry.value).also {
-                                Timber.d("## CrossSigning - user:${infoEntry.key} result:$it")
-                            }
+            val otherInfos = userList.associateWith { userId ->
+                getCrossSigningInfo(cryptoRealm, userId)
+            }
+
+            val trusts = otherInfos.mapValues { entry ->
+                when (entry.key) {
+                    myUserId -> myTrustResult
+                    else     -> {
+                        crossSigningService.checkOtherMSKTrusted(myCrossSigningInfo, entry.value).also {
+                            Timber.d("## CrossSigning - user:${entry.key} result:$it")
                         }
                     }
-                }.toMap()
+                }
+            }
 
-                // TODO! if it's me and my keys has changed... I have to reset trust for everyone!
-                // i have all the new trusts, update DB
-                trusts.forEach {
-                    val verified = it.value?.isVerified() == true
-                    updateCrossSigningKeysTrust(realm, it.key, verified)
+            // TODO! if it's me and my keys has changed... I have to reset trust for everyone!
+            // i have all the new trusts, update DB
+            trusts.forEach {
+                val verified = it.value?.isVerified() == true
+                updateCrossSigningKeysTrust(cryptoRealm, it.key, verified)
+            }
+
+            // Ok so now we have to check device trust for all these users..
+            Timber.v("## CrossSigning - Updating devices cross trust users: ${trusts.keys.logLimit()}")
+            trusts.keys.forEach { userId ->
+                val devicesEntities = cryptoRealm.where<UserEntity>()
+                        .equalTo(UserEntityFields.USER_ID, userId)
+                        .findFirst()
+                        ?.devices
+
+                val trustMap = devicesEntities?.associateWith { device ->
+                    // get up to date from DB has could have been updated
+                    val otherInfo = getCrossSigningInfo(cryptoRealm, userId)
+                    crossSigningService.checkDeviceTrust(myCrossSigningInfo, otherInfo, CryptoMapper.mapToModel(device))
                 }
 
-                // Ok so now we have to check device trust for all these users..
-                Timber.v("## CrossSigning - Updating devices cross trust users ${trusts.keys}")
-                trusts.keys.forEach {
-                    val devicesEntities = realm.where<UserEntity>()
-                            .equalTo(UserEntityFields.USER_ID, it)
-                            .findFirst()
-                            ?.devices
-
-                    val trustMap = devicesEntities?.map { device ->
-                        // get up to date from DB has could have been updated
-                        val otherInfo = realm.where(CrossSigningInfoEntity::class.java)
-                                .equalTo(CrossSigningInfoEntityFields.USER_ID, it)
-                                .findFirst()?.let { mapCrossSigningInfoEntity(it) }
-                        device to crossSigningService.checkDeviceTrust(myCrossSigningInfo, otherInfo, CryptoMapper.mapToModel(device))
-                    }?.toMap()
-
-                    // Update trust if needed
-                    devicesEntities?.forEach { device ->
-                        val crossSignedVerified = trustMap?.get(device)?.isCrossSignedVerified()
-                        Timber.d("## CrossSigning - Trust for ${device.userId}|${device.deviceId} : cross verified: ${trustMap?.get(device)}")
-                        if (device.trustLevelEntity?.crossSignedVerified != crossSignedVerified) {
-                            Timber.d("## CrossSigning - Trust change detected for ${device.userId}|${device.deviceId} : cross verified: $crossSignedVerified")
-                            // need to save
-                            val trustEntity = device.trustLevelEntity
-                            if (trustEntity == null) {
-                                realm.createObject(TrustLevelEntity::class.java).let {
-                                    it.locallyVerified = false
-                                    it.crossSignedVerified = crossSignedVerified
-                                    device.trustLevelEntity = it
-                                }
-                            } else {
-                                trustEntity.crossSignedVerified = crossSignedVerified
+                // Update trust if needed
+                devicesEntities?.forEach { device ->
+                    val crossSignedVerified = trustMap?.get(device)?.isCrossSignedVerified()
+                    Timber.d("## CrossSigning - Trust for ${device.userId}|${device.deviceId} : cross verified: ${trustMap?.get(device)}")
+                    if (device.trustLevelEntity?.crossSignedVerified != crossSignedVerified) {
+                        Timber.d("## CrossSigning - Trust change detected for ${device.userId}|${device.deviceId} : cross verified: $crossSignedVerified")
+                        // need to save
+                        val trustEntity = device.trustLevelEntity
+                        if (trustEntity == null) {
+                            device.trustLevelEntity = cryptoRealm.createObject(TrustLevelEntity::class.java).also {
+                                it.locallyVerified = false
+                                it.crossSignedVerified = crossSignedVerified
                             }
+                        } else {
+                            trustEntity.crossSignedVerified = crossSignedVerified
                         }
                     }
                 }
@@ -199,37 +198,51 @@ internal class UpdateTrustWorker(context: Context,
 
         // So Cross Signing keys trust is updated, device trust is updated
         // We can now update room shields? in the session DB?
+        updateTrustStep2(userList, myCrossSigningInfo)
+    }
 
+    private suspend fun updateTrustStep2(userList: List<String>, myCrossSigningInfo: MXCrossSigningInfo?) {
         Timber.d("## CrossSigning - Updating shields for impacted rooms...")
-        Realm.getInstance(sessionRealmConfiguration).use { it ->
-            it.executeTransaction { realm ->
-                val distinctRoomIds = realm.where(RoomMemberSummaryEntity::class.java)
+        awaitTransaction(sessionRealmConfiguration) { sessionRealm ->
+            Realm.getInstance(cryptoRealmConfiguration).use { cryptoRealm ->
+                sessionRealm.where(RoomMemberSummaryEntity::class.java)
                         .`in`(RoomMemberSummaryEntityFields.USER_ID, userList.toTypedArray())
                         .distinct(RoomMemberSummaryEntityFields.ROOM_ID)
                         .findAll()
                         .map { it.roomId }
-                Timber.d("## CrossSigning -  ... impacted rooms $distinctRoomIds")
-                distinctRoomIds.forEach { roomId ->
-                    val roomSummary = RoomSummaryEntity.where(realm, roomId).findFirst()
-                    if (roomSummary?.isEncrypted == true) {
-                        Timber.d("## CrossSigning - Check shield state for room $roomId")
-                        val allActiveRoomMembers = RoomMemberHelper(realm, roomId).getActiveRoomMemberIds()
-                        try {
-                            val updatedTrust = computeRoomShield(allActiveRoomMembers, roomSummary)
-                            if (roomSummary.roomEncryptionTrustLevel != updatedTrust) {
-                                Timber.d("## CrossSigning - Shield change detected for $roomId -> $updatedTrust")
-                                roomSummary.roomEncryptionTrustLevel = updatedTrust
-                            }
-                        } catch (failure: Throwable) {
-                            Timber.e(failure)
+                        .also { Timber.d("## CrossSigning -  ... impacted rooms ${it.logLimit()}") }
+                        .forEach { roomId ->
+                            RoomSummaryEntity.where(sessionRealm, roomId)
+                                    .equalTo(RoomSummaryEntityFields.IS_ENCRYPTED, true)
+                                    .findFirst()
+                                    ?.let { roomSummary ->
+                                        Timber.d("## CrossSigning - Check shield state for room $roomId")
+                                        val allActiveRoomMembers = RoomMemberHelper(sessionRealm, roomId).getActiveRoomMemberIds()
+                                        try {
+                                            val updatedTrust = computeRoomShield(
+                                                    myCrossSigningInfo,
+                                                    cryptoRealm,
+                                                    allActiveRoomMembers,
+                                                    roomSummary
+                                            )
+                                            if (roomSummary.roomEncryptionTrustLevel != updatedTrust) {
+                                                Timber.d("## CrossSigning - Shield change detected for $roomId -> $updatedTrust")
+                                                roomSummary.roomEncryptionTrustLevel = updatedTrust
+                                            }
+                                        } catch (failure: Throwable) {
+                                            Timber.e(failure)
+                                        }
+                                    }
                         }
-                    }
-                }
             }
         }
+    }
 
-        cleanup(params)
-        return Result.success()
+    private fun getCrossSigningInfo(cryptoRealm: Realm, userId: String): MXCrossSigningInfo? {
+        return cryptoRealm.where(CrossSigningInfoEntity::class.java)
+                .equalTo(CrossSigningInfoEntityFields.USER_ID, userId)
+                .findFirst()
+                ?.let { mapCrossSigningInfoEntity(it) }
     }
 
     private fun cleanup(params: Params) {
@@ -237,30 +250,34 @@ internal class UpdateTrustWorker(context: Context,
                 ?.let { updateTrustWorkerDataRepository.delete(it) }
     }
 
-    private fun updateCrossSigningKeysTrust(realm: Realm, userId: String, verified: Boolean) {
-        val xInfoEntity = realm.where(CrossSigningInfoEntity::class.java)
+    private fun updateCrossSigningKeysTrust(cryptoRealm: Realm, userId: String, verified: Boolean) {
+        cryptoRealm.where(CrossSigningInfoEntity::class.java)
                 .equalTo(CrossSigningInfoEntityFields.USER_ID, userId)
                 .findFirst()
-        xInfoEntity?.crossSigningKeys?.forEach { info ->
-            // optimization to avoid trigger updates when there is no change..
-            if (info.trustLevelEntity?.isVerified() != verified) {
-                Timber.d("## CrossSigning - Trust change for $userId : $verified")
-                val level = info.trustLevelEntity
-                if (level == null) {
-                    val newLevel = realm.createObject(TrustLevelEntity::class.java)
-                    newLevel.locallyVerified = verified
-                    newLevel.crossSignedVerified = verified
-                    info.trustLevelEntity = newLevel
-                } else {
-                    level.locallyVerified = verified
-                    level.crossSignedVerified = verified
+                ?.crossSigningKeys
+                ?.forEach { info ->
+                    // optimization to avoid trigger updates when there is no change..
+                    if (info.trustLevelEntity?.isVerified() != verified) {
+                        Timber.d("## CrossSigning - Trust change for $userId : $verified")
+                        val level = info.trustLevelEntity
+                        if (level == null) {
+                            info.trustLevelEntity = cryptoRealm.createObject(TrustLevelEntity::class.java).also {
+                                it.locallyVerified = verified
+                                it.crossSignedVerified = verified
+                            }
+                        } else {
+                            level.locallyVerified = verified
+                            level.crossSignedVerified = verified
+                        }
+                    }
                 }
-            }
-        }
     }
 
-    private fun computeRoomShield(activeMemberUserIds: List<String>, roomSummaryEntity: RoomSummaryEntity): RoomEncryptionTrustLevel {
-        Timber.d("## CrossSigning - computeRoomShield ${roomSummaryEntity.roomId} -> $activeMemberUserIds")
+    private fun computeRoomShield(myCrossSigningInfo: MXCrossSigningInfo?,
+                                  cryptoRealm: Realm,
+                                  activeMemberUserIds: List<String>,
+                                  roomSummaryEntity: RoomSummaryEntity): RoomEncryptionTrustLevel {
+        Timber.d("## CrossSigning - computeRoomShield ${roomSummaryEntity.roomId} -> ${activeMemberUserIds.logLimit()}")
         // The set of “all users” depends on the type of room:
         // For regular / topic rooms which have more than 2 members (including yourself) are considered when decorating a room
         // For 1:1 and group DM rooms, all other users (i.e. excluding yourself) are considered when decorating a room
@@ -272,17 +289,8 @@ internal class UpdateTrustWorker(context: Context,
 
         val allTrustedUserIds = listToCheck
                 .filter { userId ->
-                    Realm.getInstance(realmConfiguration).use {
-                        it.where(CrossSigningInfoEntity::class.java)
-                                .equalTo(CrossSigningInfoEntityFields.USER_ID, userId)
-                                .findFirst()?.let { mapCrossSigningInfoEntity(it) }?.isTrusted() == true
-                    }
+                    getCrossSigningInfo(cryptoRealm, userId)?.isTrusted() == true
                 }
-        val myCrossKeys = Realm.getInstance(realmConfiguration).use {
-            it.where(CrossSigningInfoEntity::class.java)
-                    .equalTo(CrossSigningInfoEntityFields.USER_ID, myUserId)
-                    .findFirst()?.let { mapCrossSigningInfoEntity(it) }
-        }
 
         return if (allTrustedUserIds.isEmpty()) {
             RoomEncryptionTrustLevel.Default
@@ -291,21 +299,17 @@ internal class UpdateTrustWorker(context: Context,
             // If all devices of all verified users are trusted -> green
             // else -> black
             allTrustedUserIds
-                    .mapNotNull { uid ->
-                        Realm.getInstance(realmConfiguration).use {
-                            it.where<UserEntity>()
-                                    .equalTo(UserEntityFields.USER_ID, uid)
-                                    .findFirst()
-                                    ?.devices
-                                    ?.map {
-                                        CryptoMapper.mapToModel(it)
-                                    }
-                        }
+                    .mapNotNull { userId ->
+                        cryptoRealm.where<UserEntity>()
+                                .equalTo(UserEntityFields.USER_ID, userId)
+                                .findFirst()
+                                ?.devices
+                                ?.map { CryptoMapper.mapToModel(it) }
                     }
                     .flatten()
                     .let { allDevices ->
-                        Timber.v("## CrossSigning - computeRoomShield ${roomSummaryEntity.roomId} devices ${allDevices.map { it.deviceId }}")
-                        if (myCrossKeys != null) {
+                        Timber.v("## CrossSigning - computeRoomShield ${roomSummaryEntity.roomId} devices ${allDevices.map { it.deviceId }.logLimit()}")
+                        if (myCrossSigningInfo != null) {
                             allDevices.any { !it.trustLevel?.crossSigningVerified.orFalse() }
                         } else {
                             // Legacy method
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/api/RoomKeysApi.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/api/RoomKeysApi.kt
index 3f8333528f8fbe65dd0389002e5da6a50aafa295..eb4c55a3e7f93343c88b99945d6ec603cd71c285 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/api/RoomKeysApi.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/api/RoomKeysApi.kt
@@ -25,7 +25,6 @@ import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysVersionR
 import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.RoomKeysBackupData
 import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.UpdateKeysBackupVersionBody
 import org.matrix.android.sdk.internal.network.NetworkConstants
-import retrofit2.Call
 import retrofit2.http.Body
 import retrofit2.http.DELETE
 import retrofit2.http.GET
@@ -48,14 +47,14 @@ internal interface RoomKeysApi {
      * @param createKeysBackupVersionBody the body
      */
     @POST(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "room_keys/version")
-    fun createKeysBackupVersion(@Body createKeysBackupVersionBody: CreateKeysBackupVersionBody): Call<KeysVersion>
+    suspend fun createKeysBackupVersion(@Body createKeysBackupVersionBody: CreateKeysBackupVersionBody): KeysVersion
 
     /**
      * Get the key backup last version
      * If not supported by the server, an error is returned: {"errcode":"M_NOT_FOUND","error":"No backup found"}
      */
     @GET(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "room_keys/version")
-    fun getKeysBackupLastVersion(): Call<KeysVersionResult>
+    suspend fun getKeysBackupLastVersion(): KeysVersionResult
 
     /**
      * Get information about the given version.
@@ -64,7 +63,7 @@ internal interface RoomKeysApi {
      * @param version  version
      */
     @GET(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "room_keys/version/{version}")
-    fun getKeysBackupVersion(@Path("version") version: String): Call<KeysVersionResult>
+    suspend fun getKeysBackupVersion(@Path("version") version: String): KeysVersionResult
 
     /**
      * Update information about the given version.
@@ -72,8 +71,8 @@ internal interface RoomKeysApi {
      * @param updateKeysBackupVersionBody the body
      */
     @PUT(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "room_keys/version/{version}")
-    fun updateKeysBackupVersion(@Path("version") version: String,
-                                @Body keysBackupVersionBody: UpdateKeysBackupVersionBody): Call<Unit>
+    suspend fun updateKeysBackupVersion(@Path("version") version: String,
+                                        @Body keysBackupVersionBody: UpdateKeysBackupVersionBody)
 
     /* ==========================================================================================
      * Storing keys
@@ -94,10 +93,10 @@ internal interface RoomKeysApi {
      * @param keyBackupData the data to send
      */
     @PUT(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "room_keys/keys/{roomId}/{sessionId}")
-    fun storeRoomSessionData(@Path("roomId") roomId: String,
-                             @Path("sessionId") sessionId: String,
-                             @Query("version") version: String,
-                             @Body keyBackupData: KeyBackupData): Call<BackupKeysResult>
+    suspend fun storeRoomSessionData(@Path("roomId") roomId: String,
+                                     @Path("sessionId") sessionId: String,
+                                     @Query("version") version: String,
+                                     @Body keyBackupData: KeyBackupData): BackupKeysResult
 
     /**
      * Store several keys for the given room, using the given backup version.
@@ -107,9 +106,9 @@ internal interface RoomKeysApi {
      * @param roomKeysBackupData the data to send
      */
     @PUT(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "room_keys/keys/{roomId}")
-    fun storeRoomSessionsData(@Path("roomId") roomId: String,
-                              @Query("version") version: String,
-                              @Body roomKeysBackupData: RoomKeysBackupData): Call<BackupKeysResult>
+    suspend fun storeRoomSessionsData(@Path("roomId") roomId: String,
+                                      @Query("version") version: String,
+                                      @Body roomKeysBackupData: RoomKeysBackupData): BackupKeysResult
 
     /**
      * Store several keys, using the given backup version.
@@ -118,8 +117,8 @@ internal interface RoomKeysApi {
      * @param keysBackupData the data to send
      */
     @PUT(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "room_keys/keys")
-    fun storeSessionsData(@Query("version") version: String,
-                          @Body keysBackupData: KeysBackupData): Call<BackupKeysResult>
+    suspend fun storeSessionsData(@Query("version") version: String,
+                                  @Body keysBackupData: KeysBackupData): BackupKeysResult
 
     /* ==========================================================================================
      * Retrieving keys
@@ -133,9 +132,9 @@ internal interface RoomKeysApi {
      * @param version   the version of the backup, or empty String to retrieve the last version
      */
     @GET(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "room_keys/keys/{roomId}/{sessionId}")
-    fun getRoomSessionData(@Path("roomId") roomId: String,
-                           @Path("sessionId") sessionId: String,
-                           @Query("version") version: String): Call<KeyBackupData>
+    suspend fun getRoomSessionData(@Path("roomId") roomId: String,
+                                   @Path("sessionId") sessionId: String,
+                                   @Query("version") version: String): KeyBackupData
 
     /**
      * Retrieve all the keys for the given room from the backup.
@@ -144,8 +143,8 @@ internal interface RoomKeysApi {
      * @param version  the version of the backup, or empty String to retrieve the last version
      */
     @GET(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "room_keys/keys/{roomId}")
-    fun getRoomSessionsData(@Path("roomId") roomId: String,
-                            @Query("version") version: String): Call<RoomKeysBackupData>
+    suspend fun getRoomSessionsData(@Path("roomId") roomId: String,
+                                    @Query("version") version: String): RoomKeysBackupData
 
     /**
      * Retrieve all the keys from the backup.
@@ -153,7 +152,7 @@ internal interface RoomKeysApi {
      * @param version  the version of the backup, or empty String to retrieve the last version
      */
     @GET(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "room_keys/keys")
-    fun getSessionsData(@Query("version") version: String): Call<KeysBackupData>
+    suspend fun getSessionsData(@Query("version") version: String): KeysBackupData
 
     /* ==========================================================================================
      * Deleting keys
@@ -163,22 +162,22 @@ internal interface RoomKeysApi {
      * Deletes keys from the backup.
      */
     @DELETE(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "room_keys/keys/{roomId}/{sessionId}")
-    fun deleteRoomSessionData(@Path("roomId") roomId: String,
-                              @Path("sessionId") sessionId: String,
-                              @Query("version") version: String): Call<Unit>
+    suspend fun deleteRoomSessionData(@Path("roomId") roomId: String,
+                                      @Path("sessionId") sessionId: String,
+                                      @Query("version") version: String)
 
     /**
      * Deletes keys from the backup.
      */
     @DELETE(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "room_keys/keys/{roomId}")
-    fun deleteRoomSessionsData(@Path("roomId") roomId: String,
-                               @Query("version") version: String): Call<Unit>
+    suspend fun deleteRoomSessionsData(@Path("roomId") roomId: String,
+                                       @Query("version") version: String)
 
     /**
      * Deletes keys from the backup.
      */
     @DELETE(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "room_keys/keys")
-    fun deleteSessionsData(@Query("version") version: String): Call<Unit>
+    suspend fun deleteSessionsData(@Query("version") version: String)
 
     /* ==========================================================================================
      * Deleting backup
@@ -188,5 +187,5 @@ internal interface RoomKeysApi {
      * Deletes a backup.
      */
     @DELETE(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "room_keys/version/{version}")
-    fun deleteBackup(@Path("version") version: String): Call<Unit>
+    suspend fun deleteBackup(@Path("version") version: String)
 }
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 5c59cfd80e3215ae47e104f15e03548bc02bd6c5..62610a0b7bd686783346e62b1d6848b72bd54366 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
@@ -33,7 +33,7 @@ internal class DefaultCreateKeysBackupVersionTask @Inject constructor(
 
     override suspend fun execute(params: CreateKeysBackupVersionBody): KeysVersion {
         return executeRequest(globalErrorReceiver) {
-            apiCall = roomKeysApi.createKeysBackupVersion(params)
+            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 ec09da7240cc48a97116fa0cb71ea191613253c8..7ee6f2358d3edecc445b69ba24eb9ac4b826b18a 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
@@ -35,7 +35,7 @@ internal class DefaultDeleteBackupTask @Inject constructor(
 
     override suspend fun execute(params: DeleteBackupTask.Params) {
         return executeRequest(globalErrorReceiver) {
-            apiCall = roomKeysApi.deleteBackup(params.version)
+            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 9c477efb7821e6af51e2bca87fec6621b545d267..7f1b03b9324a0ad8e898bd0827589e5d0420e9a3 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
@@ -37,7 +37,7 @@ internal class DefaultDeleteRoomSessionDataTask @Inject constructor(
 
     override suspend fun execute(params: DeleteRoomSessionDataTask.Params) {
         return executeRequest(globalErrorReceiver) {
-            apiCall = roomKeysApi.deleteRoomSessionData(
+            roomKeysApi.deleteRoomSessionData(
                     params.roomId,
                     params.sessionId,
                     params.version)
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 82d022f3abdb90478cfa620567bc5643721eb9d8..394cc861d625008d44c8a6e8ca19e1f928dda3ea 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
@@ -36,7 +36,7 @@ internal class DefaultDeleteRoomSessionsDataTask @Inject constructor(
 
     override suspend fun execute(params: DeleteRoomSessionsDataTask.Params) {
         return executeRequest(globalErrorReceiver) {
-            apiCall = roomKeysApi.deleteRoomSessionsData(
+            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 e4df379963db0a2864d13efba815acc3c6c4e303..808c6c9956d4c0396bd8203baf4c092cd3ef074b 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
@@ -35,7 +35,7 @@ internal class DefaultDeleteSessionsDataTask @Inject constructor(
 
     override suspend fun execute(params: DeleteSessionsDataTask.Params) {
         return executeRequest(globalErrorReceiver) {
-            apiCall = roomKeysApi.deleteSessionsData(params.version)
+            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 3566ff0e6856832ac3346b81946e7016a512744a..54dbf85e302730784737b23b97bf0c23f0ab7964 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
@@ -32,7 +32,7 @@ internal class DefaultGetKeysBackupLastVersionTask @Inject constructor(
 
     override suspend fun execute(params: Unit): KeysVersionResult {
         return executeRequest(globalErrorReceiver) {
-            apiCall = roomKeysApi.getKeysBackupLastVersion()
+            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 13c99fb0f4235a1278fdb23129e778e428b78858..390873eb68dc249eef0f1628b6299339c6591b68 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
@@ -32,7 +32,7 @@ internal class DefaultGetKeysBackupVersionTask @Inject constructor(
 
     override suspend fun execute(params: String): KeysVersionResult {
         return executeRequest(globalErrorReceiver) {
-            apiCall = roomKeysApi.getKeysBackupVersion(params)
+            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 168020d9cd233459645b4860a1a7887bd3fc682e..ff515ed80fa34df7256e639be9c4cdda94edde84 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
@@ -38,7 +38,7 @@ internal class DefaultGetRoomSessionDataTask @Inject constructor(
 
     override suspend fun execute(params: GetRoomSessionDataTask.Params): KeyBackupData {
         return executeRequest(globalErrorReceiver) {
-            apiCall = roomKeysApi.getRoomSessionData(
+            roomKeysApi.getRoomSessionData(
                     params.roomId,
                     params.sessionId,
                     params.version)
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 95d5ef2e53ffdee71a311236ba69ba81baa398d4..1b4fe2d96622719d80b6becd3d489b749312c2f5 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
@@ -37,7 +37,7 @@ internal class DefaultGetRoomSessionsDataTask @Inject constructor(
 
     override suspend fun execute(params: GetRoomSessionsDataTask.Params): RoomKeysBackupData {
         return executeRequest(globalErrorReceiver) {
-            apiCall = roomKeysApi.getRoomSessionsData(
+            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 e41a13e3eb258df56fe922c83794642020ab1c98..707125f4cda68328ad1976562a7e3e0e7560dac8 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
@@ -36,7 +36,7 @@ internal class DefaultGetSessionsDataTask @Inject constructor(
 
     override suspend fun execute(params: GetSessionsDataTask.Params): KeysBackupData {
         return executeRequest(globalErrorReceiver) {
-            apiCall = roomKeysApi.getSessionsData(params.version)
+            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 3954277e3925d75c88d90b31697aa2c5c4d50080..180aaecf82000b020b7c67d78bfc69357ebeae5b 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
@@ -40,7 +40,7 @@ internal class DefaultStoreRoomSessionDataTask @Inject constructor(
 
     override suspend fun execute(params: StoreRoomSessionDataTask.Params): BackupKeysResult {
         return executeRequest(globalErrorReceiver) {
-            apiCall = roomKeysApi.storeRoomSessionData(
+            roomKeysApi.storeRoomSessionData(
                     params.roomId,
                     params.sessionId,
                     params.version,
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 4e209b4abc4f6eb0d9aca677cd9803be1c8c5112..d1aa9d2eb03831701c3c71a0d5cdefa8814b24ba 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
@@ -39,7 +39,7 @@ internal class DefaultStoreRoomSessionsDataTask @Inject constructor(
 
     override suspend fun execute(params: StoreRoomSessionsDataTask.Params): BackupKeysResult {
         return executeRequest(globalErrorReceiver) {
-            apiCall = roomKeysApi.storeRoomSessionsData(
+            roomKeysApi.storeRoomSessionsData(
                     params.roomId,
                     params.version,
                     params.roomKeysBackupData)
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 a607477d21712a6369428d2927cf236b4cc342d0..3dbeafe9dee2cb866e98c33b04e762fd5713119c 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
@@ -38,7 +38,7 @@ internal class DefaultStoreSessionsDataTask @Inject constructor(
 
     override suspend fun execute(params: StoreSessionsDataTask.Params): BackupKeysResult {
         return executeRequest(globalErrorReceiver) {
-            apiCall = roomKeysApi.storeSessionsData(
+            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 f012cd13ebd48ce1e1dda4d19b6988a93ddf905a..2b3d044ab796e4573f50eebbe0627cfda4e7fc28 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
@@ -37,7 +37,7 @@ internal class DefaultUpdateKeysBackupVersionTask @Inject constructor(
 
     override suspend fun execute(params: UpdateKeysBackupVersionTask.Params) {
         return executeRequest(globalErrorReceiver) {
-            apiCall = roomKeysApi.updateKeysBackupVersion(params.version, params.keysBackupVersionBody)
+            roomKeysApi.updateKeysBackupVersion(params.version, params.keysBackupVersionBody)
         }
     }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/secrets/DefaultSharedSecretStorageService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/secrets/DefaultSharedSecretStorageService.kt
index 82b5185fe8d9e8d6693b61e96972b2c974ec74cd..1f80ce2c812ab1b359fad9dd355c7a252060534e 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/secrets/DefaultSharedSecretStorageService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/secrets/DefaultSharedSecretStorageService.kt
@@ -16,7 +16,6 @@
 
 package org.matrix.android.sdk.internal.crypto.secrets
 
-import org.matrix.android.sdk.api.MatrixCallback
 import org.matrix.android.sdk.api.extensions.orFalse
 import org.matrix.android.sdk.api.listeners.ProgressListener
 import org.matrix.android.sdk.api.session.accountdata.AccountDataService
@@ -43,10 +42,9 @@ import org.matrix.android.sdk.internal.crypto.keysbackup.util.computeRecoveryKey
 import org.matrix.android.sdk.internal.crypto.tools.HkdfSha256
 import org.matrix.android.sdk.internal.crypto.tools.withOlmDecryption
 import org.matrix.android.sdk.internal.di.UserId
-import org.matrix.android.sdk.internal.extensions.foldToCallback
 import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers
 import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
 import org.matrix.olm.OlmPkMessage
 import java.security.SecureRandom
 import javax.crypto.Cipher
@@ -64,21 +62,15 @@ internal class DefaultSharedSecretStorageService @Inject constructor(
         private val cryptoCoroutineScope: CoroutineScope
 ) : SharedSecretStorageService {
 
-    override fun generateKey(keyId: String,
-                             key: SsssKeySpec?,
-                             keyName: String,
-                             keySigner: KeySigner?,
-                             callback: MatrixCallback<SsssKeyCreationInfo>) {
-        cryptoCoroutineScope.launch(coroutineDispatchers.main) {
-            val bytes = try {
-                (key as? RawBytesKeySpec)?.privateKey
-                        ?: ByteArray(32).also {
-                            SecureRandom().nextBytes(it)
-                        }
-            } catch (failure: Throwable) {
-                callback.onFailure(failure)
-                return@launch
-            }
+    override suspend fun generateKey(keyId: String,
+                                     key: SsssKeySpec?,
+                                     keyName: String,
+                                     keySigner: KeySigner?): SsssKeyCreationInfo {
+        return withContext(cryptoCoroutineScope.coroutineContext + coroutineDispatchers.main) {
+            val bytes = (key as? RawBytesKeySpec)?.privateKey
+                    ?: ByteArray(32).also {
+                        SecureRandom().nextBytes(it)
+                    }
 
             val storageKeyContent = SecretStorageKeyContent(
                     name = keyName,
@@ -92,34 +84,22 @@ internal class DefaultSharedSecretStorageService @Inject constructor(
                 )
             } ?: storageKeyContent
 
-            accountDataService.updateAccountData(
-                    "$KEY_ID_BASE.$keyId",
-                    signedContent.toContent(),
-                    object : MatrixCallback<Unit> {
-                        override fun onFailure(failure: Throwable) {
-                            callback.onFailure(failure)
-                        }
-
-                        override fun onSuccess(data: Unit) {
-                            callback.onSuccess(SsssKeyCreationInfo(
-                                    keyId = keyId,
-                                    content = storageKeyContent,
-                                    recoveryKey = computeRecoveryKey(bytes),
-                                    keySpec = RawBytesKeySpec(bytes)
-                            ))
-                        }
-                    }
+            accountDataService.updateAccountData("$KEY_ID_BASE.$keyId", signedContent.toContent())
+            SsssKeyCreationInfo(
+                    keyId = keyId,
+                    content = storageKeyContent,
+                    recoveryKey = computeRecoveryKey(bytes),
+                    keySpec = RawBytesKeySpec(bytes)
             )
         }
     }
 
-    override fun generateKeyWithPassphrase(keyId: String,
-                                           keyName: String,
-                                           passphrase: String,
-                                           keySigner: KeySigner,
-                                           progressListener: ProgressListener?,
-                                           callback: MatrixCallback<SsssKeyCreationInfo>) {
-        cryptoCoroutineScope.launch(coroutineDispatchers.main) {
+    override suspend fun generateKeyWithPassphrase(keyId: String,
+                                                   keyName: String,
+                                                   passphrase: String,
+                                                   keySigner: KeySigner,
+                                                   progressListener: ProgressListener?): SsssKeyCreationInfo {
+        return withContext(cryptoCoroutineScope.coroutineContext + coroutineDispatchers.main) {
             val privatePart = generatePrivateKeyWithPassword(passphrase, progressListener)
 
             val storageKeyContent = SecretStorageKeyContent(
@@ -135,21 +115,13 @@ internal class DefaultSharedSecretStorageService @Inject constructor(
 
             accountDataService.updateAccountData(
                     "$KEY_ID_BASE.$keyId",
-                    signedContent.toContent(),
-                    object : MatrixCallback<Unit> {
-                        override fun onFailure(failure: Throwable) {
-                            callback.onFailure(failure)
-                        }
-
-                        override fun onSuccess(data: Unit) {
-                            callback.onSuccess(SsssKeyCreationInfo(
-                                    keyId = keyId,
-                                    content = storageKeyContent,
-                                    recoveryKey = computeRecoveryKey(privatePart.privateKey),
-                                    keySpec = RawBytesKeySpec(privatePart.privateKey)
-                            ))
-                        }
-                    }
+                    signedContent.toContent()
+            )
+            SsssKeyCreationInfo(
+                    keyId = keyId,
+                    content = storageKeyContent,
+                    recoveryKey = computeRecoveryKey(privatePart.privateKey),
+                    keySpec = RawBytesKeySpec(privatePart.privateKey)
             )
         }
     }
@@ -168,15 +140,12 @@ internal class DefaultSharedSecretStorageService @Inject constructor(
         } ?: KeyInfoResult.Error(SharedSecretStorageError.UnknownAlgorithm(keyId))
     }
 
-    override fun setDefaultKey(keyId: String, callback: MatrixCallback<Unit>) {
+    override suspend fun setDefaultKey(keyId: String) {
         val existingKey = getKey(keyId)
         if (existingKey is KeyInfoResult.Success) {
-            accountDataService.updateAccountData(DEFAULT_KEY_ID,
-                    mapOf("key" to keyId),
-                    callback
-            )
+            accountDataService.updateAccountData(DEFAULT_KEY_ID, mapOf("key" to keyId))
         } else {
-            callback.onFailure(SharedSecretStorageError.UnknownKey(keyId))
+            throw SharedSecretStorageError.UnknownKey(keyId)
         }
     }
 
@@ -188,42 +157,31 @@ internal class DefaultSharedSecretStorageService @Inject constructor(
         return getKey(keyId)
     }
 
-    override fun storeSecret(name: String, secretBase64: String, keys: List<SharedSecretStorageService.KeyRef>, callback: MatrixCallback<Unit>) {
-        cryptoCoroutineScope.launch(coroutineDispatchers.main) {
+    override suspend fun storeSecret(name: String, secretBase64: String, keys: List<SharedSecretStorageService.KeyRef>) {
+        withContext(cryptoCoroutineScope.coroutineContext + coroutineDispatchers.main) {
             val encryptedContents = HashMap<String, EncryptedSecretContent>()
-            try {
-                keys.forEach {
-                    val keyId = it.keyId
-                    // encrypt the content
-                    when (val key = keyId?.let { getKey(keyId) } ?: getDefaultKey()) {
-                        is KeyInfoResult.Success -> {
-                            if (key.keyInfo.content.algorithm == SSSS_ALGORITHM_AES_HMAC_SHA2) {
-                                encryptAesHmacSha2(it.keySpec!!, name, secretBase64).let {
-                                    encryptedContents[key.keyInfo.id] = it
-                                }
-                            } else {
-                                // Unknown algorithm
-                                callback.onFailure(SharedSecretStorageError.UnknownAlgorithm(key.keyInfo.content.algorithm ?: ""))
-                                return@launch
+            keys.forEach {
+                val keyId = it.keyId
+                // encrypt the content
+                when (val key = keyId?.let { getKey(keyId) } ?: getDefaultKey()) {
+                    is KeyInfoResult.Success -> {
+                        if (key.keyInfo.content.algorithm == SSSS_ALGORITHM_AES_HMAC_SHA2) {
+                            encryptAesHmacSha2(it.keySpec!!, name, secretBase64).let {
+                                encryptedContents[key.keyInfo.id] = it
                             }
-                        }
-                        is KeyInfoResult.Error   -> {
-                            callback.onFailure(key.error)
-                            return@launch
+                        } else {
+                            // Unknown algorithm
+                            throw SharedSecretStorageError.UnknownAlgorithm(key.keyInfo.content.algorithm ?: "")
                         }
                     }
+                    is KeyInfoResult.Error -> throw key.error
                 }
-
-                accountDataService.updateAccountData(
-                        type = name,
-                        content = mapOf(
-                                "encrypted" to encryptedContents
-                        ),
-                        callback = callback
-                )
-            } catch (failure: Throwable) {
-                callback.onFailure(failure)
             }
+
+            accountDataService.updateAccountData(
+                    type = name,
+                    content = mapOf("encrypted" to encryptedContents)
+            )
         }
     }
 
@@ -344,57 +302,40 @@ internal class DefaultSharedSecretStorageService @Inject constructor(
         return results
     }
 
-    override fun getSecret(name: String, keyId: String?, secretKey: SsssKeySpec, callback: MatrixCallback<String>) {
-        val accountData = accountDataService.getAccountDataEvent(name) ?: return Unit.also {
-            callback.onFailure(SharedSecretStorageError.UnknownSecret(name))
-        }
-        val encryptedContent = accountData.content[ENCRYPTED] as? Map<*, *> ?: return Unit.also {
-            callback.onFailure(SharedSecretStorageError.SecretNotEncrypted(name))
-        }
-        val key = keyId?.let { getKey(it) } as? KeyInfoResult.Success ?: getDefaultKey() as? KeyInfoResult.Success ?: return Unit.also {
-            callback.onFailure(SharedSecretStorageError.UnknownKey(name))
-        }
+    override suspend fun getSecret(name: String, keyId: String?, secretKey: SsssKeySpec): String {
+        val accountData = accountDataService.getAccountDataEvent(name) ?: throw SharedSecretStorageError.UnknownSecret(name)
+        val encryptedContent = accountData.content[ENCRYPTED] as? Map<*, *> ?: throw SharedSecretStorageError.SecretNotEncrypted(name)
+        val key = keyId?.let { getKey(it) } as? KeyInfoResult.Success ?: getDefaultKey() as? KeyInfoResult.Success
+        ?: throw SharedSecretStorageError.UnknownKey(name)
 
-        val encryptedForKey = encryptedContent[key.keyInfo.id] ?: return Unit.also {
-            callback.onFailure(SharedSecretStorageError.SecretNotEncryptedWithKey(name, key.keyInfo.id))
-        }
+        val encryptedForKey = encryptedContent[key.keyInfo.id] ?: throw SharedSecretStorageError.SecretNotEncryptedWithKey(name, key.keyInfo.id)
 
         val secretContent = EncryptedSecretContent.fromJson(encryptedForKey)
-                ?: return Unit.also {
-                    callback.onFailure(SharedSecretStorageError.ParsingError)
-                }
+                ?: throw SharedSecretStorageError.ParsingError
 
         val algorithm = key.keyInfo.content
         if (SSSS_ALGORITHM_CURVE25519_AES_SHA2 == algorithm.algorithm) {
-            val keySpec = secretKey as? RawBytesKeySpec ?: return Unit.also {
-                callback.onFailure(SharedSecretStorageError.BadKeyFormat)
-            }
-            cryptoCoroutineScope.launch(coroutineDispatchers.main) {
-                runCatching {
-                    // decrypt from recovery key
-                    withOlmDecryption { olmPkDecryption ->
-                        olmPkDecryption.setPrivateKey(keySpec.privateKey)
-                        olmPkDecryption.decrypt(OlmPkMessage()
-                                .apply {
-                                    mCipherText = secretContent.ciphertext
-                                    mEphemeralKey = secretContent.ephemeral
-                                    mMac = secretContent.mac
-                                }
-                        )
-                    }
-                }.foldToCallback(callback)
+            val keySpec = secretKey as? RawBytesKeySpec ?: throw SharedSecretStorageError.BadKeyFormat
+            return withContext(cryptoCoroutineScope.coroutineContext + coroutineDispatchers.main) {
+                // decrypt from recovery key
+                withOlmDecryption { olmPkDecryption ->
+                    olmPkDecryption.setPrivateKey(keySpec.privateKey)
+                    olmPkDecryption.decrypt(OlmPkMessage()
+                            .apply {
+                                mCipherText = secretContent.ciphertext
+                                mEphemeralKey = secretContent.ephemeral
+                                mMac = secretContent.mac
+                            }
+                    )
+                }
             }
         } else if (SSSS_ALGORITHM_AES_HMAC_SHA2 == algorithm.algorithm) {
-            val keySpec = secretKey as? RawBytesKeySpec ?: return Unit.also {
-                callback.onFailure(SharedSecretStorageError.BadKeyFormat)
-            }
-            cryptoCoroutineScope.launch(coroutineDispatchers.main) {
-                runCatching {
-                    decryptAesHmacSha2(keySpec, name, secretContent)
-                }.foldToCallback(callback)
+            val keySpec = secretKey as? RawBytesKeySpec ?: throw SharedSecretStorageError.BadKeyFormat
+            return withContext(cryptoCoroutineScope.coroutineContext + coroutineDispatchers.main) {
+                decryptAesHmacSha2(keySpec, name, secretContent)
             }
         } else {
-            callback.onFailure(SharedSecretStorageError.UnsupportedAlgorithm(algorithm.algorithm ?: ""))
+            throw SharedSecretStorageError.UnsupportedAlgorithm(algorithm.algorithm ?: "")
         }
     }
 
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 6f1487f80a46da8694bff3ea046b2dab37fe6709..181bd94cc7e832718a8aa3595108c03e092040cb 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
@@ -259,7 +259,7 @@ internal interface IMXCryptoStore {
      * @param deviceKey the public key of the other device.
      * @return A set of sessionId, or null if device is not known
      */
-    fun getDeviceSessionIds(deviceKey: String): Set<String>?
+    fun getDeviceSessionIds(deviceKey: String): List<String>?
 
     /**
      * Retrieve an end-to-end session between the logged-in user and another
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 b9213ba758297549950996c724cdfb279e5f87bb..9ae93d61eb88be0b71cd6aeedf42a058ff123ff3 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
@@ -692,7 +692,7 @@ internal class RealmCryptoStore @Inject constructor(
         }
     }
 
-    override fun getDeviceSessionIds(deviceKey: String): MutableSet<String> {
+    override fun getDeviceSessionIds(deviceKey: String): List<String> {
         return doWithRealm(realmConfiguration) {
             it.where<OlmSessionEntity>()
                     .equalTo(OlmSessionEntityFields.DEVICE_KEY, deviceKey)
@@ -701,7 +701,6 @@ internal class RealmCryptoStore @Inject constructor(
                         sessionEntity.sessionId
                     }
         }
-                .toMutableSet()
     }
 
     override fun storeInboundGroupSessions(sessions: List<OlmInboundGroupSessionWrapper2>) {
@@ -801,7 +800,7 @@ internal class RealmCryptoStore @Inject constructor(
      * 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
      */
-    override fun getInboundGroupSessions(): MutableList<OlmInboundGroupSessionWrapper2> {
+    override fun getInboundGroupSessions(): List<OlmInboundGroupSessionWrapper2> {
         return doWithRealm(realmConfiguration) {
             it.where<OlmInboundGroupSessionEntity>()
                     .findAll()
@@ -809,7 +808,6 @@ internal class RealmCryptoStore @Inject constructor(
                         inboundGroupSessionEntity.getInboundGroupSession()
                     }
         }
-                .toMutableList()
     }
 
     override fun removeInboundGroupSession(sessionId: String, senderKey: String) {
@@ -964,7 +962,7 @@ internal class RealmCryptoStore @Inject constructor(
         }
     }
 
-    override fun getRoomsListBlacklistUnverifiedDevices(): MutableList<String> {
+    override fun getRoomsListBlacklistUnverifiedDevices(): List<String> {
         return doWithRealm(realmConfiguration) {
             it.where<CryptoRoomEntity>()
                     .equalTo(CryptoRoomEntityFields.BLACKLIST_UNVERIFIED_DEVICES, true)
@@ -973,10 +971,9 @@ internal class RealmCryptoStore @Inject constructor(
                         cryptoRoom.roomId
                     }
         }
-                .toMutableList()
     }
 
-    override fun getDeviceTrackingStatuses(): MutableMap<String, Int> {
+    override fun getDeviceTrackingStatuses(): Map<String, Int> {
         return doWithRealm(realmConfiguration) {
             it.where<UserEntity>()
                     .findAll()
@@ -987,7 +984,6 @@ internal class RealmCryptoStore @Inject constructor(
                         entry.value.deviceTrackingStatus
                     }
         }
-                .toMutableMap()
     }
 
     override fun saveDeviceTrackingStatuses(deviceTrackingStatuses: Map<String, Int>) {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/mapper/CrossSigningKeysMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/mapper/CrossSigningKeysMapper.kt
index 8e6143f3010b00bc67811c774a0aa6bc3e291210..9e739855927de9baa03ddd3477d2634ddd2cdc1f 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/mapper/CrossSigningKeysMapper.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/mapper/CrossSigningKeysMapper.kt
@@ -45,7 +45,7 @@ internal class CrossSigningKeysMapper @Inject constructor(moshi: Moshi) {
         return CryptoCrossSigningKey(
                 userId = userId ?: "",
                 keys = mapOf("ed25519:$pubKey" to pubKey),
-                usages = keyInfo.usages.map { it },
+                usages = keyInfo.usages.toList(),
                 signatures = deserializeSignatures(keyInfo.signatures),
                 trustLevel = keyInfo.trustLevelEntity?.let {
                     DeviceTrustLevel(
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 3df6312adb63663c9f12e12f5579d9aed0d71812..d5cf749db738376f4a486f753fdd1da0123a65f1 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
@@ -20,7 +20,6 @@ import org.matrix.android.sdk.internal.crypto.api.CryptoApi
 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
@@ -42,8 +41,8 @@ internal class DefaultClaimOneTimeKeysForUsersDevice @Inject constructor(
     override suspend fun execute(params: ClaimOneTimeKeysForUsersDeviceTask.Params): MXUsersDevicesMap<MXKey> {
         val body = KeysClaimBody(oneTimeKeys = params.usersDevicesKeyTypesMap.map)
 
-        val keysClaimResponse = executeRequest<KeysClaimResponse>(globalErrorReceiver) {
-            apiCall = cryptoApi.claimOneTimeKeysForUsersDevices(body)
+        val keysClaimResponse = executeRequest(globalErrorReceiver) {
+            cryptoApi.claimOneTimeKeysForUsersDevices(body)
         }
         val map = MXUsersDevicesMap<MXKey>()
         keysClaimResponse.oneTimeKeys?.let { oneTimeKeys ->
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 61596bb5b6a58c30c32160d413dee4175ca55e3a..bdb8e8d137a99b0054d2a611999ab47fd994a536 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
@@ -42,8 +42,8 @@ internal class DefaultDeleteDeviceTask @Inject constructor(
 
     override suspend fun execute(params: DeleteDeviceTask.Params) {
         try {
-            executeRequest<Unit>(globalErrorReceiver) {
-                apiCall = cryptoApi.deleteDevice(params.deviceId, DeleteDeviceParams(params.userAuthParam?.asMap()))
+            executeRequest(globalErrorReceiver) {
+                cryptoApi.deleteDevice(params.deviceId, DeleteDeviceParams(params.userAuthParam?.asMap()))
             }
         } catch (throwable: Throwable) {
             if (params.userInteractiveAuthInterceptor == null
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 5eb24b116ad00b2073ff065d046ef92c915b34df..86f02866ae0850440b4906e5bbd8a4b982d0b652 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
@@ -16,17 +16,25 @@
 
 package org.matrix.android.sdk.internal.crypto.tasks
 
+import kotlinx.coroutines.async
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.joinAll
+import kotlinx.coroutines.sync.Mutex
+import kotlinx.coroutines.sync.withLock
 import org.matrix.android.sdk.internal.crypto.api.CryptoApi
+import org.matrix.android.sdk.internal.crypto.model.rest.DeviceKeysWithUnsigned
 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.crypto.model.rest.RestKeyInfo
 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.computeBestChunkSize
 import javax.inject.Inject
 
 internal interface DownloadKeysForUsersTask : Task<DownloadKeysForUsersTask.Params, KeysQueryResponse> {
     data class Params(
-            // the list of users to get keys for.
+            // the list of users to get keys for. The list MUST NOT be empty
             val userIds: List<String>,
             // the up-to token
             val token: String?
@@ -39,15 +47,68 @@ internal class DefaultDownloadKeysForUsers @Inject constructor(
 ) : DownloadKeysForUsersTask {
 
     override suspend fun execute(params: DownloadKeysForUsersTask.Params): KeysQueryResponse {
-        val downloadQuery = params.userIds.associateWith { emptyList<String>() }
+        val bestChunkSize = computeBestChunkSize(params.userIds.size, LIMIT)
+        val token = params.token?.takeIf { token -> token.isNotEmpty() }
 
-        val body = KeysQueryBody(
-                deviceKeys = downloadQuery,
-                token = params.token?.takeIf { it.isNotEmpty() }
-        )
+        return if (bestChunkSize.shouldChunk()) {
+            // Store server results in these mutable maps
+            val deviceKeys = mutableMapOf<String, Map<String, DeviceKeysWithUnsigned>>()
+            val failures = mutableMapOf<String, Map<String, Any>>()
+            val masterKeys = mutableMapOf<String, RestKeyInfo?>()
+            val selfSigningKeys = mutableMapOf<String, RestKeyInfo?>()
+            val userSigningKeys = mutableMapOf<String, RestKeyInfo?>()
 
-        return executeRequest(globalErrorReceiver) {
-            apiCall = cryptoApi.downloadKeysForUsers(body)
+            val mutex = Mutex()
+
+            // Split network request into smaller request (#2925)
+            coroutineScope {
+                params.userIds
+                        .chunked(bestChunkSize.chunkSize)
+                        .map {
+                            KeysQueryBody(
+                                    deviceKeys = it.associateWith { emptyList() },
+                                    token = token
+                            )
+                        }
+                        .map { body ->
+                            async {
+                                val result = executeRequest(globalErrorReceiver) {
+                                    cryptoApi.downloadKeysForUsers(body)
+                                }
+
+                                mutex.withLock {
+                                    deviceKeys.putAll(result.deviceKeys.orEmpty())
+                                    failures.putAll(result.failures.orEmpty())
+                                    masterKeys.putAll(result.masterKeys.orEmpty())
+                                    selfSigningKeys.putAll(result.selfSigningKeys.orEmpty())
+                                    userSigningKeys.putAll(result.userSigningKeys.orEmpty())
+                                }
+                            }
+                        }
+                        .joinAll()
+            }
+
+            KeysQueryResponse(
+                    deviceKeys = deviceKeys,
+                    failures = failures,
+                    masterKeys = masterKeys,
+                    selfSigningKeys = selfSigningKeys,
+                    userSigningKeys = userSigningKeys
+            )
+        } else {
+            // No need to chunk, direct request
+            executeRequest(globalErrorReceiver) {
+                cryptoApi.downloadKeysForUsers(
+                        KeysQueryBody(
+                                deviceKeys = params.userIds.associateWith { emptyList() },
+                                token = token
+                        )
+                )
+            }
         }
     }
+
+    companion object {
+        const val LIMIT = 250
+    }
 }
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 5f6d2e344fc30871cc4f89ec61b863e51d10509f..9f20ea598de6331d3d46fa2972d896232a413a3b 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
@@ -34,7 +34,7 @@ internal class DefaultGetDeviceInfoTask @Inject constructor(
 
     override suspend fun execute(params: GetDeviceInfoTask.Params): DeviceInfo {
         return executeRequest(globalErrorReceiver) {
-            apiCall = cryptoApi.getDeviceInfo(params.deviceId)
+            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 ea33a918bcf9ec91916a0335bdd6234ba0611b5f..52f9f732994ca52a96903bf6bca735f1a6a56216 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
@@ -32,7 +32,7 @@ internal class DefaultGetDevicesTask @Inject constructor(
 
     override suspend fun execute(params: Unit): DevicesListResponse {
         return executeRequest(globalErrorReceiver) {
-            apiCall = cryptoApi.getDevices()
+            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 4cc9ab2fcb3cec2b27f38297f33463654a86611d..6e524c7fbe487dac00fe52c266f3e0dab84fdc74 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
@@ -39,7 +39,7 @@ internal class DefaultGetKeyChangesTask @Inject constructor(
 
     override suspend fun execute(params: GetKeyChangesTask.Params): KeyChangesResponse {
         return executeRequest(globalErrorReceiver) {
-            apiCall = cryptoApi.getKeyChanges(params.from, params.to)
+            cryptoApi.getKeyChanges(params.from, params.to)
         }
     }
 }
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 5226e52b33f2daaf7fe6b0bce5e8e0b2927e43d1..d6a7f3c6a026c44d942911446a38fd2064341e72 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
@@ -18,7 +18,6 @@ package org.matrix.android.sdk.internal.crypto.tasks
 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
 import org.matrix.android.sdk.internal.task.Task
 import javax.inject.Inject
 
@@ -36,14 +35,14 @@ internal class DefaultRedactEventTask @Inject constructor(
         private val globalErrorReceiver: GlobalErrorReceiver) : RedactEventTask {
 
     override suspend fun execute(params: RedactEventTask.Params): String {
-        val executeRequest = executeRequest<SendResponse>(globalErrorReceiver) {
-            apiCall = roomAPI.redactEvent(
+        val response = executeRequest(globalErrorReceiver) {
+            roomAPI.redactEvent(
                     txId = params.txID,
                     roomId = params.roomId,
                     eventId = params.eventId,
                     reason = if (params.reason == null) emptyMap() else mapOf("reason" to params.reason)
             )
         }
-        return executeRequest.eventId
+        return response.eventId
     }
 }
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 573f2c3a5495a414a8971045b4fe1630b167ec72..e1e297767b100902ad4105f9f26f8fe53878f7b2 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
@@ -22,7 +22,6 @@ 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
 import javax.inject.Inject
 
@@ -52,8 +51,8 @@ internal class DefaultSendEventTask @Inject constructor(
             val event = handleEncryption(params)
             val localId = event.eventId!!
             localEchoRepository.updateSendState(localId, params.event.roomId, SendState.SENDING)
-            val executeRequest = executeRequest<SendResponse>(globalErrorReceiver) {
-                apiCall = roomAPI.send(
+            val response = executeRequest(globalErrorReceiver) {
+                roomAPI.send(
                         localId,
                         roomId = event.roomId ?: "",
                         content = event.content,
@@ -61,7 +60,7 @@ internal class DefaultSendEventTask @Inject constructor(
                 )
             }
             localEchoRepository.updateSendState(localId, params.event.roomId, SendState.SENT)
-            return executeRequest.eventId
+            return response.eventId
         } catch (e: Throwable) {
 //            localEchoRepository.updateSendState(params.event.eventId!!, SendState.UNDELIVERED)
             throw e
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 d2af91601b49a994ae4de8ed37a9e7480465ee18..41a5118be017b8f119dcd7a326b46eb0d1730fe9 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
@@ -46,14 +46,16 @@ internal class DefaultSendToDeviceTask @Inject constructor(
                 messages = params.contentMap.map
         )
 
-        return executeRequest(globalErrorReceiver) {
-            apiCall = cryptoApi.sendToDevice(
+        return executeRequest(
+                globalErrorReceiver,
+                canRetry = true,
+                maxRetriesCount = 3
+        ) {
+            cryptoApi.sendToDevice(
                     params.eventType,
                     params.transactionId ?: Random.nextInt(Integer.MAX_VALUE).toString(),
                     sendToDeviceBody
             )
-            isRetryable = true
-            maxRetryCount = 3
         }
     }
 }
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 ab125135bb393392b4b8cf3d1329c76f34ff6c14..d8b9d3cd864a91050a139fa365d84fd32a65380a 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
@@ -22,7 +22,6 @@ 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
-import org.matrix.android.sdk.internal.session.room.send.SendResponse
 import org.matrix.android.sdk.internal.task.Task
 import javax.inject.Inject
 
@@ -45,8 +44,8 @@ internal class DefaultSendVerificationMessageTask @Inject constructor(
 
         try {
             localEchoRepository.updateSendState(localId, event.roomId, SendState.SENDING)
-            val executeRequest = executeRequest<SendResponse>(globalErrorReceiver) {
-                apiCall = roomAPI.send(
+            val response = executeRequest(globalErrorReceiver) {
+                roomAPI.send(
                         localId,
                         roomId = event.roomId ?: "",
                         content = event.content,
@@ -54,7 +53,7 @@ internal class DefaultSendVerificationMessageTask @Inject constructor(
                 )
             }
             localEchoRepository.updateSendState(localId, event.roomId, SendState.SENT)
-            return executeRequest.eventId
+            return response.eventId
         } catch (e: Throwable) {
             localEchoRepository.updateSendState(localId, event.roomId, SendState.UNDELIVERED)
             throw e
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 b835d46236f2a2000e99375f3702c17808255e9f..4bedb1f393e5ffd14be3a2e60496f13658502852 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
@@ -42,7 +42,7 @@ internal class DefaultSetDeviceNameTask @Inject constructor(
                 displayName = params.deviceName
         )
         return executeRequest(globalErrorReceiver) {
-            apiCall = cryptoApi.updateDeviceInfo(params.deviceId, body)
+            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 eb53bbbf8d289375e782c51c77d86e5cefc9013e..cac4dadd935ec4b97136b3d1ed8b8b03209a1843 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
@@ -50,7 +50,7 @@ internal class DefaultUploadKeysTask @Inject constructor(
         Timber.i("## Uploading device keys -> $body")
 
         return executeRequest(globalErrorReceiver) {
-            apiCall = cryptoApi.uploadKeys(body)
+            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 c50faf37b1fa7e084ce7a51bfea4d55625c1fec5..e03e353cb1caa3dff8ec5e64b88e4b434631a4fd 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
@@ -17,7 +17,6 @@ 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
@@ -36,10 +35,12 @@ internal class DefaultUploadSignaturesTask @Inject constructor(
 
     override suspend fun execute(params: UploadSignaturesTask.Params) {
         try {
-            val response = executeRequest<SignatureUploadResponse>(globalErrorReceiver) {
-                this.isRetryable = true
-                this.maxRetryCount = 10
-                this.apiCall = cryptoApi.uploadSignatures(params.signatures)
+            val response = executeRequest(
+                    globalErrorReceiver,
+                    canRetry = true,
+                    maxRetriesCount = 10
+            ) {
+                cryptoApi.uploadSignatures(params.signatures)
             }
             if (response.failures?.isNotEmpty() == true) {
                 throw Throwable(response.failures.toString())
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 14fad2ea3899466d36e27507cf288eccdd50d0f0..08c767ba34819af3d712917258ccfc69e7db8e6c 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
@@ -19,7 +19,6 @@ 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.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.toRest
@@ -61,8 +60,8 @@ internal class DefaultUploadSigningKeysTask @Inject constructor(
     }
 
     private suspend fun doRequest(uploadQuery: UploadSigningKeysBody) {
-        val keysQueryResponse = executeRequest<KeysQueryResponse>(globalErrorReceiver) {
-            apiCall = cryptoApi.uploadSigningKeys(uploadQuery)
+        val keysQueryResponse = executeRequest(globalErrorReceiver) {
+            cryptoApi.uploadSigningKeys(uploadQuery)
         }
         if (keysQueryResponse.failures?.isNotEmpty() == true) {
             throw UploadSigningKeys(keysQueryResponse.failures)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/DefaultVerificationService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/DefaultVerificationService.kt
index a92f5c5bf1b09f4d48c7cb31c96209de8c715cc2..d9da88770cd57b505920be9db61cbbe1cbb2adf9 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/DefaultVerificationService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/DefaultVerificationService.kt
@@ -19,7 +19,6 @@ package org.matrix.android.sdk.internal.crypto.verification
 import android.os.Handler
 import android.os.Looper
 import dagger.Lazy
-import org.matrix.android.sdk.api.MatrixCallback
 import org.matrix.android.sdk.api.session.crypto.crosssigning.CrossSigningService
 import org.matrix.android.sdk.api.session.crypto.crosssigning.KEYBACKUP_SECRET_SSSS_NAME
 import org.matrix.android.sdk.api.session.crypto.crosssigning.MASTER_KEY_SSSS_NAME
@@ -1293,8 +1292,7 @@ internal class DefaultVerificationService @Inject constructor(
                                            transactionId: String,
                                            roomId: String,
                                            otherUserId: String,
-                                           otherDeviceId: String,
-                                           callback: MatrixCallback<String>?): String? {
+                                           otherDeviceId: String): String {
         if (method == VerificationMethod.SAS) {
             val tx = DefaultOutgoingSASDefaultVerificationTransaction(
                     setDeviceVerificationAction,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationTransportRoomMessage.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationTransportRoomMessage.kt
index ad490bcd277febb624a2feb19ae189a4339efcac..bcf3250ed2b9898ecedcc9665e2b10a90abc78dd 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationTransportRoomMessage.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationTransportRoomMessage.kt
@@ -183,7 +183,7 @@ internal class VerificationTransportRoomMessage(
         val workRequest = workManagerProvider.matrixOneTimeWorkRequestBuilder<SendVerificationMessageWorker>()
                 .setConstraints(WorkManagerProvider.workConstraints)
                 .setInputData(workerParams)
-                .setBackoffCriteria(BackoffPolicy.LINEAR, 2_000L, TimeUnit.MILLISECONDS)
+                .setBackoffCriteria(BackoffPolicy.LINEAR, WorkManagerProvider.BACKOFF_DELAY_MILLIS, TimeUnit.MILLISECONDS)
                 .build()
 
         workManagerProvider.workManager
@@ -280,7 +280,7 @@ internal class VerificationTransportRoomMessage(
         val workRequest = workManagerProvider.matrixOneTimeWorkRequestBuilder<SendVerificationMessageWorker>()
                 .setConstraints(WorkManagerProvider.workConstraints)
                 .setInputData(workerParams)
-                .setBackoffCriteria(BackoffPolicy.LINEAR, 2_000L, TimeUnit.MILLISECONDS)
+                .setBackoffCriteria(BackoffPolicy.LINEAR, WorkManagerProvider.BACKOFF_DELAY_MILLIS, TimeUnit.MILLISECONDS)
                 .build()
         return workManagerProvider.workManager
                 .beginUniqueWork(uniqueQueueName(), ExistingWorkPolicy.APPEND_OR_REPLACE, workRequest)
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 c7fe7ab447fb55f1242ca3d0a3db3a288a2e62cc..1daae906f2e902de3d302c776a46a29e4734861d 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
@@ -17,22 +17,27 @@
 package org.matrix.android.sdk.internal.database
 
 import io.realm.DynamicRealm
+import io.realm.FieldAttribute
 import io.realm.RealmMigration
+import org.matrix.android.sdk.api.session.room.model.tag.RoomTag
 import org.matrix.android.sdk.internal.database.model.EditAggregatedSummaryEntityFields
 import org.matrix.android.sdk.internal.database.model.EditionOfEventFields
+import org.matrix.android.sdk.internal.database.model.EventEntityFields
 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 org.matrix.android.sdk.internal.database.model.RoomTagEntityFields
+import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields
 import timber.log.Timber
 import javax.inject.Inject
 
 class RealmSessionStoreMigration @Inject constructor() : RealmMigration {
 
     companion object {
-        const val SESSION_STORE_SCHEMA_VERSION = 8L
+        const val SESSION_STORE_SCHEMA_VERSION = 9L
     }
 
     override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) {
@@ -46,6 +51,7 @@ class RealmSessionStoreMigration @Inject constructor() : RealmMigration {
         if (oldVersion <= 5) migrateTo6(realm)
         if (oldVersion <= 6) migrateTo7(realm)
         if (oldVersion <= 7) migrateTo8(realm)
+        if (oldVersion <= 8) migrateTo9(realm)
     }
 
     private fun migrateTo1(realm: DynamicRealm) {
@@ -149,4 +155,43 @@ class RealmSessionStoreMigration @Inject constructor() : RealmMigration {
                 ?.removeField("sourceLocalEchoEvents")
                 ?.addRealmListField(EditAggregatedSummaryEntityFields.EDITIONS.`$`, editionOfEventSchema)
     }
+
+    fun migrateTo9(realm: DynamicRealm) {
+        Timber.d("Step 8 -> 9")
+
+        realm.schema.get("RoomSummaryEntity")
+                ?.addField(RoomSummaryEntityFields.LAST_ACTIVITY_TIME, Long::class.java, FieldAttribute.INDEXED)
+                ?.setNullable(RoomSummaryEntityFields.LAST_ACTIVITY_TIME, true)
+                ?.addIndex(RoomSummaryEntityFields.MEMBERSHIP_STR)
+                ?.addIndex(RoomSummaryEntityFields.IS_DIRECT)
+                ?.addIndex(RoomSummaryEntityFields.VERSIONING_STATE_STR)
+
+                ?.addField(RoomSummaryEntityFields.IS_FAVOURITE, Boolean::class.java)
+                ?.addIndex(RoomSummaryEntityFields.IS_FAVOURITE)
+                ?.addField(RoomSummaryEntityFields.IS_LOW_PRIORITY, Boolean::class.java)
+                ?.addIndex(RoomSummaryEntityFields.IS_LOW_PRIORITY)
+                ?.addField(RoomSummaryEntityFields.IS_SERVER_NOTICE, Boolean::class.java)
+                ?.addIndex(RoomSummaryEntityFields.IS_SERVER_NOTICE)
+
+                ?.transform { obj ->
+
+                    val isFavorite = obj.getList(RoomSummaryEntityFields.TAGS.`$`).any {
+                        it.getString(RoomTagEntityFields.TAG_NAME) == RoomTag.ROOM_TAG_FAVOURITE
+                    }
+                    obj.setBoolean(RoomSummaryEntityFields.IS_FAVOURITE, isFavorite)
+
+                    val isLowPriority = obj.getList(RoomSummaryEntityFields.TAGS.`$`).any {
+                        it.getString(RoomTagEntityFields.TAG_NAME) == RoomTag.ROOM_TAG_LOW_PRIORITY
+                    }
+
+                    obj.setBoolean(RoomSummaryEntityFields.IS_LOW_PRIORITY, isLowPriority)
+
+//                    XXX migrate last message origin server ts
+                    obj.getObject(RoomSummaryEntityFields.LATEST_PREVIEWABLE_EVENT.`$`)
+                            ?.getObject(TimelineEventEntityFields.ROOT.`$`)
+                            ?.getLong(EventEntityFields.ORIGIN_SERVER_TS)?.let {
+                                obj.setLong(RoomSummaryEntityFields.LAST_ACTIVITY_TIME, it)
+                            }
+                }
+    }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/RoomSummaryMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/RoomSummaryMapper.kt
index 2e54a4cd5258127d4124085fd56a9bb98d583943..6dc70b60fc71937647211b6d58b8a9019096e395 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/RoomSummaryMapper.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/RoomSummaryMapper.kt
@@ -26,7 +26,7 @@ internal class RoomSummaryMapper @Inject constructor(private val timelineEventMa
                                                      private val typingUsersTracker: DefaultTypingUsersTracker) {
 
     fun map(roomSummaryEntity: RoomSummaryEntity): RoomSummary {
-        val tags = roomSummaryEntity.tags.map {
+        val tags = roomSummaryEntity.tags().map {
             RoomTag(it.tagName, it.tagOrder)
         }
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/TimelineEventMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/TimelineEventMapper.kt
index a2b36ce59004d9b960b4eae48f640e4a2f17e837..f3bea68c26e6bb1fc83cc7d6e56b1bda03b1a6b1 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/TimelineEventMapper.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/TimelineEventMapper.kt
@@ -17,7 +17,6 @@
 package org.matrix.android.sdk.internal.database.mapper
 
 import org.matrix.android.sdk.api.session.events.model.Event
-import org.matrix.android.sdk.api.session.room.model.ReadReceipt
 import org.matrix.android.sdk.api.session.room.sender.SenderInfo
 import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
 import org.matrix.android.sdk.internal.database.model.TimelineEventEntity
@@ -25,9 +24,9 @@ import javax.inject.Inject
 
 internal class TimelineEventMapper @Inject constructor(private val readReceiptsSummaryMapper: ReadReceiptsSummaryMapper) {
 
-    fun map(timelineEventEntity: TimelineEventEntity, buildReadReceipts: Boolean = true, correctedReadReceipts: List<ReadReceipt>? = null): TimelineEvent {
+    fun map(timelineEventEntity: TimelineEventEntity, buildReadReceipts: Boolean = true): TimelineEvent {
         val readReceipts = if (buildReadReceipts) {
-            correctedReadReceipts ?: timelineEventEntity.readReceipts
+            timelineEventEntity.readReceipts
                     ?.let {
                         readReceiptsSummaryMapper.map(it)
                     }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomMemberSummaryEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomMemberSummaryEntity.kt
index a48b081f0224740f78cce80568fd8d2b7875e76c..e970fab397990bb84a53cc41f184efea26ad91ec 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomMemberSummaryEntity.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomMemberSummaryEntity.kt
@@ -39,5 +39,7 @@ internal open class RoomMemberSummaryEntity(@PrimaryKey var primaryKey: String =
             membershipStr = value.name
         }
 
+    fun getBestName() = displayName?.takeIf { it.isNotBlank() } ?: userId
+
     companion object
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomSummaryEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomSummaryEntity.kt
index 37696c90824afcb14ef4ebdb09e4bc8d54591c7e..c87ac15a78337778b9be15fe13f98740349d7ea7 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomSummaryEntity.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomSummaryEntity.kt
@@ -16,61 +16,217 @@
 
 package org.matrix.android.sdk.internal.database.model
 
+import io.realm.RealmList
+import io.realm.RealmObject
+import io.realm.annotations.Index
+import io.realm.annotations.PrimaryKey
 import org.matrix.android.sdk.api.crypto.RoomEncryptionTrustLevel
 import org.matrix.android.sdk.api.session.room.model.Membership
 import org.matrix.android.sdk.api.session.room.model.RoomSummary
 import org.matrix.android.sdk.api.session.room.model.VersioningState
-import io.realm.RealmList
-import io.realm.RealmObject
-import io.realm.annotations.PrimaryKey
+import org.matrix.android.sdk.api.session.room.model.tag.RoomTag
 
 internal open class RoomSummaryEntity(
-        @PrimaryKey var roomId: String = "",
-        var displayName: String? = "",
-        var avatarUrl: String? = "",
-        var name: String? = "",
-        var topic: String? = "",
-        var latestPreviewableEvent: TimelineEventEntity? = null,
-        var heroes: RealmList<String> = RealmList(),
-        var joinedMembersCount: Int? = 0,
-        var invitedMembersCount: Int? = 0,
-        var isDirect: Boolean = false,
-        var directUserId: String? = null,
-        var otherMemberIds: RealmList<String> = RealmList(),
-        var notificationCount: Int = 0,
-        var highlightCount: Int = 0,
-        var readMarkerId: String? = null,
-        var hasUnreadMessages: Boolean = false,
-        var tags: RealmList<RoomTagEntity> = RealmList(),
-        var userDrafts: UserDraftsEntity? = null,
-        var breadcrumbsIndex: Int = RoomSummary.NOT_IN_BREADCRUMBS,
-        var canonicalAlias: String? = null,
-        var aliases: RealmList<String> = RealmList(),
-        // this is required for querying
-        var flatAliases: String = "",
-        var isEncrypted: Boolean = false,
-        var encryptionEventTs: Long? = 0,
-        var roomEncryptionTrustLevelStr: String? = null,
-        var inviterId: String? = null,
-        var hasFailedSending: Boolean = false
+        @PrimaryKey var roomId: String = ""
 ) : RealmObject() {
 
+    var displayName: String? = ""
+        set(value) {
+            if (value != field) field = value
+        }
+    var avatarUrl: String? = ""
+        set(value) {
+            if (value != field) field = value
+        }
+    var name: String? = ""
+        set(value) {
+            if (value != field) field = value
+        }
+    var topic: String? = ""
+        set(value) {
+            if (value != field) field = value
+        }
+
+    var latestPreviewableEvent: TimelineEventEntity? = null
+        set(value) {
+            if (value != field) field = value
+        }
+
+    @Index
+    var lastActivityTime: Long? = null
+        set(value) {
+            if (value != field) field = value
+        }
+
+    var heroes: RealmList<String> = RealmList()
+
+    var joinedMembersCount: Int? = 0
+        set(value) {
+            if (value != field) field = value
+        }
+
+    var invitedMembersCount: Int? = 0
+        set(value) {
+            if (value != field) field = value
+        }
+
+    @Index
+    var isDirect: Boolean = false
+        set(value) {
+            if (value != field) field = value
+        }
+
+    var directUserId: String? = null
+        set(value) {
+            if (value != field) field = value
+        }
+
+    var otherMemberIds: RealmList<String> = RealmList()
+
+    var notificationCount: Int = 0
+        set(value) {
+            if (value != field) field = value
+        }
+
+    var highlightCount: Int = 0
+        set(value) {
+            if (value != field) field = value
+        }
+
+    var readMarkerId: String? = null
+        set(value) {
+            if (value != field) field = value
+        }
+
+    var hasUnreadMessages: Boolean = false
+        set(value) {
+            if (value != field) field = value
+        }
+
+    private var tags: RealmList<RoomTagEntity> = RealmList()
+
+    fun tags(): List<RoomTagEntity> = tags
+
+    fun updateTags(newTags: List<Pair<String, Double?>>) {
+        val toDelete = mutableListOf<RoomTagEntity>()
+        tags.forEach { existingTag ->
+            val updatedTag = newTags.firstOrNull { it.first == existingTag.tagName }
+            if (updatedTag == null) {
+                toDelete.add(existingTag)
+            } else {
+                existingTag.tagOrder = updatedTag.second
+            }
+        }
+        toDelete.forEach { it.deleteFromRealm() }
+        newTags.forEach { newTag ->
+            if (tags.all { it.tagName != newTag.first }) {
+                // we must add it
+                tags.add(
+                        RoomTagEntity(newTag.first, newTag.second)
+                )
+            }
+        }
+
+        isFavourite = newTags.any { it.first == RoomTag.ROOM_TAG_FAVOURITE }
+        isLowPriority = newTags.any { it.first == RoomTag.ROOM_TAG_LOW_PRIORITY }
+        isServerNotice = newTags.any { it.first == RoomTag.ROOM_TAG_SERVER_NOTICE }
+    }
+
+    @Index
+    var isFavourite: Boolean = false
+        set(value) {
+            if (value != field) field = value
+        }
+
+    @Index
+    var isLowPriority: Boolean = false
+        set(value) {
+            if (value != field) field = value
+        }
+
+    @Index
+    var isServerNotice: Boolean = false
+        set(value) {
+            if (value != field) field = value
+        }
+
+    var userDrafts: UserDraftsEntity? = null
+        set(value) {
+            if (value != field) field = value
+        }
+
+    var breadcrumbsIndex: Int = RoomSummary.NOT_IN_BREADCRUMBS
+        set(value) {
+            if (value != field) field = value
+        }
+
+    var canonicalAlias: String? = null
+        set(value) {
+            if (value != field) field = value
+        }
+
+    var aliases: RealmList<String> = RealmList()
+
+    fun updateAliases(newAliases: List<String>) {
+        // only update underlying field if there is a diff
+        if (newAliases.distinct().sorted() != aliases.distinct().sorted()) {
+            aliases.clear()
+            aliases.addAll(newAliases)
+            flatAliases = newAliases.joinToString(separator = "|", prefix = "|")
+        }
+    }
+
+    // this is required for querying
+    var flatAliases: String = ""
+
+    var isEncrypted: Boolean = false
+        set(value) {
+            if (value != field) field = value
+        }
+
+    var encryptionEventTs: Long? = 0
+        set(value) {
+            if (value != field) field = value
+        }
+
+    var roomEncryptionTrustLevelStr: String? = null
+        set(value) {
+            if (value != field) field = value
+        }
+
+    var inviterId: String? = null
+        set(value) {
+            if (value != field) field = value
+        }
+
+    var hasFailedSending: Boolean = false
+        set(value) {
+            if (value != field) field = value
+        }
+
+    @Index
     private var membershipStr: String = Membership.NONE.name
+
     var membership: Membership
         get() {
             return Membership.valueOf(membershipStr)
         }
         set(value) {
-            membershipStr = value.name
+            if (value.name != membershipStr) {
+                membershipStr = value.name
+            }
         }
 
+    @Index
     private var versioningStateStr: String = VersioningState.NONE.name
     var versioningState: VersioningState
         get() {
             return VersioningState.valueOf(versioningStateStr)
         }
         set(value) {
-            versioningStateStr = value.name
+            if (value.name != versioningStateStr) {
+                versioningStateStr = value.name
+            }
         }
 
     var roomEncryptionTrustLevel: RoomEncryptionTrustLevel?
@@ -84,7 +240,9 @@ internal open class RoomSummaryEntity(
             }
         }
         set(value) {
-            roomEncryptionTrustLevelStr = value?.name
+            if (value?.name != roomEncryptionTrustLevelStr) {
+                roomEncryptionTrustLevelStr = value?.name
+            }
         }
 
     companion object
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/ReadQueries.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/ReadQueries.kt
index a3c741ad55a441c6a04c685f9ae5f85605b91520..5423025823dfc38206fe9fd3d49c3ff51f334c50 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/ReadQueries.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/ReadQueries.kt
@@ -38,16 +38,21 @@ internal fun isEventRead(realmConfiguration: RealmConfiguration,
     Realm.getInstance(realmConfiguration).use { realm ->
         val liveChunk = ChunkEntity.findLastForwardChunkOfRoom(realm, roomId) ?: return@use
         val eventToCheck = liveChunk.timelineEvents.find(eventId)
-        isEventRead = if (eventToCheck == null || eventToCheck.root?.sender == userId) {
-            true
-        } else {
-            val readReceipt = ReadReceiptEntity.where(realm, roomId, userId).findFirst()
-                    ?: return@use
-            val readReceiptIndex = liveChunk.timelineEvents.find(readReceipt.eventId)?.displayIndex
-                    ?: Int.MIN_VALUE
-            val eventToCheckIndex = eventToCheck.displayIndex
+        isEventRead = when {
+            eventToCheck == null                -> {
+                // This can happen in case of fast lane Event
+                false
+            }
+            eventToCheck.root?.sender == userId -> true
+            else                                -> {
+                val readReceipt = ReadReceiptEntity.where(realm, roomId, userId).findFirst()
+                        ?: return@use
+                val readReceiptIndex = liveChunk.timelineEvents.find(readReceipt.eventId)?.displayIndex
+                        ?: Int.MIN_VALUE
+                val eventToCheckIndex = eventToCheck.displayIndex
 
-            eventToCheckIndex <= readReceiptIndex
+                eventToCheckIndex <= readReceiptIndex
+            }
         }
     }
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/RoomSummaryEntityQueries.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/RoomSummaryEntityQueries.kt
index 7430b7822f591658ca4d911e5bcf0a48de7af18d..2af5dcf0ae500b448c15262ca8935aa2cd137381 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/RoomSummaryEntityQueries.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/RoomSummaryEntityQueries.kt
@@ -48,9 +48,15 @@ internal fun RoomSummaryEntity.Companion.getOrCreate(realm: Realm, roomId: Strin
     return where(realm, roomId).findFirst() ?: realm.createObject(roomId)
 }
 
-internal fun RoomSummaryEntity.Companion.getDirectRooms(realm: Realm): RealmResults<RoomSummaryEntity> {
+internal fun RoomSummaryEntity.Companion.getDirectRooms(realm: Realm,
+                                                        excludeRoomIds: Set<String>? = null): RealmResults<RoomSummaryEntity> {
     return RoomSummaryEntity.where(realm)
             .equalTo(RoomSummaryEntityFields.IS_DIRECT, true)
+            .apply {
+                if (!excludeRoomIds.isNullOrEmpty()) {
+                    not().`in`(RoomSummaryEntityFields.ROOM_ID, excludeRoomIds.toTypedArray())
+                }
+            }
             .findAll()
 }
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/NetworkModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/NetworkModule.kt
index f4688411ffe0bdacf32bb8455912db481075cd36..0d0892b608d91b68b909ca26c7ccbd3c3e147972 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/NetworkModule.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/NetworkModule.kt
@@ -28,6 +28,7 @@ import org.matrix.android.sdk.internal.network.interceptors.CurlLoggingIntercept
 import org.matrix.android.sdk.internal.network.interceptors.FormattedJsonHttpLogger
 import okhttp3.OkHttpClient
 import okhttp3.logging.HttpLoggingInterceptor
+import org.matrix.android.sdk.internal.network.ApiInterceptor
 import java.util.concurrent.TimeUnit
 
 @Module
@@ -63,7 +64,8 @@ internal object NetworkModule {
                              timeoutInterceptor: TimeOutInterceptor,
                              userAgentInterceptor: UserAgentInterceptor,
                              httpLoggingInterceptor: HttpLoggingInterceptor,
-                             curlLoggingInterceptor: CurlLoggingInterceptor): OkHttpClient {
+                             curlLoggingInterceptor: CurlLoggingInterceptor,
+                             apiInterceptor: ApiInterceptor): OkHttpClient {
         return OkHttpClient.Builder()
                 .connectTimeout(30, TimeUnit.SECONDS)
                 .readTimeout(60, TimeUnit.SECONDS)
@@ -76,6 +78,7 @@ internal object NetworkModule {
                 .addInterceptor(timeoutInterceptor)
                 .addInterceptor(userAgentInterceptor)
                 .addInterceptor(httpLoggingInterceptor)
+                .addInterceptor(apiInterceptor)
                 .apply {
                     if (BuildConfig.LOG_PRIVATE_DATA) {
                         addInterceptor(curlLoggingInterceptor)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/WorkManagerProvider.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/WorkManagerProvider.kt
index 4c9d06f7a2c727114d46f4fc4b5179b50a8b0376..bafffdf852006ea9666862210926a333990cc6a7 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/WorkManagerProvider.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/WorkManagerProvider.kt
@@ -23,6 +23,7 @@ import androidx.work.NetworkType
 import androidx.work.OneTimeWorkRequestBuilder
 import androidx.work.PeriodicWorkRequestBuilder
 import androidx.work.WorkManager
+import androidx.work.WorkRequest
 import java.util.concurrent.TimeUnit
 import javax.inject.Inject
 
@@ -69,6 +70,7 @@ internal class WorkManagerProvider @Inject constructor(
                 .setRequiredNetworkType(NetworkType.CONNECTED)
                 .build()
 
-        const val BACKOFF_DELAY = 10_000L
+        // Use min value, smaller value will be ignored
+        const val BACKOFF_DELAY_MILLIS = WorkRequest.MIN_BACKOFF_MILLIS
     }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/extensions/Try.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/extensions/Try.kt
index 8786321464959b625d4df9cb00eda6a2e5feb480..2ce0534b49bc436111dc1a2440823b2e9916ec70 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/extensions/Try.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/extensions/Try.kt
@@ -21,7 +21,6 @@ import arrow.core.Success
 import arrow.core.Try
 import arrow.core.TryOf
 import arrow.core.fix
-import org.matrix.android.sdk.api.MatrixCallback
 
 inline fun <A> TryOf<A>.onError(f: (Throwable) -> Unit): Try<A> = fix()
         .fold(
@@ -32,10 +31,6 @@ inline fun <A> TryOf<A>.onError(f: (Throwable) -> Unit): Try<A> = fix()
                 { Success(it) }
         )
 
-fun <A> Try<A>.foldToCallback(callback: MatrixCallback<A>): Unit = fold(
-        { callback.onFailure(it) },
-        { callback.onSuccess(it) })
-
 /**
  * Same as doOnNext for Observables
  */
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/federation/FederationAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/federation/FederationAPI.kt
index 1816616336c9a683a0605b91daf9da666989a625..c37392494f66f50fa1cee4d5d940fc56205db49a 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/federation/FederationAPI.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/federation/FederationAPI.kt
@@ -18,10 +18,9 @@
 package org.matrix.android.sdk.internal.federation
 
 import org.matrix.android.sdk.internal.network.NetworkConstants
-import retrofit2.Call
 import retrofit2.http.GET
 
 internal interface FederationAPI {
     @GET(NetworkConstants.URI_FEDERATION_PATH + "version")
-    fun getVersion(): Call<FederationGetVersionResult>
+    suspend fun getVersion(): FederationGetVersionResult
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/federation/GetFederationVersionTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/federation/GetFederationVersionTask.kt
index ce35e48f6b53f13e6d9c37f4f59a2df515a7fc98..b7f73a606cf0c76c215473121ca8822404eef174 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/federation/GetFederationVersionTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/federation/GetFederationVersionTask.kt
@@ -28,8 +28,8 @@ internal class DefaultGetFederationVersionTask @Inject constructor(
 ) : GetFederationVersionTask {
 
     override suspend fun execute(params: Unit): FederationVersion {
-        val result = executeRequest<FederationGetVersionResult>(null) {
-            apiCall = federationAPI.getVersion()
+        val result = executeRequest(null) {
+            federationAPI.getVersion()
         }
 
         return FederationVersion(
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/ApiInterceptor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/ApiInterceptor.kt
new file mode 100644
index 0000000000000000000000000000000000000000..1dd6e75ea56a941410ec4a7556404dfb29761101
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/ApiInterceptor.kt
@@ -0,0 +1,102 @@
+/*
+ * 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.network
+
+import okhttp3.Interceptor
+import okhttp3.Response
+import org.matrix.android.sdk.api.extensions.tryOrNull
+import org.matrix.android.sdk.api.network.ApiInterceptorListener
+import org.matrix.android.sdk.api.network.ApiPath
+import org.matrix.android.sdk.internal.di.MatrixScope
+import timber.log.Timber
+import javax.inject.Inject
+
+/**
+ * Interceptor class for provided api paths.
+ */
+@MatrixScope
+internal class ApiInterceptor @Inject constructor() : Interceptor {
+
+    init {
+        Timber.d("ApiInterceptor.init")
+    }
+
+    private val apiResponseListenersMap = mutableMapOf<ApiPath, MutableList<ApiInterceptorListener>>()
+
+    override fun intercept(chain: Interceptor.Chain): Response {
+        val request = chain.request()
+        val path = request.url.encodedPath.replaceFirst("/", "")
+        val method = request.method
+
+        val response = chain.proceed(request)
+
+        synchronized(apiResponseListenersMap) {
+            findApiPath(path, method)?.let { apiPath ->
+                response.peekBody(Long.MAX_VALUE).string().let { networkResponse ->
+                    apiResponseListenersMap[apiPath]?.forEach { listener ->
+                        tryOrNull("Error in the implementation") {
+                            listener.onApiResponse(apiPath, networkResponse)
+                        }
+                    }
+                }
+            }
+        }
+
+        return response
+    }
+
+    private fun findApiPath(path: String, method: String): ApiPath? {
+        return apiResponseListenersMap
+                .keys
+                .find { apiPath ->
+                    apiPath.method === method && isTheSamePath(apiPath.path, path)
+                }
+    }
+
+    private fun isTheSamePath(pattern: String, path: String): Boolean {
+        val patternSegments = pattern.split("/")
+        val pathSegments = path.split("/")
+
+        if (patternSegments.size != pathSegments.size) return false
+
+        return patternSegments.indices.all { i ->
+            patternSegments[i] == pathSegments[i] || patternSegments[i].startsWith("{")
+        }
+    }
+
+    /**
+     * Adds listener to send intercepted api responses through.
+     */
+    fun addListener(path: ApiPath, listener: ApiInterceptorListener) {
+        synchronized(apiResponseListenersMap) {
+            apiResponseListenersMap.getOrPut(path) { mutableListOf() }
+                    .add(listener)
+        }
+    }
+
+    /**
+     * Remove listener to send intercepted api responses through.
+     */
+    fun removeListener(path: ApiPath, listener: ApiInterceptorListener) {
+        synchronized(apiResponseListenersMap) {
+            apiResponseListenersMap[path]?.remove(listener)
+            if (apiResponseListenersMap[path]?.isEmpty() == true) {
+                apiResponseListenersMap.remove(path)
+            }
+        }
+    }
+}
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 442029127d3202fcc5e35761ba1e234b00668f3b..0246bae024eed8933e5f2224939ad9655d652a1e 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
@@ -19,38 +19,49 @@ package org.matrix.android.sdk.internal.network
 import kotlinx.coroutines.CancellationException
 import kotlinx.coroutines.delay
 import org.matrix.android.sdk.api.failure.Failure
+import org.matrix.android.sdk.api.failure.MatrixError
+import org.matrix.android.sdk.api.failure.getRetryDelay
 import org.matrix.android.sdk.api.failure.shouldBeRetried
 import org.matrix.android.sdk.internal.network.ssl.CertUtil
-import retrofit2.Call
-import retrofit2.awaitResponse
+import retrofit2.HttpException
 import timber.log.Timber
 import java.io.IOException
 
-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 globalErrorReceiver: GlobalErrorReceiver?) {
+/**
+ * Execute a request from the requestBlock and handle some of the Exception it could generate
+ * Ref: https://github.com/matrix-org/matrix-js-sdk/blob/develop/src/scheduler.js#L138-L175
+ *
+ * @param globalErrorReceiver will be use to notify error such as invalid token error. See [GlobalError]
+ * @param canRetry if set to true, the request will be executed again in case of error, after a delay
+ * @param maxDelayBeforeRetry the max delay to wait before a retry
+ * @param maxRetriesCount the max number of retries
+ * @param requestBlock a suspend lambda to perform the network request
+ */
+internal suspend inline fun <DATA> executeRequest(globalErrorReceiver: GlobalErrorReceiver?,
+                                                  canRetry: Boolean = false,
+                                                  maxDelayBeforeRetry: Long = 32_000L,
+                                                  maxRetriesCount: Int = 4,
+                                                  noinline requestBlock: suspend () -> DATA): DATA {
+    var currentRetryCount = 0
+    var currentDelay = 1_000L
 
-    var isRetryable = false
-    var initialDelay: Long = 100L
-    var maxDelay: Long = 10_000L
-    var maxRetryCount = Int.MAX_VALUE
-    private var currentRetryCount = 0
-    private var currentDelay = initialDelay
-    lateinit var apiCall: Call<DATA>
+    while (true) {
+        try {
+            return requestBlock()
+        } catch (throwable: Throwable) {
+            val exception = when (throwable) {
+                is KotlinNullPointerException -> IllegalStateException("The request returned a null body")
+                is HttpException              -> throwable.toFailure(globalErrorReceiver)
+                else                          -> throwable
+            }
 
-    suspend fun execute(): DATA {
-        return try {
-            val response = apiCall.clone().awaitResponse()
-            if (response.isSuccessful) {
-                response.body()
-                        ?: throw IllegalStateException("The request returned a null body")
+            // Log some details about the request which has failed.
+            val request = (throwable as? HttpException)?.response()?.raw()?.request
+            if (request == null) {
+                Timber.e("Exception when executing request")
             } else {
-                throw response.toFailure(globalErrorReceiver)
+                Timber.e("Exception when executing request ${request.method} ${request.url.toString().substringBefore("?")}")
             }
-        } catch (exception: Throwable) {
-            // Log some details about the request which has failed
-            Timber.e("Exception when executing request ${apiCall.request().method} ${apiCall.request().url.toString().substringBefore("?")}")
 
             // Check if this is a certificateException
             CertUtil.getCertificateException(exception)
@@ -61,10 +72,18 @@ internal class Request<DATA : Any>(private val globalErrorReceiver: GlobalErrorR
                     // }
                     ?.also { unrecognizedCertificateException -> throw unrecognizedCertificateException }
 
-            if (isRetryable && currentRetryCount++ < maxRetryCount && exception.shouldBeRetried()) {
+            currentRetryCount++
+
+            if (exception is Failure.ServerError
+                    && exception.httpCode == 429
+                    && exception.error.code == MatrixError.M_LIMIT_EXCEEDED
+                    && currentRetryCount < maxRetriesCount) {
+                // 429, we can retry
+                delay(exception.getRetryDelay(1_000))
+            } else if (canRetry && currentRetryCount < maxRetriesCount && exception.shouldBeRetried()) {
                 delay(currentDelay)
-                currentDelay = (currentDelay * 2L).coerceAtMost(maxDelay)
-                return execute()
+                currentDelay = currentDelay.times(2L).coerceAtMost(maxDelayBeforeRetry)
+                // Try again (loop)
             } else {
                 throw when (exception) {
                     is IOException              -> Failure.NetworkConnection(exception)
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 dd5a69dd3c99aa7232ff256fd6ca470204639be9..7132b4ff7a8518fb034d543f646953629f2bc203 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,6 +25,7 @@ import org.matrix.android.sdk.api.failure.MatrixError
 import org.matrix.android.sdk.internal.di.MoshiProvider
 import kotlinx.coroutines.suspendCancellableCoroutine
 import okhttp3.ResponseBody
+import retrofit2.HttpException
 import retrofit2.Response
 import timber.log.Timber
 import java.io.IOException
@@ -57,6 +58,13 @@ internal fun <T> Response<T>.toFailure(globalErrorReceiver: GlobalErrorReceiver?
     return toFailure(errorBody(), code(), globalErrorReceiver)
 }
 
+/**
+ * Convert a HttpException to a Failure, and eventually parse errorBody to convert it to a MatrixError
+ */
+internal fun HttpException.toFailure(globalErrorReceiver: GlobalErrorReceiver?): Failure {
+    return toFailure(response()?.errorBody(), code(), globalErrorReceiver)
+}
+
 /**
  * Convert a okhttp3 Response to a Failure, and eventually parse errorBody to convert it to a MatrixError
  */
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/GetUrlTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/GetUrlTask.kt
index 16633d90ef27b9a56c6b8a9102f92f4de301d8b7..d0e2534e7a1bf4f4ea014c693f87c6b6cb95d0f0 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/GetUrlTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/GetUrlTask.kt
@@ -17,7 +17,6 @@
 package org.matrix.android.sdk.internal.raw
 
 import com.zhuinden.monarchy.Monarchy
-import okhttp3.ResponseBody
 import org.matrix.android.sdk.api.cache.CacheStrategy
 import org.matrix.android.sdk.internal.database.model.RawCacheEntity
 import org.matrix.android.sdk.internal.database.query.get
@@ -58,8 +57,8 @@ internal class DefaultGetUrlTask @Inject constructor(
     }
 
     private suspend fun doRequest(url: String): String {
-        return executeRequest<ResponseBody>(null) {
-            apiCall = rawAPI.getUrl(url)
+        return executeRequest(null) {
+            rawAPI.getUrl(url)
         }
                 .string()
     }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/RawAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/RawAPI.kt
index 4b08afd711a673df4fba82da6ddab844d506bf41..338d94781bb9bb751930e5f448acd4b6796b05bf 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/RawAPI.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/RawAPI.kt
@@ -18,11 +18,10 @@
 package org.matrix.android.sdk.internal.raw
 
 import okhttp3.ResponseBody
-import retrofit2.Call
 import retrofit2.http.GET
 import retrofit2.http.Url
 
 internal interface RawAPI {
     @GET
-    fun getUrl(@Url url: String): Call<ResponseBody>
+    suspend fun getUrl(@Url url: String): ResponseBody
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt
index 07cde3da609e0f3b11e68d4b90a8d89a0a5141a2..d05ee48c1bf41c33e520600100756af615e8ef3a 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt
@@ -20,26 +20,20 @@ import android.content.Context
 import android.net.Uri
 import android.webkit.MimeTypeMap
 import androidx.core.content.FileProvider
-import arrow.core.Try
-import kotlinx.coroutines.launch
+import kotlinx.coroutines.CompletableDeferred
+import kotlinx.coroutines.completeWith
 import kotlinx.coroutines.withContext
 import okhttp3.OkHttpClient
 import okhttp3.Request
-import org.matrix.android.sdk.api.MatrixCallback
-import org.matrix.android.sdk.api.extensions.tryOrNull
 import org.matrix.android.sdk.api.session.content.ContentUrlResolver
 import org.matrix.android.sdk.api.session.file.FileService
-import org.matrix.android.sdk.api.util.Cancelable
-import org.matrix.android.sdk.api.util.NoOpCancellable
 import org.matrix.android.sdk.internal.crypto.attachments.ElementToDecrypt
 import org.matrix.android.sdk.internal.crypto.attachments.MXEncryptedAttachments
 import org.matrix.android.sdk.internal.di.SessionDownloadsDirectory
 import org.matrix.android.sdk.internal.di.UnauthenticatedWithCertificateWithProgress
 import org.matrix.android.sdk.internal.session.download.DownloadProgressInterceptor.Companion.DOWNLOAD_PROGRESS_INTERCEPTOR_HEADER
-import org.matrix.android.sdk.internal.task.TaskExecutor
 import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers
 import org.matrix.android.sdk.internal.util.md5
-import org.matrix.android.sdk.internal.util.toCancelable
 import org.matrix.android.sdk.internal.util.writeToFile
 import timber.log.Timber
 import java.io.File
@@ -53,14 +47,15 @@ internal class DefaultFileService @Inject constructor(
         private val contentUrlResolver: ContentUrlResolver,
         @UnauthenticatedWithCertificateWithProgress
         private val okHttpClient: OkHttpClient,
-        private val coroutineDispatchers: MatrixCoroutineDispatchers,
-        private val taskExecutor: TaskExecutor
+        private val coroutineDispatchers: MatrixCoroutineDispatchers
 ) : FileService {
 
     // Legacy folder, will be deleted
     private val legacyFolder = File(sessionCacheDirectory, "MF")
+
     // Folder to store downloaded files (not decrypted)
     private val downloadFolder = File(sessionCacheDirectory, "F")
+
     // Folder to store decrypted files
     private val decryptedFolder = File(downloadFolder, "D")
 
@@ -73,134 +68,113 @@ internal class DefaultFileService @Inject constructor(
      * Retain ongoing downloads to avoid re-downloading and already downloading file
      * map of mxCurl to callbacks
      */
-    private val ongoing = mutableMapOf<String, ArrayList<MatrixCallback<File>>>()
+    private val ongoing = mutableMapOf<String, CompletableDeferred<File>>()
 
     /**
      * Download file in the cache folder, and eventually decrypt it
      * TODO looks like files are copied 3 times
      */
-    override fun downloadFile(fileName: String,
-                              mimeType: String?,
-                              url: String?,
-                              elementToDecrypt: ElementToDecrypt?,
-                              callback: MatrixCallback<File>): Cancelable {
-        url ?: return NoOpCancellable.also {
-            callback.onFailure(IllegalArgumentException("url is null"))
-        }
+    override suspend fun downloadFile(fileName: String,
+                                      mimeType: String?,
+                                      url: String?,
+                                      elementToDecrypt: ElementToDecrypt?): File {
+        url ?: throw IllegalArgumentException("url is null")
 
         Timber.v("## FileService downloadFile $url")
 
-        synchronized(ongoing) {
+        // TODO: Remove use of `synchronized` in suspend function.
+        val existingDownload = synchronized(ongoing) {
             val existing = ongoing[url]
             if (existing != null) {
                 Timber.v("## FileService downloadFile is already downloading.. ")
-                existing.add(callback)
-                return NoOpCancellable
+                existing
             } else {
                 // mark as tracked
-                ongoing[url] = ArrayList()
+                ongoing[url] = CompletableDeferred()
                 // and proceed to download
+                null
             }
         }
 
-        return taskExecutor.executorScope.launch(coroutineDispatchers.main) {
-            withContext(coroutineDispatchers.io) {
-                Try {
-                    if (!decryptedFolder.exists()) {
-                        decryptedFolder.mkdirs()
-                    }
-                    // ensure we use unique file name by using URL (mapped to suitable file name)
-                    // Also we need to add extension for the FileProvider, if not it lot's of app that it's
-                    // shared with will not function well (even if mime type is passed in the intent)
-                    getFiles(url, fileName, mimeType, elementToDecrypt != null)
-                }.flatMap { cachedFiles ->
-                    if (!cachedFiles.file.exists()) {
-                        val resolvedUrl = contentUrlResolver.resolveFullSize(url) ?: return@flatMap Try.Failure(IllegalArgumentException("url is null"))
-
-                        val request = Request.Builder()
-                                .url(resolvedUrl)
-                                .header(DOWNLOAD_PROGRESS_INTERCEPTOR_HEADER, url)
-                                .build()
-
-                        val response = try {
-                            okHttpClient.newCall(request).execute()
-                        } catch (e: Throwable) {
-                            return@flatMap Try.Failure(e)
-                        }
+        if (existingDownload != null) {
+            // FIXME If the first downloader cancels then we'll unfortunately be cancelled too.
+            return existingDownload.await()
+        }
 
-                        if (!response.isSuccessful) {
-                            return@flatMap Try.Failure(IOException())
-                        }
+        val result = runCatching {
+            val cachedFiles = withContext(coroutineDispatchers.io) {
+                if (!decryptedFolder.exists()) {
+                    decryptedFolder.mkdirs()
+                }
 
-                        val source = response.body?.source()
-                                ?: return@flatMap Try.Failure(IOException())
+                // ensure we use unique file name by using URL (mapped to suitable file name)
+                // Also we need to add extension for the FileProvider, if not it lot's of app that it's
+                // shared with will not function well (even if mime type is passed in the intent)
+                val cachedFiles = getFiles(url, fileName, mimeType, elementToDecrypt != null)
 
-                        Timber.v("Response size ${response.body?.contentLength()} - Stream available: ${!source.exhausted()}")
+                if (!cachedFiles.file.exists()) {
+                    val resolvedUrl = contentUrlResolver.resolveFullSize(url) ?: throw IllegalArgumentException("url is null")
 
-                        // Write the file to cache (encrypted version if the file is encrypted)
-                        writeToFile(source.inputStream(), cachedFiles.file)
-                        response.close()
-                    } else {
-                        Timber.v("## FileService: cache hit for $url")
+                    val request = Request.Builder()
+                            .url(resolvedUrl)
+                            .header(DOWNLOAD_PROGRESS_INTERCEPTOR_HEADER, url)
+                            .build()
+
+                    val response = okHttpClient.newCall(request).execute()
+
+                    if (!response.isSuccessful) {
+                        throw IOException()
                     }
 
-                    Try.just(cachedFiles)
+                    val source = response.body?.source() ?: throw IOException()
+
+                    Timber.v("Response size ${response.body?.contentLength()} - Stream available: ${!source.exhausted()}")
+
+                    // Write the file to cache (encrypted version if the file is encrypted)
+                    writeToFile(source.inputStream(), cachedFiles.file)
+                    response.close()
+                } else {
+                    Timber.v("## FileService: cache hit for $url")
                 }
-            }.flatMap { cachedFiles ->
-                // Decrypt if necessary
-                if (cachedFiles.decryptedFile != null) {
-                    if (!cachedFiles.decryptedFile.exists()) {
-                        Timber.v("## FileService: decrypt file")
-                        // Ensure the parent folder exists
-                        cachedFiles.decryptedFile.parentFile?.mkdirs()
-                        val decryptSuccess = cachedFiles.file.inputStream().use { inputStream ->
-                            cachedFiles.decryptedFile.outputStream().buffered().use { outputStream ->
-                                MXEncryptedAttachments.decryptAttachment(
-                                        inputStream,
-                                        elementToDecrypt,
-                                        outputStream
-                                )
-                            }
-                        }
-                        if (!decryptSuccess) {
-                            return@flatMap Try.Failure(IllegalStateException("Decryption error"))
+                cachedFiles
+            }
+
+            // Decrypt if necessary
+            if (cachedFiles.decryptedFile != null) {
+                if (!cachedFiles.decryptedFile.exists()) {
+                    Timber.v("## FileService: decrypt file")
+                    // Ensure the parent folder exists
+                    cachedFiles.decryptedFile.parentFile?.mkdirs()
+                    val decryptSuccess = cachedFiles.file.inputStream().use { inputStream ->
+                        cachedFiles.decryptedFile.outputStream().buffered().use { outputStream ->
+                            MXEncryptedAttachments.decryptAttachment(
+                                    inputStream,
+                                    elementToDecrypt,
+                                    outputStream
+                            )
                         }
-                    } else {
-                        Timber.v("## FileService: cache hit for decrypted file")
                     }
-                    Try.just(cachedFiles.decryptedFile)
+                    if (!decryptSuccess) {
+                        throw IllegalStateException("Decryption error")
+                    }
                 } else {
-                    // Clear file
-                    Try.just(cachedFiles.file)
+                    Timber.v("## FileService: cache hit for decrypted file")
                 }
-            }.fold(
-                    { throwable ->
-                        callback.onFailure(throwable)
-                        // notify concurrent requests
-                        val toNotify = synchronized(ongoing) {
-                            ongoing[url]?.also {
-                                ongoing.remove(url)
-                            }
-                        }
-                        toNotify?.forEach { otherCallbacks ->
-                            tryOrNull { otherCallbacks.onFailure(throwable) }
-                        }
-                    },
-                    { file ->
-                        callback.onSuccess(file)
-                        // notify concurrent requests
-                        val toNotify = synchronized(ongoing) {
-                            ongoing[url]?.also {
-                                ongoing.remove(url)
-                            }
-                        }
-                        Timber.v("## FileService additional to notify ${toNotify?.size ?: 0} ")
-                        toNotify?.forEach { otherCallbacks ->
-                            tryOrNull { otherCallbacks.onSuccess(file) }
-                        }
-                    }
-            )
-        }.toCancelable()
+                cachedFiles.decryptedFile
+            } else {
+                // Clear file
+                cachedFiles.file
+            }
+        }
+
+        // notify concurrent requests
+        val toNotify = synchronized(ongoing) { ongoing.remove(url) }
+        result.onSuccess {
+            Timber.v("## FileService additional to notify is > 0 ")
+        }
+        toNotify?.completeWith(result)
+
+        return result.getOrThrow()
     }
 
     fun storeDataFor(mxcUrl: String,
@@ -325,6 +299,7 @@ internal class DefaultFileService @Inject constructor(
 
     companion object {
         private const val ENCRYPTED_FILENAME = "encrypted.bin"
+
         // The extension would be added from the mimetype
         private const val DEFAULT_FILENAME = "file"
     }
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 45fcc5af2d90042a7e3fd21f1924f1bfc99f00b0..821a9cba8c58f8b24493de43c5a10232b6094e53 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
@@ -33,6 +33,7 @@ import org.matrix.android.sdk.api.session.call.CallSignalingService
 import org.matrix.android.sdk.api.session.content.ContentUploadStateTracker
 import org.matrix.android.sdk.api.session.content.ContentUrlResolver
 import org.matrix.android.sdk.api.session.crypto.CryptoService
+import org.matrix.android.sdk.api.session.events.EventService
 import org.matrix.android.sdk.api.session.file.ContentDownloadStateTracker
 import org.matrix.android.sdk.api.session.file.FileService
 import org.matrix.android.sdk.api.session.group.GroupService
@@ -114,6 +115,7 @@ internal class DefaultSession @Inject constructor(
         private val accountDataService: Lazy<AccountDataService>,
         private val _sharedSecretStorageService: Lazy<SharedSecretStorageService>,
         private val accountService: Lazy<AccountService>,
+        private val eventService: Lazy<EventService>,
         private val defaultIdentityService: DefaultIdentityService,
         private val integrationManagerService: IntegrationManagerService,
         private val thirdPartyService: Lazy<ThirdPartyService>,
@@ -129,6 +131,7 @@ internal class DefaultSession @Inject constructor(
         FilterService by filterService.get(),
         PushRuleService by pushRuleService.get(),
         PushersService by pushersService.get(),
+        EventService by eventService.get(),
         TermsService by termsService.get(),
         InitialSyncProgressService by initialSyncProgressService.get(),
         SecureStorageService by secureStorageService.get(),
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 f10eb67921d03d1c4398ed5f5bb250518d8955ab..e61e4ecd893270b597253975efa76401423d1c26 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
@@ -32,10 +32,11 @@ import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
 import org.matrix.android.sdk.api.auth.data.SessionParams
 import org.matrix.android.sdk.api.auth.data.sessionId
 import org.matrix.android.sdk.api.crypto.MXCryptoConfig
-import org.matrix.android.sdk.api.session.initsync.InitialSyncProgressService
 import org.matrix.android.sdk.api.session.Session
 import org.matrix.android.sdk.api.session.accountdata.AccountDataService
+import org.matrix.android.sdk.api.session.events.EventService
 import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilitiesService
+import org.matrix.android.sdk.api.session.initsync.InitialSyncProgressService
 import org.matrix.android.sdk.api.session.permalinks.PermalinkService
 import org.matrix.android.sdk.api.session.securestorage.SecureStorageService
 import org.matrix.android.sdk.api.session.securestorage.SharedSecretStorageService
@@ -75,6 +76,7 @@ import org.matrix.android.sdk.internal.network.token.AccessTokenProvider
 import org.matrix.android.sdk.internal.network.token.HomeserverAccessTokenProvider
 import org.matrix.android.sdk.internal.session.call.CallEventProcessor
 import org.matrix.android.sdk.internal.session.download.DownloadProgressInterceptor
+import org.matrix.android.sdk.internal.session.events.DefaultEventService
 import org.matrix.android.sdk.internal.session.homeserver.DefaultHomeServerCapabilitiesService
 import org.matrix.android.sdk.internal.session.identity.DefaultIdentityService
 import org.matrix.android.sdk.internal.session.initsync.DefaultInitialSyncProgressService
@@ -357,6 +359,9 @@ internal abstract class SessionModule {
     @Binds
     abstract fun bindAccountDataService(service: DefaultAccountDataService): AccountDataService
 
+    @Binds
+    abstract fun bindEventService(service: DefaultEventService): EventService
+
     @Binds
     abstract fun bindSharedSecretStorageService(service: DefaultSharedSecretStorageService): SharedSecretStorageService
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/account/AccountAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/account/AccountAPI.kt
index 1db9d121a6077a8634cf70b597f9fe31a10d06f7..a04d0f2686f935de33c0f4f95ded495bbecc98e3 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/account/AccountAPI.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/account/AccountAPI.kt
@@ -17,7 +17,6 @@
 package org.matrix.android.sdk.internal.session.account
 
 import org.matrix.android.sdk.internal.network.NetworkConstants
-import retrofit2.Call
 import retrofit2.http.Body
 import retrofit2.http.POST
 
@@ -28,7 +27,7 @@ internal interface AccountAPI {
      * @param params parameters to change password.
      */
     @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "account/password")
-    fun changePassword(@Body params: ChangePasswordParams): Call<Unit>
+    suspend fun changePassword(@Body params: ChangePasswordParams)
 
     /**
      * Deactivate the user account
@@ -36,5 +35,5 @@ internal interface AccountAPI {
      * @param params the deactivate account params
      */
     @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "account/deactivate")
-    fun deactivate(@Body params: DeactivateAccountParams): Call<Unit>
+    suspend fun deactivate(@Body params: DeactivateAccountParams)
 }
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 1f043b0a9de61e1c9505a05c6f7804428fbb8116..02c3735998815b3e4c24015874fad8b47ad3c391 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
@@ -39,8 +39,8 @@ internal class DefaultChangePasswordTask @Inject constructor(
     override suspend fun execute(params: ChangePasswordTask.Params) {
         val changePasswordParams = ChangePasswordParams.create(userId, params.password, params.newPassword)
         try {
-            executeRequest<Unit>(globalErrorReceiver) {
-                apiCall = accountAPI.changePassword(changePasswordParams)
+            executeRequest(globalErrorReceiver) {
+                accountAPI.changePassword(changePasswordParams)
             }
         } catch (throwable: Throwable) {
             val registrationFlowResponse = throwable.toRegistrationFlowResponse()
@@ -49,8 +49,8 @@ internal class DefaultChangePasswordTask @Inject constructor(
                     /* Avoid infinite loop */
                     && changePasswordParams.auth?.session == null) {
                 // Retry with authentication
-                executeRequest<Unit>(globalErrorReceiver) {
-                    apiCall = accountAPI.changePassword(
+                executeRequest(globalErrorReceiver) {
+                    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/DeactivateAccountTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/account/DeactivateAccountTask.kt
index ca6b0554a94c2d7d10329030d71f315c2341ea59..1a8e80ab683dcb875cabc216f76548bcdca21475 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
@@ -46,8 +46,8 @@ internal class DefaultDeactivateAccountTask @Inject constructor(
         val deactivateAccountParams = DeactivateAccountParams.create(params.userAuthParam, params.eraseAllData)
 
         val canCleanup = try {
-            executeRequest<Unit>(globalErrorReceiver) {
-                apiCall = accountAPI.deactivate(deactivateAccountParams)
+            executeRequest(globalErrorReceiver) {
+                accountAPI.deactivate(deactivateAccountParams)
             }
             true
         } catch (throwable: Throwable) {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallEventProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallEventProcessor.kt
index 4887351709ffde85aa2ccdb66e03cca66de8ce1a..a190ff62acebd4687b8e2b47dd5bff3d57215d5c 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallEventProcessor.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallEventProcessor.kt
@@ -21,9 +21,11 @@ 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.internal.database.model.EventInsertType
 import org.matrix.android.sdk.internal.session.EventInsertLiveProcessor
+import org.matrix.android.sdk.internal.session.SessionScope
 import timber.log.Timber
 import javax.inject.Inject
 
+@SessionScope
 internal class CallEventProcessor @Inject constructor(private val callSignalingHandler: CallSignalingHandler)
     : EventInsertLiveProcessor {
 
@@ -51,6 +53,15 @@ internal class CallEventProcessor @Inject constructor(private val callSignalingH
         eventsToPostProcess.add(event)
     }
 
+    fun shouldProcessFastLane(eventType: String): Boolean {
+        return eventType == EventType.CALL_INVITE
+    }
+
+    suspend fun processFastLane(event: Event) {
+        eventsToPostProcess.add(event)
+        onPostProcess()
+    }
+
     override suspend fun onPostProcess() {
         eventsToPostProcess.forEach {
             dispatchToCallSignalingHandlerIfNeeded(it)
@@ -60,7 +71,7 @@ internal class CallEventProcessor @Inject constructor(private val callSignalingH
 
     private fun dispatchToCallSignalingHandlerIfNeeded(event: Event) {
         val now = System.currentTimeMillis()
-        // TODO might check if an invite is not closed (hangup/answsered) in the same event batch?
+        // TODO might check if an invite is not closed (hangup/answered) in the same event batch?
         event.roomId ?: return Unit.also {
             Timber.w("Event with no room id ${event.eventId}")
         }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallSignalingHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallSignalingHandler.kt
index 7e54301f63e7d27522e52bd1532bfc7a99d0b8d0..8d7e9e819a7bc7a95590f85622108c8797e3fc84 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallSignalingHandler.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallSignalingHandler.kt
@@ -56,25 +56,25 @@ internal class CallSignalingHandler @Inject constructor(private val activeCallHa
 
     fun onCallEvent(event: Event) {
         when (event.getClearType()) {
-            EventType.CALL_ANSWER -> {
+            EventType.CALL_ANSWER        -> {
                 handleCallAnswerEvent(event)
             }
-            EventType.CALL_INVITE -> {
+            EventType.CALL_INVITE        -> {
                 handleCallInviteEvent(event)
             }
-            EventType.CALL_HANGUP -> {
+            EventType.CALL_HANGUP        -> {
                 handleCallHangupEvent(event)
             }
-            EventType.CALL_REJECT -> {
+            EventType.CALL_REJECT        -> {
                 handleCallRejectEvent(event)
             }
-            EventType.CALL_CANDIDATES -> {
+            EventType.CALL_CANDIDATES    -> {
                 handleCallCandidatesEvent(event)
             }
             EventType.CALL_SELECT_ANSWER -> {
                 handleCallSelectAnswerEvent(event)
             }
-            EventType.CALL_NEGOTIATE -> {
+            EventType.CALL_NEGOTIATE     -> {
                 handleCallNegotiateEvent(event)
             }
         }
@@ -168,6 +168,14 @@ internal class CallSignalingHandler @Inject constructor(private val activeCallHa
             return
         }
         val content = event.getClearContent().toModel<CallInviteContent>() ?: return
+
+        content.callId ?: return
+        if (activeCallHandler.getCallWithId(content.callId) != null) {
+            // Call is already known, maybe due to fast lane. Ignore
+            Timber.d("Ignoring already known call invite")
+            return
+        }
+
         val incomingCall = mxCallFactory.createIncomingCall(
                 roomId = event.roomId,
                 opponentUserId = event.senderId,
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 b21ec1113aafd71299806b60983629b4a2f0437f..d53ddb737116385dde615ad9a342bfc5345ad8a0 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
@@ -31,7 +31,7 @@ internal class DefaultGetTurnServerTask @Inject constructor(private val voipAPI:
 
     override suspend fun execute(params: Params): TurnServerResponse {
         return executeRequest(globalErrorReceiver) {
-            apiCall = voipAPI.getTurnServer()
+            voipAPI.getTurnServer()
         }
     }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/VoipApi.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/VoipApi.kt
index 72c6c58f2774147722098446d97eb3438d6991b7..469faaae745770b56037cb32fd947bfbf66a8a72 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/VoipApi.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/VoipApi.kt
@@ -18,11 +18,10 @@ package org.matrix.android.sdk.internal.session.call
 
 import org.matrix.android.sdk.api.session.call.TurnServerResponse
 import org.matrix.android.sdk.internal.network.NetworkConstants
-import retrofit2.Call
 import retrofit2.http.GET
 
 internal interface VoipApi {
 
     @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "voip/turnServer")
-    fun getTurnServer(): Call<TurnServerResponse>
+    suspend fun getTurnServer(): TurnServerResponse
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/directory/DirectoryAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/directory/DirectoryAPI.kt
index 6a50f3ee37fb5b132295f41674d3578e1dd6f631..19bc7e1908bb3ddb2974d3957cc0fdabd09605e3 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/directory/DirectoryAPI.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/directory/DirectoryAPI.kt
@@ -19,7 +19,6 @@ package org.matrix.android.sdk.internal.session.directory
 import org.matrix.android.sdk.internal.network.NetworkConstants
 import org.matrix.android.sdk.internal.session.room.alias.AddRoomAliasBody
 import org.matrix.android.sdk.internal.session.room.alias.RoomAliasDescription
-import retrofit2.Call
 import retrofit2.http.Body
 import retrofit2.http.DELETE
 import retrofit2.http.GET
@@ -33,7 +32,7 @@ internal interface DirectoryAPI {
      * @param roomAlias the room alias.
      */
     @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "directory/room/{roomAlias}")
-    fun getRoomIdByAlias(@Path("roomAlias") roomAlias: String): Call<RoomAliasDescription>
+    suspend fun getRoomIdByAlias(@Path("roomAlias") roomAlias: String): RoomAliasDescription
 
     /**
      * Get the room directory visibility.
@@ -41,7 +40,7 @@ internal interface DirectoryAPI {
      * @param roomId the room id.
      */
     @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "directory/list/room/{roomId}")
-    fun getRoomDirectoryVisibility(@Path("roomId") roomId: String): Call<RoomDirectoryVisibilityJson>
+    suspend fun getRoomDirectoryVisibility(@Path("roomId") roomId: String): RoomDirectoryVisibilityJson
 
     /**
      * Set the room directory visibility.
@@ -50,21 +49,21 @@ internal interface DirectoryAPI {
      * @param body the body containing the new directory visibility
      */
     @PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "directory/list/room/{roomId}")
-    fun setRoomDirectoryVisibility(@Path("roomId") roomId: String,
-                                   @Body body: RoomDirectoryVisibilityJson): Call<Unit>
+    suspend fun setRoomDirectoryVisibility(@Path("roomId") roomId: String,
+                                           @Body body: RoomDirectoryVisibilityJson)
 
     /**
      * Add alias to the room.
      * @param roomAlias the room alias.
      */
     @PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "directory/room/{roomAlias}")
-    fun addRoomAlias(@Path("roomAlias") roomAlias: String,
-                     @Body body: AddRoomAliasBody): Call<Unit>
+    suspend fun addRoomAlias(@Path("roomAlias") roomAlias: String,
+                             @Body body: AddRoomAliasBody)
 
     /**
      * Delete a room alias
      * @param roomAlias the room alias.
      */
     @DELETE(NetworkConstants.URI_API_PREFIX_PATH_R0 + "directory/room/{roomAlias}")
-    fun deleteRoomAlias(@Path("roomAlias") roomAlias: String): Call<Unit>
+    suspend fun deleteRoomAlias(@Path("roomAlias") roomAlias: String)
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/events/DefaultEventService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/events/DefaultEventService.kt
new file mode 100644
index 0000000000000000000000000000000000000000..d7e9ef2ee04b1801ee883e3b987a055a2484e988
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/events/DefaultEventService.kt
@@ -0,0 +1,40 @@
+/*
+ * 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.events
+
+import org.matrix.android.sdk.api.session.events.EventService
+import org.matrix.android.sdk.api.session.events.model.Event
+import org.matrix.android.sdk.internal.session.call.CallEventProcessor
+import org.matrix.android.sdk.internal.session.room.timeline.GetEventTask
+import javax.inject.Inject
+
+internal class DefaultEventService @Inject constructor(
+        private val getEventTask: GetEventTask,
+        private val callEventProcessor: CallEventProcessor
+) : EventService {
+
+    override suspend fun getEvent(roomId: String, eventId: String): Event {
+        val event = getEventTask.execute(GetEventTask.Params(roomId, eventId))
+
+        // Fast lane to the call event processors: try to make the incoming call ring faster
+        if (callEventProcessor.shouldProcessFastLane(event.getClearType())) {
+            callEventProcessor.processFastLane(event)
+        }
+
+        return event
+    }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/events/EventExt.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/events/EventExt.kt
new file mode 100644
index 0000000000000000000000000000000000000000..91e709e464eb8dbb08e2a573850940c54944b663
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/events/EventExt.kt
@@ -0,0 +1,35 @@
+/*
+ * 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.events
+
+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.RoomMemberContent
+
+internal fun Event.getFixedRoomMemberContent(): RoomMemberContent? {
+    val content = content.toModel<RoomMemberContent>()
+    // if user is leaving, we should grab his last name and avatar from prevContent
+    return if (content?.membership?.isLeft() == true) {
+        val prevContent = resolvedPrevContent().toModel<RoomMemberContent>()
+        content.copy(
+                displayName = prevContent?.displayName,
+                avatarUrl = prevContent?.avatarUrl
+        )
+    } else {
+        content
+    }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/FilterApi.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/FilterApi.kt
index 285bd51d38766709ab0db4629a15417a2698ba2c..2809dea23b71b83dd2b584c9359cdc636450b068 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/FilterApi.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/FilterApi.kt
@@ -17,7 +17,6 @@
 package org.matrix.android.sdk.internal.session.filter
 
 import org.matrix.android.sdk.internal.network.NetworkConstants
-import retrofit2.Call
 import retrofit2.http.Body
 import retrofit2.http.GET
 import retrofit2.http.POST
@@ -32,8 +31,8 @@ internal interface FilterApi {
      * @param body   the Json representation of a FilterBody object
      */
     @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "user/{userId}/filter")
-    fun uploadFilter(@Path("userId") userId: String,
-                     @Body body: Filter): Call<FilterResponse>
+    suspend fun uploadFilter(@Path("userId") userId: String,
+                             @Body body: Filter): FilterResponse
 
     /**
      * Gets a filter with a given filterId from the homeserver
@@ -43,6 +42,6 @@ internal interface FilterApi {
      * @return Filter
      */
     @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "user/{userId}/filter/{filterId}")
-    fun getFilterById(@Path("userId") userId: String,
-                      @Path("filterId") filterId: String): Call<Filter>
+    suspend fun getFilterById(@Path("userId") userId: String,
+                              @Path("filterId") filterId: String): Filter
 }
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 d42962d54ace83a4874b7cc3707fec818b3eb7c5..3cac89ce285551228e8d7d8998a2e08449f8814b 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
@@ -59,9 +59,9 @@ internal class DefaultSaveFilterTask @Inject constructor(
         }
         val updated = filterRepository.storeFilter(filterBody, roomFilter)
         if (updated) {
-            val filterResponse = executeRequest<FilterResponse>(globalErrorReceiver) {
+            val filterResponse = executeRequest(globalErrorReceiver) {
                 // TODO auto retry
-                apiCall = filterAPI.uploadFilter(userId, filterBody)
+                filterAPI.uploadFilter(userId, filterBody)
             }
             filterRepository.storeFilterId(filterBody, filterResponse.filterId)
         }
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 9836164aecaefd84d9e5bba9c18e883846abaa25..4e0ee3422b5e44721de045f68b62fad69c8f5266 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
@@ -64,14 +64,14 @@ internal class DefaultGetGroupDataTask @Inject constructor(
         }
         Timber.v("Fetch data for group with ids: ${groupIds.joinToString(";")}")
         val data = groupIds.map { groupId ->
-            val groupSummary = executeRequest<GroupSummaryResponse>(globalErrorReceiver) {
-                apiCall = groupAPI.getSummary(groupId)
+            val groupSummary = executeRequest(globalErrorReceiver) {
+                groupAPI.getSummary(groupId)
             }
-            val groupRooms = executeRequest<GroupRooms>(globalErrorReceiver) {
-                apiCall = groupAPI.getRooms(groupId)
+            val groupRooms = executeRequest(globalErrorReceiver) {
+                groupAPI.getRooms(groupId)
             }
-            val groupUsers = executeRequest<GroupUsers>(globalErrorReceiver) {
-                apiCall = groupAPI.getUsers(groupId)
+            val groupUsers = executeRequest(globalErrorReceiver) {
+                groupAPI.getUsers(groupId)
             }
             GroupData(groupId, groupSummary, groupRooms, groupUsers)
         }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/group/GroupAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/group/GroupAPI.kt
index 004112578c9a4591b2017a4db994a5756e646b41..58dcc57dd6ab24e901e58097773890d56ff52467 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/group/GroupAPI.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/group/GroupAPI.kt
@@ -20,7 +20,6 @@ import org.matrix.android.sdk.internal.network.NetworkConstants
 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 retrofit2.Call
 import retrofit2.http.GET
 import retrofit2.http.Path
 
@@ -32,7 +31,7 @@ internal interface GroupAPI {
      * @param groupId the group id
      */
     @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "groups/{groupId}/summary")
-    fun getSummary(@Path("groupId") groupId: String): Call<GroupSummaryResponse>
+    suspend fun getSummary(@Path("groupId") groupId: String): GroupSummaryResponse
 
     /**
      * Request the rooms list.
@@ -40,7 +39,7 @@ internal interface GroupAPI {
      * @param groupId the group id
      */
     @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "groups/{groupId}/rooms")
-    fun getRooms(@Path("groupId") groupId: String): Call<GroupRooms>
+    suspend fun getRooms(@Path("groupId") groupId: String): GroupRooms
 
     /**
      * Request the users list.
@@ -48,5 +47,5 @@ internal interface GroupAPI {
      * @param groupId the group id
      */
     @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "groups/{groupId}/users")
-    fun getUsers(@Path("groupId") groupId: String): Call<GroupUsers>
+    suspend fun getUsers(@Path("groupId") groupId: String): GroupUsers
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/CapabilitiesAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/CapabilitiesAPI.kt
index 8242edac84af7c84fb6193d2b99c6191c52ff0c3..7de0cc95928c784b4466b7a0fc371b425486b569 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/CapabilitiesAPI.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/CapabilitiesAPI.kt
@@ -18,7 +18,6 @@ package org.matrix.android.sdk.internal.session.homeserver
 
 import org.matrix.android.sdk.internal.auth.version.Versions
 import org.matrix.android.sdk.internal.network.NetworkConstants
-import retrofit2.Call
 import retrofit2.http.GET
 
 internal interface CapabilitiesAPI {
@@ -26,17 +25,17 @@ internal interface CapabilitiesAPI {
      * Request the homeserver capabilities
      */
     @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "capabilities")
-    fun getCapabilities(): Call<GetCapabilitiesResult>
+    suspend fun getCapabilities(): GetCapabilitiesResult
 
     /**
      * Request the versions
      */
     @GET(NetworkConstants.URI_API_PREFIX_PATH_ + "versions")
-    fun getVersions(): Call<Versions>
+    suspend fun getVersions(): Versions
 
     /**
      * Ping the homeserver. We do not care about the returned data, so there is no use to parse them
      */
     @GET(NetworkConstants.URI_API_PREFIX_PATH_ + "versions")
-    fun ping(): Call<Unit>
+    suspend fun ping()
 }
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 84c9132d6136d7051b8ede3ce2904a9cd94d1cb1..740370123f877d92340e322b6a50c83eb4dd0803 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
@@ -71,20 +71,20 @@ internal class DefaultGetHomeServerCapabilitiesTask @Inject constructor(
         }
 
         val capabilities = runCatching {
-            executeRequest<GetCapabilitiesResult>(globalErrorReceiver) {
-                apiCall = capabilitiesAPI.getCapabilities()
+            executeRequest(globalErrorReceiver) {
+                capabilitiesAPI.getCapabilities()
             }
         }.getOrNull()
 
         val mediaConfig = runCatching {
-            executeRequest<GetMediaConfigResult>(globalErrorReceiver) {
-                apiCall = mediaAPI.getMediaConfig()
+            executeRequest(globalErrorReceiver) {
+                mediaAPI.getMediaConfig()
             }
         }.getOrNull()
 
         val versions = runCatching {
-            executeRequest<Versions>(null) {
-                apiCall = capabilitiesAPI.getVersions()
+            executeRequest(null) {
+                capabilitiesAPI.getVersions()
             }
         }.getOrNull()
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/HomeServerPinger.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/HomeServerPinger.kt
index 522097acbffa4ed4c1a3132e4f5370d2a40bce72..bb526adf4a88172e2320b5fa2498005db8c1e02b 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/HomeServerPinger.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/HomeServerPinger.kt
@@ -34,8 +34,8 @@ internal class HomeServerPinger @Inject constructor(private val taskExecutor: Ta
 
     suspend fun canReachHomeServer(): Boolean {
         return try {
-            executeRequest<Unit>(null) {
-                apiCall = capabilitiesAPI.ping()
+            executeRequest(null) {
+                capabilitiesAPI.ping()
             }
             true
         } catch (throwable: Throwable) {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/DefaultIdentityService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/DefaultIdentityService.kt
index 948e387cb18c47a607e2a11a77470a4e60dda6bb..f5391d6cdb113bcc3b3d1a244aabfca9d2eff3f4 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/DefaultIdentityService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/DefaultIdentityService.kt
@@ -20,7 +20,6 @@ import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.LifecycleOwner
 import androidx.lifecycle.LifecycleRegistry
 import dagger.Lazy
-import org.matrix.android.sdk.api.MatrixCallback
 import org.matrix.android.sdk.api.auth.data.SessionParams
 import org.matrix.android.sdk.api.extensions.tryOrNull
 import org.matrix.android.sdk.api.failure.Failure
@@ -33,8 +32,6 @@ import org.matrix.android.sdk.api.session.identity.IdentityServiceError
 import org.matrix.android.sdk.api.session.identity.IdentityServiceListener
 import org.matrix.android.sdk.api.session.identity.SharedState
 import org.matrix.android.sdk.api.session.identity.ThreePid
-import org.matrix.android.sdk.api.util.Cancelable
-import org.matrix.android.sdk.api.util.NoOpCancellable
 import org.matrix.android.sdk.internal.di.AuthenticatedIdentity
 import org.matrix.android.sdk.internal.di.UnauthenticatedWithCertificate
 import org.matrix.android.sdk.internal.extensions.observeNotNull
@@ -49,8 +46,6 @@ import org.matrix.android.sdk.internal.session.sync.model.accountdata.IdentitySe
 import org.matrix.android.sdk.api.session.accountdata.UserAccountDataTypes
 import org.matrix.android.sdk.internal.session.user.accountdata.AccountDataDataSource
 import org.matrix.android.sdk.internal.session.user.accountdata.UpdateUserAccountDataTask
-import org.matrix.android.sdk.internal.task.TaskExecutor
-import org.matrix.android.sdk.internal.task.launchToCallback
 import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers
 import org.matrix.android.sdk.internal.util.ensureProtocol
 import kotlinx.coroutines.withContext
@@ -83,8 +78,7 @@ internal class DefaultIdentityService @Inject constructor(
         private val identityApiProvider: IdentityApiProvider,
         private val accountDataDataSource: AccountDataDataSource,
         private val homeServerCapabilitiesService: HomeServerCapabilitiesService,
-        private val sessionParams: SessionParams,
-        private val taskExecutor: TaskExecutor
+        private val sessionParams: SessionParams
 ) : IdentityService, SessionLifecycleObserver {
 
     private val lifecycleOwner: LifecycleOwner = LifecycleOwner { lifecycleRegistry }
@@ -136,101 +130,81 @@ internal class DefaultIdentityService @Inject constructor(
         return identityStore.getIdentityData()?.identityServerUrl
     }
 
-    override fun startBindThreePid(threePid: ThreePid, callback: MatrixCallback<Unit>): Cancelable {
+    override suspend fun startBindThreePid(threePid: ThreePid) {
         if (homeServerCapabilitiesService.getHomeServerCapabilities().lastVersionIdentityServerSupported.not()) {
-            callback.onFailure(IdentityServiceError.OutdatedHomeServer)
-            return NoOpCancellable
+            throw IdentityServiceError.OutdatedHomeServer
         }
 
-        return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, callback) {
-            identityRequestTokenForBindingTask.execute(IdentityRequestTokenForBindingTask.Params(threePid, false))
-        }
+        identityRequestTokenForBindingTask.execute(IdentityRequestTokenForBindingTask.Params(threePid, false))
     }
 
-    override fun cancelBindThreePid(threePid: ThreePid, callback: MatrixCallback<Unit>): Cancelable {
-        return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, callback) {
-            identityStore.deletePendingBinding(threePid)
-        }
+    override suspend fun cancelBindThreePid(threePid: ThreePid) {
+        identityStore.deletePendingBinding(threePid)
     }
 
-    override fun sendAgainValidationCode(threePid: ThreePid, callback: MatrixCallback<Unit>): Cancelable {
-        return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, callback) {
-            identityRequestTokenForBindingTask.execute(IdentityRequestTokenForBindingTask.Params(threePid, true))
-        }
+    override suspend fun sendAgainValidationCode(threePid: ThreePid) {
+        identityRequestTokenForBindingTask.execute(IdentityRequestTokenForBindingTask.Params(threePid, true))
     }
 
-    override fun finalizeBindThreePid(threePid: ThreePid, callback: MatrixCallback<Unit>): Cancelable {
+    override suspend fun finalizeBindThreePid(threePid: ThreePid) {
         if (homeServerCapabilitiesService.getHomeServerCapabilities().lastVersionIdentityServerSupported.not()) {
-            callback.onFailure(IdentityServiceError.OutdatedHomeServer)
-            return NoOpCancellable
+            throw IdentityServiceError.OutdatedHomeServer
         }
 
-        return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, callback) {
-            bindThreePidsTask.execute(BindThreePidsTask.Params(threePid))
-        }
+        bindThreePidsTask.execute(BindThreePidsTask.Params(threePid))
     }
 
-    override fun submitValidationToken(threePid: ThreePid, code: String, callback: MatrixCallback<Unit>): Cancelable {
-        return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, callback) {
-            submitTokenForBindingTask.execute(IdentitySubmitTokenForBindingTask.Params(threePid, code))
-        }
+    override suspend fun submitValidationToken(threePid: ThreePid, code: String) {
+        submitTokenForBindingTask.execute(IdentitySubmitTokenForBindingTask.Params(threePid, code))
     }
 
-    override fun unbindThreePid(threePid: ThreePid, callback: MatrixCallback<Unit>): Cancelable {
+    override suspend fun unbindThreePid(threePid: ThreePid) {
         if (homeServerCapabilitiesService.getHomeServerCapabilities().lastVersionIdentityServerSupported.not()) {
-            callback.onFailure(IdentityServiceError.OutdatedHomeServer)
-            return NoOpCancellable
+            throw IdentityServiceError.OutdatedHomeServer
         }
 
-        return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, callback) {
-            unbindThreePidsTask.execute(UnbindThreePidsTask.Params(threePid))
-        }
+        unbindThreePidsTask.execute(UnbindThreePidsTask.Params(threePid))
     }
 
-    override fun isValidIdentityServer(url: String, callback: MatrixCallback<Unit>): Cancelable {
-        return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, callback) {
-            val api = retrofitFactory.create(unauthenticatedOkHttpClient, url).create(IdentityAuthAPI::class.java)
+    override suspend fun isValidIdentityServer(url: String) {
+        val api = retrofitFactory.create(unauthenticatedOkHttpClient, url).create(IdentityAuthAPI::class.java)
 
-            identityPingTask.execute(IdentityPingTask.Params(api))
-        }
+        identityPingTask.execute(IdentityPingTask.Params(api))
     }
 
-    override fun disconnect(callback: MatrixCallback<Unit>): Cancelable {
-        return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, callback) {
-            identityDisconnectTask.execute(Unit)
+    override suspend fun disconnect() {
+        identityDisconnectTask.execute(Unit)
 
-            identityStore.setUrl(null)
-            updateIdentityAPI(null)
-            updateAccountData(null)
-        }
+        identityStore.setUrl(null)
+        updateIdentityAPI(null)
+        updateAccountData(null)
     }
 
-    override fun setNewIdentityServer(url: String, callback: MatrixCallback<String>): Cancelable {
+    override suspend fun setNewIdentityServer(url: String): String {
         val urlCandidate = url.ensureProtocol()
 
-        return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, callback) {
-            val current = getCurrentIdentityServerUrl()
-            if (urlCandidate == current) {
-                // Nothing to do
-                Timber.d("Same URL, nothing to do")
-            } else {
-                // Disconnect previous one if any, first, because the token will change.
-                // In case of error when configuring the new identity server, this is not a big deal,
-                // we will ask for a new token on the previous Identity server
-                runCatching { identityDisconnectTask.execute(Unit) }
-                        .onFailure { Timber.w(it, "Unable to disconnect identity server") }
+        val current = getCurrentIdentityServerUrl()
+        if (urlCandidate == current) {
+            // Nothing to do
+            Timber.d("Same URL, nothing to do")
+        } else {
+            // Disconnect previous one if any, first, because the token will change.
+            // In case of error when configuring the new identity server, this is not a big deal,
+            // we will ask for a new token on the previous Identity server
+            runCatching { identityDisconnectTask.execute(Unit) }
+                    .onFailure { Timber.w(it, "Unable to disconnect identity server") }
 
-                // Try to get a token
-                val token = getNewIdentityServerToken(urlCandidate)
+            // Try to get a token
+            val token = getNewIdentityServerToken(urlCandidate)
 
-                identityStore.setUrl(urlCandidate)
-                identityStore.setToken(token)
-                updateIdentityAPI(urlCandidate)
+            identityStore.setUrl(urlCandidate)
+            identityStore.setToken(token)
+            updateIdentityAPI(urlCandidate)
 
-                updateAccountData(urlCandidate)
-            }
-            urlCandidate
+            updateAccountData(urlCandidate)
         }
+
+        return urlCandidate
     }
 
     private suspend fun updateAccountData(url: String?) {
@@ -252,45 +226,38 @@ internal class DefaultIdentityService @Inject constructor(
         identityStore.setUserConsent(newValue)
     }
 
-    override fun lookUp(threePids: List<ThreePid>, callback: MatrixCallback<List<FoundThreePid>>): Cancelable {
+    override suspend fun lookUp(threePids: List<ThreePid>): List<FoundThreePid> {
         if (!getUserConsent()) {
-            callback.onFailure(IdentityServiceError.UserConsentNotProvided)
-            return NoOpCancellable
+            throw IdentityServiceError.UserConsentNotProvided
         }
 
         if (threePids.isEmpty()) {
-            callback.onSuccess(emptyList())
-            return NoOpCancellable
+            return emptyList()
         }
 
-        return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, callback) {
-            lookUpInternal(true, threePids)
-        }
+        return lookUpInternal(true, threePids)
     }
 
-    override fun getShareStatus(threePids: List<ThreePid>, callback: MatrixCallback<Map<ThreePid, SharedState>>): Cancelable {
+    override suspend fun getShareStatus(threePids: List<ThreePid>): Map<ThreePid, SharedState> {
         // Note: we do not require user consent here, because it is used for emails and phone numbers that the user has already sent
         // to the home server, and not emails and phone numbers from the contact book of the user
 
         if (threePids.isEmpty()) {
-            callback.onSuccess(emptyMap())
-            return NoOpCancellable
+            return emptyMap()
         }
 
-        return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, callback) {
-            val lookupResult = lookUpInternal(true, threePids)
-
-            threePids.associateWith { threePid ->
-                // If not in lookup result, check if there is a pending binding
-                if (lookupResult.firstOrNull { it.threePid == threePid } == null) {
-                    if (identityStore.getPendingBinding(threePid) == null) {
-                        SharedState.NOT_SHARED
-                    } else {
-                        SharedState.BINDING_IN_PROGRESS
-                    }
+        val lookupResult = lookUpInternal(true, threePids)
+
+        return threePids.associateWith { threePid ->
+            // If not in lookup result, check if there is a pending binding
+            if (lookupResult.firstOrNull { it.threePid == threePid } == null) {
+                if (identityStore.getPendingBinding(threePid) == null) {
+                    SharedState.NOT_SHARED
                 } else {
-                    SharedState.SHARED
+                    SharedState.BINDING_IN_PROGRESS
                 }
+            } else {
+                SharedState.SHARED
             }
         }
     }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityAPI.kt
index 7e2702e70dfd1f54258d07672015eccbe6812ee1..e9e4d17e3a72727af7a3fda3ca8861dee0be11f8 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityAPI.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityAPI.kt
@@ -26,7 +26,6 @@ import org.matrix.android.sdk.internal.session.identity.model.IdentityRequestOwn
 import org.matrix.android.sdk.internal.session.identity.model.IdentityRequestTokenForEmailBody
 import org.matrix.android.sdk.internal.session.identity.model.IdentityRequestTokenForMsisdnBody
 import org.matrix.android.sdk.internal.session.identity.model.IdentityRequestTokenResponse
-import retrofit2.Call
 import retrofit2.http.Body
 import retrofit2.http.GET
 import retrofit2.http.POST
@@ -43,20 +42,20 @@ internal interface IdentityAPI {
      * Ref: https://matrix.org/docs/spec/identity_service/latest#get-matrix-identity-v2-account
      */
     @GET(NetworkConstants.URI_IDENTITY_PATH_V2 + "account")
-    fun getAccount(): Call<IdentityAccountResponse>
+    suspend fun getAccount(): IdentityAccountResponse
 
     /**
      * Logs out the access token, preventing it from being used to authenticate future requests to the server.
      */
     @POST(NetworkConstants.URI_IDENTITY_PATH_V2 + "account/logout")
-    fun logout(): Call<Unit>
+    suspend fun logout()
 
     /**
      * Request the hash detail to request a bunch of 3PIDs
      * Ref: https://matrix.org/docs/spec/identity_service/latest#get-matrix-identity-v2-hash-details
      */
     @GET(NetworkConstants.URI_IDENTITY_PATH_V2 + "hash_details")
-    fun hashDetails(): Call<IdentityHashDetailResponse>
+    suspend fun hashDetails(): IdentityHashDetailResponse
 
     /**
      * Request a bunch of 3PIDs
@@ -65,7 +64,7 @@ internal interface IdentityAPI {
      * @param body the body request
      */
     @POST(NetworkConstants.URI_IDENTITY_PATH_V2 + "lookup")
-    fun lookup(@Body body: IdentityLookUpParams): Call<IdentityLookUpResponse>
+    suspend fun lookup(@Body body: IdentityLookUpParams): IdentityLookUpResponse
 
     /**
      * Create a session to change the bind status of an email to an identity server
@@ -75,7 +74,7 @@ internal interface IdentityAPI {
      * @return the sid
      */
     @POST(NetworkConstants.URI_IDENTITY_PATH_V2 + "validate/email/requestToken")
-    fun requestTokenToBindEmail(@Body body: IdentityRequestTokenForEmailBody): Call<IdentityRequestTokenResponse>
+    suspend fun requestTokenToBindEmail(@Body body: IdentityRequestTokenForEmailBody): IdentityRequestTokenResponse
 
     /**
      * Create a session to change the bind status of an phone number to an identity server
@@ -85,7 +84,7 @@ internal interface IdentityAPI {
      * @return the sid
      */
     @POST(NetworkConstants.URI_IDENTITY_PATH_V2 + "validate/msisdn/requestToken")
-    fun requestTokenToBindMsisdn(@Body body: IdentityRequestTokenForMsisdnBody): Call<IdentityRequestTokenResponse>
+    suspend fun requestTokenToBindMsisdn(@Body body: IdentityRequestTokenForMsisdnBody): IdentityRequestTokenResponse
 
     /**
      * Validate ownership of an email address, or a phone number.
@@ -94,6 +93,6 @@ internal interface IdentityAPI {
      * - https://matrix.org/docs/spec/identity_service/latest#post-matrix-identity-v2-validate-email-submittoken
      */
     @POST(NetworkConstants.URI_IDENTITY_PATH_V2 + "validate/{medium}/submitToken")
-    fun submitToken(@Path("medium") medium: String,
-                    @Body body: IdentityRequestOwnershipParams): Call<SuccessResult>
+    suspend fun submitToken(@Path("medium") medium: String,
+                            @Body body: IdentityRequestOwnershipParams): SuccessResult
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityAuthAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityAuthAPI.kt
index fd6e1163ef79fbd6fc208951139b10bd473dbbb6..1671859585f1d35cff1f218d1df1d69dae8ff8ae 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityAuthAPI.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityAuthAPI.kt
@@ -19,7 +19,6 @@ package org.matrix.android.sdk.internal.session.identity
 import org.matrix.android.sdk.internal.network.NetworkConstants
 import org.matrix.android.sdk.internal.session.identity.model.IdentityRegisterResponse
 import org.matrix.android.sdk.internal.session.openid.RequestOpenIdTokenResponse
-import retrofit2.Call
 import retrofit2.http.Body
 import retrofit2.http.GET
 import retrofit2.http.POST
@@ -40,18 +39,18 @@ internal interface IdentityAuthAPI {
      * @return 200 in case of success
      */
     @GET(NetworkConstants.URI_IDENTITY_PREFIX_PATH)
-    fun ping(): Call<Unit>
+    suspend fun ping()
 
     /**
      * Ping v1 will be used to check outdated Identity server
      */
     @GET("_matrix/identity/api/v1")
-    fun pingV1(): Call<Unit>
+    suspend fun pingV1()
 
     /**
      * Exchanges an OpenID token from the homeserver for an access token to access the identity server.
      * The request body is the same as the values returned by /openid/request_token in the Client-Server API.
      */
     @POST(NetworkConstants.URI_IDENTITY_PATH_V2 + "account/register")
-    fun register(@Body openIdToken: RequestOpenIdTokenResponse): Call<IdentityRegisterResponse>
+    suspend fun register(@Body openIdToken: RequestOpenIdTokenResponse): IdentityRegisterResponse
 }
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 67f3b2aa56952c02e80e9d5d17556a0653fa4fad..4f6e906766801806c67a0d6cc154b1d25fff4828 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
@@ -83,7 +83,7 @@ internal class DefaultIdentityBulkLookupTask @Inject constructor(
         return try {
             LookUpData(hashedAddresses,
                     executeRequest(null) {
-                        apiCall = identityAPI.lookup(IdentityLookUpParams(
+                        identityAPI.lookup(IdentityLookUpParams(
                                 hashedAddresses,
                                 IdentityHashDetailResponse.ALGORITHM_SHA256,
                                 hashDetailResponse.pepper
@@ -126,7 +126,7 @@ internal class DefaultIdentityBulkLookupTask @Inject constructor(
 
     private suspend fun fetchHashDetails(identityAPI: IdentityAPI): IdentityHashDetailResponse {
         return executeRequest(null) {
-            apiCall = identityAPI.hashDetails()
+            identityAPI.hashDetails()
         }
     }
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityDisconnectTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityDisconnectTask.kt
index 50e24f1245ab44bf02fb35f3725fa7b31a13f359..fc84a144fed99807439c8bccfe0651662a55aa40 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityDisconnectTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityDisconnectTask.kt
@@ -42,8 +42,8 @@ internal class DefaultIdentityDisconnectTask @Inject constructor(
             return
         }
 
-        executeRequest<Unit>(null) {
-            apiCall = identityAPI.logout()
+        executeRequest(null) {
+            identityAPI.logout()
         }
     }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityPingTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityPingTask.kt
index b0d33811bdcb9ef78b467bcf6b9a2e8c775b0ee4..fca9408d9cf8af498b89606448f07c8de4e7e45d 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityPingTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityPingTask.kt
@@ -33,14 +33,14 @@ internal class DefaultIdentityPingTask @Inject constructor() : IdentityPingTask
 
     override suspend fun execute(params: IdentityPingTask.Params) {
         try {
-            executeRequest<Unit>(null) {
-                apiCall = params.identityAuthAPI.ping()
+            executeRequest(null) {
+                params.identityAuthAPI.ping()
             }
         } catch (throwable: Throwable) {
             if (throwable is Failure.ServerError && throwable.httpCode == HttpsURLConnection.HTTP_NOT_FOUND /* 404 */) {
                 // Check if API v1 is available
-                executeRequest<Unit>(null) {
-                    apiCall = params.identityAuthAPI.pingV1()
+                executeRequest(null) {
+                    params.identityAuthAPI.pingV1()
                 }
                 // API V1 is responding, but not V2 -> Outdated
                 throw IdentityServiceError.OutdatedIdentityServer
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityRegisterTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityRegisterTask.kt
index 19215f353ac9919e430d2a5e77add335690f878d..8cc854bd940daa821d0f785db5fb31375aafa547 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityRegisterTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityRegisterTask.kt
@@ -33,7 +33,7 @@ internal class DefaultIdentityRegisterTask @Inject constructor() : IdentityRegis
 
     override suspend fun execute(params: IdentityRegisterTask.Params): IdentityRegisterResponse {
         return executeRequest(null) {
-            apiCall = params.identityAuthAPI.register(params.openIdTokenResponse)
+            params.identityAuthAPI.register(params.openIdTokenResponse)
         }
     }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityRequestTokenForBindingTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityRequestTokenForBindingTask.kt
index bd4cd763f00233d295f66e816958dd69f901f93b..9c89048176f6d8d918f324e6bf34845d882524dc 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityRequestTokenForBindingTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityRequestTokenForBindingTask.kt
@@ -25,7 +25,6 @@ import org.matrix.android.sdk.internal.session.identity.data.IdentityPendingBind
 import org.matrix.android.sdk.internal.session.identity.data.IdentityStore
 import org.matrix.android.sdk.internal.session.identity.model.IdentityRequestTokenForEmailBody
 import org.matrix.android.sdk.internal.session.identity.model.IdentityRequestTokenForMsisdnBody
-import org.matrix.android.sdk.internal.session.identity.model.IdentityRequestTokenResponse
 import org.matrix.android.sdk.internal.task.Task
 import java.util.UUID
 import javax.inject.Inject
@@ -56,8 +55,8 @@ internal class DefaultIdentityRequestTokenForBindingTask @Inject constructor(
         val clientSecret = identityPendingBinding?.clientSecret ?: UUID.randomUUID().toString()
         val sendAttempt = identityPendingBinding?.sendAttempt?.inc() ?: 1
 
-        val tokenResponse = executeRequest<IdentityRequestTokenResponse>(null) {
-            apiCall = when (params.threePid) {
+        val tokenResponse = executeRequest(null) {
+            when (params.threePid) {
                 is ThreePid.Email  -> identityAPI.requestTokenToBindEmail(IdentityRequestTokenForEmailBody(
                         clientSecret = clientSecret,
                         sendAttempt = sendAttempt,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentitySubmitTokenForBindingTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentitySubmitTokenForBindingTask.kt
index ebc71c715df27f6922ae43e409cdd5104656750d..f884e2816dccfb9e3e63b572e1976b3a83f995cb 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentitySubmitTokenForBindingTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentitySubmitTokenForBindingTask.kt
@@ -19,7 +19,6 @@ package org.matrix.android.sdk.internal.session.identity
 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.auth.registration.SuccessResult
 import org.matrix.android.sdk.internal.di.UserId
 import org.matrix.android.sdk.internal.network.executeRequest
 import org.matrix.android.sdk.internal.session.identity.data.IdentityStore
@@ -44,8 +43,8 @@ internal class DefaultIdentitySubmitTokenForBindingTask @Inject constructor(
         val identityAPI = getIdentityApiAndEnsureTerms(identityApiProvider, userId)
         val identityPendingBinding = identityStore.getPendingBinding(params.threePid) ?: throw IdentityServiceError.NoCurrentBindingError
 
-        val tokenResponse = executeRequest<SuccessResult>(null) {
-            apiCall = identityAPI.submitToken(
+        val tokenResponse = executeRequest(null) {
+            identityAPI.submitToken(
                     params.threePid.toMedium(),
                     IdentityRequestOwnershipParams(
                             clientSecret = identityPendingBinding.clientSecret,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityTaskHelper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityTaskHelper.kt
index d3aecce38162f76ff7535f0629ff04719f8f9327..d06b157f226884416ad771c5f7f7de985a5c7dc9 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityTaskHelper.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityTaskHelper.kt
@@ -18,14 +18,13 @@ package org.matrix.android.sdk.internal.session.identity
 
 import org.matrix.android.sdk.api.session.identity.IdentityServiceError
 import org.matrix.android.sdk.internal.network.executeRequest
-import org.matrix.android.sdk.internal.session.identity.model.IdentityAccountResponse
 
 internal suspend fun getIdentityApiAndEnsureTerms(identityApiProvider: IdentityApiProvider, userId: String): IdentityAPI {
     val identityAPI = identityApiProvider.identityApi ?: throw IdentityServiceError.NoIdentityServerConfigured
 
     // Always check that we have access to the service (regarding terms)
-    val identityAccountResponse = executeRequest<IdentityAccountResponse>(null) {
-        apiCall = identityAPI.getAccount()
+    val identityAccountResponse = executeRequest(null) {
+        identityAPI.getAccount()
     }
 
     assert(userId == identityAccountResponse.userId)
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 d85e471f1d1945ef42a87793c928df6f488a76c8..e707c2351c1edc7d055d1dd5eb88c428e0cc246d 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
@@ -65,8 +65,8 @@ internal class DefaultGetPreviewUrlTask @Inject constructor(
     }
 
     private suspend fun doRequest(url: String, timestamp: Long?): PreviewUrlData {
-        return executeRequest<JsonDict>(globalErrorReceiver) {
-            apiCall = mediaAPI.getPreviewUrlData(url, timestamp)
+        return executeRequest(globalErrorReceiver) {
+            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 32305cd4e405419470c8e1a4d9a392c849d008e9..fd906f0dc8a6d8aa708dbe9f72b416bb0e67ee14 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
@@ -36,7 +36,7 @@ internal class DefaultGetRawPreviewUrlTask @Inject constructor(
 
     override suspend fun execute(params: GetRawPreviewUrlTask.Params): JsonDict {
         return executeRequest(globalErrorReceiver) {
-            apiCall = mediaAPI.getPreviewUrlData(params.url, params.timestamp)
+            mediaAPI.getPreviewUrlData(params.url, params.timestamp)
         }
     }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/MediaAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/MediaAPI.kt
index bbb4f1e06a17c9068fd2e44b443ddd11a517538f..9ee1d26cdcac6a09150356b20b512b6879814e0b 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/MediaAPI.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/MediaAPI.kt
@@ -18,7 +18,6 @@ package org.matrix.android.sdk.internal.session.media
 
 import org.matrix.android.sdk.api.util.JsonDict
 import org.matrix.android.sdk.internal.network.NetworkConstants
-import retrofit2.Call
 import retrofit2.http.GET
 import retrofit2.http.Query
 
@@ -28,7 +27,7 @@ internal interface MediaAPI {
      * Ref: https://matrix.org/docs/spec/client_server/r0.6.1#get-matrix-media-r0-config
      */
     @GET(NetworkConstants.URI_API_MEDIA_PREFIX_PATH_R0 + "config")
-    fun getMediaConfig(): Call<GetMediaConfigResult>
+    suspend fun getMediaConfig(): GetMediaConfigResult
 
     /**
      * Get information about a URL for the client. Typically this is called when a client
@@ -39,5 +38,5 @@ internal interface MediaAPI {
      * if it does not have the requested version available.
      */
     @GET(NetworkConstants.URI_API_MEDIA_PREFIX_PATH_R0 + "preview_url")
-    fun getPreviewUrlData(@Query("url") url: String, @Query("ts") ts: Long?): Call<JsonDict>
+    suspend fun getPreviewUrlData(@Query("url") url: String, @Query("ts") ts: Long?): JsonDict
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/notification/DefaultPushRuleService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/notification/DefaultPushRuleService.kt
index e00d2ff26cf1fbdfc01f423314052dc7c1735807..38f6b08b43b39d9eba976a7820f0faaeb0f6652a 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/notification/DefaultPushRuleService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/notification/DefaultPushRuleService.kt
@@ -16,8 +16,10 @@
 package org.matrix.android.sdk.internal.session.notification
 
 import com.zhuinden.monarchy.Monarchy
+import org.matrix.android.sdk.api.pushrules.Action
 import org.matrix.android.sdk.api.pushrules.PushRuleService
 import org.matrix.android.sdk.api.pushrules.RuleKind
+import org.matrix.android.sdk.api.pushrules.RuleScope
 import org.matrix.android.sdk.api.pushrules.RuleSetKey
 import org.matrix.android.sdk.api.pushrules.getActions
 import org.matrix.android.sdk.api.pushrules.rest.PushRule
@@ -45,6 +47,7 @@ internal class DefaultPushRuleService @Inject constructor(
         private val addPushRuleTask: AddPushRuleTask,
         private val updatePushRuleActionsTask: UpdatePushRuleActionsTask,
         private val removePushRuleTask: RemovePushRuleTask,
+        private val pushRuleFinder: PushRuleFinder,
         private val taskExecutor: TaskExecutor,
         @SessionDatabase private val monarchy: Monarchy
 ) : PushRuleService {
@@ -130,6 +133,12 @@ internal class DefaultPushRuleService @Inject constructor(
         }
     }
 
+    override fun getActions(event: Event): List<Action> {
+        val rules = getPushRules(RuleScope.GLOBAL).getAllRules()
+
+        return pushRuleFinder.fulfilledBingRule(event, rules)?.getActions().orEmpty()
+    }
+
 //    fun processEvents(events: List<Event>) {
 //        var hasDoneSomething = false
 //        events.forEach { event ->
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/notification/ProcessEventForPushTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/notification/ProcessEventForPushTask.kt
index 54883b51e663a0672d3500d00050780d90e1675f..0ece07fc1574eb72b2390989c25a91c3d0e742fe 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/notification/ProcessEventForPushTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/notification/ProcessEventForPushTask.kt
@@ -16,9 +16,7 @@
 
 package org.matrix.android.sdk.internal.session.notification
 
-import org.matrix.android.sdk.api.pushrules.ConditionResolver
 import org.matrix.android.sdk.api.pushrules.rest.PushRule
-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.internal.di.UserId
 import org.matrix.android.sdk.internal.session.sync.model.RoomsSyncResponse
@@ -35,7 +33,7 @@ internal interface ProcessEventForPushTask : Task<ProcessEventForPushTask.Params
 
 internal class DefaultProcessEventForPushTask @Inject constructor(
         private val defaultPushRuleService: DefaultPushRuleService,
-        private val conditionResolver: ConditionResolver,
+        private val pushRuleFinder: PushRuleFinder,
         @UserId private val userId: String
 ) : ProcessEventForPushTask {
 
@@ -72,7 +70,7 @@ internal class DefaultProcessEventForPushTask @Inject constructor(
         Timber.v("[PushRules] Found ${allEvents.size} out of ${(newJoinEvents + inviteEvents).size}" +
                 " to check for push rules with ${params.rules.size} rules")
         allEvents.forEach { event ->
-            fulfilledBingRule(event, params.rules)?.let {
+            pushRuleFinder.fulfilledBingRule(event, params.rules)?.let {
                 Timber.v("[PushRules] Rule $it match for event ${event.eventId}")
                 defaultPushRuleService.dispatchBing(event, it)
             }
@@ -94,13 +92,4 @@ internal class DefaultProcessEventForPushTask @Inject constructor(
 
         defaultPushRuleService.dispatchFinish()
     }
-
-    private fun fulfilledBingRule(event: Event, rules: List<PushRule>): PushRule? {
-        return rules.firstOrNull { rule ->
-            // All conditions must hold true for an event in order to apply the action for the event.
-            rule.enabled && rule.conditions?.all {
-                it.asExecutableCondition(rule)?.isSatisfied(event, conditionResolver) ?: false
-            } ?: false
-        }
-    }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/notification/PushRuleFinder.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/notification/PushRuleFinder.kt
new file mode 100644
index 0000000000000000000000000000000000000000..6e302d373d819dfe5944685165bd39c06200c729
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/notification/PushRuleFinder.kt
@@ -0,0 +1,35 @@
+/*
+ * 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.notification
+
+import org.matrix.android.sdk.api.pushrules.ConditionResolver
+import org.matrix.android.sdk.api.pushrules.rest.PushRule
+import org.matrix.android.sdk.api.session.events.model.Event
+import javax.inject.Inject
+
+internal class PushRuleFinder @Inject constructor(
+        private val conditionResolver: ConditionResolver
+) {
+    fun fulfilledBingRule(event: Event, rules: List<PushRule>): PushRule? {
+        return rules.firstOrNull { rule ->
+            // All conditions must hold true for an event in order to apply the action for the event.
+            rule.enabled && rule.conditions?.all {
+                it.asExecutableCondition(rule)?.isSatisfied(event, conditionResolver) ?: false
+            } ?: false
+        }
+    }
+}
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 f83c6b770af5fa3883d84f73c6163bfebf8e6cb9..8481a6ab93d6eaec9e1af4b454c8f86746b6d11d 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
@@ -31,7 +31,7 @@ internal class DefaultGetOpenIdTokenTask @Inject constructor(
 
     override suspend fun execute(params: Unit): RequestOpenIdTokenResponse {
         return executeRequest(globalErrorReceiver) {
-            apiCall = openIdAPI.openIdToken(userId)
+            openIdAPI.openIdToken(userId)
         }
     }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/openid/OpenIdAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/openid/OpenIdAPI.kt
index 4614d824536e1260806eac913f61452b5e989fcf..ed090b845d02888f912ede1657b20bab8ce540c9 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/openid/OpenIdAPI.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/openid/OpenIdAPI.kt
@@ -18,7 +18,6 @@ package org.matrix.android.sdk.internal.session.openid
 
 import org.matrix.android.sdk.api.util.JsonDict
 import org.matrix.android.sdk.internal.network.NetworkConstants
-import retrofit2.Call
 import retrofit2.http.Body
 import retrofit2.http.POST
 import retrofit2.http.Path
@@ -34,6 +33,6 @@ internal interface OpenIdAPI {
      * @param userId the user id
      */
     @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "user/{userId}/openid/request_token")
-    fun openIdToken(@Path("userId") userId: String,
-                    @Body body: JsonDict = emptyMap()): Call<RequestOpenIdTokenResponse>
+    suspend fun openIdToken(@Path("userId") userId: String,
+                            @Body body: JsonDict = emptyMap()): RequestOpenIdTokenResponse
 }
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 6d6d70bb0d5d51a84200bfee1d6f78bb99cdc57f..678d3994284c65dccdc033e9aeebeab2a6900447 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
@@ -50,13 +50,14 @@ internal class DefaultAddThreePidTask @Inject constructor(
         val clientSecret = UUID.randomUUID().toString()
         val sendAttempt = 1
 
-        val result = executeRequest<AddEmailResponse>(globalErrorReceiver) {
-            val body = AddEmailBody(
-                    clientSecret = clientSecret,
-                    email = threePid.email,
-                    sendAttempt = sendAttempt
-            )
-            apiCall = profileAPI.addEmail(body)
+        val body = AddEmailBody(
+                clientSecret = clientSecret,
+                email = threePid.email,
+                sendAttempt = sendAttempt
+        )
+
+        val result = executeRequest(globalErrorReceiver) {
+            profileAPI.addEmail(body)
         }
 
         // Store as a pending three pid
@@ -84,14 +85,15 @@ internal class DefaultAddThreePidTask @Inject constructor(
         val countryCode = parsedNumber.countryCode
         val country = phoneNumberUtil.getRegionCodeForCountryCode(countryCode)
 
-        val result = executeRequest<AddMsisdnResponse>(globalErrorReceiver) {
-            val body = AddMsisdnBody(
-                    clientSecret = clientSecret,
-                    country = country,
-                    phoneNumber = parsedNumber.nationalNumber.toString(),
-                    sendAttempt = sendAttempt
-            )
-            apiCall = profileAPI.addMsisdn(body)
+        val body = AddMsisdnBody(
+                clientSecret = clientSecret,
+                country = country,
+                phoneNumber = parsedNumber.nationalNumber.toString(),
+                sendAttempt = sendAttempt
+        )
+
+        val result = executeRequest(globalErrorReceiver) {
+            profileAPI.addMsisdn(body)
         }
 
         // Store as a pending three pid
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 a37e5380bcc9a0cdeb6b8d5159dafd98f18b3401..87e51181e636952d757bb71c92607f06ef3b5447 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
@@ -43,8 +43,8 @@ internal class DefaultBindThreePidsTask @Inject constructor(private val profileA
         val identityServerAccessToken = accessTokenProvider.getToken() ?: throw IdentityServiceError.NoIdentityServerConfigured
         val identityPendingBinding = identityStore.getPendingBinding(params.threePid) ?: throw IdentityServiceError.NoCurrentBindingError
 
-        executeRequest<Unit>(globalErrorReceiver) {
-            apiCall = profileAPI.bindThreePid(
+        executeRequest(globalErrorReceiver) {
+            profileAPI.bindThreePid(
                     BindThreePidBody(
                             clientSecret = identityPendingBinding.clientSecret,
                             identityServerUrlWithoutProtocol = identityServerUrlWithoutProtocol,
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 b3216d744d4bbf006cbbd1dc2a4905f9713715b5..386fec8256f62d8b13fbe71c693c2f1fccf9d9da 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
@@ -21,11 +21,10 @@ import android.net.Uri
 import androidx.lifecycle.LiveData
 import com.zhuinden.monarchy.Monarchy
 import io.realm.kotlin.where
-import org.matrix.android.sdk.api.MatrixCallback
+import kotlinx.coroutines.withContext
 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
 import org.matrix.android.sdk.api.util.JsonDict
 import org.matrix.android.sdk.api.util.MimeTypes
 import org.matrix.android.sdk.api.util.Optional
@@ -36,7 +35,6 @@ import org.matrix.android.sdk.internal.session.content.FileUploader
 import org.matrix.android.sdk.internal.session.user.UserStore
 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
 
@@ -55,64 +53,38 @@ internal class DefaultProfileService @Inject constructor(private val taskExecuto
                                                          private val userStore: UserStore,
                                                          private val fileUploader: FileUploader) : ProfileService {
 
-    override fun getDisplayName(userId: String, matrixCallback: MatrixCallback<Optional<String>>): Cancelable {
+    override suspend fun getDisplayName(userId: String): Optional<String> {
         val params = GetProfileInfoTask.Params(userId)
-        return getProfileInfoTask
-                .configureWith(params) {
-                    this.callback = object : MatrixCallback<JsonDict> {
-                        override fun onSuccess(data: JsonDict) {
-                            val displayName = data[ProfileService.DISPLAY_NAME_KEY] as? String
-                            matrixCallback.onSuccess(Optional.from(displayName))
-                        }
-
-                        override fun onFailure(failure: Throwable) {
-                            matrixCallback.onFailure(failure)
-                        }
-                    }
-                }
-                .executeBy(taskExecutor)
+        val data = getProfileInfoTask.execute(params)
+        val displayName = data[ProfileService.DISPLAY_NAME_KEY] as? String
+        return Optional.from(displayName)
     }
 
-    override fun setDisplayName(userId: String, newDisplayName: String, matrixCallback: MatrixCallback<Unit>): Cancelable {
-        return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.io, matrixCallback) {
+    override suspend fun setDisplayName(userId: String, newDisplayName: String) {
+        withContext(coroutineDispatchers.io) {
             setDisplayNameTask.execute(SetDisplayNameTask.Params(userId = userId, newDisplayName = newDisplayName))
             userStore.updateDisplayName(userId, newDisplayName)
         }
     }
 
-    override fun updateAvatar(userId: String, newAvatarUri: Uri, fileName: String, matrixCallback: MatrixCallback<Unit>): Cancelable {
-        return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, matrixCallback) {
+    override suspend fun updateAvatar(userId: String, newAvatarUri: Uri, fileName: String) {
+        withContext(coroutineDispatchers.main) {
             val response = fileUploader.uploadFromUri(newAvatarUri, fileName, MimeTypes.Jpeg)
             setAvatarUrlTask.execute(SetAvatarUrlTask.Params(userId = userId, newAvatarUrl = response.contentUri))
             userStore.updateAvatar(userId, response.contentUri)
         }
     }
 
-    override fun getAvatarUrl(userId: String, matrixCallback: MatrixCallback<Optional<String>>): Cancelable {
+    override suspend fun getAvatarUrl(userId: String): Optional<String> {
         val params = GetProfileInfoTask.Params(userId)
-        return getProfileInfoTask
-                .configureWith(params) {
-                    this.callback = object : MatrixCallback<JsonDict> {
-                        override fun onSuccess(data: JsonDict) {
-                            val avatarUrl = data[ProfileService.AVATAR_URL_KEY] as? String
-                            matrixCallback.onSuccess(Optional.from(avatarUrl))
-                        }
-
-                        override fun onFailure(failure: Throwable) {
-                            matrixCallback.onFailure(failure)
-                        }
-                    }
-                }
-                .executeBy(taskExecutor)
+        val data = getProfileInfoTask.execute(params)
+        val avatarUrl = data[ProfileService.AVATAR_URL_KEY] as? String
+        return Optional.from(avatarUrl)
     }
 
-    override fun getProfile(userId: String, matrixCallback: MatrixCallback<JsonDict>): Cancelable {
+    override suspend fun getProfile(userId: String): JsonDict {
         val params = GetProfileInfoTask.Params(userId)
-        return getProfileInfoTask
-                .configureWith(params) {
-                    this.callback = matrixCallback
-                }
-                .executeBy(taskExecutor)
+        return getProfileInfoTask.execute(params)
     }
 
     override fun getThreePids(): List<ThreePid> {
@@ -154,70 +126,38 @@ internal class DefaultProfileService @Inject constructor(private val taskExecuto
         )
     }
 
-    override fun addThreePid(threePid: ThreePid, matrixCallback: MatrixCallback<Unit>): Cancelable {
-        return addThreePidTask
-                .configureWith(AddThreePidTask.Params(threePid)) {
-                    callback = matrixCallback
-                }
-                .executeBy(taskExecutor)
+    override suspend fun addThreePid(threePid: ThreePid) {
+        addThreePidTask.execute(AddThreePidTask.Params(threePid))
     }
 
-    override fun submitSmsCode(threePid: ThreePid.Msisdn, code: String, matrixCallback: MatrixCallback<Unit>): Cancelable {
-        return validateSmsCodeTask
-                .configureWith(ValidateSmsCodeTask.Params(threePid, code)) {
-                    callback = matrixCallback
-                }
-                .executeBy(taskExecutor)
+    override suspend fun submitSmsCode(threePid: ThreePid.Msisdn, code: String) {
+        validateSmsCodeTask.execute(ValidateSmsCodeTask.Params(threePid, code))
     }
 
-    override fun finalizeAddingThreePid(threePid: ThreePid,
-                                        userInteractiveAuthInterceptor: UserInteractiveAuthInterceptor,
-                                        matrixCallback: MatrixCallback<Unit>): Cancelable {
-        return finalizeAddingThreePidTask
-                .configureWith(FinalizeAddingThreePidTask.Params(
+    override suspend fun finalizeAddingThreePid(threePid: ThreePid,
+                                                userInteractiveAuthInterceptor: UserInteractiveAuthInterceptor) {
+        finalizeAddingThreePidTask
+                .execute(FinalizeAddingThreePidTask.Params(
                         threePid = threePid,
                         userInteractiveAuthInterceptor = userInteractiveAuthInterceptor,
                         userWantsToCancel = false
-                )) {
-                    callback = alsoRefresh(matrixCallback)
-                }
-                .executeBy(taskExecutor)
+                ))
+        refreshThreePids()
     }
 
-    override fun cancelAddingThreePid(threePid: ThreePid, matrixCallback: MatrixCallback<Unit>): Cancelable {
-        return finalizeAddingThreePidTask
-                .configureWith(FinalizeAddingThreePidTask.Params(
+    override suspend fun cancelAddingThreePid(threePid: ThreePid) {
+        finalizeAddingThreePidTask
+                .execute(FinalizeAddingThreePidTask.Params(
                         threePid = threePid,
                         userInteractiveAuthInterceptor = null,
                         userWantsToCancel = true
-                )) {
-                    callback = alsoRefresh(matrixCallback)
-                }
-                .executeBy(taskExecutor)
-    }
-
-    /**
-     * Wrap the callback to fetch 3Pids from the server in case of success
-     */
-    private fun alsoRefresh(callback: MatrixCallback<Unit>): MatrixCallback<Unit> {
-        return object : MatrixCallback<Unit> {
-            override fun onFailure(failure: Throwable) {
-                callback.onFailure(failure)
-            }
-
-            override fun onSuccess(data: Unit) {
-                refreshThreePids()
-                callback.onSuccess(data)
-            }
-        }
+                ))
+        refreshThreePids()
     }
 
-    override fun deleteThreePid(threePid: ThreePid, matrixCallback: MatrixCallback<Unit>): Cancelable {
-        return deleteThreePidTask
-                .configureWith(DeleteThreePidTask.Params(threePid)) {
-                    callback = alsoRefresh(matrixCallback)
-                }
-                .executeBy(taskExecutor)
+    override suspend fun deleteThreePid(threePid: ThreePid) {
+        deleteThreePidTask.execute(DeleteThreePidTask.Params(threePid))
+        refreshThreePids()
     }
 }
 
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 3549f3613f62f35372304162e4d6a25b0714cb23..7b7617aa8094906b8f453aef4066082aea354d3e 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
@@ -34,12 +34,12 @@ internal class DefaultDeleteThreePidTask @Inject constructor(
         private val globalErrorReceiver: GlobalErrorReceiver) : DeleteThreePidTask() {
 
     override suspend fun execute(params: Params) {
-        executeRequest<DeleteThreePidResponse>(globalErrorReceiver) {
-            val body = DeleteThreePidBody(
-                    medium = params.threePid.toMedium(),
-                    address = params.threePid.value
-            )
-            apiCall = profileAPI.deleteThreePid(body)
+        val body = DeleteThreePidBody(
+                medium = params.threePid.toMedium(),
+                address = params.threePid.value
+        )
+        executeRequest(globalErrorReceiver) {
+            profileAPI.deleteThreePid(body)
         }
 
         // We do not really care about the result for the moment
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 c2a38af0938a17ed1e64335191a375912c5de5d2..5f063365e004ad16d525aa14fc8b46053b07c201 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
@@ -61,13 +61,13 @@ internal class DefaultFinalizeAddingThreePidTask @Inject constructor(
                     ?: throw IllegalArgumentException("unknown threepid")
 
             try {
-                executeRequest<Unit>(globalErrorReceiver) {
+                executeRequest(globalErrorReceiver) {
                     val body = FinalizeAddThreePidBody(
                             clientSecret = pendingThreePids.clientSecret,
                             sid = pendingThreePids.sid,
                             auth = params.userAuthParam?.asMap()
                     )
-                    apiCall = profileAPI.finalizeAddThreePid(body)
+                    profileAPI.finalizeAddThreePid(body)
                 }
                 true
             } catch (throwable: 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 ed60c4a368d5f8acd7668b35adca3e54caf05e49..fed4288f849e0bae23f7da2f0a7e7d58d5ebabcd 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
@@ -34,7 +34,7 @@ internal class DefaultGetProfileInfoTask @Inject constructor(private val profile
 
     override suspend fun execute(params: Params): JsonDict {
         return executeRequest(globalErrorReceiver) {
-            apiCall = profileAPI.getProfile(params.userId)
+            profileAPI.getProfile(params.userId)
         }
     }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/ProfileAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/ProfileAPI.kt
index 7794f578b0058f2ec0c307d9b07d9b97da3df080..5113b821e8726764e8530b112f342b7c5edf0a24 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/ProfileAPI.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/ProfileAPI.kt
@@ -21,7 +21,6 @@ import org.matrix.android.sdk.api.util.JsonDict
 import org.matrix.android.sdk.internal.auth.registration.SuccessResult
 import org.matrix.android.sdk.internal.auth.registration.ValidationCodeBody
 import org.matrix.android.sdk.internal.network.NetworkConstants
-import retrofit2.Call
 import retrofit2.http.Body
 import retrofit2.http.GET
 import retrofit2.http.POST
@@ -37,70 +36,70 @@ internal interface ProfileAPI {
      * @param userId the user id to fetch profile info
      */
     @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "profile/{userId}")
-    fun getProfile(@Path("userId") userId: String): Call<JsonDict>
+    suspend fun getProfile(@Path("userId") userId: String): JsonDict
 
     /**
      * List all 3PIDs linked to the Matrix user account.
      */
     @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "account/3pid")
-    fun getThreePIDs(): Call<AccountThreePidsResponse>
+    suspend fun getThreePIDs(): AccountThreePidsResponse
 
     /**
      * Change user display name
      */
     @PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "profile/{userId}/displayname")
-    fun setDisplayName(@Path("userId") userId: String,
-                       @Body body: SetDisplayNameBody): Call<Unit>
+    suspend fun setDisplayName(@Path("userId") userId: String,
+                               @Body body: SetDisplayNameBody)
 
     /**
      * Change user avatar url.
      */
     @PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "profile/{userId}/avatar_url")
-    fun setAvatarUrl(@Path("userId") userId: String,
-                     @Body body: SetAvatarUrlBody): Call<Unit>
+    suspend fun setAvatarUrl(@Path("userId") userId: String,
+                             @Body body: SetAvatarUrlBody)
 
     /**
      * Bind a threePid
      * Ref: https://matrix.org/docs/spec/client_server/latest#post-matrix-client-r0-account-3pid-bind
      */
     @POST(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "account/3pid/bind")
-    fun bindThreePid(@Body body: BindThreePidBody): Call<Unit>
+    suspend fun bindThreePid(@Body body: BindThreePidBody)
 
     /**
      * Unbind a threePid
      * Ref: https://matrix.org/docs/spec/client_server/latest#post-matrix-client-r0-account-3pid-unbind
      */
     @POST(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "account/3pid/unbind")
-    fun unbindThreePid(@Body body: UnbindThreePidBody): Call<UnbindThreePidResponse>
+    suspend fun unbindThreePid(@Body body: UnbindThreePidBody): UnbindThreePidResponse
 
     /**
      * Ref: https://matrix.org/docs/spec/client_server/r0.6.1#post-matrix-client-r0-account-3pid-email-requesttoken
      */
     @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "account/3pid/email/requestToken")
-    fun addEmail(@Body body: AddEmailBody): Call<AddEmailResponse>
+    suspend fun addEmail(@Body body: AddEmailBody): AddEmailResponse
 
     /**
      * Ref: https://matrix.org/docs/spec/client_server/r0.6.1#post-matrix-client-r0-account-3pid-msisdn-requesttoken
      */
     @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "account/3pid/msisdn/requestToken")
-    fun addMsisdn(@Body body: AddMsisdnBody): Call<AddMsisdnResponse>
+    suspend fun addMsisdn(@Body body: AddMsisdnBody): AddMsisdnResponse
 
     /**
      * Validate Msisdn code (same model than for Identity server API)
      */
     @POST
-    fun validateMsisdn(@Url url: String,
-                       @Body params: ValidationCodeBody): Call<SuccessResult>
+    suspend fun validateMsisdn(@Url url: String,
+                               @Body params: ValidationCodeBody): SuccessResult
 
     /**
      * Ref: https://matrix.org/docs/spec/client_server/r0.6.1#post-matrix-client-r0-account-3pid-add
      */
     @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "account/3pid/add")
-    fun finalizeAddThreePid(@Body body: FinalizeAddThreePidBody): Call<Unit>
+    suspend fun finalizeAddThreePid(@Body body: FinalizeAddThreePidBody)
 
     /**
      * Ref: https://matrix.org/docs/spec/client_server/r0.6.1#post-matrix-client-r0-account-3pid-delete
      */
     @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "account/3pid/delete")
-    fun deleteThreePid(@Body body: DeleteThreePidBody): Call<DeleteThreePidResponse>
+    suspend fun deleteThreePid(@Body body: DeleteThreePidBody): DeleteThreePidResponse
 }
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 552ad874eeeba0bea3e88e06589b3fecdb7ddc09..8a064b4fd12c93d98e70e0b7ffb8e19d3b03db76 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
@@ -33,8 +33,8 @@ internal class DefaultRefreshUserThreePidsTask @Inject constructor(private val p
                                                                    private val globalErrorReceiver: GlobalErrorReceiver) : RefreshUserThreePidsTask() {
 
     override suspend fun execute(params: Unit) {
-        val accountThreePidsResponse = executeRequest<AccountThreePidsResponse>(globalErrorReceiver) {
-            apiCall = profileAPI.getThreePIDs()
+        val accountThreePidsResponse = executeRequest(globalErrorReceiver) {
+            profileAPI.getThreePIDs()
         }
 
         Timber.d("Get ${accountThreePidsResponse.threePids?.size} threePids")
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 b29153d665f634d7c529885622022dbd423989f9..a7d116d919df94c6a64fc71f31abba8011f5df17 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
@@ -33,11 +33,11 @@ internal class DefaultSetAvatarUrlTask @Inject constructor(
         private val globalErrorReceiver: GlobalErrorReceiver) : SetAvatarUrlTask() {
 
     override suspend fun execute(params: Params) {
+        val body = SetAvatarUrlBody(
+                avatarUrl = params.newAvatarUrl
+        )
         return executeRequest(globalErrorReceiver) {
-            val body = SetAvatarUrlBody(
-                    avatarUrl = params.newAvatarUrl
-            )
-            apiCall = profileAPI.setAvatarUrl(params.userId, body)
+            profileAPI.setAvatarUrl(params.userId, body)
         }
     }
 }
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 3f236bc589ba22750b31a41d55d59acec236fc2e..61d304231038a889a6f8ce5422d0029712ef7ea0 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
@@ -33,11 +33,11 @@ internal class DefaultSetDisplayNameTask @Inject constructor(
         private val globalErrorReceiver: GlobalErrorReceiver) : SetDisplayNameTask() {
 
     override suspend fun execute(params: Params) {
+        val body = SetDisplayNameBody(
+                displayName = params.newDisplayName
+        )
         return executeRequest(globalErrorReceiver) {
-            val body = SetDisplayNameBody(
-                    displayName = params.newDisplayName
-            )
-            apiCall = profileAPI.setDisplayName(params.userId, body)
+            profileAPI.setDisplayName(params.userId, body)
         }
     }
 }
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 3439f6f840a980af59d0324327a5dc3e2bc50362..df8a1c97ffb5a29854d4af2cf9ad229a45e18797 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
@@ -39,8 +39,8 @@ internal class DefaultUnbindThreePidsTask @Inject constructor(private val profil
         val identityServerUrlWithoutProtocol = identityStore.getIdentityServerUrlWithoutProtocol()
                 ?: throw IdentityServiceError.NoIdentityServerConfigured
 
-        return executeRequest<UnbindThreePidResponse>(globalErrorReceiver) {
-            apiCall = profileAPI.unbindThreePid(
+        return executeRequest(globalErrorReceiver) {
+            profileAPI.unbindThreePid(
                     UnbindThreePidBody(
                             identityServerUrlWithoutProtocol,
                             params.threePid.toMedium(),
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 efb6c6e836ca4dabbb7b255e664c4483281d4c67..c898fc6c5cc74e1eb906d1288d35b76330ab739a 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
@@ -19,7 +19,6 @@ package org.matrix.android.sdk.internal.session.profile
 import com.zhuinden.monarchy.Monarchy
 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
@@ -58,8 +57,8 @@ internal class DefaultValidateSmsCodeTask @Inject constructor(
                 sid = pendingThreePids.sid,
                 code = params.code
         )
-        val result = executeRequest<SuccessResult>(globalErrorReceiver) {
-            apiCall = profileAPI.validateMsisdn(url, body)
+        val result = executeRequest(globalErrorReceiver) {
+            profileAPI.validateMsisdn(url, body)
         }
 
         if (!result.isSuccess()) {
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 d0f7cbfca3d0d5b0ec03bead41e847c95932fb54..c9d7ad21938c960dee8debfb7e6efc8c1d15cdaa 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
@@ -81,8 +81,8 @@ internal class AddHttpPusherWorker(context: Context, params: WorkerParameters)
     }
 
     private suspend fun setPusher(pusher: JsonPusher) {
-        executeRequest<Unit>(globalErrorReceiver) {
-            apiCall = pushersAPI.setPusher(pusher)
+        executeRequest(globalErrorReceiver) {
+            pushersAPI.setPusher(pusher)
         }
         monarchy.awaitTransaction { realm ->
             val echo = PusherEntity.where(realm, pusher.pushKey).findFirst()
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 03748b15283517d9ee03f653a659ed3d73d62cc2..b2176871688bf631e5aa35f253169bc3ff0006d2 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
@@ -36,7 +36,7 @@ internal class DefaultAddPushRuleTask @Inject constructor(
 
     override suspend fun execute(params: AddPushRuleTask.Params) {
         return executeRequest(globalErrorReceiver) {
-            apiCall = pushRulesApi.addRule(params.kind.value, params.pushRule.ruleId, params.pushRule)
+            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/DefaultPushersService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/DefaultPushersService.kt
index e239182b4a68f5090f6ecdade6b082e134c94b8f..a772cf5ebb17ceb97c86f85174f00d23864ebec1 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/DefaultPushersService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/DefaultPushersService.kt
@@ -18,10 +18,8 @@ package org.matrix.android.sdk.internal.session.pushers
 import androidx.lifecycle.LiveData
 import androidx.work.BackoffPolicy
 import com.zhuinden.monarchy.Monarchy
-import org.matrix.android.sdk.api.MatrixCallback
 import org.matrix.android.sdk.api.session.pushers.Pusher
 import org.matrix.android.sdk.api.session.pushers.PushersService
-import org.matrix.android.sdk.api.util.Cancelable
 import org.matrix.android.sdk.internal.database.mapper.asDomain
 import org.matrix.android.sdk.internal.database.model.PusherEntity
 import org.matrix.android.sdk.internal.database.query.where
@@ -47,16 +45,11 @@ internal class DefaultPushersService @Inject constructor(
         private val taskExecutor: TaskExecutor
 ) : PushersService {
 
-    override fun testPush(url: String,
-                          appId: String,
-                          pushkey: String,
-                          eventId: String,
-                          callback: MatrixCallback<Unit>): Cancelable {
-        return pushGatewayNotifyTask
-                .configureWith(PushGatewayNotifyTask.Params(url, appId, pushkey, eventId)) {
-                    this.callback = callback
-                }
-                .executeBy(taskExecutor)
+    override suspend fun testPush(url: String,
+                                  appId: String,
+                                  pushkey: String,
+                                  eventId: String) {
+        pushGatewayNotifyTask.execute(PushGatewayNotifyTask.Params(url, appId, pushkey, eventId))
     }
 
     override fun refreshPushers() {
@@ -96,20 +89,15 @@ internal class DefaultPushersService @Inject constructor(
         val request = workManagerProvider.matrixOneTimeWorkRequestBuilder<AddHttpPusherWorker>()
                 .setConstraints(WorkManagerProvider.workConstraints)
                 .setInputData(WorkerParamsFactory.toData(params))
-                .setBackoffCriteria(BackoffPolicy.LINEAR, 10_000L, TimeUnit.MILLISECONDS)
+                .setBackoffCriteria(BackoffPolicy.LINEAR, WorkManagerProvider.BACKOFF_DELAY_MILLIS, TimeUnit.MILLISECONDS)
                 .build()
         workManagerProvider.workManager.enqueue(request)
         return request.id
     }
 
-    override fun removeHttpPusher(pushkey: String, appId: String, callback: MatrixCallback<Unit>): Cancelable {
+    override suspend fun removeHttpPusher(pushkey: String, appId: String) {
         val params = RemovePusherTask.Params(pushkey, appId)
-        return removePusherTask
-                .configureWith(params) {
-                    this.callback = callback
-                }
-                // .enableRetry() ??
-                .executeBy(taskExecutor)
+        removePusherTask.execute(params)
     }
 
     override fun getPushersLive(): LiveData<List<Pusher>> {
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 9fb2d51664290bb3fbba12c385efc63990b216cb..8cf861d2853c88dc769cc0d76b31916cb4a9463f 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
@@ -15,7 +15,6 @@
  */
 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
@@ -35,8 +34,8 @@ internal class DefaultGetPushRulesTask @Inject constructor(
 ) : GetPushRulesTask {
 
     override suspend fun execute(params: GetPushRulesTask.Params) {
-        val response = executeRequest<GetPushRulesResponse>(globalErrorReceiver) {
-            apiCall = pushRulesApi.getAllRules()
+        val response = executeRequest(globalErrorReceiver) {
+            pushRulesApi.getAllRules()
         }
 
         savePushRulesTask.execute(SavePushRulesTask.Params(response))
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 125c8f0022d6dad1301fbfb9eb3049ca6baa1a89..ba413a34db2d2c1aeff3151bf97ed436e8c65bf4 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
@@ -36,8 +36,8 @@ internal class DefaultGetPushersTask @Inject constructor(
 ) : GetPushersTask {
 
     override suspend fun execute(params: Unit) {
-        val response = executeRequest<GetPushersResponse>(globalErrorReceiver) {
-            apiCall = pushersAPI.getPushers()
+        val response = executeRequest(globalErrorReceiver) {
+            pushersAPI.getPushers()
         }
         monarchy.awaitTransaction { realm ->
             // clear existings?
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/PushRulesApi.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/PushRulesApi.kt
index cbcb7d2b37033650d09ed0f819a7c6e5323185e4..daf9397ce8de0cde3d91da79c965890987574bc6 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/PushRulesApi.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/PushRulesApi.kt
@@ -18,7 +18,6 @@ package org.matrix.android.sdk.internal.session.pushers
 import org.matrix.android.sdk.api.pushrules.rest.GetPushRulesResponse
 import org.matrix.android.sdk.api.pushrules.rest.PushRule
 import org.matrix.android.sdk.internal.network.NetworkConstants
-import retrofit2.Call
 import retrofit2.http.Body
 import retrofit2.http.DELETE
 import retrofit2.http.GET
@@ -30,7 +29,7 @@ internal interface PushRulesApi {
      * Get all push rules
      */
     @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "pushrules/")
-    fun getAllRules(): Call<GetPushRulesResponse>
+    suspend fun getAllRules(): GetPushRulesResponse
 
     /**
      * Update the ruleID enable status
@@ -40,10 +39,9 @@ internal interface PushRulesApi {
      * @param enable the new enable status
      */
     @PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "pushrules/global/{kind}/{ruleId}/enabled")
-    fun updateEnableRuleStatus(@Path("kind") kind: String,
-                               @Path("ruleId") ruleId: String,
-                               @Body enable: Boolean?)
-            : Call<Unit>
+    suspend fun updateEnableRuleStatus(@Path("kind") kind: String,
+                                       @Path("ruleId") ruleId: String,
+                                       @Body enable: Boolean?)
 
     /**
      * Update the ruleID action
@@ -54,10 +52,9 @@ internal interface PushRulesApi {
      * @param actions the actions
      */
     @PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "pushrules/global/{kind}/{ruleId}/actions")
-    fun updateRuleActions(@Path("kind") kind: String,
-                          @Path("ruleId") ruleId: String,
-                          @Body actions: Any)
-            : Call<Unit>
+    suspend fun updateRuleActions(@Path("kind") kind: String,
+                                  @Path("ruleId") ruleId: String,
+                                  @Body actions: Any)
 
     /**
      * Delete a rule
@@ -66,9 +63,8 @@ internal interface PushRulesApi {
      * @param ruleId the ruleId
      */
     @DELETE(NetworkConstants.URI_API_PREFIX_PATH_R0 + "pushrules/global/{kind}/{ruleId}")
-    fun deleteRule(@Path("kind") kind: String,
-                   @Path("ruleId") ruleId: String)
-            : Call<Unit>
+    suspend fun deleteRule(@Path("kind") kind: String,
+                           @Path("ruleId") ruleId: String)
 
     /**
      * Add the ruleID enable status
@@ -78,8 +74,7 @@ internal interface PushRulesApi {
      * @param rule   the rule to add.
      */
     @PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "pushrules/global/{kind}/{ruleId}")
-    fun addRule(@Path("kind") kind: String,
-                @Path("ruleId") ruleId: String,
-                @Body rule: PushRule)
-            : Call<Unit>
+    suspend fun addRule(@Path("kind") kind: String,
+                        @Path("ruleId") ruleId: String,
+                        @Body rule: PushRule)
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/PushersAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/PushersAPI.kt
index ed4fb73e1b127e659e405481777871d5f18a504a..0afea6996ddc1f8f061e4a6fd11fff2f5119470f 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/PushersAPI.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/PushersAPI.kt
@@ -16,7 +16,6 @@
 package org.matrix.android.sdk.internal.session.pushers
 
 import org.matrix.android.sdk.internal.network.NetworkConstants
-import retrofit2.Call
 import retrofit2.http.Body
 import retrofit2.http.GET
 import retrofit2.http.POST
@@ -29,7 +28,7 @@ internal interface PushersAPI {
      * Ref: https://matrix.org/docs/spec/client_server/latest#get-matrix-client-r0-pushers
      */
     @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "pushers")
-    fun getPushers(): Call<GetPushersResponse>
+    suspend fun getPushers(): GetPushersResponse
 
     /**
      * This endpoint allows the creation, modification and deletion of pushers for this user ID.
@@ -38,5 +37,5 @@ internal interface PushersAPI {
      * Ref: https://matrix.org/docs/spec/client_server/latest#post-matrix-client-r0-pushers-set
      */
     @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "pushers/set")
-    fun setPusher(@Body jsonPusher: JsonPusher): Call<Unit>
+    suspend fun setPusher(@Body jsonPusher: JsonPusher)
 }
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 ff3122f5665de05f80865c81284751cd44e21a95..23d0515f414c833245f884a13e5ec8786bd78157 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
@@ -36,7 +36,7 @@ internal class DefaultRemovePushRuleTask @Inject constructor(
 
     override suspend fun execute(params: RemovePushRuleTask.Params) {
         return executeRequest(globalErrorReceiver) {
-            apiCall = pushRulesApi.deleteRule(params.kind.value, params.pushRule.ruleId)
+            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 e3f4fdb789671ff7aed078be2c4cacf0c4a3056f..3a2ebf40c2f0ae89ade69cd562a6f72600768d1a 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
@@ -62,8 +62,8 @@ internal class DefaultRemovePusherTask @Inject constructor(
                 data = JsonPusherData(existing.data.url, existing.data.format),
                 append = false
         )
-        executeRequest<Unit>(globalErrorReceiver) {
-            apiCall = pushersAPI.setPusher(deleteBody)
+        executeRequest(globalErrorReceiver) {
+            pushersAPI.setPusher(deleteBody)
         }
         monarchy.awaitTransaction {
             PusherEntity.where(it, params.pushKey).findFirst()?.deleteFromRealm()
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 a5c220e662583512910b307d1d3b8cb37ece7bd9..2a24aee8929111559584efbf6fda0e3d97deeca3 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
@@ -38,8 +38,8 @@ internal class DefaultUpdatePushRuleActionsTask @Inject constructor(
     override suspend fun execute(params: UpdatePushRuleActionsTask.Params) {
         if (params.oldPushRule.enabled != params.newPushRule.enabled) {
             // First change enabled state
-            executeRequest<Unit>(globalErrorReceiver) {
-                apiCall = pushRulesApi.updateEnableRuleStatus(params.kind.value, params.newPushRule.ruleId, params.newPushRule.enabled)
+            executeRequest(globalErrorReceiver) {
+                pushRulesApi.updateEnableRuleStatus(params.kind.value, params.newPushRule.ruleId, params.newPushRule.enabled)
             }
         }
 
@@ -47,8 +47,8 @@ internal class DefaultUpdatePushRuleActionsTask @Inject constructor(
             // Also ensure the actions are up to date
             val body = mapOf("actions" to params.newPushRule.actions)
 
-            executeRequest<Unit>(globalErrorReceiver) {
-                apiCall = pushRulesApi.updateRuleActions(params.kind.value, params.newPushRule.ruleId, body)
+            executeRequest(globalErrorReceiver) {
+                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 f36b5c55fb1933a4cc4a400a4aadd822ce78d2a9..9d7a46bede1e80071a9dc269deaa5de8c4e6ebd2 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
@@ -35,7 +35,7 @@ internal class DefaultUpdatePushRuleEnableStatusTask @Inject constructor(
 
     override suspend fun execute(params: UpdatePushRuleEnableStatusTask.Params) {
         return executeRequest(globalErrorReceiver) {
-            apiCall = pushRulesApi.updateEnableRuleStatus(params.kind.value, params.pushRule.ruleId, params.enabled)
+            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/pushers/gateway/PushGatewayAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/gateway/PushGatewayAPI.kt
index d95587fc222dc74ed1b3152eb13460c40a8aeee7..4333d6c7b8850c389f148bfb6cca482b5d470a75 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/gateway/PushGatewayAPI.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/gateway/PushGatewayAPI.kt
@@ -16,7 +16,6 @@
 package org.matrix.android.sdk.internal.session.pushers.gateway
 
 import org.matrix.android.sdk.internal.network.NetworkConstants
-import retrofit2.Call
 import retrofit2.http.Body
 import retrofit2.http.POST
 
@@ -27,5 +26,5 @@ internal interface PushGatewayAPI {
      * Ref: https://matrix.org/docs/spec/push_gateway/r0.1.1#post-matrix-push-v1-notify
      */
     @POST(NetworkConstants.URI_PUSH_GATEWAY_PREFIX_PATH + "notify")
-    fun notify(@Body body: PushGatewayNotifyBody): Call<PushGatewayNotifyResponse>
+    suspend fun notify(@Body body: PushGatewayNotifyBody): PushGatewayNotifyResponse
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/gateway/PushGatewayNotifyTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/gateway/PushGatewayNotifyTask.kt
index df6f46fa81cd44ecf471134f1f178b7bb91474c3..316e221b32ef1fa3c3a8b8d47555d4507ab29e2c 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/gateway/PushGatewayNotifyTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/gateway/PushGatewayNotifyTask.kt
@@ -45,8 +45,8 @@ internal class DefaultPushGatewayNotifyTask @Inject constructor(
         )
                 .create(PushGatewayAPI::class.java)
 
-        val response = executeRequest<PushGatewayNotifyResponse>(null) {
-            apiCall = sygnalApi.notify(
+        val response = executeRequest(null) {
+            sygnalApi.notify(
                     PushGatewayNotifyBody(
                             PushGatewayNotification(
                                     eventId = params.eventId,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoom.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoom.kt
index 8e817ec31a0dcb4537bbf4a4422207bfaefd16fd..1d8eb6c95ea7d2628b15ed3ecb86e9f8517eb10b 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoom.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoom.kt
@@ -17,7 +17,6 @@
 package org.matrix.android.sdk.internal.session.room
 
 import androidx.lifecycle.LiveData
-import org.matrix.android.sdk.api.MatrixCallback
 import org.matrix.android.sdk.api.session.crypto.CryptoService
 import org.matrix.android.sdk.api.session.events.model.EventType
 import org.matrix.android.sdk.api.session.room.Room
@@ -37,14 +36,11 @@ import org.matrix.android.sdk.api.session.room.timeline.TimelineService
 import org.matrix.android.sdk.api.session.room.typing.TypingService
 import org.matrix.android.sdk.api.session.room.uploads.UploadsService
 import org.matrix.android.sdk.api.session.search.SearchResult
-import org.matrix.android.sdk.api.util.Cancelable
 import org.matrix.android.sdk.api.util.Optional
 import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM
 import org.matrix.android.sdk.internal.session.room.state.SendStateTask
 import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryDataSource
 import org.matrix.android.sdk.internal.session.search.SearchTask
-import org.matrix.android.sdk.internal.task.TaskExecutor
-import org.matrix.android.sdk.internal.task.configureWith
 import org.matrix.android.sdk.internal.util.awaitCallback
 import java.security.InvalidParameterException
 import javax.inject.Inject
@@ -66,7 +62,6 @@ internal class DefaultRoom @Inject constructor(override val roomId: String,
                                                private val relationService: RelationService,
                                                private val roomMembersService: MembershipService,
                                                private val roomPushRuleService: RoomPushRuleService,
-                                               private val taskExecutor: TaskExecutor,
                                                private val sendStateTask: SendStateTask,
                                                private val searchTask: SearchTask) :
         Room,
@@ -133,16 +128,15 @@ internal class DefaultRoom @Inject constructor(override val roomId: String,
         }
     }
 
-    override fun search(searchTerm: String,
-                        nextBatch: String?,
-                        orderByRecent: Boolean,
-                        limit: Int,
-                        beforeLimit: Int,
-                        afterLimit: Int,
-                        includeProfile: Boolean,
-                        callback: MatrixCallback<SearchResult>): Cancelable {
-        return searchTask
-                .configureWith(SearchTask.Params(
+    override suspend fun search(searchTerm: String,
+                                nextBatch: String?,
+                                orderByRecent: Boolean,
+                                limit: Int,
+                                beforeLimit: Int,
+                                afterLimit: Int,
+                                includeProfile: Boolean): SearchResult {
+        return searchTask.execute(
+                SearchTask.Params(
                         searchTerm = searchTerm,
                         roomId = roomId,
                         nextBatch = nextBatch,
@@ -151,8 +145,7 @@ internal class DefaultRoom @Inject constructor(override val roomId: String,
                         beforeLimit = beforeLimit,
                         afterLimit = afterLimit,
                         includeProfile = includeProfile
-                )) {
-                    this.callback = callback
-                }.executeBy(taskExecutor)
+                )
+        )
     }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomService.kt
index 383dd876d3de84160f87f44ef966298b79181852..bd63ba480eb8e3c29f6491dda18ed54b29dff56a 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomService.kt
@@ -18,17 +18,20 @@ package org.matrix.android.sdk.internal.session.room
 
 import androidx.lifecycle.LiveData
 import androidx.lifecycle.Transformations
+import androidx.paging.PagedList
 import com.zhuinden.monarchy.Monarchy
 import org.matrix.android.sdk.api.MatrixCallback
 import org.matrix.android.sdk.api.session.events.model.Event
 import org.matrix.android.sdk.api.session.room.Room
 import org.matrix.android.sdk.api.session.room.RoomService
 import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams
+import org.matrix.android.sdk.api.session.room.UpdatableFilterLivePageResult
 import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState
 import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary
 import org.matrix.android.sdk.api.session.room.model.RoomSummary
 import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams
 import org.matrix.android.sdk.api.session.room.peeking.PeekResult
+import org.matrix.android.sdk.api.session.room.summary.RoomAggregateNotificationCount
 import org.matrix.android.sdk.api.util.Cancelable
 import org.matrix.android.sdk.api.util.Optional
 import org.matrix.android.sdk.api.util.toOptional
@@ -96,6 +99,20 @@ internal class DefaultRoomService @Inject constructor(
         return roomSummaryDataSource.getRoomSummariesLive(queryParams)
     }
 
+    override fun getPagedRoomSummariesLive(queryParams: RoomSummaryQueryParams, pagedListConfig: PagedList.Config)
+            : LiveData<PagedList<RoomSummary>> {
+        return roomSummaryDataSource.getSortedPagedRoomSummariesLive(queryParams, pagedListConfig)
+    }
+
+    override fun getFilteredPagedRoomSummariesLive(queryParams: RoomSummaryQueryParams, pagedListConfig: PagedList.Config)
+            : UpdatableFilterLivePageResult {
+        return roomSummaryDataSource.getFilteredPagedRoomSummariesLive(queryParams, pagedListConfig)
+    }
+
+    override fun getNotificationCountForRooms(queryParams: RoomSummaryQueryParams): RoomAggregateNotificationCount {
+        return roomSummaryDataSource.getNotificationCountForRooms(queryParams)
+    }
+
     override fun getBreadcrumbs(queryParams: RoomSummaryQueryParams): List<RoomSummary> {
         return roomSummaryDataSource.getBreadcrumbs(queryParams)
     }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt
index 60440c6359f54dffa16e99f743db07724f53c4ae..c7e09e595450ea0fcc5a312e80bdc0b3c13a4927 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt
@@ -53,8 +53,9 @@ import org.matrix.android.sdk.internal.session.EventInsertLiveProcessor
 import timber.log.Timber
 import javax.inject.Inject
 
-internal class EventRelationsAggregationProcessor @Inject constructor(@UserId private val userId: String)
-    : EventInsertLiveProcessor {
+internal class EventRelationsAggregationProcessor @Inject constructor(
+        @UserId private val userId: String
+) : EventInsertLiveProcessor {
 
     private val allowedTypes = listOf(
             EventType.MESSAGE,
@@ -87,12 +88,12 @@ internal class EventRelationsAggregationProcessor @Inject constructor(@UserId pr
                 EventType.REACTION             -> {
                     // we got a reaction!!
                     Timber.v("###REACTION in room $roomId , reaction eventID ${event.eventId}")
-                    handleReaction(event, roomId, realm, userId, isLocalEcho)
+                    handleReaction(realm, event, roomId, isLocalEcho)
                 }
                 EventType.MESSAGE              -> {
                     if (event.unsignedData?.relations?.annotations != null) {
-                        Timber.v("###REACTION Agreggation in room $roomId for event ${event.eventId}")
-                        handleInitialAggregatedRelations(event, roomId, event.unsignedData.relations.annotations, realm)
+                        Timber.v("###REACTION Aggregation in room $roomId for event ${event.eventId}")
+                        handleInitialAggregatedRelations(realm, event, roomId, event.unsignedData.relations.annotations)
 
                         EventAnnotationsSummaryEntity.where(realm, roomId, event.eventId ?: "").findFirst()
                                 ?.let {
@@ -108,7 +109,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor(@UserId pr
                         handleReplace(realm, event, content, roomId, isLocalEcho)
                     } else if (content?.relatesTo?.type == RelationType.RESPONSE) {
                         Timber.v("###RESPONSE in room $roomId for event ${event.eventId}")
-                        handleResponse(realm, userId, event, content, roomId, isLocalEcho)
+                        handleResponse(realm, event, content, roomId, isLocalEcho)
                     }
                 }
 
@@ -122,7 +123,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor(@UserId pr
                     Timber.v("## SAS REF in room $roomId for event ${event.eventId}")
                     event.content.toModel<MessageRelationContent>()?.relatesTo?.let {
                         if (it.type == RelationType.REFERENCE && it.eventId != null) {
-                            handleVerification(realm, event, roomId, isLocalEcho, it.eventId, userId)
+                            handleVerification(realm, event, roomId, isLocalEcho, it.eventId)
                         }
                     }
                 }
@@ -140,7 +141,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor(@UserId pr
                                 handleReplace(realm, event, it, roomId, isLocalEcho, encryptedEventContent.relatesTo.eventId)
                             } else if (encryptedEventContent.relatesTo.type == RelationType.RESPONSE) {
                                 Timber.v("###RESPONSE in room $roomId for event ${event.eventId}")
-                                handleResponse(realm, userId, event, it, roomId, isLocalEcho, encryptedEventContent.relatesTo.eventId)
+                                handleResponse(realm, event, it, roomId, isLocalEcho, encryptedEventContent.relatesTo.eventId)
                             }
                         }
                     } else if (encryptedEventContent?.relatesTo?.type == RelationType.REFERENCE) {
@@ -154,10 +155,17 @@ internal class EventRelationsAggregationProcessor @Inject constructor(@UserId pr
                             EventType.KEY_VERIFICATION_KEY -> {
                                 Timber.v("## SAS REF in room $roomId for event ${event.eventId}")
                                 encryptedEventContent.relatesTo.eventId?.let {
-                                    handleVerification(realm, event, roomId, isLocalEcho, it, userId)
+                                    handleVerification(realm, event, roomId, isLocalEcho, it)
                                 }
                             }
                         }
+                    } else if (encryptedEventContent?.relatesTo?.type == RelationType.ANNOTATION) {
+                        // Reaction
+                        if (event.getClearType() == EventType.REACTION) {
+                            // we got a reaction!!
+                            Timber.v("###REACTION e2e in room $roomId , reaction eventID ${event.eventId}")
+                            handleReaction(realm, event, roomId, isLocalEcho)
+                        }
                     }
                 }
                 EventType.REDACTION            -> {
@@ -172,11 +180,11 @@ internal class EventRelationsAggregationProcessor @Inject constructor(@UserId pr
                             // was this event a m.replace
                             val contentModel = ContentMapper.map(eventToPrune.content)?.toModel<MessageContent>()
                             if (RelationType.REPLACE == contentModel?.relatesTo?.type && contentModel.relatesTo?.eventId != null) {
-                                handleRedactionOfReplace(eventToPrune, contentModel.relatesTo!!.eventId!!, realm)
+                                handleRedactionOfReplace(realm, eventToPrune, contentModel.relatesTo!!.eventId!!)
                             }
                         }
                         EventType.REACTION -> {
-                            handleReactionRedact(eventToPrune, realm, userId)
+                            handleReactionRedact(realm, eventToPrune)
                         }
                     }
                 }
@@ -267,7 +275,6 @@ internal class EventRelationsAggregationProcessor @Inject constructor(@UserId pr
     }
 
     private fun handleResponse(realm: Realm,
-                               userId: String,
                                event: Event,
                                content: MessageContent,
                                roomId: String,
@@ -354,7 +361,10 @@ internal class EventRelationsAggregationProcessor @Inject constructor(@UserId pr
         existingPollSummary.aggregatedContent = ContentMapper.map(sumModel.toContent())
     }
 
-    private fun handleInitialAggregatedRelations(event: Event, roomId: String, aggregation: AggregatedAnnotation, realm: Realm) {
+    private fun handleInitialAggregatedRelations(realm: Realm,
+                                                 event: Event,
+                                                 roomId: String,
+                                                 aggregation: AggregatedAnnotation) {
         if (SHOULD_HANDLE_SERVER_AGREGGATION) {
             aggregation.chunk?.forEach {
                 if (it.type == EventType.REACTION) {
@@ -376,7 +386,10 @@ internal class EventRelationsAggregationProcessor @Inject constructor(@UserId pr
         }
     }
 
-    private fun handleReaction(event: Event, roomId: String, realm: Realm, userId: String, isLocalEcho: Boolean) {
+    private fun handleReaction(realm: Realm,
+                               event: Event,
+                               roomId: String,
+                               isLocalEcho: Boolean) {
         val content = event.content.toModel<ReactionContent>()
         if (content == null) {
             Timber.e("Malformed reaction content ${event.content}")
@@ -441,7 +454,9 @@ internal class EventRelationsAggregationProcessor @Inject constructor(@UserId pr
     /**
      * Called when an event is deleted
      */
-    private fun handleRedactionOfReplace(redacted: EventEntity, relatedEventId: String, realm: Realm) {
+    private fun handleRedactionOfReplace(realm: Realm,
+                                         redacted: EventEntity,
+                                         relatedEventId: String) {
         Timber.d("Handle redaction of m.replace")
         val eventSummary = EventAnnotationsSummaryEntity.where(realm, redacted.roomId, relatedEventId).findFirst()
         if (eventSummary == null) {
@@ -457,7 +472,8 @@ internal class EventRelationsAggregationProcessor @Inject constructor(@UserId pr
         sourceToDiscard.deleteFromRealm()
     }
 
-    private fun handleReactionRedact(eventToPrune: EventEntity, realm: Realm, userId: String) {
+    private fun handleReactionRedact(realm: Realm,
+                                     eventToPrune: EventEntity) {
         Timber.v("REDACTION of reaction ${eventToPrune.eventId}")
         // delete a reaction, need to update the annotation summary if any
         val reactionContent: ReactionContent = EventMapper.map(eventToPrune).content.toModel() ?: return
@@ -494,7 +510,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor(@UserId pr
         }
     }
 
-    private fun handleVerification(realm: Realm, event: Event, roomId: String, isLocalEcho: Boolean, relatedEventId: String, userId: String) {
+    private fun handleVerification(realm: Realm, event: Event, roomId: String, isLocalEcho: Boolean, relatedEventId: String) {
         val eventSummary = EventAnnotationsSummaryEntity.getOrCreate(realm, roomId, relatedEventId)
 
         val verifSummary = eventSummary.referencesSummaryEntity
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAPI.kt
index 20cb49ee8a8e771840b8ff4a5c3258bff5f62d35..6fee630510b01624b9904d9ec7d576af895438a6 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAPI.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAPI.kt
@@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.session.room
 
 import org.matrix.android.sdk.api.session.events.model.Content
 import org.matrix.android.sdk.api.session.events.model.Event
+import org.matrix.android.sdk.api.session.room.model.Membership
 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.api.util.JsonDict
@@ -37,7 +38,6 @@ import org.matrix.android.sdk.internal.session.room.tags.TagBody
 import org.matrix.android.sdk.internal.session.room.timeline.EventContextResponse
 import org.matrix.android.sdk.internal.session.room.timeline.PaginationResponse
 import org.matrix.android.sdk.internal.session.room.typing.TypingBody
-import retrofit2.Call
 import retrofit2.http.Body
 import retrofit2.http.DELETE
 import retrofit2.http.GET
@@ -56,9 +56,9 @@ internal interface RoomAPI {
      * Ref: https://matrix.org/docs/spec/client_server/r0.4.0.html#post-matrix-client-r0-publicrooms
      */
     @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "publicRooms")
-    fun publicRooms(@Query("server") server: String?,
-                    @Body publicRoomsParams: PublicRoomsParams
-    ): Call<PublicRoomsResponse>
+    suspend fun publicRooms(@Query("server") server: String?,
+                            @Body publicRoomsParams: PublicRoomsParams
+    ): PublicRoomsResponse
 
     /**
      * Create a room.
@@ -70,7 +70,7 @@ internal interface RoomAPI {
      */
     @Headers("CONNECT_TIMEOUT:60000", "READ_TIMEOUT:60000", "WRITE_TIMEOUT:60000")
     @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "createRoom")
-    fun createRoom(@Body param: CreateRoomBody): Call<CreateRoomResponse>
+    suspend fun createRoom(@Body param: CreateRoomBody): CreateRoomResponse
 
     /**
      * Get a list of messages starting from a reference.
@@ -82,12 +82,12 @@ internal interface RoomAPI {
      * @param filter A JSON RoomEventFilter to filter returned events with. Optional.
      */
     @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/messages")
-    fun getRoomMessagesFrom(@Path("roomId") roomId: String,
-                            @Query("from") from: String,
-                            @Query("dir") dir: String,
-                            @Query("limit") limit: Int,
-                            @Query("filter") filter: String?
-    ): Call<PaginationResponse>
+    suspend fun getRoomMessagesFrom(@Path("roomId") roomId: String,
+                                    @Query("from") from: String,
+                                    @Query("dir") dir: String,
+                                    @Query("limit") limit: Int,
+                                    @Query("filter") filter: String?
+    ): PaginationResponse
 
     /**
      * Get all members of a room
@@ -98,11 +98,11 @@ internal interface RoomAPI {
      * @param notMembership to exclude one type of membership (optional)
      */
     @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/members")
-    fun getMembers(@Path("roomId") roomId: String,
-                   @Query("at") syncToken: String?,
-                   @Query("membership") membership: String?,
-                   @Query("not_membership") notMembership: String?
-    ): Call<RoomMembersResponse>
+    suspend fun getMembers(@Path("roomId") roomId: String,
+                           @Query("at") syncToken: String?,
+                           @Query("membership") membership: Membership?,
+                           @Query("not_membership") notMembership: Membership?
+    ): RoomMembersResponse
 
     /**
      * Send an event to a room.
@@ -113,11 +113,11 @@ internal interface RoomAPI {
      * @param content   the event content
      */
     @PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/send/{eventType}/{txId}")
-    fun send(@Path("txId") txId: String,
-             @Path("roomId") roomId: String,
-             @Path("eventType") eventType: String,
-             @Body content: Content?
-    ): Call<SendResponse>
+    suspend fun send(@Path("txId") txId: String,
+                     @Path("roomId") roomId: String,
+                     @Path("eventType") eventType: String,
+                     @Body content: Content?
+    ): SendResponse
 
     /**
      * Get the context surrounding an event.
@@ -128,10 +128,10 @@ internal interface RoomAPI {
      * @param filter  A JSON RoomEventFilter to filter returned events with. Optional.
      */
     @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/context/{eventId}")
-    fun getContextOfEvent(@Path("roomId") roomId: String,
-                          @Path("eventId") eventId: String,
-                          @Query("limit") limit: Int,
-                          @Query("filter") filter: String? = null): Call<EventContextResponse>
+    suspend fun getContextOfEvent(@Path("roomId") roomId: String,
+                                  @Path("eventId") eventId: String,
+                                  @Query("limit") limit: Int,
+                                  @Query("filter") filter: String? = null): EventContextResponse
 
     /**
      * Retrieve an event from its room id / events id
@@ -140,8 +140,8 @@ internal interface RoomAPI {
      * @param eventId the event Id
      */
     @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/event/{eventId}")
-    fun getEvent(@Path("roomId") roomId: String,
-                 @Path("eventId") eventId: String): Call<Event>
+    suspend fun getEvent(@Path("roomId") roomId: String,
+                         @Path("eventId") eventId: String): Event
 
     /**
      * Send read markers.
@@ -150,8 +150,16 @@ internal interface RoomAPI {
      * @param markers the read markers
      */
     @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/read_markers")
-    fun sendReadMarker(@Path("roomId") roomId: String,
-                       @Body markers: Map<String, String>): Call<Unit>
+    suspend fun sendReadMarker(@Path("roomId") roomId: String,
+                               @Body markers: Map<String, String>)
+
+    /**
+     * Send receipt to a room
+     */
+    @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/receipt/{receiptType}/{eventId}")
+    suspend fun sendReceipt(@Path("roomId") roomId: String,
+                            @Path("receiptType") receiptType: String,
+                            @Path("eventId") eventId: String)
 
     /**
      * Invite a user to the given room.
@@ -161,8 +169,8 @@ internal interface RoomAPI {
      * @param body   a object that just contains a user id
      */
     @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/invite")
-    fun invite(@Path("roomId") roomId: String,
-               @Body body: InviteBody): Call<Unit>
+    suspend fun invite(@Path("roomId") roomId: String,
+                       @Body body: InviteBody)
 
     /**
      * Invite a user to a room, using a ThreePid
@@ -170,8 +178,8 @@ internal interface RoomAPI {
      * @param roomId Required. The room identifier (not alias) to which to invite the user.
      */
     @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/invite")
-    fun invite3pid(@Path("roomId") roomId: String,
-                   @Body body: ThreePidInviteBody): Call<Unit>
+    suspend fun invite3pid(@Path("roomId") roomId: String,
+                           @Body body: ThreePidInviteBody)
 
     /**
      * Send a generic state event
@@ -181,9 +189,9 @@ internal interface RoomAPI {
      * @param params         the request parameters
      */
     @PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/state/{state_event_type}")
-    fun sendStateEvent(@Path("roomId") roomId: String,
-                       @Path("state_event_type") stateEventType: String,
-                       @Body params: JsonDict): Call<Unit>
+    suspend fun sendStateEvent(@Path("roomId") roomId: String,
+                               @Path("state_event_type") stateEventType: String,
+                               @Body params: JsonDict)
 
     /**
      * Send a generic state event
@@ -194,17 +202,17 @@ internal interface RoomAPI {
      * @param params         the request parameters
      */
     @PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/state/{state_event_type}/{state_key}")
-    fun sendStateEvent(@Path("roomId") roomId: String,
-                       @Path("state_event_type") stateEventType: String,
-                       @Path("state_key") stateKey: String,
-                       @Body params: JsonDict): Call<Unit>
+    suspend fun sendStateEvent(@Path("roomId") roomId: String,
+                               @Path("state_event_type") stateEventType: String,
+                               @Path("state_key") stateKey: String,
+                               @Body params: JsonDict)
 
     /**
      * Get state events of a room
      * Ref: https://matrix.org/docs/spec/client_server/r0.6.1#get-matrix-client-r0-rooms-roomid-state
      */
     @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/state")
-    fun getRoomState(@Path("roomId") roomId: String) : Call<List<Event>>
+    suspend fun getRoomState(@Path("roomId") roomId: String): List<Event>
 
     /**
      * Send a relation event to a room.
@@ -215,12 +223,12 @@ internal interface RoomAPI {
      * @param content   the event content
      */
     @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/send_relation/{parent_id}/{relation_type}/{event_type}")
-    fun sendRelation(@Path("roomId") roomId: String,
-                     @Path("parent_id") parentId: String,
-                     @Path("relation_type") relationType: String,
-                     @Path("event_type") eventType: String,
-                     @Body content: Content?
-    ): Call<SendResponse>
+    suspend fun sendRelation(@Path("roomId") roomId: String,
+                             @Path("parent_id") parentId: String,
+                             @Path("relation_type") relationType: String,
+                             @Path("event_type") eventType: String,
+                             @Body content: Content?
+    ): SendResponse
 
     /**
      * Paginate relations for event based in normal topological order
@@ -229,11 +237,11 @@ internal interface RoomAPI {
      * @param eventType filter for this event type
      */
     @GET(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "rooms/{roomId}/relations/{eventId}/{relationType}/{eventType}")
-    fun getRelations(@Path("roomId") roomId: String,
-                     @Path("eventId") eventId: String,
-                     @Path("relationType") relationType: String,
-                     @Path("eventType") eventType: String
-    ): Call<RelationsResponse>
+    suspend fun getRelations(@Path("roomId") roomId: String,
+                             @Path("eventId") eventId: String,
+                             @Path("relationType") relationType: String,
+                             @Path("eventType") eventType: String
+    ): RelationsResponse
 
     /**
      * Join the given room.
@@ -243,9 +251,9 @@ internal interface RoomAPI {
      * @param params the request body
      */
     @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "join/{roomIdOrAlias}")
-    fun join(@Path("roomIdOrAlias") roomIdOrAlias: String,
-             @Query("server_name") viaServers: List<String>,
-             @Body params: Map<String, String?>): Call<JoinRoomResponse>
+    suspend fun join(@Path("roomIdOrAlias") roomIdOrAlias: String,
+                     @Query("server_name") viaServers: List<String>,
+                     @Body params: Map<String, String?>): JoinRoomResponse
 
     /**
      * Leave the given room.
@@ -254,8 +262,8 @@ internal interface RoomAPI {
      * @param params the request body
      */
     @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/leave")
-    fun leave(@Path("roomId") roomId: String,
-              @Body params: Map<String, String?>): Call<Unit>
+    suspend fun leave(@Path("roomId") roomId: String,
+                      @Body params: Map<String, String?>)
 
     /**
      * Ban a user from the given room.
@@ -264,8 +272,8 @@ internal interface RoomAPI {
      * @param userIdAndReason the banned user object (userId and reason for ban)
      */
     @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/ban")
-    fun ban(@Path("roomId") roomId: String,
-            @Body userIdAndReason: UserIdAndReason): Call<Unit>
+    suspend fun ban(@Path("roomId") roomId: String,
+                    @Body userIdAndReason: UserIdAndReason)
 
     /**
      * unban a user from the given room.
@@ -274,8 +282,8 @@ internal interface RoomAPI {
      * @param userIdAndReason the unbanned user object (userId and reason for unban)
      */
     @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/unban")
-    fun unban(@Path("roomId") roomId: String,
-              @Body userIdAndReason: UserIdAndReason): Call<Unit>
+    suspend fun unban(@Path("roomId") roomId: String,
+                      @Body userIdAndReason: UserIdAndReason)
 
     /**
      * Kick a user from the given room.
@@ -284,8 +292,8 @@ internal interface RoomAPI {
      * @param userIdAndReason the kicked user object (userId and reason for kicking)
      */
     @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/kick")
-    fun kick(@Path("roomId") roomId: String,
-             @Body userIdAndReason: UserIdAndReason): Call<Unit>
+    suspend fun kick(@Path("roomId") roomId: String,
+                     @Body userIdAndReason: UserIdAndReason)
 
     /**
      * Strips all information out of an event which isn't critical to the integrity of the server-side representation of the room.
@@ -298,12 +306,12 @@ internal interface RoomAPI {
      * @param reason   json containing reason key {"reason": "Indecent material"}
      */
     @PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/redact/{eventId}/{txnId}")
-    fun redactEvent(
+    suspend fun redactEvent(
             @Path("txnId") txId: String,
             @Path("roomId") roomId: String,
             @Path("eventId") eventId: String,
             @Body reason: Map<String, String>
-    ): Call<SendResponse>
+    ): SendResponse
 
     /**
      * Reports an event as inappropriate to the server, which may then notify the appropriate people.
@@ -313,24 +321,24 @@ internal interface RoomAPI {
      * @param body    body containing score and reason
      */
     @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/report/{eventId}")
-    fun reportContent(@Path("roomId") roomId: String,
-                      @Path("eventId") eventId: String,
-                      @Body body: ReportContentBody): Call<Unit>
+    suspend fun reportContent(@Path("roomId") roomId: String,
+                              @Path("eventId") eventId: String,
+                              @Body body: ReportContentBody)
 
     /**
      * Get a list of aliases maintained by the local server for the given room.
      * Ref: https://matrix.org/docs/spec/client_server/r0.6.1#get-matrix-client-r0-rooms-roomid-aliases
      */
     @GET(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "org.matrix.msc2432/rooms/{roomId}/aliases")
-    fun getAliases(@Path("roomId") roomId: String): Call<GetAliasesResponse>
+    suspend fun getAliases(@Path("roomId") roomId: String): GetAliasesResponse
 
     /**
      * Inform that the user is starting to type or has stopped typing
      */
     @PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/typing/{userId}")
-    fun sendTypingState(@Path("roomId") roomId: String,
-                        @Path("userId") userId: String,
-                        @Body body: TypingBody): Call<Unit>
+    suspend fun sendTypingState(@Path("roomId") roomId: String,
+                                @Path("userId") userId: String,
+                                @Body body: TypingBody)
 
     /**
      * Room tagging
@@ -340,16 +348,16 @@ internal interface RoomAPI {
      * Add a tag to a room.
      */
     @PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "user/{userId}/rooms/{roomId}/tags/{tag}")
-    fun putTag(@Path("userId") userId: String,
-               @Path("roomId") roomId: String,
-               @Path("tag") tag: String,
-               @Body body: TagBody): Call<Unit>
+    suspend fun putTag(@Path("userId") userId: String,
+                       @Path("roomId") roomId: String,
+                       @Path("tag") tag: String,
+                       @Body body: TagBody)
 
     /**
      * Delete a tag from a room.
      */
     @DELETE(NetworkConstants.URI_API_PREFIX_PATH_R0 + "user/{userId}/rooms/{roomId}/tags/{tag}")
-    fun deleteTag(@Path("userId") userId: String,
-                  @Path("roomId") roomId: String,
-                  @Path("tag") tag: String): Call<Unit>
+    suspend fun deleteTag(@Path("userId") userId: String,
+                          @Path("roomId") roomId: String,
+                          @Path("tag") tag: String)
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAvatarResolver.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAvatarResolver.kt
index 99f9d3644d58e867a97bb47d3e55df2a622838e9..60ad83ee055ebc668b1e7047138c33c8f4b08b36 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAvatarResolver.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAvatarResolver.kt
@@ -16,18 +16,19 @@
 
 package org.matrix.android.sdk.internal.session.room
 
+import io.realm.Realm
+import org.matrix.android.sdk.api.extensions.orFalse
 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.RoomAvatarContent
-import org.matrix.android.sdk.internal.database.mapper.ContentMapper
+import org.matrix.android.sdk.internal.database.mapper.asDomain
 import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntity
 import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntityFields
+import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity
 import org.matrix.android.sdk.internal.database.query.getOrNull
+import org.matrix.android.sdk.internal.database.query.where
 import org.matrix.android.sdk.internal.di.UserId
 import org.matrix.android.sdk.internal.session.room.membership.RoomMemberHelper
-import io.realm.Realm
-import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity
-import org.matrix.android.sdk.internal.database.query.where
 import javax.inject.Inject
 
 internal class RoomAvatarResolver @Inject constructor(@UserId private val userId: String) {
@@ -39,24 +40,35 @@ internal class RoomAvatarResolver @Inject constructor(@UserId private val userId
      * @return the room avatar url, can be a fallback to a room member avatar or null
      */
     fun resolve(realm: Realm, roomId: String): String? {
-        var res: String?
-        val roomName = CurrentStateEventEntity.getOrNull(realm, roomId, type = EventType.STATE_ROOM_AVATAR, stateKey = "")?.root
-        res = ContentMapper.map(roomName?.content).toModel<RoomAvatarContent>()?.avatarUrl
-        if (!res.isNullOrEmpty()) {
-            return res
+        val roomName = CurrentStateEventEntity.getOrNull(realm, roomId, type = EventType.STATE_ROOM_AVATAR, stateKey = "")
+                ?.root
+                ?.asDomain()
+                ?.content
+                ?.toModel<RoomAvatarContent>()
+                ?.avatarUrl
+        if (!roomName.isNullOrEmpty()) {
+            return roomName
         }
         val roomMembers = RoomMemberHelper(realm, roomId)
         val members = roomMembers.queryActiveRoomMembersEvent().findAll()
         // detect if it is a room with no more than 2 members (i.e. an alone or a 1:1 chat)
-        val isDirectRoom =  RoomSummaryEntity.where(realm, roomId).findFirst()?.isDirect ?: false
+        val isDirectRoom = RoomSummaryEntity.where(realm, roomId).findFirst()?.isDirect.orFalse()
+
         if (isDirectRoom) {
             if (members.size == 1) {
-                res = members.firstOrNull()?.avatarUrl
+                // Use avatar of a left user
+                val firstLeftAvatarUrl = roomMembers.queryLeftRoomMembersEvent()
+                        .findAll()
+                        .firstOrNull { !it.avatarUrl.isNullOrEmpty() }
+                        ?.avatarUrl
+
+                return firstLeftAvatarUrl ?: members.firstOrNull()?.avatarUrl
             } else if (members.size == 2) {
                 val firstOtherMember = members.where().notEqualTo(RoomMemberSummaryEntityFields.USER_ID, userId).findFirst()
-                res = firstOtherMember?.avatarUrl
+                return firstOtherMember?.avatarUrl
             }
         }
-        return res
+
+        return null
     }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomFactory.kt
index 63370a1ad8f6dd884ec15dcf0181bf0a8a177578..90640b47000dbfa674b9a0d793c1ae6ed39a35af 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomFactory.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomFactory.kt
@@ -36,7 +36,6 @@ import org.matrix.android.sdk.internal.session.room.timeline.DefaultTimelineServ
 import org.matrix.android.sdk.internal.session.room.typing.DefaultTypingService
 import org.matrix.android.sdk.internal.session.room.uploads.DefaultUploadsService
 import org.matrix.android.sdk.internal.session.search.SearchTask
-import org.matrix.android.sdk.internal.task.TaskExecutor
 import javax.inject.Inject
 
 internal interface RoomFactory {
@@ -60,7 +59,6 @@ internal class DefaultRoomFactory @Inject constructor(private val cryptoService:
                                                       private val relationServiceFactory: DefaultRelationService.Factory,
                                                       private val membershipServiceFactory: DefaultMembershipService.Factory,
                                                       private val roomPushRuleServiceFactory: DefaultRoomPushRuleService.Factory,
-                                                      private val taskExecutor: TaskExecutor,
                                                       private val sendStateTask: SendStateTask,
                                                       private val searchTask: SearchTask) :
         RoomFactory {
@@ -84,7 +82,6 @@ internal class DefaultRoomFactory @Inject constructor(private val cryptoService:
                 relationService = relationServiceFactory.create(roomId),
                 roomMembersService = membershipServiceFactory.create(roomId),
                 roomPushRuleService = roomPushRuleServiceFactory.create(roomId),
-                taskExecutor = taskExecutor,
                 sendStateTask = sendStateTask,
                 searchTask = searchTask
         )
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt
index 66b727236077cb25f132323659fa9092891e711e..5133f72932b2fe3d0ffe05009bb0cc9a2eea7c7a 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt
@@ -79,9 +79,11 @@ import org.matrix.android.sdk.internal.session.room.tags.DefaultDeleteTagFromRoo
 import org.matrix.android.sdk.internal.session.room.tags.DeleteTagFromRoomTask
 import org.matrix.android.sdk.internal.session.room.timeline.DefaultFetchTokenAndPaginateTask
 import org.matrix.android.sdk.internal.session.room.timeline.DefaultGetContextOfEventTask
+import org.matrix.android.sdk.internal.session.room.timeline.DefaultGetEventTask
 import org.matrix.android.sdk.internal.session.room.timeline.DefaultPaginationTask
 import org.matrix.android.sdk.internal.session.room.timeline.FetchTokenAndPaginateTask
 import org.matrix.android.sdk.internal.session.room.timeline.GetContextOfEventTask
+import org.matrix.android.sdk.internal.session.room.timeline.GetEventTask
 import org.matrix.android.sdk.internal.session.room.timeline.PaginationTask
 import org.matrix.android.sdk.internal.session.room.typing.DefaultSendTypingTask
 import org.matrix.android.sdk.internal.session.room.typing.SendTypingTask
@@ -228,4 +230,7 @@ internal abstract class RoomModule {
 
     @Binds
     abstract fun bindPeekRoomTask(task: DefaultPeekRoomTask): PeekRoomTask
+
+    @Binds
+    abstract fun bindGetEventTask(task: DefaultGetEventTask): GetEventTask
 }
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 9e4ec6f77779258ba3c550ce2f3795af69f68c24..97ea1d6ad15aced5961edeadcff1bbcc5f2ef630 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
@@ -45,8 +45,8 @@ internal class DefaultAddRoomAliasTask @Inject constructor(
     override suspend fun execute(params: AddRoomAliasTask.Params) {
         aliasAvailabilityChecker.check(params.aliasLocalPart)
 
-        executeRequest<Unit>(globalErrorReceiver) {
-            apiCall = directoryAPI.addRoomAlias(
+        executeRequest(globalErrorReceiver) {
+            directoryAPI.addRoomAlias(
                     roomAlias = params.aliasLocalPart.toFullLocalAlias(userId),
                     body = AddRoomAliasBody(
                             roomId = params.roomId
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 6ad3db90a92f42be824ee8c53235a022545bb93f..01ac3fcec84717b63c926fff7bd4b649d066f5cf 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
@@ -34,8 +34,8 @@ internal class DefaultDeleteRoomAliasTask @Inject constructor(
 ) : DeleteRoomAliasTask {
 
     override suspend fun execute(params: DeleteRoomAliasTask.Params) {
-        executeRequest<Unit>(globalErrorReceiver) {
-            apiCall = directoryAPI.deleteRoomAlias(
+        executeRequest(globalErrorReceiver) {
+            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 a53ffc4fcdaba71d05cf0ec0bfb0519bacbd63e0..71c8c9cd38cf0472c585b4b846c7fb6362c4327c 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
@@ -52,8 +52,8 @@ internal class DefaultGetRoomIdByAliasTask @Inject constructor(
             Optional.from(null)
         } else {
             val description  = tryOrNull("## Failed to get roomId from alias") {
-                executeRequest<RoomAliasDescription>(globalErrorReceiver) {
-                    apiCall = directoryAPI.getRoomIdByAlias(params.roomAlias)
+                executeRequest(globalErrorReceiver) {
+                    directoryAPI.getRoomIdByAlias(params.roomAlias)
                 }
             }
             Optional.from(description)
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 202cb1f6de9ea47249e56d0b557d9441ba44dc91..1ff4156ed3e857b2439c007af34ca91018383587 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
@@ -35,8 +35,8 @@ internal class DefaultGetRoomLocalAliasesTask @Inject constructor(
 
     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>(globalErrorReceiver) {
-            apiCall = roomAPI.getAliases(roomId = params.roomId)
+        val response = executeRequest(globalErrorReceiver) {
+            roomAPI.getAliases(roomId = params.roomId)
         }
 
         return response.aliases
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 51a849a35e9f9a190284e09a264cca16ac64da70..9faf50dd8b70b2a6fadab5387ef51644f578c853 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
@@ -41,8 +41,8 @@ internal class RoomAliasAvailabilityChecker @Inject constructor(
         // Check alias availability
         val fullAlias = aliasLocalPart.toFullLocalAlias(userId)
         try {
-            executeRequest<RoomAliasDescription>(globalErrorReceiver) {
-                apiCall = directoryAPI.getRoomIdByAlias(fullAlias)
+            executeRequest(globalErrorReceiver) {
+                directoryAPI.getRoomIdByAlias(fullAlias)
             }
         } catch (throwable: Throwable) {
             if (throwable is Failure.ServerError && throwable.httpCode == 404) {
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 9c16bd1b0f2214ec1d56a9f3eb448145fa6f3b78..bafe2b90ae0b197773436d386d29ff56b8083080 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
@@ -17,18 +17,19 @@
 package org.matrix.android.sdk.internal.session.room.create
 
 import com.zhuinden.monarchy.Monarchy
+import io.realm.Realm
 import io.realm.RealmConfiguration
 import kotlinx.coroutines.TimeoutCancellationException
 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
 import org.matrix.android.sdk.api.session.room.failure.CreateRoomFailure
+import org.matrix.android.sdk.api.session.room.model.Membership
 import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams
 import org.matrix.android.sdk.api.session.room.model.create.CreateRoomPreset
 import org.matrix.android.sdk.internal.database.awaitNotEmptyResult
-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.RoomSummaryEntity
+import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields
 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
@@ -75,8 +76,8 @@ internal class DefaultCreateRoomTask @Inject constructor(
         val createRoomBody = createRoomBodyBuilder.build(params)
 
         val createRoomResponse = try {
-            executeRequest<CreateRoomResponse>(globalErrorReceiver) {
-                apiCall = roomAPI.createRoom(createRoomBody)
+            executeRequest(globalErrorReceiver) {
+                roomAPI.createRoom(createRoomBody)
             }
         } catch (throwable: Throwable) {
             if (throwable is Failure.ServerError) {
@@ -96,12 +97,18 @@ internal class DefaultCreateRoomTask @Inject constructor(
         // Wait for room to come back from the sync (but it can maybe be in the DB if the sync response is received before)
         try {
             awaitNotEmptyResult(realmConfiguration, TimeUnit.MINUTES.toMillis(1L)) { realm ->
-                realm.where(RoomEntity::class.java)
-                        .equalTo(RoomEntityFields.ROOM_ID, roomId)
+                realm.where(RoomSummaryEntity::class.java)
+                        .equalTo(RoomSummaryEntityFields.ROOM_ID, roomId)
+                        .equalTo(RoomSummaryEntityFields.MEMBERSHIP_STR, Membership.JOIN.name)
             }
         } catch (exception: TimeoutCancellationException) {
             throw CreateRoomFailure.CreatedWithTimeout
         }
+
+        Realm.getInstance(realmConfiguration).executeTransactionAsync {
+            RoomSummaryEntity.where(it, roomId).findFirst()?.lastActivityTime = System.currentTimeMillis()
+        }
+
         if (otherUserId != null) {
             handleDirectChatCreation(roomId, otherUserId)
         }
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 edd8ae9b0dfe9a0531f5ee8179604502ffb0f1e0..4a6b0703c58067d6c85491a838f1e52fd6337003 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
@@ -38,7 +38,7 @@ internal class DefaultGetPublicRoomTask @Inject constructor(
 
     override suspend fun execute(params: GetPublicRoomTask.Params): PublicRoomsResponse {
         return executeRequest(globalErrorReceiver) {
-            apiCall = roomAPI.publicRooms(params.server, params.publicRoomsParams)
+            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 8d71001ef958345e5e8fe97703a8536111a61bbc..77492e429f3bee1f7dd2102a6711648791566378 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
@@ -20,7 +20,6 @@ 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
 import org.matrix.android.sdk.internal.task.Task
 import javax.inject.Inject
 
@@ -36,8 +35,8 @@ internal class DefaultGetRoomDirectoryVisibilityTask @Inject constructor(
 ) : GetRoomDirectoryVisibilityTask {
 
     override suspend fun execute(params: GetRoomDirectoryVisibilityTask.Params): RoomDirectoryVisibility {
-        return executeRequest<RoomDirectoryVisibilityJson>(globalErrorReceiver) {
-            apiCall = directoryAPI.getRoomDirectoryVisibility(params.roomId)
+        return executeRequest(globalErrorReceiver) {
+            directoryAPI.getRoomDirectoryVisibility(params.roomId)
         }
                 .visibility
     }
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 cbb0b6d5d1b85259fe308ad6652823fa51933316..f46d06bd5cd58a493d85099a5a6a2b625e32cc45 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
@@ -37,8 +37,8 @@ internal class DefaultSetRoomDirectoryVisibilityTask @Inject constructor(
 ) : SetRoomDirectoryVisibilityTask {
 
     override suspend fun execute(params: SetRoomDirectoryVisibilityTask.Params) {
-        executeRequest<Unit>(globalErrorReceiver) {
-            apiCall = directoryAPI.setRoomDirectoryVisibility(
+        executeRequest(globalErrorReceiver) {
+            directoryAPI.setRoomDirectoryVisibility(
                     params.roomId,
                     RoomDirectoryVisibilityJson(visibility = params.roomDirectoryVisibility)
             )
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 cd1c9bbbdd8e56a7f32ec0eb26cc24956320bc5a..41e891f78e6221c5dcd3f1be16b5abca5b7ee74c 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
@@ -21,13 +21,11 @@ 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
 import org.matrix.android.sdk.api.session.room.members.MembershipService
 import org.matrix.android.sdk.api.session.room.members.RoomMemberQueryParams
 import org.matrix.android.sdk.api.session.room.model.Membership
 import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary
-import org.matrix.android.sdk.api.util.Cancelable
 import org.matrix.android.sdk.internal.database.mapper.asDomain
 import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntity
 import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntityFields
@@ -39,8 +37,6 @@ import org.matrix.android.sdk.internal.session.room.membership.joining.InviteTas
 import org.matrix.android.sdk.internal.session.room.membership.joining.JoinRoomTask
 import org.matrix.android.sdk.internal.session.room.membership.leaving.LeaveRoomTask
 import org.matrix.android.sdk.internal.session.room.membership.threepid.InviteThreePidTask
-import org.matrix.android.sdk.internal.task.TaskExecutor
-import org.matrix.android.sdk.internal.task.configureWith
 import org.matrix.android.sdk.internal.util.fetchCopied
 import io.realm.Realm
 import io.realm.RealmQuery
@@ -48,7 +44,6 @@ import io.realm.RealmQuery
 internal class DefaultMembershipService @AssistedInject constructor(
         @Assisted private val roomId: String,
         @SessionDatabase private val monarchy: Monarchy,
-        private val taskExecutor: TaskExecutor,
         private val loadRoomMembersTask: LoadRoomMembersTask,
         private val inviteTask: InviteTask,
         private val inviteThreePidTask: InviteThreePidTask,
@@ -64,13 +59,9 @@ internal class DefaultMembershipService @AssistedInject constructor(
         fun create(roomId: String): DefaultMembershipService
     }
 
-    override fun loadRoomMembersIfNeeded(matrixCallback: MatrixCallback<Unit>): Cancelable {
+    override suspend fun loadRoomMembersIfNeeded() {
         val params = LoadRoomMembersTask.Params(roomId, Membership.LEAVE)
-        return loadRoomMembersTask
-                .configureWith(params) {
-                    this.callback = matrixCallback
-                }
-                .executeBy(taskExecutor)
+        loadRoomMembersTask.execute(params)
     }
 
     override fun getRoomMember(userId: String): RoomMemberSummary? {
@@ -120,66 +111,38 @@ internal class DefaultMembershipService @AssistedInject constructor(
         }
     }
 
-    override fun ban(userId: String, reason: String?, callback: MatrixCallback<Unit>): Cancelable {
+    override suspend fun ban(userId: String, reason: String?) {
         val params = MembershipAdminTask.Params(MembershipAdminTask.Type.BAN, roomId, userId, reason)
-        return membershipAdminTask
-                .configureWith(params) {
-                    this.callback = callback
-                }
-                .executeBy(taskExecutor)
+        membershipAdminTask.execute(params)
     }
 
-    override fun unban(userId: String, reason: String?, callback: MatrixCallback<Unit>): Cancelable {
+    override suspend fun unban(userId: String, reason: String?) {
         val params = MembershipAdminTask.Params(MembershipAdminTask.Type.UNBAN, roomId, userId, reason)
-        return membershipAdminTask
-                .configureWith(params) {
-                    this.callback = callback
-                }
-                .executeBy(taskExecutor)
+        membershipAdminTask.execute(params)
     }
 
-    override fun kick(userId: String, reason: String?, callback: MatrixCallback<Unit>): Cancelable {
+    override suspend fun kick(userId: String, reason: String?) {
         val params = MembershipAdminTask.Params(MembershipAdminTask.Type.KICK, roomId, userId, reason)
-        return membershipAdminTask
-                .configureWith(params) {
-                    this.callback = callback
-                }
-                .executeBy(taskExecutor)
+        membershipAdminTask.execute(params)
     }
 
-    override fun invite(userId: String, reason: String?, callback: MatrixCallback<Unit>): Cancelable {
+    override suspend fun invite(userId: String, reason: String?) {
         val params = InviteTask.Params(roomId, userId, reason)
-        return inviteTask
-                .configureWith(params) {
-                    this.callback = callback
-                }
-                .executeBy(taskExecutor)
+        inviteTask.execute(params)
     }
 
-    override fun invite3pid(threePid: ThreePid, callback: MatrixCallback<Unit>): Cancelable {
+    override suspend fun invite3pid(threePid: ThreePid) {
         val params = InviteThreePidTask.Params(roomId, threePid)
-        return inviteThreePidTask
-                .configureWith(params) {
-                    this.callback = callback
-                }
-                .executeBy(taskExecutor)
+        return inviteThreePidTask.execute(params)
     }
 
-    override fun join(reason: String?, viaServers: List<String>, callback: MatrixCallback<Unit>): Cancelable {
+    override suspend fun join(reason: String?, viaServers: List<String>) {
         val params = JoinRoomTask.Params(roomId, reason, viaServers)
-        return joinTask
-                .configureWith(params) {
-                    this.callback = callback
-                }
-                .executeBy(taskExecutor)
+        joinTask.execute(params)
     }
 
-    override fun leave(reason: String?, callback: MatrixCallback<Unit>): Cancelable {
+    override suspend fun leave(reason: String?) {
         val params = LeaveRoomTask.Params(roomId, reason)
-        return leaveRoomTask
-                .configureWith(params) {
-                    this.callback = callback
-                }
-                .executeBy(taskExecutor)
+        leaveRoomTask.execute(params)
     }
 }
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 97cfcdaa443bc285080dcd84a9fedf080b1f6113..3d0f51b831cf67c98394608a0d0b67f51f758a79 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
@@ -22,6 +22,8 @@ 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.crypto.CryptoSessionInfoProvider
+import org.matrix.android.sdk.internal.crypto.DeviceListManager
 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
@@ -57,6 +59,8 @@ internal class DefaultLoadRoomMembersTask @Inject constructor(
         private val syncTokenStore: SyncTokenStore,
         private val roomSummaryUpdater: RoomSummaryUpdater,
         private val roomMemberEventHandler: RoomMemberEventHandler,
+        private val cryptoSessionInfoProvider: CryptoSessionInfoProvider,
+        private val deviceListManager: DeviceListManager,
         private val globalErrorReceiver: GlobalErrorReceiver
 ) : LoadRoomMembersTask {
 
@@ -86,8 +90,8 @@ internal class DefaultLoadRoomMembersTask @Inject constructor(
 
         val lastToken = syncTokenStore.getLastToken()
         val response = try {
-            executeRequest<RoomMembersResponse>(globalErrorReceiver) {
-                apiCall = roomAPI.getMembers(params.roomId, lastToken, null, params.excludeMembership?.value)
+            executeRequest(globalErrorReceiver) {
+                roomAPI.getMembers(params.roomId, lastToken, null, params.excludeMembership)
             }
         } catch (throwable: Throwable) {
             // Revert status to NONE
@@ -124,6 +128,10 @@ internal class DefaultLoadRoomMembersTask @Inject constructor(
             roomEntity.membersLoadStatus = RoomMembersLoadStatusType.LOADED
             roomSummaryUpdater.update(realm, roomId, updateMembers = true)
         }
+
+        if (cryptoSessionInfoProvider.isRoomEncrypted(roomId)) {
+            deviceListManager.onRoomMembersLoadedFor(roomId)
+        }
     }
 
     private fun getRoomMembersLoadStatus(roomId: String): RoomMembersLoadStatusType {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomDisplayNameResolver.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomDisplayNameResolver.kt
index 0e18e30b130ab73a6551f1087f50e94c63853c9a..3aa812d93db69d5ea28e2235fa0b9e25eb221dfb 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomDisplayNameResolver.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomDisplayNameResolver.kt
@@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.session.room.membership
 
 import io.realm.Realm
 import org.matrix.android.sdk.api.MatrixConfiguration
+import org.matrix.android.sdk.api.extensions.orFalse
 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.Membership
@@ -51,14 +52,14 @@ internal class RoomDisplayNameResolver @Inject constructor(
      * @param roomId: the roomId to resolve the name of.
      * @return the room display name
      */
-    fun resolve(realm: Realm, roomId: String): CharSequence {
+    fun resolve(realm: Realm, roomId: String): String {
         // this algorithm is the one defined in
         // https://github.com/matrix-org/matrix-js-sdk/blob/develop/lib/models/room.js#L617
         // calculateRoomName(room, userId)
 
         // For Lazy Loaded room, see algorithm here:
         // https://docs.google.com/document/d/11i14UI1cUz-OJ0knD5BFu7fmT6Fo327zvMYqfSAR7xs/edit#heading=h.qif6pkqyjgzn
-        var name: CharSequence?
+        var name: String?
         val roomEntity = RoomEntity.where(realm, roomId = roomId).findFirst()
         val roomName = CurrentStateEventEntity.getOrNull(realm, roomId, type = EventType.STATE_ROOM_NAME, stateKey = "")?.root
         name = ContentMapper.map(roomName?.content).toModel<RoomNameContent>()?.name
@@ -77,14 +78,14 @@ internal class RoomDisplayNameResolver @Inject constructor(
         if (roomEntity?.membership == Membership.INVITE) {
             val inviteMeEvent = roomMembers.getLastStateEvent(userId)
             val inviterId = inviteMeEvent?.sender
-            name = if (inviterId != null) {
-                activeMembers.where()
-                        .equalTo(RoomMemberSummaryEntityFields.USER_ID, inviterId)
-                        .findFirst()
-                        ?.displayName
-            } else {
-                roomDisplayNameFallbackProvider.getNameForRoomInvite()
-            }
+            name = inviterId
+                    ?.let {
+                        activeMembers.where()
+                                .equalTo(RoomMemberSummaryEntityFields.USER_ID, it)
+                                .findFirst()
+                                ?.getBestName()
+                    }
+                    ?: roomDisplayNameFallbackProvider.getNameForRoomInvite()
         } else if (roomEntity?.membership == Membership.JOIN) {
             val roomSummary = RoomSummaryEntity.where(realm, roomId).findFirst()
             val invitedCount = roomSummary?.invitedMembersCount ?: 0
@@ -105,10 +106,17 @@ internal class RoomDisplayNameResolver @Inject constructor(
             val otherMembersCount = otherMembersSubset.count()
             name = when (otherMembersCount) {
                 0    -> {
-                    roomDisplayNameFallbackProvider.getNameForEmptyRoom()
-                    // TODO (was xx and yyy) ...
+                    // Get left members if any
+                    val leftMembersNames = roomMembers.queryLeftRoomMembersEvent()
+                            .findAll()
+                            .map { it.getBestName() }
+                    roomDisplayNameFallbackProvider.getNameForEmptyRoom(roomSummary?.isDirect.orFalse(), leftMembersNames)
+                }
+                1    -> {
+                    roomDisplayNameFallbackProvider.getNameFor1member(
+                            resolveRoomMemberName(otherMembersSubset[0], roomMembers)
+                    )
                 }
-                1    -> resolveRoomMemberName(otherMembersSubset[0], roomMembers)
                 2    -> {
                     roomDisplayNameFallbackProvider.getNameFor2members(
                             resolveRoomMemberName(otherMembersSubset[0], roomMembers),
@@ -145,12 +153,11 @@ internal class RoomDisplayNameResolver @Inject constructor(
     }
 
     /** See [org.matrix.android.sdk.api.session.room.sender.SenderInfo.disambiguatedDisplayName] */
-    private fun resolveRoomMemberName(roomMemberSummary: RoomMemberSummaryEntity?,
-                                      roomMemberHelper: RoomMemberHelper): String? {
-        if (roomMemberSummary == null) return null
+    private fun resolveRoomMemberName(roomMemberSummary: RoomMemberSummaryEntity,
+                                      roomMemberHelper: RoomMemberHelper): String {
         val isUnique = roomMemberHelper.isUniqueDisplayName(roomMemberSummary.displayName)
         return if (isUnique) {
-            roomMemberSummary.displayName
+            roomMemberSummary.getBestName()
         } else {
             "${roomMemberSummary.displayName} (${roomMemberSummary.userId})"
         }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomMemberEventHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomMemberEventHandler.kt
index 89fe2901c085e8ced39006bdd0c6594684c8b508..2ecacf335b0a365cd9d9c5c43fc996a1630470c3 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomMemberEventHandler.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomMemberEventHandler.kt
@@ -16,12 +16,12 @@
 
 package org.matrix.android.sdk.internal.session.room.membership
 
+import io.realm.Realm
 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.RoomMemberContent
+import org.matrix.android.sdk.internal.session.events.getFixedRoomMemberContent
 import org.matrix.android.sdk.internal.session.user.UserEntityFactory
-import io.realm.Realm
 import javax.inject.Inject
 
 internal class RoomMemberEventHandler @Inject constructor() {
@@ -31,7 +31,7 @@ internal class RoomMemberEventHandler @Inject constructor() {
             return false
         }
         val userId = event.stateKey ?: return false
-        val roomMember = event.content.toModel<RoomMemberContent>()
+        val roomMember = event.getFixedRoomMemberContent()
         return handle(realm, roomId, userId, roomMember)
     }
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomMemberHelper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomMemberHelper.kt
index 2a7c46bd4238fff80d69c97f6f9b56bcbdda7218..9ce8db25a5bec865c4412bc7d7c257b096f94e6d 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomMemberHelper.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomMemberHelper.kt
@@ -75,6 +75,11 @@ internal class RoomMemberHelper(private val realm: Realm,
                 .equalTo(RoomMemberSummaryEntityFields.MEMBERSHIP_STR, Membership.INVITE.name)
     }
 
+    fun queryLeftRoomMembersEvent(): RealmQuery<RoomMemberSummaryEntity> {
+        return queryRoomMembersEvent()
+                .equalTo(RoomMemberSummaryEntityFields.MEMBERSHIP_STR, Membership.LEAVE.name)
+    }
+
     fun queryActiveRoomMembersEvent(): RealmQuery<RoomMemberSummaryEntity> {
         return queryRoomMembersEvent()
                 .beginGroup()
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/admin/MembershipAdminTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/admin/MembershipAdminTask.kt
index 4654a2853692d4e813c954d4bd23cb6743fb33cc..d2c21f352038aff9cbc9c0fff3ab87a18769719a 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/admin/MembershipAdminTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/admin/MembershipAdminTask.kt
@@ -41,8 +41,8 @@ internal class DefaultMembershipAdminTask @Inject constructor(private val roomAP
 
     override suspend fun execute(params: MembershipAdminTask.Params) {
         val userIdAndReason = UserIdAndReason(params.userId, params.reason)
-        executeRequest<Unit>(null) {
-            apiCall = when (params.type) {
+        executeRequest(null) {
+            when (params.type) {
                 MembershipAdminTask.Type.BAN   -> roomAPI.ban(params.roomId, userIdAndReason)
                 MembershipAdminTask.Type.UNBAN -> roomAPI.unban(params.roomId, userIdAndReason)
                 MembershipAdminTask.Type.KICK  -> roomAPI.kick(params.roomId, userIdAndReason)
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 05503bd6436742c551a6991463b1fd0b98fc51d5..7e7b80baaf5cc946ede7df91afcfb98063b7bd73 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
@@ -36,11 +36,13 @@ internal class DefaultInviteTask @Inject constructor(
 ) : InviteTask {
 
     override suspend fun execute(params: InviteTask.Params) {
-        return executeRequest(globalErrorReceiver) {
-            val body = InviteBody(params.userId, params.reason)
-            apiCall = roomAPI.invite(params.roomId, body)
-            isRetryable = true
-            maxRetryCount = 3
+        val body = InviteBody(params.userId, params.reason)
+        return executeRequest(
+                globalErrorReceiver,
+                canRetry = true,
+                maxRetriesCount = 3
+        ) {
+            roomAPI.invite(params.roomId, body)
         }
     }
 }
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 3b7639d42fa28b0fd4f9693451a8e23144c598f8..33776e4f6ece17d41a1e1910167425093ff6d291 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
@@ -16,21 +16,23 @@
 
 package org.matrix.android.sdk.internal.session.room.membership.joining
 
+import io.realm.Realm
+import io.realm.RealmConfiguration
+import kotlinx.coroutines.TimeoutCancellationException
 import org.matrix.android.sdk.api.session.room.failure.JoinRoomFailure
 import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState
+import org.matrix.android.sdk.api.session.room.model.Membership
 import org.matrix.android.sdk.internal.database.awaitNotEmptyResult
-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.RoomSummaryEntity
+import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields
+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.create.JoinRoomResponse
 import org.matrix.android.sdk.internal.session.room.membership.RoomChangeMembershipStateDataSource
 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.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import java.util.concurrent.TimeUnit
 import javax.inject.Inject
 
@@ -54,8 +56,8 @@ internal class DefaultJoinRoomTask @Inject constructor(
     override suspend fun execute(params: JoinRoomTask.Params) {
         roomChangeMembershipStateDataSource.updateState(params.roomIdOrAlias, ChangeMembershipState.Joining)
         val joinRoomResponse = try {
-            executeRequest<JoinRoomResponse>(globalErrorReceiver) {
-                apiCall = roomAPI.join(
+            executeRequest(globalErrorReceiver) {
+                roomAPI.join(
                         roomIdOrAlias = params.roomIdOrAlias,
                         viaServers = params.viaServers.take(3),
                         params = mapOf("reason" to params.reason)
@@ -69,12 +71,18 @@ internal class DefaultJoinRoomTask @Inject constructor(
         val roomId = joinRoomResponse.roomId
         try {
             awaitNotEmptyResult(realmConfiguration, TimeUnit.MINUTES.toMillis(1L)) { realm ->
-                realm.where(RoomEntity::class.java)
-                        .equalTo(RoomEntityFields.ROOM_ID, roomId)
+                realm.where(RoomSummaryEntity::class.java)
+                        .equalTo(RoomSummaryEntityFields.ROOM_ID, roomId)
+                        .equalTo(RoomSummaryEntityFields.MEMBERSHIP_STR, Membership.JOIN.name)
             }
         } catch (exception: TimeoutCancellationException) {
             throw JoinRoomFailure.JoinedWithTimeout
         }
+
+        Realm.getInstance(realmConfiguration).executeTransactionAsync {
+            RoomSummaryEntity.where(it, roomId).findFirst()?.lastActivityTime = System.currentTimeMillis()
+        }
+
         setReadMarkers(roomId)
     }
 
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 37bb7570d17d599ab2e984ed4da88b343a1647c4..1b836e36a681da6db9d3031050c7b761cc25726f 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
@@ -68,8 +68,8 @@ internal class DefaultLeaveRoomTask @Inject constructor(
             leaveRoom(predecessorRoomId, reason)
         }
         try {
-            executeRequest<Unit>(globalErrorReceiver) {
-                apiCall = roomAPI.leave(roomId, mapOf("reason" to reason))
+            executeRequest(globalErrorReceiver) {
+                roomAPI.leave(roomId, mapOf("reason" to reason))
             }
         } catch (failure: Throwable) {
             roomChangeMembershipStateDataSource.updateState(roomId, ChangeMembershipState.FailedLeaving(failure))
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 d237ec795e2334b2ad92c8c75e2dbe35ba57bbb6..fa0a2d608a4bbc34c165cbb14904042f60e0667e 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
@@ -59,7 +59,7 @@ internal class DefaultInviteThreePidTask @Inject constructor(
                     medium = params.threePid.toMedium(),
                     address = params.threePid.value
             )
-            apiCall = roomAPI.invite3pid(params.roomId, body)
+            roomAPI.invite3pid(params.roomId, body)
         }
     }
 }
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 dbec6b555c6fa791b43c5c9fbe2417298c9f9f6e..64cbef23ecda6186b8c1aa202005b2053f579f5c 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
@@ -36,7 +36,7 @@ internal class DefaultResolveRoomStateTask @Inject constructor(
 
     override suspend fun execute(params: ResolveRoomStateTask.Params): List<Event> {
         return executeRequest(globalErrorReceiver) {
-            apiCall = roomAPI.getRoomState(params.roomId)
+            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 3cf8cfe5eef3d8f181004a51b666cb07347c3448..d4d03dca05abe3f788cd349e81f914d9b8ed3658 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
@@ -22,7 +22,6 @@ 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
 import org.matrix.android.sdk.api.session.room.read.ReadService
 import org.matrix.android.sdk.api.util.Optional
@@ -36,7 +35,6 @@ import org.matrix.android.sdk.internal.database.query.where
 import org.matrix.android.sdk.internal.di.SessionDatabase
 import org.matrix.android.sdk.internal.di.UserId
 import org.matrix.android.sdk.internal.task.TaskExecutor
-import org.matrix.android.sdk.internal.task.configureWith
 
 internal class DefaultReadService @AssistedInject constructor(
         @Assisted private val roomId: String,
@@ -52,35 +50,23 @@ internal class DefaultReadService @AssistedInject constructor(
         fun create(roomId: String): DefaultReadService
     }
 
-    override fun markAsRead(params: ReadService.MarkAsReadParams, callback: MatrixCallback<Unit>) {
+    override suspend fun markAsRead(params: ReadService.MarkAsReadParams) {
         val taskParams = SetReadMarkersTask.Params(
                 roomId = roomId,
                 forceReadMarker = params.forceReadMarker(),
                 forceReadReceipt = params.forceReadReceipt()
         )
-        setReadMarkersTask
-                .configureWith(taskParams) {
-                    this.callback = callback
-                }
-                .executeBy(taskExecutor)
+        setReadMarkersTask.execute(taskParams)
     }
 
-    override fun setReadReceipt(eventId: String, callback: MatrixCallback<Unit>) {
+    override suspend fun setReadReceipt(eventId: String) {
         val params = SetReadMarkersTask.Params(roomId, fullyReadEventId = null, readReceiptEventId = eventId)
-        setReadMarkersTask
-                .configureWith(params) {
-                    this.callback = callback
-                }
-                .executeBy(taskExecutor)
+        setReadMarkersTask.execute(params)
     }
 
-    override fun setReadMarker(fullyReadEventId: String, callback: MatrixCallback<Unit>) {
+    override suspend fun setReadMarker(fullyReadEventId: String) {
         val params = SetReadMarkersTask.Params(roomId, fullyReadEventId = fullyReadEventId, readReceiptEventId = null)
-        setReadMarkersTask
-                .configureWith(params) {
-                    this.callback = callback
-                }
-                .executeBy(taskExecutor)
+        setReadMarkersTask.execute(params)
     }
 
     override fun isEventRead(eventId: String): Boolean {
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 c7f962a699349900f6fba87f8d8f0d85470c1914..e4147d55b8e24e2542dd8706898db79dbed7800c 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
@@ -62,7 +62,7 @@ internal class DefaultSetReadMarkersTask @Inject constructor(
 ) : SetReadMarkersTask {
 
     override suspend fun execute(params: SetReadMarkersTask.Params) {
-        val markers = HashMap<String, String>()
+        val markers = mutableMapOf<String, String>()
         Timber.v("Execute set read marker with params: $params")
         val latestSyncedEventId = latestSyncedEventId(params.roomId)
         val fullyReadEventId = if (params.forceReadMarker) {
@@ -96,9 +96,18 @@ internal class DefaultSetReadMarkersTask @Inject constructor(
             updateDatabase(params.roomId, markers, shouldUpdateRoomSummary)
         }
         if (markers.isNotEmpty()) {
-            executeRequest<Unit>(globalErrorReceiver) {
-                isRetryable = true
-                apiCall = roomAPI.sendReadMarker(params.roomId, markers)
+            executeRequest(
+                    globalErrorReceiver,
+                    canRetry = true
+            ) {
+                if (markers[READ_MARKER] == null) {
+                    if (readReceiptEventId != null) {
+                        roomAPI.sendReceipt(params.roomId, READ_RECEIPT, readReceiptEventId)
+                    }
+                } else {
+                    // "m.fully_read" value is mandatory to make this call
+                    roomAPI.sendReadMarker(params.roomId, markers)
+                }
             }
         }
     }
@@ -108,7 +117,7 @@ internal class DefaultSetReadMarkersTask @Inject constructor(
                 TimelineEventEntity.latestEvent(realm, roomId = roomId, includesSending = false)?.eventId
             }
 
-    private suspend fun updateDatabase(roomId: String, markers: HashMap<String, String>, shouldUpdateRoomSummary: Boolean) {
+    private suspend fun updateDatabase(roomId: String, markers: Map<String, String>, shouldUpdateRoomSummary: Boolean) {
         monarchy.awaitTransaction { realm ->
             val readMarkerId = markers[READ_MARKER]
             val readReceiptId = markers[READ_RECEIPT]
@@ -117,7 +126,7 @@ internal class DefaultSetReadMarkersTask @Inject constructor(
             }
             if (readReceiptId != null) {
                 val readReceiptContent = ReadReceiptHandler.createContent(userId, readReceiptId)
-                readReceiptHandler.handle(realm, roomId, readReceiptContent, false)
+                readReceiptHandler.handle(realm, roomId, readReceiptContent, false, null)
             }
             if (shouldUpdateRoomSummary) {
                 val roomSummary = RoomSummaryEntity.where(realm, roomId).findFirst()
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 f9fd5f9348755bc43f58b5cf80f009ec045791cc..5f5c000171aeb122cbb525a618dbc1e74c7dec29 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
@@ -40,8 +40,8 @@ internal class DefaultFetchEditHistoryTask @Inject constructor(
 
     override suspend fun execute(params: FetchEditHistoryTask.Params): List<Event> {
         val isRoomEncrypted = cryptoSessionInfoProvider.isRoomEncrypted(params.roomId)
-        val response = executeRequest<RelationsResponse>(globalErrorReceiver) {
-            apiCall = roomAPI.getRelations(
+        val response = executeRequest(globalErrorReceiver) {
+            roomAPI.getRelations(
                     roomId = params.roomId,
                     eventId = params.eventId,
                     relationType = RelationType.REPLACE,
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 403aa274fed8e541cb0f3eabf8495bfb64b25699..5d0879d706e538048adec505b3abed64c6b701a0 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
@@ -28,7 +28,6 @@ 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
 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.worker.SessionSafeCoroutineWorker
 import org.matrix.android.sdk.internal.worker.SessionWorkerParams
 import javax.inject.Inject
@@ -84,8 +83,8 @@ internal class SendRelationWorker(context: Context, params: WorkerParameters)
     }
 
     private suspend fun sendRelation(roomId: String, relationType: String, relatedEventId: String, localEvent: Event) {
-        executeRequest<SendResponse>(globalErrorReceiver) {
-            apiCall = roomAPI.sendRelation(
+        executeRequest(globalErrorReceiver) {
+            roomAPI.sendRelation(
                     roomId = roomId,
                     parentId = relatedEventId,
                     relationType = relationType,
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 9c6e9907a4119e88435779f6d850fab12e61a540..29d507dfc546218c4f75d286434fd73f6adf8ce8 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
@@ -38,7 +38,7 @@ internal class DefaultReportContentTask @Inject constructor(
 
     override suspend fun execute(params: ReportContentTask.Params) {
         return executeRequest(globalErrorReceiver) {
-            apiCall = roomAPI.reportContent(params.roomId, params.eventId, ReportContentBody(params.score, params.reason))
+            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 c5b8b42b3c48916acb37e1d5460ec044217d2074..26a87557ff490686964c163542bf0965c92e2768 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
@@ -318,7 +318,7 @@ internal class DefaultSendService @AssistedInject constructor(
                 .setConstraints(WorkManagerProvider.workConstraints)
                 .startChain(true)
                 .setInputData(uploadWorkData)
-                .setBackoffCriteria(BackoffPolicy.LINEAR, WorkManagerProvider.BACKOFF_DELAY, TimeUnit.MILLISECONDS)
+                .setBackoffCriteria(BackoffPolicy.LINEAR, WorkManagerProvider.BACKOFF_DELAY_MILLIS, TimeUnit.MILLISECONDS)
                 .build()
     }
 
@@ -332,7 +332,7 @@ internal class DefaultSendService @AssistedInject constructor(
                 // .setConstraints(WorkManagerProvider.workConstraints)
                 .startChain(false)
                 .setInputData(workData)
-                .setBackoffCriteria(BackoffPolicy.LINEAR, WorkManagerProvider.BACKOFF_DELAY, TimeUnit.MILLISECONDS)
+                .setBackoffCriteria(BackoffPolicy.LINEAR, WorkManagerProvider.BACKOFF_DELAY_MILLIS, TimeUnit.MILLISECONDS)
                 .build()
     }
 }
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 c901c7e18eb0d72c1c95ce56cdcf1f4b29e68d49..306f865408395917213e7d47e787449df414f284 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
@@ -55,8 +55,8 @@ internal class RedactEventWorker(context: Context, params: WorkerParameters)
     override suspend fun doSafeWork(params: Params): Result {
         val eventId = params.eventId
         return runCatching {
-            executeRequest<SendResponse>(globalErrorReceiver) {
-                apiCall = roomAPI.redactEvent(
+            executeRequest(globalErrorReceiver) {
+                roomAPI.redactEvent(
                         params.txID,
                         params.roomId,
                         eventId,
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 c1fc2fd9fee405d1f0936a8b44bb8477b8f377a4..d55dce57afaadb87a322ba60772616008c7d482f 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
@@ -91,7 +91,7 @@ internal class SendEventWorker(context: Context,
             if (/*currentAttemptCount >= MAX_NUMBER_OF_RETRY_BEFORE_FAILING ||**/ !exception.shouldBeRetried()) {
                 Timber.e("## SendEvent: [${System.currentTimeMillis()}]  Send event Failed cannot retry ${params.eventId} > ${exception.localizedMessage}")
                 localEchoRepository.updateSendState(event.eventId, event.roomId, SendState.UNDELIVERED)
-                return Result.success()
+                Result.success()
             } else {
                 Timber.e("## SendEvent: [${System.currentTimeMillis()}]  Send event Failed schedule retry ${params.eventId} > ${exception.localizedMessage}")
                 Result.retry()
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/EventSenderProcessorCoroutine.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/EventSenderProcessorCoroutine.kt
index 297233298956f176a8494dc0182e68ea853ff21f..a5c09f5ff69aa80ab593fa0bb49479e0efd6b332 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/EventSenderProcessorCoroutine.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/EventSenderProcessorCoroutine.kt
@@ -24,6 +24,7 @@ import kotlinx.coroutines.withContext
 import org.matrix.android.sdk.api.auth.data.SessionParams
 import org.matrix.android.sdk.api.failure.Failure
 import org.matrix.android.sdk.api.failure.MatrixError
+import org.matrix.android.sdk.api.failure.getRetryDelay
 import org.matrix.android.sdk.api.session.crypto.CryptoService
 import org.matrix.android.sdk.api.session.events.model.Event
 import org.matrix.android.sdk.api.util.Cancelable
@@ -148,8 +149,7 @@ internal class EventSenderProcessorCoroutine @Inject constructor(
                     task.markAsFailedOrRetry(exception, 0)
                 }
                 (exception is Failure.ServerError && exception.error.code == MatrixError.M_LIMIT_EXCEEDED) -> {
-                    val delay = exception.error.retryAfterMillis?.plus(100) ?: 3_000
-                    task.markAsFailedOrRetry(exception, delay)
+                    task.markAsFailedOrRetry(exception, exception.getRetryDelay(3_000))
                 }
                 exception is CancellationException                                                         -> {
                     Timber.v("## $task has been cancelled, try next task")
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 f2640fd1e733d21629f505d8a3b3a4434403273a..615bc99096d63055e1df98b7cea56f9279a998bc 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
@@ -27,10 +27,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.GuestAccess
 import org.matrix.android.sdk.api.session.room.model.RoomCanonicalAliasContent
-import org.matrix.android.sdk.api.session.room.model.RoomGuestAccessContent
 import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility
 import org.matrix.android.sdk.api.session.room.model.RoomJoinRules
-import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesContent
 import org.matrix.android.sdk.api.session.room.state.StateService
 import org.matrix.android.sdk.api.util.JsonDict
 import org.matrix.android.sdk.api.util.MimeTypes
@@ -131,14 +129,14 @@ internal class DefaultStateService @AssistedInject constructor(@Assisted private
         if (joinRules != null) {
             sendStateEvent(
                     eventType = EventType.STATE_ROOM_JOIN_RULES,
-                    body = RoomJoinRulesContent(joinRules).toContent(),
+                    body = mapOf("join_rule" to joinRules),
                     stateKey = null
             )
         }
         if (guestAccess != null) {
             sendStateEvent(
                     eventType = EventType.STATE_ROOM_GUEST_ACCESS,
-                    body = RoomGuestAccessContent(guestAccess).toContent(),
+                    body = mapOf("guest_access" to guestAccess),
                     stateKey = null
             )
         }
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 63691d9207e816109f0f3a8fdc24dd8a0f91d77c..998e116a0ec94de5759a30bc59ba955812448f7a 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
@@ -39,7 +39,7 @@ internal class DefaultSendStateTask @Inject constructor(
 
     override suspend fun execute(params: SendStateTask.Params) {
         return executeRequest(globalErrorReceiver) {
-            apiCall = if (params.stateKey == null) {
+            if (params.stateKey == null) {
                 roomAPI.sendStateEvent(
                         roomId = params.roomId,
                         stateEventType = params.eventType,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt
index 107055b8c3ce308704201ba4f98585bb1cdf7a1c..dd3fbe04b27890a13a3325e0ac39bb28a65c476d 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt
@@ -18,10 +18,18 @@ package org.matrix.android.sdk.internal.session.room.summary
 
 import androidx.lifecycle.LiveData
 import androidx.lifecycle.Transformations
+import androidx.paging.LivePagedListBuilder
+import androidx.paging.PagedList
 import com.zhuinden.monarchy.Monarchy
+import io.realm.Realm
+import io.realm.RealmQuery
+import io.realm.Sort
+import org.matrix.android.sdk.api.query.RoomCategoryFilter
 import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams
+import org.matrix.android.sdk.api.session.room.UpdatableFilterLivePageResult
 import org.matrix.android.sdk.api.session.room.model.RoomSummary
 import org.matrix.android.sdk.api.session.room.model.VersioningState
+import org.matrix.android.sdk.api.session.room.summary.RoomAggregateNotificationCount
 import org.matrix.android.sdk.api.util.Optional
 import org.matrix.android.sdk.api.util.toOptional
 import org.matrix.android.sdk.internal.database.mapper.RoomSummaryMapper
@@ -32,8 +40,6 @@ import org.matrix.android.sdk.internal.database.query.where
 import org.matrix.android.sdk.internal.di.SessionDatabase
 import org.matrix.android.sdk.internal.query.process
 import org.matrix.android.sdk.internal.util.fetchCopyMap
-import io.realm.Realm
-import io.realm.RealmQuery
 import javax.inject.Inject
 
 internal class RoomSummaryDataSource @Inject constructor(@SessionDatabase private val monarchy: Monarchy,
@@ -98,6 +104,62 @@ internal class RoomSummaryDataSource @Inject constructor(@SessionDatabase privat
                 .sort(RoomSummaryEntityFields.BREADCRUMBS_INDEX)
     }
 
+    fun getSortedPagedRoomSummariesLive(queryParams: RoomSummaryQueryParams,
+                                        pagedListConfig: PagedList.Config): LiveData<PagedList<RoomSummary>> {
+        val realmDataSourceFactory = monarchy.createDataSourceFactory { realm ->
+            roomSummariesQuery(realm, queryParams)
+                    .sort(RoomSummaryEntityFields.LAST_ACTIVITY_TIME, Sort.DESCENDING)
+        }
+        val dataSourceFactory = realmDataSourceFactory.map {
+            roomSummaryMapper.map(it)
+        }
+        return monarchy.findAllPagedWithChanges(
+                realmDataSourceFactory,
+                LivePagedListBuilder(dataSourceFactory, pagedListConfig)
+        )
+    }
+
+    fun getFilteredPagedRoomSummariesLive(queryParams: RoomSummaryQueryParams,
+                                          pagedListConfig: PagedList.Config): UpdatableFilterLivePageResult {
+        val realmDataSourceFactory = monarchy.createDataSourceFactory { realm ->
+            roomSummariesQuery(realm, queryParams)
+                    .sort(RoomSummaryEntityFields.LAST_ACTIVITY_TIME, Sort.DESCENDING)
+        }
+        val dataSourceFactory = realmDataSourceFactory.map {
+            roomSummaryMapper.map(it)
+        }
+
+        val mapped = monarchy.findAllPagedWithChanges(
+                realmDataSourceFactory,
+                LivePagedListBuilder(dataSourceFactory, pagedListConfig)
+        )
+
+        return object : UpdatableFilterLivePageResult {
+            override val livePagedList: LiveData<PagedList<RoomSummary>> = mapped
+
+            override fun updateQuery(queryParams: RoomSummaryQueryParams) {
+                realmDataSourceFactory.updateQuery {
+                    roomSummariesQuery(it, queryParams)
+                            .sort(RoomSummaryEntityFields.LAST_ACTIVITY_TIME, Sort.DESCENDING)
+                }
+            }
+        }
+    }
+
+    fun getNotificationCountForRooms(queryParams: RoomSummaryQueryParams): RoomAggregateNotificationCount {
+        var notificationCount: RoomAggregateNotificationCount? = null
+        monarchy.doWithRealm { realm ->
+            val roomSummariesQuery = roomSummariesQuery(realm, queryParams)
+            val notifCount = roomSummariesQuery.sum(RoomSummaryEntityFields.NOTIFICATION_COUNT).toInt()
+            val highlightCount = roomSummariesQuery.sum(RoomSummaryEntityFields.HIGHLIGHT_COUNT).toInt()
+            notificationCount = RoomAggregateNotificationCount(
+                    notifCount,
+                    highlightCount
+            )
+        }
+        return notificationCount!!
+    }
+
     private fun roomSummariesQuery(realm: Realm, queryParams: RoomSummaryQueryParams): RealmQuery<RoomSummaryEntity> {
         val query = RoomSummaryEntity.where(realm)
         query.process(RoomSummaryEntityFields.ROOM_ID, queryParams.roomId)
@@ -105,6 +167,28 @@ internal class RoomSummaryDataSource @Inject constructor(@SessionDatabase privat
         query.process(RoomSummaryEntityFields.CANONICAL_ALIAS, queryParams.canonicalAlias)
         query.process(RoomSummaryEntityFields.MEMBERSHIP_STR, queryParams.memberships)
         query.notEqualTo(RoomSummaryEntityFields.VERSIONING_STATE_STR, VersioningState.UPGRADED_ROOM_JOINED.name)
+
+        queryParams.roomCategoryFilter?.let {
+            when (it) {
+                RoomCategoryFilter.ONLY_DM                 -> query.equalTo(RoomSummaryEntityFields.IS_DIRECT, true)
+                RoomCategoryFilter.ONLY_ROOMS              -> query.equalTo(RoomSummaryEntityFields.IS_DIRECT, false)
+                RoomCategoryFilter.ONLY_WITH_NOTIFICATIONS -> query.greaterThan(RoomSummaryEntityFields.NOTIFICATION_COUNT, 0)
+                RoomCategoryFilter.ALL                     -> {
+                    // nop
+                }
+            }
+        }
+        queryParams.roomTagQueryFilter?.let {
+            it.isFavorite?.let { fav ->
+                query.equalTo(RoomSummaryEntityFields.IS_FAVOURITE, fav)
+            }
+            it.isLowPriority?.let { lp ->
+                query.equalTo(RoomSummaryEntityFields.IS_LOW_PRIORITY, lp)
+            }
+            it.isServerNotice?.let { sn ->
+                query.equalTo(RoomSummaryEntityFields.IS_SERVER_NOTICE, sn)
+            }
+        }
         return query
     }
 }
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 fff780fb0c65ce5d063d502c087797424c700394..7913bf71a2ac69f55687fc0c840e8091ac47af56 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
@@ -98,11 +98,16 @@ internal class RoomSummaryUpdater @Inject constructor(
 
         val latestPreviewableEvent = RoomSummaryEventsHelper.getLatestPreviewableEvent(realm, roomId)
 
+        val lastActivityFromEvent = latestPreviewableEvent?.root?.originServerTs
+        if (lastActivityFromEvent != null) {
+            roomSummaryEntity.lastActivityTime = lastActivityFromEvent
+        }
+
         roomSummaryEntity.hasUnreadMessages = roomSummaryEntity.notificationCount > 0
                 // avoid this call if we are sure there are unread events
                 || !isEventRead(realm.configuration, userId, roomId, latestPreviewableEvent?.eventId)
 
-        roomSummaryEntity.displayName = roomDisplayNameResolver.resolve(realm, roomId).toString()
+        roomSummaryEntity.displayName = roomDisplayNameResolver.resolve(realm, roomId)
         roomSummaryEntity.avatarUrl = roomAvatarResolver.resolve(realm, roomId)
         roomSummaryEntity.name = ContentMapper.map(lastNameEvent?.content).toModel<RoomNameContent>()?.name
         roomSummaryEntity.topic = ContentMapper.map(lastTopicEvent?.content).toModel<RoomTopicContent>()?.topic
@@ -112,9 +117,7 @@ internal class RoomSummaryUpdater @Inject constructor(
 
         val roomAliases = ContentMapper.map(lastAliasesEvent?.content).toModel<RoomAliasesContent>()?.aliases
                 .orEmpty()
-        roomSummaryEntity.aliases.clear()
-        roomSummaryEntity.aliases.addAll(roomAliases)
-        roomSummaryEntity.flatAliases = roomAliases.joinToString(separator = "|", prefix = "|")
+        roomSummaryEntity.updateAliases(roomAliases)
         roomSummaryEntity.isEncrypted = encryptionEvent != null
         roomSummaryEntity.encryptionEventTs = encryptionEvent?.originServerTs
 
@@ -131,8 +134,8 @@ internal class RoomSummaryUpdater @Inject constructor(
             // mmm i want to decrypt now or is it ok to do it async?
             tryOrNull {
                 eventDecryptor.decryptEvent(root.asDomain(), "")
-                // eventDecryptor.decryptEventAsync(root.asDomain(), "", NoOpMatrixCallback())
             }
+                    ?.let { root.setDecryptionResult(it) }
         }
 
         if (updateMembers) {
@@ -144,7 +147,7 @@ internal class RoomSummaryUpdater @Inject constructor(
 
             roomSummaryEntity.otherMemberIds.clear()
             roomSummaryEntity.otherMemberIds.addAll(otherRoomMembers)
-            if (roomSummaryEntity.isEncrypted) {
+            if (roomSummaryEntity.isEncrypted && otherRoomMembers.isNotEmpty()) {
                 // mmm maybe we could only refresh shield instead of checking trust also?
                 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 c3b5c3f78fa2448e6099b7167efb77562ad89323..3e82d674ce0768b9952753bda77bccb898009ccd 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
@@ -39,8 +39,8 @@ internal class DefaultAddTagToRoomTask @Inject constructor(
 ) : AddTagToRoomTask {
 
     override suspend fun execute(params: AddTagToRoomTask.Params) {
-        executeRequest<Unit>(globalErrorReceiver) {
-            apiCall = roomAPI.putTag(
+        executeRequest(globalErrorReceiver) {
+            roomAPI.putTag(
                     userId = userId,
                     roomId = params.roomId,
                     tag = params.tag,
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 d578d21fde13c558aa488a3cd5b89232f28d2b86..ae2a050659caa9be7e1ccd65ef316bbc6cfe59b9 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
@@ -38,8 +38,8 @@ internal class DefaultDeleteTagFromRoomTask @Inject constructor(
 ) : DeleteTagFromRoomTask {
 
     override suspend fun execute(params: DeleteTagFromRoomTask.Params) {
-        executeRequest<Unit>(globalErrorReceiver) {
-            apiCall = roomAPI.deleteTag(
+        executeRequest(globalErrorReceiver) {
+            roomAPI.deleteTag(
                     userId = userId,
                     roomId = params.roomId,
                     tag = params.tag
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 d0946abe28c1747ff705dc52d962ee169a1a31c1..e230599f8f6d1d013cfd4d03ae6d720be6bcff18 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
@@ -28,7 +28,6 @@ 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
-import org.matrix.android.sdk.api.session.room.model.ReadReceipt
 import org.matrix.android.sdk.api.session.room.send.SendState
 import org.matrix.android.sdk.api.session.room.timeline.Timeline
 import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
@@ -44,6 +43,7 @@ import org.matrix.android.sdk.internal.database.query.findAllInRoomWithSendState
 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.session.sync.ReadReceiptHandler
 import org.matrix.android.sdk.internal.task.TaskExecutor
 import org.matrix.android.sdk.internal.task.configureWith
 import org.matrix.android.sdk.internal.util.Debouncer
@@ -69,13 +69,12 @@ internal class DefaultTimeline(
         private val paginationTask: PaginationTask,
         private val timelineEventMapper: TimelineEventMapper,
         private val settings: TimelineSettings,
-        private val hiddenReadReceipts: TimelineHiddenReadReceipts,
         private val timelineInput: TimelineInput,
         private val eventDecryptor: TimelineEventDecryptor,
         private val realmSessionProvider: RealmSessionProvider,
-        private val loadRoomMembersTask: LoadRoomMembersTask
+        private val loadRoomMembersTask: LoadRoomMembersTask,
+        private val readReceiptHandler: ReadReceiptHandler
 ) : Timeline,
-        TimelineHiddenReadReceipts.Delegate,
         TimelineInput.Listener,
         UIEchoManager.Listener {
 
@@ -91,8 +90,7 @@ internal class DefaultTimeline(
     private val cancelableBag = CancelableBag()
     private val debouncer = Debouncer(mainHandler)
 
-    private lateinit var nonFilteredEvents: RealmResults<TimelineEventEntity>
-    private lateinit var filteredEvents: RealmResults<TimelineEventEntity>
+    private lateinit var timelineEvents: RealmResults<TimelineEventEntity>
     private lateinit var sendingEvents: RealmResults<TimelineEventEntity>
 
     private var prevDisplayIndex: Int? = null
@@ -166,29 +164,34 @@ internal class DefaultTimeline(
                     postSnapshot()
                 }
 
-                nonFilteredEvents = buildEventQuery(realm).sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.DESCENDING).findAll()
-                filteredEvents = nonFilteredEvents.where()
-                        .filterEventsWithSettings(settings)
-                        .findAll()
-                nonFilteredEvents.addChangeListener(eventsChangeListener)
+                timelineEvents = buildEventQuery(realm).sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.DESCENDING).findAll()
+                timelineEvents.addChangeListener(eventsChangeListener)
                 handleInitialLoad()
-                if (settings.shouldHandleHiddenReadReceipts()) {
-                    hiddenReadReceipts.start(realm, filteredEvents, nonFilteredEvents, this)
-                }
-
                 loadRoomMembersTask
                         .configureWith(LoadRoomMembersTask.Params(roomId)) {
                             this.callback = NoOpMatrixCallback()
                         }
                         .executeBy(taskExecutor)
 
+                // Ensure ReadReceipt from init sync are loaded
+                ensureReadReceiptAreLoaded(realm)
+
                 isReady.set(true)
             }
         }
     }
 
-    private fun TimelineSettings.shouldHandleHiddenReadReceipts(): Boolean {
-        return buildReadReceipts && (filters.filterEdits || filters.filterTypes)
+    private fun ensureReadReceiptAreLoaded(realm: Realm) {
+        readReceiptHandler.getContentFromInitSync(roomId)
+                ?.also {
+                    Timber.w("INIT_SYNC Insert when opening timeline RR for room $roomId")
+                }
+                ?.let { readReceiptContent ->
+                    realm.executeTransactionAsync {
+                        readReceiptHandler.handle(it, roomId, readReceiptContent, false, null)
+                        readReceiptHandler.onContentFromInitSyncHandled(roomId)
+                    }
+                }
     }
 
     override fun dispose() {
@@ -202,11 +205,8 @@ internal class DefaultTimeline(
                 if (this::sendingEvents.isInitialized) {
                     sendingEvents.removeAllChangeListeners()
                 }
-                if (this::nonFilteredEvents.isInitialized) {
-                    nonFilteredEvents.removeAllChangeListeners()
-                }
-                if (settings.shouldHandleHiddenReadReceipts()) {
-                    hiddenReadReceipts.dispose()
+                if (this::timelineEvents.isInitialized) {
+                    timelineEvents.removeAllChangeListeners()
                 }
                 clearAllValues()
                 backgroundRealm.getAndSet(null).also {
@@ -238,48 +238,6 @@ internal class DefaultTimeline(
         }
     }
 
-    override fun getFirstDisplayableEventId(eventId: String): String? {
-        // If the item is built, the id is obviously displayable
-        val builtIndex = builtEventsIdMap[eventId]
-        if (builtIndex != null) {
-            return eventId
-        }
-        // Otherwise, we should check if the event is in the db, but is hidden because of filters
-        return realmSessionProvider.withRealm { localRealm ->
-            val nonFilteredEvents = buildEventQuery(localRealm)
-                    .sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.DESCENDING)
-                    .findAll()
-
-            val nonFilteredEvent = nonFilteredEvents.where()
-                    .equalTo(TimelineEventEntityFields.EVENT_ID, eventId)
-                    .findFirst()
-
-            val filteredEvents = nonFilteredEvents.where()
-                    .filterEventsWithSettings(settings)
-                    .findAll()
-            val isEventInDb = nonFilteredEvent != null
-
-            val isHidden = isEventInDb && filteredEvents.where()
-                    .equalTo(TimelineEventEntityFields.EVENT_ID, eventId)
-                    .findFirst() == null
-
-            if (isHidden) {
-                val displayIndex = nonFilteredEvent?.displayIndex
-                if (displayIndex != null) {
-                    // Then we are looking for the first displayable event after the hidden one
-                    val firstDisplayedEvent = filteredEvents.where()
-                            .lessThanOrEqualTo(TimelineEventEntityFields.DISPLAY_INDEX, displayIndex)
-                            .findFirst()
-                    firstDisplayedEvent?.eventId
-                } else {
-                    null
-                }
-            } else {
-                null
-            }
-        }
-    }
-
     override fun hasMoreToLoad(direction: Timeline.Direction): Boolean {
         return hasMoreInCache(direction) || !hasReachedEnd(direction)
     }
@@ -301,18 +259,6 @@ internal class DefaultTimeline(
         listeners.clear()
     }
 
-// TimelineHiddenReadReceipts.Delegate
-
-    override fun rebuildEvent(eventId: String, readReceipts: List<ReadReceipt>): Boolean {
-        return rebuildEvent(eventId) { te ->
-            te.copy(readReceipts = readReceipts)
-        }
-    }
-
-    override fun onReadReceiptsUpdated() {
-        postSnapshot()
-    }
-
     override fun onNewTimelineEvents(roomId: String, eventIds: List<String>) {
         if (isLive && this.roomId == roomId) {
             listeners.forEach {
@@ -323,18 +269,13 @@ internal class DefaultTimeline(
 
     override fun onLocalEchoCreated(roomId: String, timelineEvent: TimelineEvent) {
         if (roomId != this.roomId || !isLive) return
-
-        val postSnapShot = uiEchoManager.onLocalEchoCreated(timelineEvent)
-
-        if (listOf(timelineEvent).filterEventsWithSettings(settings).isNotEmpty()) {
-            listeners.forEach {
+        uiEchoManager.onLocalEchoCreated(timelineEvent)
+        listeners.forEach {
+            tryOrNull {
                 it.onNewTimelineEvents(listOf(timelineEvent.eventId))
             }
         }
-
-        if (postSnapShot) {
-            postSnapshot()
-        }
+        postSnapshot()
     }
 
     override fun onLocalEchoUpdated(roomId: String, eventId: String, sendState: SendState) {
@@ -421,23 +362,21 @@ internal class DefaultTimeline(
         val builtSendingEvents = mutableListOf<TimelineEvent>()
         if (hasReachedEnd(Timeline.Direction.FORWARDS) && !hasMoreInCache(Timeline.Direction.FORWARDS)) {
             uiEchoManager.getInMemorySendingEvents()
-                    .filterSendingEventsTo(builtSendingEvents)
+                    .updateWithUiEchoInto(builtSendingEvents)
             sendingEvents
                     .filter { timelineEvent ->
                         builtSendingEvents.none { it.eventId == timelineEvent.eventId }
                     }
                     .map { timelineEventMapper.map(it) }
-                    .filterSendingEventsTo(builtSendingEvents)
+                    .updateWithUiEchoInto(builtSendingEvents)
         }
         return builtSendingEvents
     }
 
-    private fun List<TimelineEvent>.filterSendingEventsTo(target: MutableList<TimelineEvent>) {
+    private fun List<TimelineEvent>.updateWithUiEchoInto(target: MutableList<TimelineEvent>) {
         target.addAll(
-                // Filter out sending event that are not displayable!
-                filterEventsWithSettings(settings)
-                        // Get most up to date send state (in memory)
-                        .map { uiEchoManager.updateSentStateWithUiEcho(it) }
+                // Get most up to date send state (in memory)
+                map { uiEchoManager.updateSentStateWithUiEcho(it) }
         )
     }
 
@@ -469,9 +408,9 @@ internal class DefaultTimeline(
         var shouldFetchInitialEvent = false
         val currentInitialEventId = initialEventId
         val initialDisplayIndex = if (currentInitialEventId == null) {
-            nonFilteredEvents.firstOrNull()?.displayIndex
+            timelineEvents.firstOrNull()?.displayIndex
         } else {
-            val initialEvent = nonFilteredEvents.where()
+            val initialEvent = timelineEvents.where()
                     .equalTo(TimelineEventEntityFields.EVENT_ID, initialEventId)
                     .findFirst()
 
@@ -483,7 +422,7 @@ internal class DefaultTimeline(
         if (currentInitialEventId != null && shouldFetchInitialEvent) {
             fetchEvent(currentInitialEventId)
         } else {
-            val count = filteredEvents.size.coerceAtMost(settings.initialSize)
+            val count = timelineEvents.size.coerceAtMost(settings.initialSize)
             if (initialEventId == null) {
                 paginateInternal(initialDisplayIndex, Timeline.Direction.BACKWARDS, count)
             } else {
@@ -523,8 +462,7 @@ internal class DefaultTimeline(
             val eventEntity = results[index]
             eventEntity?.eventId?.let { eventId ->
                 postSnapshot = rebuildEvent(eventId) {
-                    val builtEvent = buildTimelineEvent(eventEntity)
-                    listOf(builtEvent).filterEventsWithSettings(settings).firstOrNull()
+                    buildTimelineEvent(eventEntity)
                 } || postSnapshot
             }
         }
@@ -545,9 +483,9 @@ internal class DefaultTimeline(
                 // We are in the case where event exists, but we do not know the token.
                 // Fetch (again) the last event to get a token
                 val lastKnownEventId = if (direction == Timeline.Direction.FORWARDS) {
-                    nonFilteredEvents.firstOrNull()?.eventId
+                    timelineEvents.firstOrNull()?.eventId
                 } else {
-                    nonFilteredEvents.lastOrNull()?.eventId
+                    timelineEvents.lastOrNull()?.eventId
                 }
                 if (lastKnownEventId == null) {
                     updateState(direction) { it.copy(isPaginating = false, requestedPaginationCount = 0) }
@@ -618,7 +556,7 @@ internal class DefaultTimeline(
      * Return the current Chunk
      */
     private fun getLiveChunk(): ChunkEntity? {
-        return nonFilteredEvents.firstOrNull()?.chunk?.firstOrNull()
+        return timelineEvents.firstOrNull()?.chunk?.firstOrNull()
     }
 
     /**
@@ -662,17 +600,18 @@ internal class DefaultTimeline(
         val time = System.currentTimeMillis() - start
         Timber.v("Built ${offsetResults.size} items from db in $time ms")
         // For the case where wo reach the lastForward chunk
-        updateLoadingStates(filteredEvents)
+        updateLoadingStates(timelineEvents)
         return offsetResults.size
     }
 
-    private fun buildTimelineEvent(eventEntity: TimelineEventEntity) = timelineEventMapper.map(
-            timelineEventEntity = eventEntity,
-            buildReadReceipts = settings.buildReadReceipts,
-            correctedReadReceipts = hiddenReadReceipts.correctedReadReceipts(eventEntity.eventId)
-    ).let {
-        // eventually enhance with ui echo?
-        (uiEchoManager.decorateEventWithReactionUiEcho(it) ?: it)
+    private fun buildTimelineEvent(eventEntity: TimelineEventEntity): TimelineEvent {
+        return timelineEventMapper.map(
+                timelineEventEntity = eventEntity,
+                buildReadReceipts = settings.buildReadReceipts
+        ).let { timelineEvent ->
+            // eventually enhance with ui echo?
+            uiEchoManager.decorateEventWithReactionUiEcho(timelineEvent) ?: timelineEvent
+        }
     }
 
     /**
@@ -681,7 +620,7 @@ internal class DefaultTimeline(
     private fun getOffsetResults(startDisplayIndex: Int,
                                  direction: Timeline.Direction,
                                  count: Long): RealmResults<TimelineEventEntity> {
-        val offsetQuery = filteredEvents.where()
+        val offsetQuery = timelineEvents.where()
         if (direction == Timeline.Direction.BACKWARDS) {
             offsetQuery
                     .sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.DESCENDING)
@@ -729,7 +668,7 @@ internal class DefaultTimeline(
             if (isReady.get().not()) {
                 return@post
             }
-            updateLoadingStates(filteredEvents)
+            updateLoadingStates(timelineEvents)
             val snapshot = createSnapshot()
             val runnable = Runnable {
                 listeners.forEach {
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 d000bbeb5027b17fcf08512053fa03980caf49e7..8de36d0427633af6a7fdfbfa150cf0c1df95028d 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
@@ -17,10 +17,10 @@
 package org.matrix.android.sdk.internal.session.room.timeline
 
 import androidx.lifecycle.LiveData
+import com.zhuinden.monarchy.Monarchy
 import dagger.assisted.Assisted
-import dagger.assisted.AssistedInject
 import dagger.assisted.AssistedFactory
-import com.zhuinden.monarchy.Monarchy
+import dagger.assisted.AssistedInject
 import io.realm.Sort
 import io.realm.kotlin.where
 import org.matrix.android.sdk.api.session.events.model.isImageMessage
@@ -31,27 +31,28 @@ import org.matrix.android.sdk.api.session.room.timeline.TimelineService
 import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings
 import org.matrix.android.sdk.api.util.Optional
 import org.matrix.android.sdk.internal.database.RealmSessionProvider
-import org.matrix.android.sdk.internal.database.mapper.ReadReceiptsSummaryMapper
 import org.matrix.android.sdk.internal.database.mapper.TimelineEventMapper
 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.session.sync.ReadReceiptHandler
 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 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 loadRoomMembersTask: LoadRoomMembersTask
+internal class DefaultTimelineService @AssistedInject constructor(
+        @Assisted private val roomId: String,
+        @SessionDatabase private val monarchy: Monarchy,
+        private val realmSessionProvider: RealmSessionProvider,
+        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 loadRoomMembersTask: LoadRoomMembersTask,
+        private val readReceiptHandler: ReadReceiptHandler
 ) : TimelineService {
 
     @AssistedFactory
@@ -69,12 +70,12 @@ internal class DefaultTimelineService @AssistedInject constructor(@Assisted priv
                 paginationTask = paginationTask,
                 timelineEventMapper = timelineEventMapper,
                 settings = settings,
-                hiddenReadReceipts = TimelineHiddenReadReceipts(readReceiptsSummaryMapper, roomId, settings),
                 timelineInput = timelineInput,
                 eventDecryptor = eventDecryptor,
                 fetchTokenAndPaginateTask = fetchTokenAndPaginateTask,
                 realmSessionProvider = realmSessionProvider,
-                loadRoomMembersTask = loadRoomMembersTask
+                loadRoomMembersTask = loadRoomMembersTask,
+                readReceiptHandler = readReceiptHandler
         )
     }
 
@@ -87,7 +88,7 @@ internal class DefaultTimelineService @AssistedInject constructor(@Assisted priv
     }
 
     override fun getTimeLineEventLive(eventId: String): LiveData<Optional<TimelineEvent>> {
-        return LiveTimelineEvent(timelineInput, monarchy, taskExecutor.executorScope, timelineEventMapper, roomId, eventId)
+        return LiveTimelineEvent(monarchy, taskExecutor.executorScope, timelineEventMapper, roomId, eventId)
     }
 
     override fun getAttachmentMessages(): List<TimelineEvent> {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/Extensions.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/Extensions.kt
deleted file mode 100644
index b2c8021f3b49487f3d705369c0b18c4db9709bc6..0000000000000000000000000000000000000000
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/Extensions.kt
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * 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 io.realm.RealmQuery
-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.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.TimelineEvent
-import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings
-import org.matrix.android.sdk.internal.database.model.TimelineEventEntity
-import org.matrix.android.sdk.internal.database.query.filterEvents
-
-internal fun RealmQuery<TimelineEventEntity>.filterEventsWithSettings(settings: TimelineSettings): RealmQuery<TimelineEventEntity> {
-    return filterEvents(settings.filters)
-}
-
-internal fun List<TimelineEvent>.filterEventsWithSettings(settings: TimelineSettings): List<TimelineEvent> {
-    return filter { event ->
-        val filterType = !settings.filters.filterTypes
-                || settings.filters.allowedTypes.any { it.eventType == event.root.type && (it.stateKey == null || it.stateKey == event.root.senderId) }
-        if (!filterType) return@filter false
-
-        val filterEdits = if (settings.filters.filterEdits && event.root.getClearType() == EventType.MESSAGE) {
-            val messageContent = event.root.getClearContent().toModel<MessageContent>()
-            messageContent?.relatesTo?.type != RelationType.REPLACE && messageContent?.relatesTo?.type != RelationType.RESPONSE
-        } else {
-            true
-        }
-        if (!filterEdits) return@filter false
-
-        val filterRedacted = settings.filters.filterRedacted && event.root.isRedacted()
-        !filterRedacted
-    }
-}
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 76c4b3812c59f8b281090a72423d28b8820d743b..96646b42edbf6bafd6595894b1c2becc2d484373 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
@@ -48,8 +48,8 @@ internal class DefaultFetchTokenAndPaginateTask @Inject constructor(
 
     override suspend fun execute(params: FetchTokenAndPaginateTask.Params): TokenChunkEventPersistor.Result {
         val filter = filterRepository.getRoomFilter()
-        val response = executeRequest<EventContextResponse>(globalErrorReceiver) {
-            apiCall = roomAPI.getContextOfEvent(params.roomId, params.lastKnownEventId, 0, filter)
+        val response = executeRequest(globalErrorReceiver) {
+            roomAPI.getContextOfEvent(params.roomId, params.lastKnownEventId, 0, filter)
         }
         val fromToken = if (params.direction == PaginationDirection.FORWARDS) {
             response.end
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 d02a7bafe9a99dcf893ce56e623015479ef5b2e0..015e55f070365d45453d392ddbf4dc19c6fb6e41 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
@@ -40,9 +40,9 @@ internal class DefaultGetContextOfEventTask @Inject constructor(
 
     override suspend fun execute(params: GetContextOfEventTask.Params): TokenChunkEventPersistor.Result {
         val filter = filterRepository.getRoomFilter()
-        val response = executeRequest<EventContextResponse>(globalErrorReceiver) {
+        val response = executeRequest(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)
+            roomAPI.getContextOfEvent(params.roomId, params.eventId, 0, filter)
         }
         return tokenChunkEventPersistor.insertInDb(response, params.roomId, PaginationDirection.FORWARDS)
     }
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 b8585b1e747979135b7be0a7aef059b324e7c4d0..cbbc54e90d10b5b3759974bd6cfbea6bc1df026b 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
@@ -16,28 +16,49 @@
 
 package org.matrix.android.sdk.internal.session.room.timeline
 
+import org.matrix.android.sdk.api.extensions.tryOrNull
 import org.matrix.android.sdk.api.session.events.model.Event
+import org.matrix.android.sdk.internal.crypto.EventDecryptor
+import org.matrix.android.sdk.internal.crypto.algorithms.olm.OlmDecryptionResult
 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 javax.inject.Inject
 
-// TODO Add parent task
-
-internal class GetEventTask @Inject constructor(
-        private val roomAPI: RoomAPI,
-        private val globalErrorReceiver: GlobalErrorReceiver
-) : Task<GetEventTask.Params, Event> {
-
-    internal data class Params(
+internal interface GetEventTask : Task<GetEventTask.Params, Event> {
+    data class Params(
             val roomId: String,
             val eventId: String
     )
+}
 
-    override suspend fun execute(params: Params): Event {
-        return executeRequest(globalErrorReceiver) {
-            apiCall = roomAPI.getEvent(params.roomId, params.eventId)
+internal class DefaultGetEventTask @Inject constructor(
+        private val roomAPI: RoomAPI,
+        private val globalErrorReceiver: GlobalErrorReceiver,
+        private val eventDecryptor: EventDecryptor
+) : GetEventTask {
+
+    override suspend fun execute(params: GetEventTask.Params): Event {
+        val event = executeRequest(globalErrorReceiver) {
+            roomAPI.getEvent(params.roomId, params.eventId)
+        }
+
+        // Try to decrypt the Event
+        if (event.isEncrypted()) {
+            tryOrNull(message = "Unable to decrypt the event") {
+                eventDecryptor.decryptEvent(event, "")
+            }
+                    ?.let { result ->
+                        event.mxDecryptionResult = OlmDecryptionResult(
+                                payload = result.clearEvent,
+                                senderKey = result.senderCurve25519Key,
+                                keysClaimed = result.claimedEd25519Key?.let { mapOf("ed25519" to it) },
+                                forwardingCurve25519KeyChain = result.forwardingCurve25519KeyChain
+                        )
+                    }
         }
+
+        return event
     }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/LiveTimelineEvent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/LiveTimelineEvent.kt
index 3c0f101e11da4f0a3dd3c892f12a508c31b565fd..eb4900553b89ecec4a81727dce7605b6f3212e2c 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/LiveTimelineEvent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/LiveTimelineEvent.kt
@@ -18,8 +18,9 @@ package org.matrix.android.sdk.internal.session.room.timeline
 
 import androidx.lifecycle.LiveData
 import androidx.lifecycle.MediatorLiveData
-import androidx.lifecycle.Transformations
 import com.zhuinden.monarchy.Monarchy
+import io.realm.Realm
+import io.realm.RealmQuery
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.launch
@@ -29,66 +30,57 @@ import org.matrix.android.sdk.api.util.Optional
 import org.matrix.android.sdk.api.util.toOptional
 import org.matrix.android.sdk.internal.database.mapper.TimelineEventMapper
 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 java.util.concurrent.atomic.AtomicBoolean
 
 /**
  * This class takes care of handling case where local echo is replaced by the synced event in the db.
  */
-internal class LiveTimelineEvent(private val timelineInput: TimelineInput,
-                                 private val monarchy: Monarchy,
+internal class LiveTimelineEvent(private val monarchy: Monarchy,
                                  private val coroutineScope: CoroutineScope,
                                  private val timelineEventMapper: TimelineEventMapper,
                                  private val roomId: String,
                                  private val eventId: String)
-    : TimelineInput.Listener,
-        MediatorLiveData<Optional<TimelineEvent>>() {
-
-    private var queryLiveData: LiveData<Optional<TimelineEvent>>? = null
-
-    // If we are listening to local echo, we want to be aware when event is synced
-    private var shouldObserveSync = AtomicBoolean(LocalEcho.isLocalEchoId(eventId))
+    : MediatorLiveData<Optional<TimelineEvent>>() {
 
     init {
-        buildAndObserveQuery(eventId)
+        buildAndObserveQuery()
     }
 
+    private var initialLiveData: LiveData<List<TimelineEvent>>? = null
+
     // Makes sure it's made on the main thread
-    private fun buildAndObserveQuery(eventIdToObserve: String) = coroutineScope.launch(Dispatchers.Main) {
-        queryLiveData?.also {
-            removeSource(it)
-        }
+    private fun buildAndObserveQuery() = coroutineScope.launch(Dispatchers.Main) {
         val liveData = monarchy.findAllMappedWithChanges(
-                { TimelineEventEntity.where(it, roomId = roomId, eventId = eventIdToObserve) },
+                { TimelineEventEntity.where(it, roomId = roomId, eventId = eventId) },
                 { timelineEventMapper.map(it) }
         )
-        queryLiveData = Transformations.map(liveData) { events ->
-            events.firstOrNull().toOptional()
-        }.also {
-            addSource(it) { newValue -> value = newValue }
+        addSource(liveData) { newValue ->
+            value = newValue.firstOrNull().toOptional()
         }
-    }
-
-    override fun onLocalEchoSynced(roomId: String, localEchoEventId: String, syncedEventId: String) {
-        if (this.roomId == roomId && localEchoEventId == this.eventId) {
-            timelineInput.listeners.remove(this)
-            shouldObserveSync.set(false)
-            // rebuild the query with the new eventId
-            buildAndObserveQuery(syncedEventId)
+        initialLiveData = liveData
+        if (LocalEcho.isLocalEchoId(eventId)) {
+            observeTimelineEventWithTxId()
         }
     }
 
-    override fun onActive() {
-        super.onActive()
-        if (shouldObserveSync.get()) {
-            timelineInput.listeners.add(this)
+    private fun observeTimelineEventWithTxId() {
+        val liveData = monarchy.findAllMappedWithChanges(
+                { it.queryTimelineEventWithTxId() },
+                { timelineEventMapper.map(it) }
+        )
+        addSource(liveData) { newValue ->
+            val optionalValue = newValue.firstOrNull().toOptional()
+            if (optionalValue.hasValue()) {
+                initialLiveData?.also { removeSource(it) }
+                value = optionalValue
+            }
         }
     }
 
-    override fun onInactive() {
-        super.onInactive()
-        if (shouldObserveSync.get()) {
-            timelineInput.listeners.remove(this)
-        }
+    private fun Realm.queryTimelineEventWithTxId(): RealmQuery<TimelineEventEntity> {
+        return where(TimelineEventEntity::class.java)
+                .equalTo(TimelineEventEntityFields.ROOM_ID, roomId)
+                .like(TimelineEventEntityFields.ROOT.UNSIGNED_DATA, """{*"transaction_id":*"$eventId"*}""")
     }
 }
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 1f99893e17142c0373343a569c2bcca8c64d259e..8aeccb66c898ccb2fe777d702fd5c4b1370e7859 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
@@ -42,9 +42,11 @@ internal class DefaultPaginationTask @Inject constructor(
 
     override suspend fun execute(params: PaginationTask.Params): TokenChunkEventPersistor.Result {
         val filter = filterRepository.getRoomFilter()
-        val chunk = executeRequest<PaginationResponse>(globalErrorReceiver) {
-            isRetryable = true
-            apiCall = roomAPI.getRoomMessagesFrom(params.roomId, params.from, params.direction.value, params.limit, filter)
+        val chunk = executeRequest(
+                globalErrorReceiver,
+                canRetry = true
+        ) {
+            roomAPI.getRoomMessagesFrom(params.roomId, params.from, params.direction.value, params.limit, filter)
         }
         return tokenChunkEventPersistor.insertInDb(chunk, params.roomId, params.direction)
     }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineHiddenReadReceipts.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineHiddenReadReceipts.kt
deleted file mode 100644
index 0ade8ad3b8e6f2a9d55f2f02541dde714b1e7e62..0000000000000000000000000000000000000000
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineHiddenReadReceipts.kt
+++ /dev/null
@@ -1,195 +0,0 @@
-/*
- * Copyright 2020 The Matrix.org Foundation C.I.C.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.matrix.android.sdk.internal.session.room.timeline
-
-import android.util.SparseArray
-import io.realm.OrderedRealmCollectionChangeListener
-import io.realm.Realm
-import io.realm.RealmQuery
-import io.realm.RealmResults
-import org.matrix.android.sdk.api.session.room.model.ReadReceipt
-import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings
-import org.matrix.android.sdk.internal.database.mapper.ReadReceiptsSummaryMapper
-import org.matrix.android.sdk.internal.database.model.EventEntityFields
-import org.matrix.android.sdk.internal.database.model.ReadReceiptsSummaryEntity
-import org.matrix.android.sdk.internal.database.model.ReadReceiptsSummaryEntityFields
-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.TimelineEventFilter
-import org.matrix.android.sdk.internal.database.query.whereInRoom
-
-/**
- * This class is responsible for handling the read receipts for hidden events (check [TimelineSettings] to see filtering).
- * When an hidden event has read receipts, we want to transfer these read receipts on the first older displayed event.
- * It has to be used in [DefaultTimeline] and we should call the [start] and [dispose] methods to properly handle realm subscription.
- */
-internal class TimelineHiddenReadReceipts constructor(private val readReceiptsSummaryMapper: ReadReceiptsSummaryMapper,
-                                                      private val roomId: String,
-                                                      private val settings: TimelineSettings) {
-
-    interface Delegate {
-        fun rebuildEvent(eventId: String, readReceipts: List<ReadReceipt>): Boolean
-        fun onReadReceiptsUpdated()
-    }
-
-    private val correctedReadReceiptsEventByIndex = SparseArray<String>()
-    private val correctedReadReceiptsByEvent = HashMap<String, MutableList<ReadReceipt>>()
-
-    private lateinit var hiddenReadReceipts: RealmResults<ReadReceiptsSummaryEntity>
-    private lateinit var nonFilteredEvents: RealmResults<TimelineEventEntity>
-    private lateinit var filteredEvents: RealmResults<TimelineEventEntity>
-    private lateinit var delegate: Delegate
-
-    private val hiddenReadReceiptsListener = OrderedRealmCollectionChangeListener<RealmResults<ReadReceiptsSummaryEntity>> { collection, changeSet ->
-        if (!collection.isLoaded || !collection.isValid) {
-            return@OrderedRealmCollectionChangeListener
-        }
-        var hasChange = false
-        // Deletion here means we don't have any readReceipts for the given hidden events
-        changeSet.deletions.forEach {
-            val eventId = correctedReadReceiptsEventByIndex.get(it, "")
-            val timelineEvent = filteredEvents.where()
-                    .equalTo(TimelineEventEntityFields.EVENT_ID, eventId)
-                    .findFirst()
-
-            // We are rebuilding the corresponding event with only his own RR
-            val readReceipts = readReceiptsSummaryMapper.map(timelineEvent?.readReceipts)
-            hasChange = delegate.rebuildEvent(eventId, readReceipts) || hasChange
-        }
-        correctedReadReceiptsEventByIndex.clear()
-        correctedReadReceiptsByEvent.clear()
-        for (index in 0 until hiddenReadReceipts.size) {
-            val summary = hiddenReadReceipts[index] ?: continue
-            val timelineEvent = summary.timelineEvent?.firstOrNull() ?: continue
-            val isLoaded = nonFilteredEvents.where()
-                    .equalTo(TimelineEventEntityFields.EVENT_ID, timelineEvent.eventId).findFirst() != null
-            val displayIndex = timelineEvent.displayIndex
-
-            if (isLoaded) {
-                // Then we are looking for the first displayable event after the hidden one
-                val firstDisplayedEvent = filteredEvents.where()
-                        .lessThanOrEqualTo(TimelineEventEntityFields.DISPLAY_INDEX, displayIndex)
-                        .findFirst()
-
-                // If we find one, we should
-                if (firstDisplayedEvent != null) {
-                    correctedReadReceiptsEventByIndex.put(index, firstDisplayedEvent.eventId)
-                    correctedReadReceiptsByEvent
-                            .getOrPut(firstDisplayedEvent.eventId, {
-                                ArrayList(readReceiptsSummaryMapper.map(firstDisplayedEvent.readReceipts))
-                            })
-                            .addAll(readReceiptsSummaryMapper.map(summary))
-                }
-            }
-        }
-        if (correctedReadReceiptsByEvent.isNotEmpty()) {
-            correctedReadReceiptsByEvent.forEach { (eventId, correctedReadReceipts) ->
-                val sortedReadReceipts = correctedReadReceipts.sortedByDescending {
-                    it.originServerTs
-                }
-                hasChange = delegate.rebuildEvent(eventId, sortedReadReceipts) || hasChange
-            }
-        }
-        if (hasChange) {
-            delegate.onReadReceiptsUpdated()
-        }
-    }
-
-    /**
-     * Start the realm query subscription. Has to be called on an HandlerThread
-     */
-    fun start(realm: Realm,
-              filteredEvents: RealmResults<TimelineEventEntity>,
-              nonFilteredEvents: RealmResults<TimelineEventEntity>,
-              delegate: Delegate) {
-        this.filteredEvents = filteredEvents
-        this.nonFilteredEvents = nonFilteredEvents
-        this.delegate = delegate
-        // We are looking for read receipts set on hidden events.
-        // We only accept those with a timelineEvent (so coming from pagination/sync).
-        this.hiddenReadReceipts = ReadReceiptsSummaryEntity.whereInRoom(realm, roomId)
-                .isNotEmpty(ReadReceiptsSummaryEntityFields.TIMELINE_EVENT.`$`)
-                .isNotEmpty(ReadReceiptsSummaryEntityFields.READ_RECEIPTS.`$`)
-                .filterReceiptsWithSettings()
-                .findAllAsync()
-                .also { it.addChangeListener(hiddenReadReceiptsListener) }
-    }
-
-    /**
-     * Dispose the realm query subscription. Has to be called on an HandlerThread
-     */
-    fun dispose() {
-        if (this::hiddenReadReceipts.isInitialized) {
-            this.hiddenReadReceipts.removeAllChangeListeners()
-        }
-    }
-
-    /**
-     * Return the current corrected [ReadReceipt] list for an event, or null
-     */
-    fun correctedReadReceipts(eventId: String?): List<ReadReceipt>? {
-        return correctedReadReceiptsByEvent[eventId]
-    }
-
-    /**
-     * We are looking for receipts related to filtered events. So, it's the opposite of [DefaultTimeline.filterEventsWithSettings] method.
-     */
-    private fun RealmQuery<ReadReceiptsSummaryEntity>.filterReceiptsWithSettings(): RealmQuery<ReadReceiptsSummaryEntity> {
-        beginGroup()
-        var needOr = false
-        if (settings.filters.filterTypes) {
-            beginGroup()
-            // Events: A, B, C, D, (E and S1), F, G, (H and S1), I
-            // Allowed: A, B, C, (E and S1), G, (H and S2)
-            // Result: D, F, H, I
-            settings.filters.allowedTypes.forEachIndexed { index, filter ->
-                if (filter.stateKey == null) {
-                    notEqualTo("${ReadReceiptsSummaryEntityFields.TIMELINE_EVENT.ROOT}.${EventEntityFields.TYPE}", filter.eventType)
-                } else {
-                    beginGroup()
-                    notEqualTo("${ReadReceiptsSummaryEntityFields.TIMELINE_EVENT.ROOT}.${EventEntityFields.TYPE}", filter.eventType)
-                    or()
-                    notEqualTo("${ReadReceiptsSummaryEntityFields.TIMELINE_EVENT.ROOT}.${EventEntityFields.STATE_KEY}", filter.stateKey)
-                    endGroup()
-                }
-                if (index != settings.filters.allowedTypes.size - 1) {
-                    and()
-                }
-            }
-            endGroup()
-            needOr = true
-        }
-        if (settings.filters.filterUseless) {
-            if (needOr) or()
-            equalTo("${ReadReceiptsSummaryEntityFields.TIMELINE_EVENT.ROOT}.${EventEntityFields.IS_USELESS}", true)
-            needOr = true
-        }
-        if (settings.filters.filterEdits) {
-            if (needOr) or()
-            like("${ReadReceiptsSummaryEntityFields.TIMELINE_EVENT.ROOT}.${EventEntityFields.CONTENT}", TimelineEventFilter.Content.EDIT)
-            or()
-            like("${ReadReceiptsSummaryEntityFields.TIMELINE_EVENT.ROOT}.${EventEntityFields.CONTENT}", TimelineEventFilter.Content.RESPONSE)
-            needOr = true
-        }
-        if (settings.filters.filterRedacted) {
-            if (needOr) or()
-            like("${ReadReceiptsSummaryEntityFields.TIMELINE_EVENT.ROOT}.${EventEntityFields.UNSIGNED_DATA}", TimelineEventFilter.Unsigned.REDACTED)
-        }
-        endGroup()
-        return this
-    }
-}
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
index 8911f265d57367d56fdb1706981b57c9e53ee825..cdc85ea722793334bfb8c1d2b7db38967ee58ae4 100644
--- 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
@@ -35,16 +35,11 @@ internal class TimelineInput @Inject constructor() {
         listeners.toSet().forEach { it.onNewTimelineEvents(roomId, eventIds) }
     }
 
-    fun onLocalEchoSynced(roomId: String, localEchoEventId: String, syncEventId: String) {
-        listeners.toSet().forEach { it.onLocalEchoSynced(roomId, localEchoEventId, syncEventId) }
-    }
-
     val listeners = mutableSetOf<Listener>()
 
     internal interface Listener {
         fun onLocalEchoCreated(roomId: String, timelineEvent: TimelineEvent) = Unit
         fun onLocalEchoUpdated(roomId: String, eventId: String, sendState: SendState) = Unit
         fun onNewTimelineEvents(roomId: String, eventIds: List<String>) = Unit
-        fun onLocalEchoSynced(roomId: String, localEchoEventId: String, syncedEventId: String) = Unit
     }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineSendEventWorkCommon.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineSendEventWorkCommon.kt
index bfd4e22cc228cbb15ceed7f7c420d2cc709a4a93..21b508d35a387ceae24da95278ce9e588156c84e 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineSendEventWorkCommon.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineSendEventWorkCommon.kt
@@ -50,7 +50,7 @@ internal class TimelineSendEventWorkCommon @Inject constructor(
                 .setConstraints(WorkManagerProvider.workConstraints)
                 .startChain(startChain)
                 .setInputData(data)
-                .setBackoffCriteria(BackoffPolicy.LINEAR, BACKOFF_DELAY, TimeUnit.MILLISECONDS)
+                .setBackoffCriteria(BackoffPolicy.LINEAR, WorkManagerProvider.BACKOFF_DELAY_MILLIS, TimeUnit.MILLISECONDS)
                 .build()
     }
 
@@ -60,6 +60,5 @@ internal class TimelineSendEventWorkCommon @Inject constructor(
 
     companion object {
         private const val SEND_WORK = "SEND_WORK"
-        private const val BACKOFF_DELAY = 10_000L
     }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/UIEchoManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/UIEchoManager.kt
index 67d0d90d7705d31d43a3c247aa26d48c19c770c3..4804fbd73143d2ec0b5db1e271d94c717e542bb6 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/UIEchoManager.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/UIEchoManager.kt
@@ -70,15 +70,12 @@ internal class UIEchoManager(
         return existingState != sendState
     }
 
-    // return true if should update
-    fun onLocalEchoCreated(timelineEvent: TimelineEvent): Boolean {
-        var postSnapshot = false
-
+    fun onLocalEchoCreated(timelineEvent: TimelineEvent)  {
         // Manage some ui echos (do it before filter because actual event could be filtered out)
         when (timelineEvent.root.getClearType()) {
             EventType.REDACTION -> {
             }
-            EventType.REACTION  -> {
+            EventType.REACTION -> {
                 val content = timelineEvent.root.content?.toModel<ReactionContent>()
                 if (RelationType.ANNOTATION == content?.relatesTo?.type) {
                     val reaction = content.relatesTo.key
@@ -91,21 +88,14 @@ internal class UIEchoManager(
                                             reaction = reaction
                                     )
                             )
-                    postSnapshot = listener.rebuildEvent(relatedEventID) {
+                    listener.rebuildEvent(relatedEventID) {
                         decorateEventWithReactionUiEcho(it)
-                    } || postSnapshot
+                    }
                 }
             }
         }
-
-        // do not add events that would have been filtered
-        if (listOf(timelineEvent).filterEventsWithSettings(settings).isNotEmpty()) {
-            Timber.v("On local echo created: ${timelineEvent.eventId}")
-            inMemorySendingEvents.add(0, timelineEvent)
-            postSnapshot = true
-        }
-
-        return postSnapshot
+        Timber.v("On local echo created: ${timelineEvent.eventId}")
+        inMemorySendingEvents.add(0, timelineEvent)
     }
 
     fun decorateEventWithReactionUiEcho(timelineEvent: TimelineEvent): TimelineEvent? {
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 3b56d04872820b454fa234c56e030069dcc26727..0b0df743113285d432cc586afbef88cb5237e0a6 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
@@ -44,8 +44,8 @@ internal class DefaultSendTypingTask @Inject constructor(
     override suspend fun execute(params: SendTypingTask.Params) {
         delay(params.delay ?: -1)
 
-        executeRequest<Unit>(globalErrorReceiver) {
-            apiCall = roomAPI.sendTypingState(
+        executeRequest(globalErrorReceiver) {
+            roomAPI.sendTypingState(
                     params.roomId,
                     userId,
                     TypingBody(params.isTyping, params.typingTimeoutMillis?.takeIf { params.isTyping })
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 b3e4a5aa0597cdcc6353a19b9429ef64530cc392..028c3e91933c1ecbd4bcc555909d9c5be6894995 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
@@ -37,7 +37,6 @@ import org.matrix.android.sdk.internal.session.filter.FilterFactory
 import org.matrix.android.sdk.internal.session.room.RoomAPI
 import org.matrix.android.sdk.internal.session.room.membership.RoomMemberHelper
 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 javax.inject.Inject
@@ -86,8 +85,8 @@ 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>(globalErrorReceiver) {
-                apiCall = roomAPI.getRoomMessagesFrom(params.roomId, since, PaginationDirection.BACKWARDS.value, params.numberOfEvents, filter)
+            val chunk = executeRequest(globalErrorReceiver) {
+                roomAPI.getRoomMessagesFrom(params.roomId, since, PaginationDirection.BACKWARDS.value, params.numberOfEvents, filter)
             }
 
             result = GetUploadsResult(
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/search/SearchAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/search/SearchAPI.kt
index 4a74b0a023ed540fc0f23d8039c64ee1ae76acbd..b5099e723874eb70aadbe87c254b6810cdac082b 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/search/SearchAPI.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/search/SearchAPI.kt
@@ -19,7 +19,6 @@ package org.matrix.android.sdk.internal.session.search
 import org.matrix.android.sdk.internal.network.NetworkConstants
 import org.matrix.android.sdk.internal.session.search.request.SearchRequestBody
 import org.matrix.android.sdk.internal.session.search.response.SearchResponse
-import retrofit2.Call
 import retrofit2.http.Body
 import retrofit2.http.POST
 import retrofit2.http.Query
@@ -31,6 +30,6 @@ internal interface SearchAPI {
      * Ref: https://matrix.org/docs/spec/client_server/r0.6.1#post-matrix-client-r0-search
      */
     @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "search")
-    fun search(@Query("next_batch") nextBatch: String?,
-               @Body body: SearchRequestBody): Call<SearchResponse>
+    suspend fun search(@Query("next_batch") nextBatch: String?,
+                       @Body body: SearchRequestBody): SearchResponse
 }
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 402602e4d5488382a8c29f375b2ae76b2ea1543b..8de762ee1b6ae540356a2c7749b80e7e639bd20a 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
@@ -51,25 +51,25 @@ internal class DefaultSearchTask @Inject constructor(
 ) : SearchTask {
 
     override suspend fun execute(params: SearchTask.Params): SearchResult {
-        return executeRequest<SearchResponse>(globalErrorReceiver) {
-            val searchRequestBody = SearchRequestBody(
-                    searchCategories = SearchRequestCategories(
-                            roomEvents = SearchRequestRoomEvents(
-                                    searchTerm = params.searchTerm,
-                                    orderBy = if (params.orderByRecent) SearchRequestOrder.RECENT else SearchRequestOrder.RANK,
-                                    filter = SearchRequestFilter(
-                                            limit = params.limit,
-                                            rooms = listOf(params.roomId)
-                                    ),
-                                    eventContext = SearchRequestEventContext(
-                                            beforeLimit = params.beforeLimit,
-                                            afterLimit = params.afterLimit,
-                                            includeProfile = params.includeProfile
-                                    )
-                            )
-                    )
-            )
-            apiCall = searchAPI.search(params.nextBatch, searchRequestBody)
+        val searchRequestBody = SearchRequestBody(
+                searchCategories = SearchRequestCategories(
+                        roomEvents = SearchRequestRoomEvents(
+                                searchTerm = params.searchTerm,
+                                orderBy = if (params.orderByRecent) SearchRequestOrder.RECENT else SearchRequestOrder.RANK,
+                                filter = SearchRequestFilter(
+                                        limit = params.limit,
+                                        rooms = listOf(params.roomId)
+                                ),
+                                eventContext = SearchRequestEventContext(
+                                        beforeLimit = params.beforeLimit,
+                                        afterLimit = params.afterLimit,
+                                        includeProfile = params.includeProfile
+                                )
+                        )
+                )
+        )
+        return executeRequest(globalErrorReceiver) {
+            searchAPI.search(params.nextBatch, searchRequestBody)
         }.toDomain()
     }
 
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 2c3cd5d2703fc9423c6073bf08a1604f57b51475..563e85aefc5edbd2e23664a4103a057731314620 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
@@ -16,7 +16,6 @@
 
 package org.matrix.android.sdk.internal.session.signout
 
-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
@@ -39,8 +38,8 @@ internal class DefaultSignInAgainTask @Inject constructor(
 ) : SignInAgainTask {
 
     override suspend fun execute(params: SignInAgainTask.Params) {
-        val newCredentials = executeRequest<Credentials>(globalErrorReceiver) {
-            apiCall = signOutAPI.loginAgain(
+        val newCredentials = executeRequest(globalErrorReceiver) {
+            signOutAPI.loginAgain(
                     PasswordLoginParams.userIdentifier(
                             // Reuse the same userId
                             sessionParams.userId,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/signout/SignOutAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/signout/SignOutAPI.kt
index 4c92938b7731d9059a1e6b99e6f99ab47eb5a671..a56362e587e26dfa79ad05c67a17adacf86ed2f9 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/signout/SignOutAPI.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/signout/SignOutAPI.kt
@@ -19,7 +19,6 @@ package org.matrix.android.sdk.internal.session.signout
 import org.matrix.android.sdk.api.auth.data.Credentials
 import org.matrix.android.sdk.internal.auth.data.PasswordLoginParams
 import org.matrix.android.sdk.internal.network.NetworkConstants
-import retrofit2.Call
 import retrofit2.http.Body
 import retrofit2.http.Headers
 import retrofit2.http.POST
@@ -35,11 +34,11 @@ internal interface SignOutAPI {
      */
     @Headers("CONNECT_TIMEOUT:60000", "READ_TIMEOUT:60000", "WRITE_TIMEOUT:60000")
     @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "login")
-    fun loginAgain(@Body loginParams: PasswordLoginParams): Call<Credentials>
+    suspend fun loginAgain(@Body loginParams: PasswordLoginParams): Credentials
 
     /**
      * Invalidate the access token, so that it can no longer be used for authorization.
      */
     @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "logout")
-    fun signOut(): Call<Unit>
+    suspend fun signOut()
 }
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 0cb8704782fa329d354de51da3952bd93553196e..9c25eccb3a50b468450a452dd4c62a50e44c9451 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
@@ -45,8 +45,8 @@ internal class DefaultSignOutTask @Inject constructor(
         if (params.signOutFromHomeserver) {
             Timber.d("SignOut: send request...")
             try {
-                executeRequest<Unit>(globalErrorReceiver) {
-                    apiCall = signOutAPI.signOut()
+                executeRequest(globalErrorReceiver) {
+                    signOutAPI.signOut()
                 }
             } catch (throwable: Throwable) {
                 // Maybe due to https://github.com/matrix-org/synapse/issues/5756
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/InitialSyncStatusRepository.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/InitialSyncStatusRepository.kt
index 4b82ecc3e5878e450d47f2d7c63ea929ee287f63..cf67bbd805d92b7e18c50ddf30bca13901cce6e7 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/InitialSyncStatusRepository.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/InitialSyncStatusRepository.kt
@@ -65,7 +65,7 @@ internal class FileInitialSyncStatusRepository(directory: File) : InitialSyncSta
         val state = cache?.step ?: InitialSyncStatus.STEP_INIT
         return if (state >= InitialSyncStatus.STEP_DOWNLOADED
                 && System.currentTimeMillis() > (cache?.downloadedDate ?: 0) + INIT_SYNC_FILE_LIFETIME) {
-            Timber.v("INIT_SYNC downloaded file is outdated, download it again")
+            Timber.d("INIT_SYNC downloaded file is outdated, download it again")
             // The downloaded file is outdated
             setStep(InitialSyncStatus.STEP_INIT)
             InitialSyncStatus.STEP_INIT
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/InitialSyncStrategy.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/InitialSyncStrategy.kt
index 297cc213ed704f6c76058d05d34901e40a074d32..7d93e30191cbf7ea15907a6e3765e1580f8d7904 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/InitialSyncStrategy.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/InitialSyncStrategy.kt
@@ -42,9 +42,9 @@ sealed class InitialSyncStrategy {
             val minSizeToSplit: Long = 1024 * 1024,
             /**
              * Limit per room to reach to decide to store a join room ephemeral Events into a file
-             * Empiric value: 6 kilobytes
+             * Empiric value: 1 kilobytes
              */
-            val minSizeToStoreInFile: Long = 6 * 1024,
+            val minSizeToStoreInFile: Long = 1024,
             /**
              * Max number of rooms to insert at a time in database (to avoid too much RAM usage)
              */
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/ReadReceiptHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/ReadReceiptHandler.kt
index a3c5891f68a678cd3f74350027de5d02d668d5cb..e5d9217db71da4c0a25dda37a5bc2494688b9d51 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/ReadReceiptHandler.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/ReadReceiptHandler.kt
@@ -16,12 +16,13 @@
 
 package org.matrix.android.sdk.internal.session.sync
 
+import io.realm.Realm
+import org.matrix.android.sdk.api.session.events.model.EventType
 import org.matrix.android.sdk.internal.database.model.ReadReceiptEntity
 import org.matrix.android.sdk.internal.database.model.ReadReceiptsSummaryEntity
 import org.matrix.android.sdk.internal.database.query.createUnmanaged
 import org.matrix.android.sdk.internal.database.query.getOrCreate
 import org.matrix.android.sdk.internal.database.query.where
-import io.realm.Realm
 import timber.log.Timber
 import javax.inject.Inject
 
@@ -35,7 +36,9 @@ typealias ReadReceiptContent = Map<String, Map<String, Map<String, Map<String, D
 private const val READ_KEY = "m.read"
 private const val TIMESTAMP_KEY = "ts"
 
-internal class ReadReceiptHandler @Inject constructor() {
+internal class ReadReceiptHandler @Inject constructor(
+        private val roomSyncEphemeralTemporaryStore: RoomSyncEphemeralTemporaryStore
+) {
 
     companion object {
 
@@ -52,22 +55,29 @@ internal class ReadReceiptHandler @Inject constructor() {
         }
     }
 
-    fun handle(realm: Realm, roomId: String, content: ReadReceiptContent?, isInitialSync: Boolean) {
-        if (content == null) {
-            return
-        }
+    fun handle(realm: Realm,
+               roomId: String,
+               content: ReadReceiptContent?,
+               isInitialSync: Boolean,
+               aggregator: SyncResponsePostTreatmentAggregator?) {
+        content ?: return
+
         try {
-            handleReadReceiptContent(realm, roomId, content, isInitialSync)
+            handleReadReceiptContent(realm, roomId, content, isInitialSync, aggregator)
         } catch (exception: Exception) {
             Timber.e("Fail to handle read receipt for room $roomId")
         }
     }
 
-    private fun handleReadReceiptContent(realm: Realm, roomId: String, content: ReadReceiptContent, isInitialSync: Boolean) {
+    private fun handleReadReceiptContent(realm: Realm,
+                                         roomId: String,
+                                         content: ReadReceiptContent,
+                                         isInitialSync: Boolean,
+                                         aggregator: SyncResponsePostTreatmentAggregator?) {
         if (isInitialSync) {
             initialSyncStrategy(realm, roomId, content)
         } else {
-            incrementalSyncStrategy(realm, roomId, content)
+            incrementalSyncStrategy(realm, roomId, content, aggregator)
         }
     }
 
@@ -87,7 +97,21 @@ internal class ReadReceiptHandler @Inject constructor() {
         realm.insertOrUpdate(readReceiptSummaries)
     }
 
-    private fun incrementalSyncStrategy(realm: Realm, roomId: String, content: ReadReceiptContent) {
+    private fun incrementalSyncStrategy(realm: Realm,
+                                        roomId: String,
+                                        content: ReadReceiptContent,
+                                        aggregator: SyncResponsePostTreatmentAggregator?) {
+        // First check if we have data from init sync to handle
+        getContentFromInitSync(roomId)?.let {
+            Timber.w("INIT_SYNC Insert during incremental sync RR for room $roomId")
+            doIncrementalSyncStrategy(realm, roomId, it)
+            aggregator?.ephemeralFilesToDelete?.add(roomId)
+        }
+
+        doIncrementalSyncStrategy(realm, roomId, content)
+    }
+
+    private fun doIncrementalSyncStrategy(realm: Realm, roomId: String, content: ReadReceiptContent) {
         for ((eventId, receiptDict) in content) {
             val userIdsDict = receiptDict[READ_KEY] ?: continue
             val readReceiptsSummary = ReadReceiptsSummaryEntity.where(realm, eventId).findFirst()
@@ -110,4 +134,27 @@ internal class ReadReceiptHandler @Inject constructor() {
             }
         }
     }
+
+    fun getContentFromInitSync(roomId: String): ReadReceiptContent? {
+        val dataFromFile = roomSyncEphemeralTemporaryStore.read(roomId)
+
+        dataFromFile ?: return null
+
+        @Suppress("UNCHECKED_CAST")
+        val content = dataFromFile
+                .events
+                .firstOrNull { it.type == EventType.RECEIPT }
+                ?.content as? ReadReceiptContent
+
+        if (content == null) {
+            // We can delete the file now
+            roomSyncEphemeralTemporaryStore.delete(roomId)
+        }
+
+        return content
+    }
+
+    fun onContentFromInitSyncHandled(roomId: String) {
+        roomSyncEphemeralTemporaryStore.delete(roomId)
+    }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomSyncEphemeralTemporaryStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomSyncEphemeralTemporaryStore.kt
new file mode 100644
index 0000000000000000000000000000000000000000..c6ff71cfcfdcc2e979dcf16d4e4fda4ff8694834
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomSyncEphemeralTemporaryStore.kt
@@ -0,0 +1,79 @@
+/*
+ * 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.sync
+
+import com.squareup.moshi.JsonReader
+import com.squareup.moshi.Moshi
+import okio.buffer
+import okio.source
+import org.matrix.android.sdk.internal.di.SessionFilesDirectory
+import org.matrix.android.sdk.internal.session.sync.model.RoomSyncEphemeral
+import org.matrix.android.sdk.internal.util.md5
+import timber.log.Timber
+import java.io.File
+import javax.inject.Inject
+
+internal interface RoomSyncEphemeralTemporaryStore {
+    fun write(roomId: String, roomSyncEphemeralJson: String)
+    fun read(roomId: String): RoomSyncEphemeral?
+    fun reset()
+    fun delete(roomId: String)
+}
+
+internal class RoomSyncEphemeralTemporaryStoreFile @Inject constructor(
+        @SessionFilesDirectory fileDirectory: File,
+        moshi: Moshi
+) : RoomSyncEphemeralTemporaryStore {
+
+    private val workingDir = File(fileDirectory, "rr")
+            .also { it.mkdirs() }
+
+    private val roomSyncEphemeralAdapter = moshi.adapter(RoomSyncEphemeral::class.java)
+
+    /**
+     * Write RoomSyncEphemeral to a file
+     */
+    override fun write(roomId: String, roomSyncEphemeralJson: String) {
+        Timber.w("INIT_SYNC Store ephemeral events for room $roomId")
+        getFile(roomId).writeText(roomSyncEphemeralJson)
+    }
+
+    /**
+     * Read RoomSyncEphemeral from a file, or null if there is no file to read
+     */
+    override fun read(roomId: String): RoomSyncEphemeral? {
+        return getFile(roomId)
+                .takeIf { it.exists() }
+                ?.inputStream()
+                ?.use { pos ->
+                    roomSyncEphemeralAdapter.fromJson(JsonReader.of(pos.source().buffer()))
+                }
+    }
+
+    override fun delete(roomId: String) {
+        getFile(roomId).delete()
+    }
+
+    override fun reset() {
+        workingDir.deleteRecursively()
+        workingDir.mkdirs()
+    }
+
+    private fun getFile(roomId: String): File {
+        return File(workingDir, "${roomId.md5()}.json")
+    }
+}
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 336a83eaadd1d5a26b9f746f599c977c99a36b29..2bb606e921f7536400e4f03820335c4f08a6a82a 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
@@ -49,6 +49,7 @@ import org.matrix.android.sdk.internal.database.query.where
 import org.matrix.android.sdk.internal.di.MoshiProvider
 import org.matrix.android.sdk.internal.di.UserId
 import org.matrix.android.sdk.internal.extensions.clearWith
+import org.matrix.android.sdk.internal.session.events.getFixedRoomMemberContent
 import org.matrix.android.sdk.internal.session.initsync.ProgressReporter
 import org.matrix.android.sdk.internal.session.initsync.mapWithProgress
 import org.matrix.android.sdk.internal.session.initsync.reportSubtask
@@ -60,12 +61,13 @@ 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.LazyRoomSyncEphemeral
 import org.matrix.android.sdk.internal.session.sync.model.RoomSync
 import org.matrix.android.sdk.internal.session.sync.model.RoomSyncAccountData
 import org.matrix.android.sdk.internal.session.sync.model.RoomsSyncResponse
+import org.matrix.android.sdk.internal.util.computeBestChunkSize
 import timber.log.Timber
 import javax.inject.Inject
-import kotlin.math.ceil
 
 internal class RoomSyncHandler @Inject constructor(private val readReceiptHandler: ReadReceiptHandler,
                                                    private val roomSummaryUpdater: RoomSummaryUpdater,
@@ -87,29 +89,21 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
     fun handle(realm: Realm,
                roomsSyncResponse: RoomsSyncResponse,
                isInitialSync: Boolean,
+               aggregator: SyncResponsePostTreatmentAggregator,
                reporter: ProgressReporter? = null) {
         Timber.v("Execute transaction from $this")
-        handleRoomSync(realm, HandlingStrategy.JOINED(roomsSyncResponse.join), isInitialSync, reporter)
-        handleRoomSync(realm, HandlingStrategy.INVITED(roomsSyncResponse.invite), isInitialSync, reporter)
-        handleRoomSync(realm, HandlingStrategy.LEFT(roomsSyncResponse.leave), isInitialSync, reporter)
-    }
-
-    fun handleInitSyncEphemeral(realm: Realm,
-                                roomsSyncResponse: RoomsSyncResponse) {
-        roomsSyncResponse.join.forEach { roomSync ->
-            val ephemeralResult = roomSync.value.ephemeral
-                    ?.roomSyncEphemeral
-                    ?.events
-                    ?.takeIf { it.isNotEmpty() }
-                    ?.let { events -> handleEphemeral(realm, roomSync.key, events, true) }
-
-            roomTypingUsersHandler.handle(realm, roomSync.key, ephemeralResult)
-        }
+        handleRoomSync(realm, HandlingStrategy.JOINED(roomsSyncResponse.join), isInitialSync, aggregator, reporter)
+        handleRoomSync(realm, HandlingStrategy.INVITED(roomsSyncResponse.invite), isInitialSync, aggregator, reporter)
+        handleRoomSync(realm, HandlingStrategy.LEFT(roomsSyncResponse.leave), isInitialSync, aggregator, reporter)
     }
 
     // PRIVATE METHODS *****************************************************************************
 
-    private fun handleRoomSync(realm: Realm, handlingStrategy: HandlingStrategy, isInitialSync: Boolean, reporter: ProgressReporter?) {
+    private fun handleRoomSync(realm: Realm,
+                               handlingStrategy: HandlingStrategy,
+                               isInitialSync: Boolean,
+                               aggregator: SyncResponsePostTreatmentAggregator,
+                               reporter: ProgressReporter?) {
         val insertType = if (isInitialSync) {
             EventInsertType.INITIAL_SYNC
         } else {
@@ -119,12 +113,12 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
         val rooms = when (handlingStrategy) {
             is HandlingStrategy.JOINED  -> {
                 if (isInitialSync && initialSyncStrategy is InitialSyncStrategy.Optimized) {
-                    insertJoinRoomsFromInitSync(realm, handlingStrategy, syncLocalTimeStampMillis, reporter)
+                    insertJoinRoomsFromInitSync(realm, handlingStrategy, syncLocalTimeStampMillis, aggregator, reporter)
                     // Rooms are already inserted, return an empty list
                     emptyList()
                 } else {
                     handlingStrategy.data.mapWithProgress(reporter, InitSyncStep.ImportingAccountJoinedRooms, 0.6f) {
-                        handleJoinedRoom(realm, it.key, it.value, true, insertType, syncLocalTimeStampMillis)
+                        handleJoinedRoom(realm, it.key, it.value, insertType, syncLocalTimeStampMillis, aggregator)
                     }
                 }
             }
@@ -145,29 +139,30 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
     private fun insertJoinRoomsFromInitSync(realm: Realm,
                                             handlingStrategy: HandlingStrategy.JOINED,
                                             syncLocalTimeStampMillis: Long,
+                                            aggregator: SyncResponsePostTreatmentAggregator,
                                             reporter: ProgressReporter?) {
-        val maxSize = (initialSyncStrategy as? InitialSyncStrategy.Optimized)?.maxRoomsToInsert ?: Int.MAX_VALUE
-        val listSize = handlingStrategy.data.keys.size
-        val numberOfChunks = ceil(listSize / maxSize.toDouble()).toInt()
+        val bestChunkSize = computeBestChunkSize(
+                listSize = handlingStrategy.data.keys.size,
+                limit = (initialSyncStrategy as? InitialSyncStrategy.Optimized)?.maxRoomsToInsert ?: Int.MAX_VALUE
+        )
 
-        if (numberOfChunks > 1) {
-            reportSubtask(reporter, InitSyncStep.ImportingAccountJoinedRooms, numberOfChunks, 0.6f) {
-                val chunkSize = listSize / numberOfChunks
-                Timber.v("INIT_SYNC $listSize rooms to insert, split into $numberOfChunks sublists of $chunkSize items")
+        if (bestChunkSize.shouldChunk()) {
+            reportSubtask(reporter, InitSyncStep.ImportingAccountJoinedRooms, bestChunkSize.numberOfChunks, 0.6f) {
+                Timber.d("INIT_SYNC ${handlingStrategy.data.keys.size} rooms to insert, split with $bestChunkSize")
                 // I cannot find a better way to chunk a map, so chunk the keys and then create new maps
                 handlingStrategy.data.keys
-                        .chunked(chunkSize)
+                        .chunked(bestChunkSize.chunkSize)
                         .forEachIndexed { index, roomIds ->
                             val roomEntities = roomIds
-                                    .also { Timber.v("INIT_SYNC insert ${roomIds.size} rooms") }
+                                    .also { Timber.d("INIT_SYNC insert ${roomIds.size} rooms") }
                                     .map {
                                         handleJoinedRoom(
                                                 realm = realm,
                                                 roomId = it,
                                                 roomSync = handlingStrategy.data[it] ?: error("Should not happen"),
-                                                handleEphemeralEvents = false,
                                                 insertType = EventInsertType.INITIAL_SYNC,
-                                                syncLocalTimestampMillis = syncLocalTimeStampMillis
+                                                syncLocalTimestampMillis = syncLocalTimeStampMillis,
+                                                aggregator
                                         )
                                     }
                             realm.insertOrUpdate(roomEntities)
@@ -177,7 +172,7 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
         } else {
             // No need to split
             val rooms = handlingStrategy.data.mapWithProgress(reporter, InitSyncStep.ImportingAccountJoinedRooms, 0.6f) {
-                handleJoinedRoom(realm, it.key, it.value, false, EventInsertType.INITIAL_SYNC, syncLocalTimeStampMillis)
+                handleJoinedRoom(realm, it.key, it.value, EventInsertType.INITIAL_SYNC, syncLocalTimeStampMillis, aggregator)
             }
             realm.insertOrUpdate(rooms)
         }
@@ -186,17 +181,16 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
     private fun handleJoinedRoom(realm: Realm,
                                  roomId: String,
                                  roomSync: RoomSync,
-                                 handleEphemeralEvents: Boolean,
                                  insertType: EventInsertType,
-                                 syncLocalTimestampMillis: Long): RoomEntity {
+                                 syncLocalTimestampMillis: Long,
+                                 aggregator: SyncResponsePostTreatmentAggregator): RoomEntity {
         Timber.v("Handle join sync for room $roomId")
 
-        var ephemeralResult: EphemeralResult? = null
-        if (handleEphemeralEvents) {
-            ephemeralResult = roomSync.ephemeral?.roomSyncEphemeral?.events
-                    ?.takeIf { it.isNotEmpty() }
-                    ?.let { handleEphemeral(realm, roomId, it, insertType == EventInsertType.INITIAL_SYNC) }
-        }
+        val ephemeralResult = (roomSync.ephemeral as? LazyRoomSyncEphemeral.Parsed)
+                ?._roomSyncEphemeral
+                ?.events
+                ?.takeIf { it.isNotEmpty() }
+                ?.let { handleEphemeral(realm, roomId, it, insertType == EventInsertType.INITIAL_SYNC, aggregator) }
 
         if (roomSync.accountData?.events?.isNotEmpty() == true) {
             handleRoomAccountDataEvents(realm, roomId, roomSync.accountData)
@@ -400,7 +394,6 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
                             event.mxDecryptionResult = adapter.fromJson(json)
                         }
                     }
-                    timelineInput.onLocalEchoSynced(roomId, it, event.eventId)
                     // Finally delete the local echo
                     sendingEventEntity.deleteOnCascade(true)
                 } else {
@@ -415,6 +408,7 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
 
     private fun decryptIfNeeded(event: Event, roomId: String) {
         try {
+            // Event from sync does not have roomId, so add it to the event first
             val result = cryptoService.decryptEvent(event.copy(roomId = roomId), "")
             event.mxDecryptionResult = OlmDecryptionResult(
                     payload = result.clearEvent,
@@ -437,14 +431,15 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
     private fun handleEphemeral(realm: Realm,
                                 roomId: String,
                                 ephemeralEvents: List<Event>,
-                                isInitialSync: Boolean): EphemeralResult {
+                                isInitialSync: Boolean,
+                                aggregator: SyncResponsePostTreatmentAggregator): EphemeralResult {
         var result = EphemeralResult()
         for (event in ephemeralEvents) {
             when (event.type) {
                 EventType.RECEIPT -> {
                     @Suppress("UNCHECKED_CAST")
                     (event.content as? ReadReceiptContent)?.let { readReceiptContent ->
-                        readReceiptHandler.handle(realm, roomId, readReceiptContent, isInitialSync)
+                        readReceiptHandler.handle(realm, roomId, readReceiptContent, isInitialSync, aggregator)
                     }
                 }
                 EventType.TYPING  -> {
@@ -471,18 +466,4 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
             }
         }
     }
-
-    private fun Event.getFixedRoomMemberContent(): RoomMemberContent? {
-        val content = content.toModel<RoomMemberContent>()
-        // if user is leaving, we should grab his last name and avatar from prevContent
-        return if (content?.membership?.isLeft() == true) {
-            val prevContent = resolvedPrevContent().toModel<RoomMemberContent>()
-            content.copy(
-                    displayName = prevContent?.displayName,
-                    avatarUrl = prevContent?.avatarUrl
-            )
-        } else {
-            content
-        }
-    }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomTagHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomTagHandler.kt
index f9ae41bc9406ea81d5195bfa8c45d9ec0d3cb190..add5d841d1b9682292a52e552165c868cdaa84eb 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomTagHandler.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomTagHandler.kt
@@ -19,8 +19,8 @@ package org.matrix.android.sdk.internal.session.sync
 import org.matrix.android.sdk.api.session.room.model.tag.RoomTagContent
 import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity
 import org.matrix.android.sdk.internal.database.model.RoomTagEntity
-import org.matrix.android.sdk.internal.database.query.where
 import io.realm.Realm
+import org.matrix.android.sdk.internal.database.query.getOrCreate
 import javax.inject.Inject
 
 internal class RoomTagHandler @Inject constructor() {
@@ -31,12 +31,8 @@ internal class RoomTagHandler @Inject constructor() {
         }
         val tags = content.tags.entries.map { (tagName, params) ->
             RoomTagEntity(tagName, params["order"] as? Double)
+            Pair(tagName, params["order"] as? Double)
         }
-        val roomSummaryEntity = RoomSummaryEntity.where(realm, roomId).findFirst()
-                ?: RoomSummaryEntity(roomId)
-
-        roomSummaryEntity.tags.clear()
-        roomSummaryEntity.tags.addAll(tags)
-        realm.insertOrUpdate(roomSummaryEntity)
+        RoomSummaryEntity.getOrCreate(realm, roomId).updateTags(tags)
     }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomTypingUsersHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomTypingUsersHandler.kt
index f4f3e6ce43714f8b113fe7f1e470d4f74925ad13..b7851031adcf6ba1f14ca328c328af7c5c4d396d 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomTypingUsersHandler.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomTypingUsersHandler.kt
@@ -26,6 +26,7 @@ import javax.inject.Inject
 internal class RoomTypingUsersHandler @Inject constructor(@UserId private val userId: String,
                                                           private val typingUsersTracker: DefaultTypingUsersTracker) {
 
+    // TODO This could be handled outside of the Realm transaction. Use the new aggregator?
     fun handle(realm: Realm, roomId: String, ephemeralResult: RoomSyncHandler.EphemeralResult?) {
         val roomMemberHelper = RoomMemberHelper(realm, roomId)
         val typingIds = ephemeralResult?.typingUserIds?.filter { it != userId }.orEmpty()
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncAPI.kt
index 8e3523bc5767ab44d773d1463770b8bb317a0e95..2616803463a6c6707bda2a772951349dac1c6783 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncAPI.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncAPI.kt
@@ -31,11 +31,11 @@ internal interface SyncAPI {
      * Set all the timeouts to 1 minute by default
      */
     @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "sync")
-    fun sync(@QueryMap params: Map<String, String>,
-             @Header(TimeOutInterceptor.CONNECT_TIMEOUT) connectTimeOut: Long = TimeOutInterceptor.DEFAULT_LONG_TIMEOUT,
-             @Header(TimeOutInterceptor.READ_TIMEOUT) readTimeOut: Long = TimeOutInterceptor.DEFAULT_LONG_TIMEOUT,
-             @Header(TimeOutInterceptor.WRITE_TIMEOUT) writeTimeOut: Long = TimeOutInterceptor.DEFAULT_LONG_TIMEOUT
-    ): Call<SyncResponse>
+    suspend fun sync(@QueryMap params: Map<String, String>,
+                     @Header(TimeOutInterceptor.CONNECT_TIMEOUT) connectTimeOut: Long = TimeOutInterceptor.DEFAULT_LONG_TIMEOUT,
+                     @Header(TimeOutInterceptor.READ_TIMEOUT) readTimeOut: Long = TimeOutInterceptor.DEFAULT_LONG_TIMEOUT,
+                     @Header(TimeOutInterceptor.WRITE_TIMEOUT) writeTimeOut: Long = TimeOutInterceptor.DEFAULT_LONG_TIMEOUT
+    ): SyncResponse
 
     /**
      * Set all the timeouts to 1 minute by default
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncModule.kt
index 010c029c97f14ae7951bb6893f69c640194f01c3..4b31dc4d9b00b7d9b488c0abc50604ca1c98364f 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncModule.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncModule.kt
@@ -37,4 +37,7 @@ internal abstract class SyncModule {
 
     @Binds
     abstract fun bindSyncTask(task: DefaultSyncTask): SyncTask
+
+    @Binds
+    abstract fun bindRoomSyncEphemeralTemporaryStore(store: RoomSyncEphemeralTemporaryStoreFile): RoomSyncEphemeralTemporaryStore
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponseHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponseHandler.kt
index d17a672485d7b4ceb077bc8f3bfe59bd4c8647eb..8e243c3443d9deb6b09f2479f230bc215e3b9656 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponseHandler.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponseHandler.kt
@@ -41,17 +41,19 @@ import kotlin.system.measureTimeMillis
 
 private const val GET_GROUP_DATA_WORKER = "GET_GROUP_DATA_WORKER"
 
-internal class SyncResponseHandler @Inject constructor(@SessionDatabase private val monarchy: Monarchy,
-                                                       @SessionId private val sessionId: String,
-                                                       private val workManagerProvider: WorkManagerProvider,
-                                                       private val roomSyncHandler: RoomSyncHandler,
-                                                       private val userAccountDataSyncHandler: UserAccountDataSyncHandler,
-                                                       private val groupSyncHandler: GroupSyncHandler,
-                                                       private val cryptoSyncHandler: CryptoSyncHandler,
-                                                       private val cryptoService: DefaultCryptoService,
-                                                       private val tokenStore: SyncTokenStore,
-                                                       private val processEventForPushTask: ProcessEventForPushTask,
-                                                       private val pushRuleService: PushRuleService) {
+internal class SyncResponseHandler @Inject constructor(
+        @SessionDatabase private val monarchy: Monarchy,
+        @SessionId private val sessionId: String,
+        private val workManagerProvider: WorkManagerProvider,
+        private val roomSyncHandler: RoomSyncHandler,
+        private val userAccountDataSyncHandler: UserAccountDataSyncHandler,
+        private val groupSyncHandler: GroupSyncHandler,
+        private val cryptoSyncHandler: CryptoSyncHandler,
+        private val aggregatorHandler: SyncResponsePostTreatmentAggregatorHandler,
+        private val cryptoService: DefaultCryptoService,
+        private val tokenStore: SyncTokenStore,
+        private val processEventForPushTask: ProcessEventForPushTask,
+        private val pushRuleService: PushRuleService) {
 
     suspend fun handleResponse(syncResponse: SyncResponse,
                                fromToken: String?,
@@ -81,13 +83,14 @@ internal class SyncResponseHandler @Inject constructor(@SessionDatabase private
         }.also {
             Timber.v("Finish handling toDevice in $it ms")
         }
+        val aggregator = SyncResponsePostTreatmentAggregator()
         // Start one big transaction
         monarchy.awaitTransaction { realm ->
             measureTimeMillis {
                 Timber.v("Handle rooms")
                 reportSubtask(reporter, InitSyncStep.ImportingAccountRoom, 1, 0.7f) {
                     if (syncResponse.rooms != null) {
-                        roomSyncHandler.handle(realm, syncResponse.rooms, isInitialSync, reporter)
+                        roomSyncHandler.handle(realm, syncResponse.rooms, isInitialSync, aggregator, reporter)
                     }
                 }
             }.also {
@@ -115,7 +118,10 @@ internal class SyncResponseHandler @Inject constructor(@SessionDatabase private
             }
             tokenStore.saveToken(realm, syncResponse.nextBatch)
         }
+
         // Everything else we need to do outside the transaction
+        aggregatorHandler.handle(aggregator)
+
         syncResponse.rooms?.let {
             checkPushRules(it, isInitialSync)
             userAccountDataSyncHandler.synchronizeWithServerIfNeeded(it.invite)
@@ -128,15 +134,6 @@ internal class SyncResponseHandler @Inject constructor(@SessionDatabase private
         cryptoSyncHandler.onSyncCompleted(syncResponse)
     }
 
-    suspend fun handleInitSyncSecondTransaction(syncResponse: SyncResponse) {
-        // Start another transaction to handle the ephemeral events
-        monarchy.awaitTransaction { realm ->
-            if (syncResponse.rooms != null) {
-                roomSyncHandler.handleInitSyncEphemeral(realm, syncResponse.rooms)
-            }
-        }
-    }
-
     /**
      * At the moment we don't get any group data through the sync, so we poll where every hour.
      * You can also force to refetch group data using [Group] API.
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponsePostTreatmentAggregator.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponsePostTreatmentAggregator.kt
new file mode 100644
index 0000000000000000000000000000000000000000..ea10a32f3eef33350edc5450b858d28d82d0b656
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponsePostTreatmentAggregator.kt
@@ -0,0 +1,22 @@
+/*
+ * 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.sync
+
+internal class SyncResponsePostTreatmentAggregator {
+    // List of RoomId
+    val ephemeralFilesToDelete = mutableListOf<String>()
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponsePostTreatmentAggregatorHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponsePostTreatmentAggregatorHandler.kt
new file mode 100644
index 0000000000000000000000000000000000000000..12b77c706b3759ffd993554a296fddd326d7102d
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponsePostTreatmentAggregatorHandler.kt
@@ -0,0 +1,33 @@
+/*
+ * 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.sync
+
+import javax.inject.Inject
+
+internal class SyncResponsePostTreatmentAggregatorHandler @Inject constructor(
+        private val ephemeralTemporaryStore: RoomSyncEphemeralTemporaryStore
+) {
+    fun handle(synResHaResponsePostTreatmentAggregator: SyncResponsePostTreatmentAggregator) {
+        cleanupEphemeralFiles(synResHaResponsePostTreatmentAggregator.ephemeralFilesToDelete)
+    }
+
+    private fun cleanupEphemeralFiles(ephemeralFilesToDelete: List<String>) {
+        ephemeralFilesToDelete.forEach {
+            ephemeralTemporaryStore.delete(it)
+        }
+    }
+}
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 00060a33b11db2fee0dbcf34228b59520e651147..83a2ffc4466cd7a90e4ef890413562f8de4c122c 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
@@ -29,7 +29,6 @@ import org.matrix.android.sdk.internal.session.homeserver.GetHomeServerCapabilit
 import org.matrix.android.sdk.internal.session.initsync.DefaultInitialSyncProgressService
 import org.matrix.android.sdk.internal.session.initsync.reportSubtask
 import org.matrix.android.sdk.internal.session.sync.model.LazyRoomSyncEphemeral
-import org.matrix.android.sdk.internal.session.sync.model.SyncResponse
 import org.matrix.android.sdk.internal.session.sync.parsing.InitialSyncResponseParser
 import org.matrix.android.sdk.internal.session.user.UserStore
 import org.matrix.android.sdk.internal.task.Task
@@ -62,7 +61,8 @@ internal class DefaultSyncTask @Inject constructor(
         private val globalErrorReceiver: GlobalErrorReceiver,
         @SessionFilesDirectory
         private val fileDirectory: File,
-        private val syncResponseParser: InitialSyncResponseParser
+        private val syncResponseParser: InitialSyncResponseParser,
+        private val roomSyncEphemeralTemporaryStore: RoomSyncEphemeralTemporaryStore
 ) : SyncTask {
 
     private val workingDir = File(fileDirectory, "is")
@@ -100,19 +100,22 @@ internal class DefaultSyncTask @Inject constructor(
         val readTimeOut = (params.timeout + TIMEOUT_MARGIN).coerceAtLeast(TimeOutInterceptor.DEFAULT_LONG_TIMEOUT)
 
         if (isInitialSync) {
-            Timber.v("INIT_SYNC with filter: ${requestParams["filter"]}")
+            Timber.d("INIT_SYNC with filter: ${requestParams["filter"]}")
             val initSyncStrategy = initialSyncStrategy
-            var syncResp: SyncResponse? = null
             logDuration("INIT_SYNC strategy: $initSyncStrategy") {
                 if (initSyncStrategy is InitialSyncStrategy.Optimized) {
+                    roomSyncEphemeralTemporaryStore.reset()
+                    workingDir.mkdirs()
                     val file = downloadInitSyncResponse(requestParams)
-                    syncResp = reportSubtask(initialSyncProgressService, InitSyncStep.ImportingAccount, 1, 0.7F) {
+                    reportSubtask(initialSyncProgressService, InitSyncStep.ImportingAccount, 1, 0.7F) {
                         handleSyncFile(file, initSyncStrategy)
                     }
+                    // Delete all files
+                    workingDir.deleteRecursively()
                 } else {
                     val syncResponse = logDuration("INIT_SYNC Request") {
-                        executeRequest<SyncResponse>(globalErrorReceiver) {
-                            apiCall = syncAPI.sync(
+                        executeRequest(globalErrorReceiver) {
+                            syncAPI.sync(
                                     params = requestParams,
                                     readTimeOut = readTimeOut
                             )
@@ -125,18 +128,9 @@ internal class DefaultSyncTask @Inject constructor(
                 }
             }
             initialSyncProgressService.endAll()
-
-            if (initSyncStrategy is InitialSyncStrategy.Optimized) {
-                logDuration("INIT_SYNC Handle ephemeral") {
-                    syncResponseHandler.handleInitSyncSecondTransaction(syncResp!!)
-                }
-                initialSyncStatusRepository.setStep(InitialSyncStatus.STEP_SUCCESS)
-                // Delete all files
-                workingDir.deleteRecursively()
-            }
         } else {
-            val syncResponse = executeRequest<SyncResponse>(globalErrorReceiver) {
-                apiCall = syncAPI.sync(
+            val syncResponse = executeRequest(globalErrorReceiver) {
+                syncAPI.sync(
                         params = requestParams,
                         readTimeOut = readTimeOut
                 )
@@ -147,11 +141,10 @@ internal class DefaultSyncTask @Inject constructor(
     }
 
     private suspend fun downloadInitSyncResponse(requestParams: Map<String, String>): File {
-        workingDir.mkdirs()
         val workingFile = File(workingDir, "initSync.json")
         val status = initialSyncStatusRepository.getStep()
         if (workingFile.exists() && status >= InitialSyncStatus.STEP_DOWNLOADED) {
-            Timber.v("INIT_SYNC file is already here")
+            Timber.d("INIT_SYNC file is already here")
             reportSubtask(initialSyncProgressService, InitSyncStep.Downloading, 1, 0.3f) {
                 // Empty task
             }
@@ -201,8 +194,8 @@ internal class DefaultSyncTask @Inject constructor(
         }
     }
 
-    private suspend fun handleSyncFile(workingFile: File, initSyncStrategy: InitialSyncStrategy.Optimized): SyncResponse {
-        return logDuration("INIT_SYNC handleSyncFile()") {
+    private suspend fun handleSyncFile(workingFile: File, initSyncStrategy: InitialSyncStrategy.Optimized) {
+        logDuration("INIT_SYNC handleSyncFile()") {
             val syncResponse = logDuration("INIT_SYNC Read file and parse") {
                 syncResponseParser.parse(initSyncStrategy, workingFile)
             }
@@ -210,12 +203,12 @@ internal class DefaultSyncTask @Inject constructor(
             // Log some stats
             val nbOfJoinedRooms = syncResponse.rooms?.join?.size ?: 0
             val nbOfJoinedRoomsInFile = syncResponse.rooms?.join?.values?.count { it.ephemeral is LazyRoomSyncEphemeral.Stored }
-            Timber.v("INIT_SYNC $nbOfJoinedRooms rooms, $nbOfJoinedRoomsInFile ephemeral stored into files")
+            Timber.d("INIT_SYNC $nbOfJoinedRooms rooms, $nbOfJoinedRoomsInFile ephemeral stored into files")
 
             logDuration("INIT_SYNC Database insertion") {
                 syncResponseHandler.handleResponse(syncResponse, null, initialSyncProgressService)
             }
-            syncResponse
+            initialSyncStatusRepository.setStep(InitialSyncStatus.STEP_SUCCESS)
         }
     }
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/UserAccountDataSyncHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/UserAccountDataSyncHandler.kt
index 449d47abe5580670a8a245623b6210062fb3da81..b8d987d5009471f7a08b1ce7553dccdc01ea976a 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/UserAccountDataSyncHandler.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/UserAccountDataSyncHandler.kt
@@ -45,6 +45,8 @@ 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.di.UserId
+import org.matrix.android.sdk.internal.session.room.RoomAvatarResolver
+import org.matrix.android.sdk.internal.session.room.membership.RoomDisplayNameResolver
 import org.matrix.android.sdk.internal.session.room.membership.RoomMemberHelper
 import org.matrix.android.sdk.internal.session.sync.model.InvitedRoomSync
 import org.matrix.android.sdk.internal.session.sync.model.accountdata.BreadcrumbsContent
@@ -60,7 +62,10 @@ internal class UserAccountDataSyncHandler @Inject constructor(
         @SessionDatabase private val monarchy: Monarchy,
         @UserId private val userId: String,
         private val directChatsHelper: DirectChatsHelper,
-        private val updateUserAccountDataTask: UpdateUserAccountDataTask) {
+        private val updateUserAccountDataTask: UpdateUserAccountDataTask,
+        private val roomAvatarResolver: RoomAvatarResolver,
+        private val roomDisplayNameResolver: RoomDisplayNameResolver
+) {
 
     fun handle(realm: Realm, accountData: UserAccountDataSync?) {
         accountData?.list?.forEach { event ->
@@ -151,23 +156,29 @@ internal class UserAccountDataSyncHandler @Inject constructor(
     }
 
     private fun handleDirectChatRooms(realm: Realm, event: UserAccountDataEvent) {
-        val oldDirectRooms = RoomSummaryEntity.getDirectRooms(realm)
-        oldDirectRooms.forEach {
-            it.isDirect = false
-            it.directUserId = null
-        }
         val content = event.content.toModel<DirectMessagesContent>() ?: return
-        content.forEach {
-            val userId = it.key
-            it.value.forEach { roomId ->
+        content.forEach { (userId, roomIds) ->
+            roomIds.forEach { roomId ->
                 val roomSummaryEntity = RoomSummaryEntity.where(realm, roomId).findFirst()
                 if (roomSummaryEntity != null) {
                     roomSummaryEntity.isDirect = true
                     roomSummaryEntity.directUserId = userId
-                    realm.insertOrUpdate(roomSummaryEntity)
+                    // Also update the avatar and displayname, there is a specific treatment for DMs
+                    roomSummaryEntity.avatarUrl = roomAvatarResolver.resolve(realm, roomId)
+                    roomSummaryEntity.displayName = roomDisplayNameResolver.resolve(realm, roomId)
                 }
             }
         }
+
+        // Handle previous direct rooms
+        RoomSummaryEntity.getDirectRooms(realm, excludeRoomIds = content.values.flatten().toSet())
+                .forEach {
+                    it.isDirect = false
+                    it.directUserId = null
+                    // Also update the avatar and displayname, there was a specific treatment for DMs
+                    it.avatarUrl = roomAvatarResolver.resolve(realm, it.roomId)
+                    it.displayName = roomDisplayNameResolver.resolve(realm, it.roomId)
+                }
     }
 
     private fun handleIgnoredUsers(realm: Realm, event: UserAccountDataEvent) {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncWorker.kt
index 211b643516d5cb729703698130345f950d837ab7..b81804feb53983556b1a5bd834dfe06c25519e36 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncWorker.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncWorker.kt
@@ -106,7 +106,7 @@ internal class SyncWorker(context: Context,
             val data = WorkerParamsFactory.toData(Params(sessionId, serverTimeout, 0L, false))
             val workRequest = workManagerProvider.matrixOneTimeWorkRequestBuilder<SyncWorker>()
                     .setConstraints(WorkManagerProvider.workConstraints)
-                    .setBackoffCriteria(BackoffPolicy.LINEAR, 1_000, TimeUnit.MILLISECONDS)
+                    .setBackoffCriteria(BackoffPolicy.LINEAR, WorkManagerProvider.BACKOFF_DELAY_MILLIS, TimeUnit.MILLISECONDS)
                     .setInputData(data)
                     .build()
             workManagerProvider.workManager
@@ -118,7 +118,7 @@ internal class SyncWorker(context: Context,
             val workRequest = workManagerProvider.matrixOneTimeWorkRequestBuilder<SyncWorker>()
                     .setConstraints(WorkManagerProvider.workConstraints)
                     .setInputData(data)
-                    .setBackoffCriteria(BackoffPolicy.LINEAR, 1_000, TimeUnit.MILLISECONDS)
+                    .setBackoffCriteria(BackoffPolicy.LINEAR, WorkManagerProvider.BACKOFF_DELAY_MILLIS, TimeUnit.MILLISECONDS)
                     .setInitialDelay(delayInSeconds, TimeUnit.SECONDS)
                     .build()
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/model/LazyRoomSyncEphemeral.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/model/LazyRoomSyncEphemeral.kt
index 938168b5f4bb368c0863c763f6813cca5a79e078..83006c646b2710eac33e92e9838a9fbaa31928cf 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/model/LazyRoomSyncEphemeral.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/model/LazyRoomSyncEphemeral.kt
@@ -16,28 +16,10 @@
 
 package org.matrix.android.sdk.internal.session.sync.model
 
-import com.squareup.moshi.JsonAdapter
 import com.squareup.moshi.JsonClass
-import com.squareup.moshi.JsonReader
-import okio.buffer
-import okio.source
-import java.io.File
 
 @JsonClass(generateAdapter = false)
 internal sealed class LazyRoomSyncEphemeral {
     data class Parsed(val _roomSyncEphemeral: RoomSyncEphemeral) : LazyRoomSyncEphemeral()
-    data class Stored(val roomSyncEphemeralAdapter: JsonAdapter<RoomSyncEphemeral>, val file: File) : LazyRoomSyncEphemeral()
-
-    val roomSyncEphemeral: RoomSyncEphemeral
-        get() {
-            return when (this) {
-                is Parsed -> _roomSyncEphemeral
-                is Stored -> {
-                    // Parse the file now
-                    file.inputStream().use { pos ->
-                        roomSyncEphemeralAdapter.fromJson(JsonReader.of(pos.source().buffer()))!!
-                    }
-                }
-            }
-        }
+    object Stored : LazyRoomSyncEphemeral()
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/model/accountdata/DirectMessagesContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/model/accountdata/DirectMessagesContent.kt
index c406f3acf1e9212f38750afc3060df7febb8ce5e..41173dea96b8b3ef5aa27ea0e25f1f4848c04aae 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/model/accountdata/DirectMessagesContent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/model/accountdata/DirectMessagesContent.kt
@@ -16,4 +16,7 @@
 
 package org.matrix.android.sdk.internal.session.sync.model.accountdata
 
-typealias DirectMessagesContent = Map<String, List<String>>
+/**
+ * Keys are userIds, values are list of roomIds
+ */
+internal typealias DirectMessagesContent = Map<String, List<String>>
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/parsing/DefaultLazyRoomSyncEphemeralJsonAdapter.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/parsing/DefaultLazyRoomSyncEphemeralJsonAdapter.kt
index ef56802a668c2206d0d73f24f1be0732c098ec3c..940ea219fb61c9d544bf9c7da8168644c8821bee 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/parsing/DefaultLazyRoomSyncEphemeralJsonAdapter.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/parsing/DefaultLazyRoomSyncEphemeralJsonAdapter.kt
@@ -22,11 +22,10 @@ import com.squareup.moshi.JsonReader
 import com.squareup.moshi.JsonWriter
 import com.squareup.moshi.ToJson
 import org.matrix.android.sdk.internal.session.sync.InitialSyncStrategy
+import org.matrix.android.sdk.internal.session.sync.RoomSyncEphemeralTemporaryStore
 import org.matrix.android.sdk.internal.session.sync.model.LazyRoomSyncEphemeral
 import org.matrix.android.sdk.internal.session.sync.model.RoomSyncEphemeral
 import timber.log.Timber
-import java.io.File
-import java.util.concurrent.atomic.AtomicInteger
 
 internal class DefaultLazyRoomSyncEphemeralJsonAdapter {
 
@@ -44,32 +43,26 @@ internal class DefaultLazyRoomSyncEphemeralJsonAdapter {
     }
 }
 
-internal class SplitLazyRoomSyncJsonAdapter(
-        private val workingDirectory: File,
+internal class SplitLazyRoomSyncEphemeralJsonAdapter(
+        private val roomSyncEphemeralTemporaryStore: RoomSyncEphemeralTemporaryStore,
         private val syncStrategy: InitialSyncStrategy.Optimized
 ) {
-    private val atomicInteger = AtomicInteger(0)
-
-    private fun createFile(): File {
-        val index = atomicInteger.getAndIncrement()
-        return File(workingDirectory, "room_$index.json")
-    }
-
     @FromJson
     fun fromJson(reader: JsonReader, delegate: JsonAdapter<RoomSyncEphemeral>): LazyRoomSyncEphemeral? {
         val path = reader.path
+        val roomId = path.substringAfter("\$.rooms.join.").substringBeforeLast(".ephemeral")
+
         val json = reader.nextSource().inputStream().bufferedReader().use {
             it.readText()
         }
         val limit = syncStrategy.minSizeToStoreInFile
         return if (json.length > limit) {
-            Timber.v("INIT_SYNC $path content length: ${json.length} copy to a file")
+            Timber.d("INIT_SYNC $path content length: ${json.length} copy to a file")
             // Copy the source to a file
-            val file = createFile()
-            file.writeText(json)
-            LazyRoomSyncEphemeral.Stored(delegate, file)
+            roomSyncEphemeralTemporaryStore.write(roomId, json)
+            LazyRoomSyncEphemeral.Stored
         } else {
-            Timber.v("INIT_SYNC $path content length: ${json.length} parse it now")
+            Timber.d("INIT_SYNC $path content length: ${json.length} parse it now")
             val roomSync = delegate.fromJson(json) ?: return null
             LazyRoomSyncEphemeral.Parsed(roomSync)
         }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/parsing/InitialSyncResponseParser.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/parsing/InitialSyncResponseParser.kt
index ae7b2a44681f2369b5301f6cd9eeaf5b1d8487d8..0b44887aedcff07a73fe9f87bd7e75ba023e1c97 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/parsing/InitialSyncResponseParser.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/parsing/InitialSyncResponseParser.kt
@@ -20,29 +20,33 @@ import com.squareup.moshi.Moshi
 import okio.buffer
 import okio.source
 import org.matrix.android.sdk.internal.session.sync.InitialSyncStrategy
+import org.matrix.android.sdk.internal.session.sync.RoomSyncEphemeralTemporaryStore
 import org.matrix.android.sdk.internal.session.sync.model.SyncResponse
 import timber.log.Timber
 import java.io.File
 import javax.inject.Inject
 
-internal class InitialSyncResponseParser @Inject constructor(private val moshi: Moshi) {
+internal class InitialSyncResponseParser @Inject constructor(
+        private val moshi: Moshi,
+        private val roomSyncEphemeralTemporaryStore: RoomSyncEphemeralTemporaryStore
+) {
 
     fun parse(syncStrategy: InitialSyncStrategy.Optimized, workingFile: File): SyncResponse {
         val syncResponseLength = workingFile.length().toInt()
-        Timber.v("INIT_SYNC Sync file size is $syncResponseLength bytes")
+        Timber.d("INIT_SYNC Sync file size is $syncResponseLength bytes")
         val shouldSplit = syncResponseLength >= syncStrategy.minSizeToSplit
-        Timber.v("INIT_SYNC should split in several files: $shouldSplit")
-        return getMoshi(syncStrategy, workingFile.parentFile!!, shouldSplit)
+        Timber.d("INIT_SYNC should split in several files: $shouldSplit")
+        return getMoshi(syncStrategy, shouldSplit)
                 .adapter(SyncResponse::class.java)
                 .fromJson(workingFile.source().buffer())!!
     }
 
-    private fun getMoshi(syncStrategy: InitialSyncStrategy.Optimized, workingDirectory: File, shouldSplit: Boolean): Moshi {
+    private fun getMoshi(syncStrategy: InitialSyncStrategy.Optimized, shouldSplit: Boolean): Moshi {
         // If we don't have to split we'll rely on the already default moshi
         if (!shouldSplit) return moshi
         // Otherwise, we create a new adapter for handling Map of Lazy sync
         return moshi.newBuilder()
-                .add(SplitLazyRoomSyncJsonAdapter(workingDirectory, syncStrategy))
+                .add(SplitLazyRoomSyncEphemeralJsonAdapter(roomSyncEphemeralTemporaryStore, syncStrategy))
                 .build()
     }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/terms/DefaultTermsService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/terms/DefaultTermsService.kt
index 41914cc7997b6419ee273c49c64681bf11285b52..bac725fad28f25cb3a495b62656ee3fea94f4815 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/terms/DefaultTermsService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/terms/DefaultTermsService.kt
@@ -17,7 +17,8 @@
 package org.matrix.android.sdk.internal.session.terms
 
 import dagger.Lazy
-import kotlinx.coroutines.withContext
+import okhttp3.OkHttpClient
+import org.matrix.android.sdk.api.session.accountdata.UserAccountDataTypes
 import org.matrix.android.sdk.api.session.events.model.toModel
 import org.matrix.android.sdk.api.session.terms.GetTermsResponse
 import org.matrix.android.sdk.api.session.terms.TermsService
@@ -29,12 +30,9 @@ import org.matrix.android.sdk.internal.session.identity.IdentityAuthAPI
 import org.matrix.android.sdk.internal.session.identity.IdentityRegisterTask
 import org.matrix.android.sdk.internal.session.openid.GetOpenIdTokenTask
 import org.matrix.android.sdk.internal.session.sync.model.accountdata.AcceptedTermsContent
-import org.matrix.android.sdk.api.session.accountdata.UserAccountDataTypes
 import org.matrix.android.sdk.internal.session.user.accountdata.AccountDataDataSource
 import org.matrix.android.sdk.internal.session.user.accountdata.UpdateUserAccountDataTask
-import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers
 import org.matrix.android.sdk.internal.util.ensureTrailingSlash
-import okhttp3.OkHttpClient
 import javax.inject.Inject
 
 internal class DefaultTermsService @Inject constructor(
@@ -45,43 +43,39 @@ internal class DefaultTermsService @Inject constructor(
         private val retrofitFactory: RetrofitFactory,
         private val getOpenIdTokenTask: GetOpenIdTokenTask,
         private val identityRegisterTask: IdentityRegisterTask,
-        private val updateUserAccountDataTask: UpdateUserAccountDataTask,
-        private val coroutineDispatchers: MatrixCoroutineDispatchers
+        private val updateUserAccountDataTask: UpdateUserAccountDataTask
 ) : TermsService {
+
     override suspend fun getTerms(serviceType: TermsService.ServiceType,
-                                 baseUrl: String): GetTermsResponse {
-        return withContext(coroutineDispatchers.main) {
-            val url = buildUrl(baseUrl, serviceType)
-            val termsResponse = executeRequest<TermsResponse>(null) {
-                apiCall = termsAPI.getTerms("${url}terms")
-            }
-            GetTermsResponse(termsResponse, getAlreadyAcceptedTermUrlsFromAccountData())
+                                  baseUrl: String): GetTermsResponse {
+        val url = buildUrl(baseUrl, serviceType)
+        val termsResponse = executeRequest(null) {
+            termsAPI.getTerms("${url}terms")
         }
+        return GetTermsResponse(termsResponse, getAlreadyAcceptedTermUrlsFromAccountData())
     }
 
     override suspend fun agreeToTerms(serviceType: TermsService.ServiceType,
                                       baseUrl: String,
                                       agreedUrls: List<String>,
                                       token: String?) {
-        withContext(coroutineDispatchers.main) {
-            val url = buildUrl(baseUrl, serviceType)
-            val tokenToUse = token?.takeIf { it.isNotEmpty() } ?: getToken(baseUrl)
+        val url = buildUrl(baseUrl, serviceType)
+        val tokenToUse = token?.takeIf { it.isNotEmpty() } ?: getToken(baseUrl)
 
-            executeRequest<Unit>(null) {
-                apiCall = termsAPI.agreeToTerms("${url}terms", AcceptTermsBody(agreedUrls), "Bearer $tokenToUse")
-            }
+        executeRequest(null) {
+            termsAPI.agreeToTerms("${url}terms", AcceptTermsBody(agreedUrls), "Bearer $tokenToUse")
+        }
 
-            // client SHOULD update this account data section adding any the URLs
-            // of any additional documents that the user agreed to this list.
-            // Get current m.accepted_terms append new ones and update account data
-            val listOfAcceptedTerms = getAlreadyAcceptedTermUrlsFromAccountData()
+        // client SHOULD update this account data section adding any the URLs
+        // of any additional documents that the user agreed to this list.
+        // Get current m.accepted_terms append new ones and update account data
+        val listOfAcceptedTerms = getAlreadyAcceptedTermUrlsFromAccountData()
 
-            val newList = listOfAcceptedTerms.toMutableSet().apply { addAll(agreedUrls) }.toList()
+        val newList = listOfAcceptedTerms.toMutableSet().apply { addAll(agreedUrls) }.toList()
 
-            updateUserAccountDataTask.execute(UpdateUserAccountDataTask.AcceptedTermsParams(
-                    acceptedTermsContent = AcceptedTermsContent(newList)
-            ))
-        }
+        updateUserAccountDataTask.execute(UpdateUserAccountDataTask.AcceptedTermsParams(
+                acceptedTermsContent = AcceptedTermsContent(newList)
+        ))
     }
 
     private suspend fun getToken(url: String): String {
@@ -97,7 +91,7 @@ internal class DefaultTermsService @Inject constructor(
     private fun buildUrl(baseUrl: String, serviceType: TermsService.ServiceType): String {
         val servicePath = when (serviceType) {
             TermsService.ServiceType.IntegrationManager -> NetworkConstants.URI_INTEGRATION_MANAGER_PATH
-            TermsService.ServiceType.IdentityService    -> NetworkConstants.URI_IDENTITY_PATH_V2
+            TermsService.ServiceType.IdentityService -> NetworkConstants.URI_IDENTITY_PATH_V2
         }
         return "${baseUrl.ensureTrailingSlash()}$servicePath"
     }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/terms/TermsAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/terms/TermsAPI.kt
index 4c97f462eb16c2b4ce410cb2dca0bd45eeb73781..91d27030de905d3d20172a2decd09eee08568005 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/terms/TermsAPI.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/terms/TermsAPI.kt
@@ -17,7 +17,6 @@
 package org.matrix.android.sdk.internal.session.terms
 
 import org.matrix.android.sdk.internal.network.HttpHeaders
-import retrofit2.Call
 import retrofit2.http.Body
 import retrofit2.http.GET
 import retrofit2.http.Header
@@ -29,13 +28,13 @@ internal interface TermsAPI {
      * This request does not require authentication
      */
     @GET
-    fun getTerms(@Url url: String): Call<TermsResponse>
+    suspend fun getTerms(@Url url: String): TermsResponse
 
     /**
      * This request requires authentication
      */
     @POST
-    fun agreeToTerms(@Url url: String,
-                     @Body params: AcceptTermsBody,
-                     @Header(HttpHeaders.Authorization) token: String): Call<Unit>
+    suspend fun agreeToTerms(@Url url: String,
+                             @Body params: AcceptTermsBody,
+                             @Header(HttpHeaders.Authorization) token: String)
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/thirdparty/GetThirdPartyProtocolsTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/thirdparty/GetThirdPartyProtocolsTask.kt
index fd1ed741e991b3f80506f4571abcab2c908ad394..026e17b51301ffd869b91ead48e549b041b7d0a4 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/thirdparty/GetThirdPartyProtocolsTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/thirdparty/GetThirdPartyProtocolsTask.kt
@@ -31,7 +31,7 @@ internal class DefaultGetThirdPartyProtocolsTask @Inject constructor(
 
     override suspend fun execute(params: Unit): Map<String, ThirdPartyProtocol> {
         return executeRequest(globalErrorReceiver) {
-            apiCall = thirdPartyAPI.thirdPartyProtocols()
+            thirdPartyAPI.thirdPartyProtocols()
         }
     }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/thirdparty/GetThirdPartyUserTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/thirdparty/GetThirdPartyUserTask.kt
index 01a8b576783c404f53409176ec48d6f5a48b6c6b..f541dcb8145ad315d4265152f961cf62dbec92c5 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/thirdparty/GetThirdPartyUserTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/thirdparty/GetThirdPartyUserTask.kt
@@ -37,7 +37,7 @@ internal class DefaultGetThirdPartyUserTask @Inject constructor(
 
     override suspend fun execute(params: GetThirdPartyUserTask.Params): List<ThirdPartyUser> {
         return executeRequest(globalErrorReceiver) {
-            apiCall = thirdPartyAPI.getThirdPartyUser(params.protocol, params.fields)
+            thirdPartyAPI.getThirdPartyUser(params.protocol, params.fields)
         }
     }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/thirdparty/ThirdPartyAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/thirdparty/ThirdPartyAPI.kt
index 0c60a27341239c661054f993e2cfbe757a735557..2e03bc7a869e76a8284cafe0ff016b3d20233c9d 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/thirdparty/ThirdPartyAPI.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/thirdparty/ThirdPartyAPI.kt
@@ -19,7 +19,6 @@ package org.matrix.android.sdk.internal.session.thirdparty
 import org.matrix.android.sdk.api.session.room.model.thirdparty.ThirdPartyProtocol
 import org.matrix.android.sdk.api.session.thirdparty.model.ThirdPartyUser
 import org.matrix.android.sdk.internal.network.NetworkConstants
-import retrofit2.Call
 import retrofit2.http.GET
 import retrofit2.http.Path
 import retrofit2.http.QueryMap
@@ -32,7 +31,7 @@ internal interface ThirdPartyAPI {
      * Ref: https://matrix.org/docs/spec/client_server/r0.6.1.html#get-matrix-client-r0-thirdparty-protocols
      */
     @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "thirdparty/protocols")
-    fun thirdPartyProtocols(): Call<Map<String, ThirdPartyProtocol>>
+    suspend fun thirdPartyProtocols(): Map<String, ThirdPartyProtocol>
 
     /**
      * Retrieve a Matrix User ID linked to a user on the third party service, given a set of user parameters.
@@ -40,5 +39,6 @@ internal interface ThirdPartyAPI {
      * Ref: https://matrix.org/docs/spec/client_server/r0.6.1#get-matrix-client-r0-thirdparty-user-protocol
      */
     @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "thirdparty/protocols/user/{protocol}")
-    fun getThirdPartyUser(@Path("protocol") protocol: String, @QueryMap params: Map<String, String>?): Call<List<ThirdPartyUser>>
+    suspend fun getThirdPartyUser(@Path("protocol") protocol: String,
+                                  @QueryMap params: Map<String, String>?): List<ThirdPartyUser>
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/DefaultUserService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/DefaultUserService.kt
index 17409569150f97a462ad72fee0087dd8d9691e1b..52b8cc36892422ea50766d714dbf66c16c74d031 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/DefaultUserService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/DefaultUserService.kt
@@ -18,54 +18,35 @@ package org.matrix.android.sdk.internal.session.user
 
 import androidx.lifecycle.LiveData
 import androidx.paging.PagedList
-import org.matrix.android.sdk.api.MatrixCallback
 import org.matrix.android.sdk.api.session.profile.ProfileService
 import org.matrix.android.sdk.api.session.user.UserService
 import org.matrix.android.sdk.api.session.user.model.User
-import org.matrix.android.sdk.api.util.Cancelable
-import org.matrix.android.sdk.api.util.JsonDict
 import org.matrix.android.sdk.api.util.Optional
 import org.matrix.android.sdk.internal.session.profile.GetProfileInfoTask
 import org.matrix.android.sdk.internal.session.user.accountdata.UpdateIgnoredUserIdsTask
 import org.matrix.android.sdk.internal.session.user.model.SearchUserTask
-import org.matrix.android.sdk.internal.task.TaskExecutor
-import org.matrix.android.sdk.internal.task.configureWith
 import javax.inject.Inject
 
 internal class DefaultUserService @Inject constructor(private val userDataSource: UserDataSource,
                                                       private val searchUserTask: SearchUserTask,
                                                       private val updateIgnoredUserIdsTask: UpdateIgnoredUserIdsTask,
-                                                      private val getProfileInfoTask: GetProfileInfoTask,
-                                                      private val taskExecutor: TaskExecutor) : UserService {
+                                                      private val getProfileInfoTask: GetProfileInfoTask) : UserService {
 
     override fun getUser(userId: String): User? {
         return userDataSource.getUser(userId)
     }
 
-    override fun resolveUser(userId: String, callback: MatrixCallback<User>) {
+    override suspend fun resolveUser(userId: String): User {
         val known = getUser(userId)
         if (known != null) {
-            callback.onSuccess(known)
+            return known
         } else {
             val params = GetProfileInfoTask.Params(userId)
-            getProfileInfoTask
-                    .configureWith(params) {
-                        this.callback = object : MatrixCallback<JsonDict> {
-                            override fun onSuccess(data: JsonDict) {
-                                callback.onSuccess(
-                                        User(
-                                                userId,
-                                                data[ProfileService.DISPLAY_NAME_KEY] as? String,
-                                                data[ProfileService.AVATAR_URL_KEY] as? String)
-                                )
-                            }
-
-                            override fun onFailure(failure: Throwable) {
-                                callback.onFailure(failure)
-                            }
-                        }
-                    }
-                    .executeBy(taskExecutor)
+            val data = getProfileInfoTask.execute(params)
+            return User(
+                    userId,
+                    data[ProfileService.DISPLAY_NAME_KEY] as? String,
+                    data[ProfileService.AVATAR_URL_KEY] as? String)
         }
     }
 
@@ -85,33 +66,20 @@ internal class DefaultUserService @Inject constructor(private val userDataSource
         return userDataSource.getIgnoredUsersLive()
     }
 
-    override fun searchUsersDirectory(search: String,
-                                      limit: Int,
-                                      excludedUserIds: Set<String>,
-                                      callback: MatrixCallback<List<User>>): Cancelable {
+    override suspend fun searchUsersDirectory(search: String,
+                                              limit: Int,
+                                              excludedUserIds: Set<String>): List<User> {
         val params = SearchUserTask.Params(limit, search, excludedUserIds)
-        return searchUserTask
-                .configureWith(params) {
-                    this.callback = callback
-                }
-                .executeBy(taskExecutor)
+        return searchUserTask.execute(params)
     }
 
-    override fun ignoreUserIds(userIds: List<String>, callback: MatrixCallback<Unit>): Cancelable {
+    override suspend fun ignoreUserIds(userIds: List<String>) {
         val params = UpdateIgnoredUserIdsTask.Params(userIdsToIgnore = userIds.toList())
-        return updateIgnoredUserIdsTask
-                .configureWith(params) {
-                    this.callback = callback
-                }
-                .executeBy(taskExecutor)
+        updateIgnoredUserIdsTask.execute(params)
     }
 
-    override fun unIgnoreUserIds(userIds: List<String>, callback: MatrixCallback<Unit>): Cancelable {
+    override suspend fun unIgnoreUserIds(userIds: List<String>) {
         val params = UpdateIgnoredUserIdsTask.Params(userIdsToUnIgnore = userIds.toList())
-        return updateIgnoredUserIdsTask
-                .configureWith(params) {
-                    this.callback = callback
-                }
-                .executeBy(taskExecutor)
+        updateIgnoredUserIdsTask.execute(params)
     }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/SearchUserAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/SearchUserAPI.kt
index c5c546bbed55a2382d4bd43d9ec5f96f0400ebc2..e03d4066399697f183fd04b75db134156dea6efa 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/SearchUserAPI.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/SearchUserAPI.kt
@@ -19,7 +19,6 @@ package org.matrix.android.sdk.internal.session.user
 import org.matrix.android.sdk.internal.network.NetworkConstants
 import org.matrix.android.sdk.internal.session.user.model.SearchUsersParams
 import org.matrix.android.sdk.internal.session.user.model.SearchUsersResponse
-import retrofit2.Call
 import retrofit2.http.Body
 import retrofit2.http.POST
 
@@ -31,5 +30,5 @@ internal interface SearchUserAPI {
      * @param searchUsersParams the search params.
      */
     @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "user_directory/search")
-    fun searchUsers(@Body searchUsersParams: SearchUsersParams): Call<SearchUsersResponse>
+    suspend fun searchUsers(@Body searchUsersParams: SearchUsersParams): SearchUsersResponse
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/AccountDataAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/AccountDataAPI.kt
index 3de484fab344f3bab31ed97fe6329c570290824f..cc5625b2554b3b4d56954d595cf81e58fdd69373 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/AccountDataAPI.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/AccountDataAPI.kt
@@ -17,7 +17,6 @@
 package org.matrix.android.sdk.internal.session.user.accountdata
 
 import org.matrix.android.sdk.internal.network.NetworkConstants
-import retrofit2.Call
 import retrofit2.http.Body
 import retrofit2.http.PUT
 import retrofit2.http.Path
@@ -32,7 +31,7 @@ interface AccountDataAPI {
      * @param params the put params
      */
     @PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "user/{userId}/account_data/{type}")
-    fun setAccountData(@Path("userId") userId: String,
-                       @Path("type") type: String,
-                       @Body params: Any): Call<Unit>
+    suspend fun setAccountData(@Path("userId") userId: String,
+                               @Path("type") type: String,
+                               @Body params: Any)
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/DefaultAccountDataService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/DefaultAccountDataService.kt
index 1f1e987ebfd8391ec5e72ac85f0532933cc24f8d..27db30f3b3dc6f9671d06ef4178d97c77318e192 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/DefaultAccountDataService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/DefaultAccountDataService.kt
@@ -18,16 +18,15 @@ package org.matrix.android.sdk.internal.session.user.accountdata
 
 import androidx.lifecycle.LiveData
 import com.zhuinden.monarchy.Monarchy
-import org.matrix.android.sdk.api.MatrixCallback
 import org.matrix.android.sdk.api.session.accountdata.AccountDataService
 import org.matrix.android.sdk.api.session.events.model.Content
-import org.matrix.android.sdk.api.util.Cancelable
 import org.matrix.android.sdk.api.util.Optional
 import org.matrix.android.sdk.internal.di.SessionDatabase
 import org.matrix.android.sdk.internal.session.sync.UserAccountDataSyncHandler
 import org.matrix.android.sdk.api.session.accountdata.UserAccountDataEvent
 import org.matrix.android.sdk.internal.task.TaskExecutor
 import org.matrix.android.sdk.internal.task.configureWith
+import org.matrix.android.sdk.internal.util.awaitCallback
 import javax.inject.Inject
 
 internal class DefaultAccountDataService @Inject constructor(
@@ -54,26 +53,18 @@ internal class DefaultAccountDataService @Inject constructor(
         return accountDataDataSource.getLiveAccountDataEvents(types)
     }
 
-    override fun updateAccountData(type: String, content: Content, callback: MatrixCallback<Unit>?): Cancelable {
-        return updateUserAccountDataTask.configureWith(UpdateUserAccountDataTask.AnyParams(
-                type = type,
-                any = content
-        )) {
-            this.retryCount = 5
-            this.callback = object : MatrixCallback<Unit> {
-                override fun onSuccess(data: Unit) {
-                    // TODO Move that to the task (but it created a circular dependencies...)
-                    monarchy.runTransactionSync { realm ->
-                        userAccountDataSyncHandler.handleGenericAccountData(realm, type, content)
-                    }
-                    callback?.onSuccess(data)
-                }
-
-                override fun onFailure(failure: Throwable) {
-                    callback?.onFailure(failure)
-                }
+    override suspend fun updateAccountData(type: String, content: Content) {
+        val params = UpdateUserAccountDataTask.AnyParams(type = type, any = content)
+        awaitCallback<Unit> { callback ->
+            updateUserAccountDataTask.configureWith(params) {
+                this.retryCount = 5 // TODO: Need to refactor retrying out into a helper method.
+                this.callback = callback
             }
+                    .executeBy(taskExecutor)
+        }
+        // TODO Move that to the task (but it created a circular dependencies...)
+        monarchy.runTransactionSync { realm ->
+            userAccountDataSyncHandler.handleGenericAccountData(realm, type, content)
         }
-                .executeBy(taskExecutor)
     }
 }
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 26e8d3380a04cf8f2dff84e73cf6ffbcd9f633c2..445b78104cfb7bd0431ab34a4296f9266a07bd13 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
@@ -63,8 +63,8 @@ internal class DefaultUpdateIgnoredUserIdsTask @Inject constructor(
         val list = ignoredUserIds.toList()
         val body = IgnoredUsersContent.createWithUserIds(list)
 
-        executeRequest<Unit>(globalErrorReceiver) {
-            apiCall = accountDataApi.setAccountData(userId, UserAccountDataTypes.TYPE_IGNORED_USER_LIST, body)
+        executeRequest(globalErrorReceiver) {
+            accountDataApi.setAccountData(userId, UserAccountDataTypes.TYPE_IGNORED_USER_LIST, body)
         }
 
         // Update the DB right now (do not wait for the sync to come back with updated data, for a faster UI update)
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 dba28253a7440fc1358c6605a86792634068d39a..1a588d2245b0ad494d6d9db294aa3325490f12a6 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
@@ -105,7 +105,7 @@ internal class DefaultUpdateUserAccountDataTask @Inject constructor(
 
     override suspend fun execute(params: UpdateUserAccountDataTask.Params) {
         return executeRequest(globalErrorReceiver) {
-            apiCall = accountDataApi.setAccountData(userId, params.type, params.getData())
+            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 380fa6e209cdb47293c1ee9df2c63bc79356036d..5a8779f40fedd299a950d07363a6419cdc60cc62 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
@@ -38,8 +38,8 @@ internal class DefaultSearchUserTask @Inject constructor(
 ) : SearchUserTask {
 
     override suspend fun execute(params: SearchUserTask.Params): List<User> {
-        val response = executeRequest<SearchUsersResponse>(globalErrorReceiver) {
-            apiCall = searchUserAPI.searchUsers(SearchUsersParams(params.search, params.limit))
+        val response = executeRequest(globalErrorReceiver) {
+            searchUserAPI.searchUsers(SearchUsersParams(params.search, params.limit))
         }
         return response.users.map {
             User(it.userId, it.displayName, it.avatarUrl)
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 ae807ce30fefed2f472b4101b386c611d7105699..18a043be453177a78d791e88ee854ad6a6eb1a0c 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
@@ -46,8 +46,8 @@ internal class DefaultCreateWidgetTask @Inject constructor(@SessionDatabase priv
                                                            private val globalErrorReceiver: GlobalErrorReceiver) : CreateWidgetTask {
 
     override suspend fun execute(params: CreateWidgetTask.Params) {
-        executeRequest<Unit>(globalErrorReceiver) {
-            apiCall = roomAPI.sendStateEvent(
+        executeRequest(globalErrorReceiver) {
+            roomAPI.sendStateEvent(
                     roomId = params.roomId,
                     stateEventType = EventType.STATE_ROOM_WIDGET_LEGACY,
                     stateKey = params.widgetId,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/DefaultWidgetService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/DefaultWidgetService.kt
index 9f5a9360ee9a54fed08196a7954b638e5302dcb4..5912dc7b537c42ba39a3c76e707ca5b7097b9db3 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/DefaultWidgetService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/DefaultWidgetService.kt
@@ -17,14 +17,12 @@
 package org.matrix.android.sdk.internal.session.widgets
 
 import androidx.lifecycle.LiveData
-import org.matrix.android.sdk.api.MatrixCallback
 import org.matrix.android.sdk.api.query.QueryStringValue
 import org.matrix.android.sdk.api.session.events.model.Content
 import org.matrix.android.sdk.api.session.widgets.WidgetPostAPIMediator
 import org.matrix.android.sdk.api.session.widgets.WidgetService
 import org.matrix.android.sdk.api.session.widgets.WidgetURLFormatter
 import org.matrix.android.sdk.api.session.widgets.model.Widget
-import org.matrix.android.sdk.api.util.Cancelable
 import javax.inject.Inject
 import javax.inject.Provider
 
@@ -77,21 +75,19 @@ internal class DefaultWidgetService @Inject constructor(private val widgetManage
         return widgetManager.getUserWidgets(widgetTypes, excludedTypes)
     }
 
-    override fun createRoomWidget(
+    override suspend fun createRoomWidget(
             roomId: String,
             widgetId: String,
-            content: Content,
-            callback: MatrixCallback<Widget>
-    ): Cancelable {
-        return widgetManager.createRoomWidget(roomId, widgetId, content, callback)
+            content: Content
+    ): Widget {
+        return widgetManager.createRoomWidget(roomId, widgetId, content)
     }
 
-    override fun destroyRoomWidget(
+    override suspend fun destroyRoomWidget(
             roomId: String,
-            widgetId: String,
-            callback: MatrixCallback<Unit>
-    ): Cancelable {
-        return widgetManager.destroyRoomWidget(roomId, widgetId, callback)
+            widgetId: String
+    ) {
+        return widgetManager.destroyRoomWidget(roomId, widgetId)
     }
 
     override fun hasPermissionsToHandleWidgets(roomId: String): Boolean {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/WidgetManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/WidgetManager.kt
index 73a4cc697dc320bd03e401f2a943858fc3506144..32442124872eaf11da5faab986e5f585a01aac4e 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/WidgetManager.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/WidgetManager.kt
@@ -21,7 +21,6 @@ import androidx.lifecycle.LifecycleOwner
 import androidx.lifecycle.LifecycleRegistry
 import androidx.lifecycle.LiveData
 import androidx.lifecycle.Transformations
-import org.matrix.android.sdk.api.MatrixCallback
 import org.matrix.android.sdk.api.query.QueryStringValue
 import org.matrix.android.sdk.api.session.accountdata.UserAccountDataEvent
 import org.matrix.android.sdk.api.session.accountdata.UserAccountDataTypes
@@ -34,7 +33,6 @@ import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent
 import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper
 import org.matrix.android.sdk.api.session.widgets.WidgetManagementFailure
 import org.matrix.android.sdk.api.session.widgets.model.Widget
-import org.matrix.android.sdk.api.util.Cancelable
 import org.matrix.android.sdk.internal.di.UserId
 import org.matrix.android.sdk.internal.session.SessionLifecycleObserver
 import org.matrix.android.sdk.internal.session.SessionScope
@@ -43,8 +41,6 @@ import org.matrix.android.sdk.internal.session.room.state.StateEventDataSource
 import org.matrix.android.sdk.internal.session.user.accountdata.AccountDataDataSource
 import org.matrix.android.sdk.internal.session.widgets.helper.WidgetFactory
 import org.matrix.android.sdk.internal.session.widgets.helper.extractWidgetSequence
-import org.matrix.android.sdk.internal.task.TaskExecutor
-import org.matrix.android.sdk.internal.task.launchToCallback
 import java.util.HashMap
 import javax.inject.Inject
 
@@ -52,7 +48,6 @@ import javax.inject.Inject
 internal class WidgetManager @Inject constructor(private val integrationManager: IntegrationManager,
                                                  private val accountDataDataSource: AccountDataDataSource,
                                                  private val stateEventDataSource: StateEventDataSource,
-                                                 private val taskExecutor: TaskExecutor,
                                                  private val createWidgetTask: CreateWidgetTask,
                                                  private val widgetFactory: WidgetFactory,
                                                  @UserId private val userId: String)
@@ -165,37 +160,33 @@ internal class WidgetManager @Inject constructor(private val integrationManager:
                 .toList()
     }
 
-    fun createRoomWidget(roomId: String, widgetId: String, content: Content, callback: MatrixCallback<Widget>): Cancelable {
-        return taskExecutor.executorScope.launchToCallback(callback = callback) {
-            if (!hasPermissionsToHandleWidgets(roomId)) {
-                throw WidgetManagementFailure.NotEnoughPower
-            }
-            val params = CreateWidgetTask.Params(
-                    roomId = roomId,
-                    widgetId = widgetId,
-                    content = content
-            )
-            createWidgetTask.execute(params)
-            try {
-                getRoomWidgets(roomId, widgetId = QueryStringValue.Equals(widgetId, QueryStringValue.Case.INSENSITIVE)).first()
-            } catch (failure: Throwable) {
-                throw WidgetManagementFailure.CreationFailed
-            }
+    suspend fun createRoomWidget(roomId: String, widgetId: String, content: Content): Widget {
+        if (!hasPermissionsToHandleWidgets(roomId)) {
+            throw WidgetManagementFailure.NotEnoughPower
+        }
+        val params = CreateWidgetTask.Params(
+                roomId = roomId,
+                widgetId = widgetId,
+                content = content
+        )
+        createWidgetTask.execute(params)
+        try {
+            return getRoomWidgets(roomId, widgetId = QueryStringValue.Equals(widgetId, QueryStringValue.Case.INSENSITIVE)).first()
+        } catch (failure: Throwable) {
+            throw WidgetManagementFailure.CreationFailed
         }
     }
 
-    fun destroyRoomWidget(roomId: String, widgetId: String, callback: MatrixCallback<Unit>): Cancelable {
-        return taskExecutor.executorScope.launchToCallback(callback = callback) {
-            if (!hasPermissionsToHandleWidgets(roomId)) {
-                throw WidgetManagementFailure.NotEnoughPower
-            }
-            val params = CreateWidgetTask.Params(
-                    roomId = roomId,
-                    widgetId = widgetId,
-                    content = emptyMap()
-            )
-            createWidgetTask.execute(params)
+    suspend fun destroyRoomWidget(roomId: String, widgetId: String) {
+        if (!hasPermissionsToHandleWidgets(roomId)) {
+            throw WidgetManagementFailure.NotEnoughPower
         }
+        val params = CreateWidgetTask.Params(
+                roomId = roomId,
+                widgetId = widgetId,
+                content = emptyMap()
+        )
+        createWidgetTask.execute(params)
     }
 
     fun hasPermissionsToHandleWidgets(roomId: String): Boolean {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/WidgetsAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/WidgetsAPI.kt
index 1fece8b5807f83d44e9890849670d2fda69a63cf..6652628026018bc3defde3f164151ef4391646f8 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/WidgetsAPI.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/WidgetsAPI.kt
@@ -16,7 +16,6 @@
 package org.matrix.android.sdk.internal.session.widgets
 
 import org.matrix.android.sdk.internal.session.openid.RequestOpenIdTokenResponse
-import retrofit2.Call
 import retrofit2.http.Body
 import retrofit2.http.GET
 import retrofit2.http.POST
@@ -30,10 +29,10 @@ internal interface WidgetsAPI {
      * @param body the body content (Ref: https://github.com/matrix-org/matrix-doc/pull/1961)
      */
     @POST("register")
-    fun register(@Body body: RequestOpenIdTokenResponse,
-                 @Query("v") version: String?): Call<RegisterWidgetResponse>
+    suspend fun register(@Body body: RequestOpenIdTokenResponse,
+                         @Query("v") version: String?): RegisterWidgetResponse
 
     @GET("account")
-    fun validateToken(@Query("scalar_token") scalarToken: String?,
-                      @Query("v") version: String?): Call<Unit>
+    suspend fun validateToken(@Query("scalar_token") scalarToken: String?,
+                              @Query("v") version: String?)
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/token/GetScalarTokenTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/token/GetScalarTokenTask.kt
index 6db79da35f3cf068872702c6226a2778f5afb607..78a40d197759e131c91639d45a175a0fa8effdbf 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/token/GetScalarTokenTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/token/GetScalarTokenTask.kt
@@ -20,7 +20,6 @@ import org.matrix.android.sdk.api.failure.Failure
 import org.matrix.android.sdk.api.failure.MatrixError
 import org.matrix.android.sdk.internal.network.executeRequest
 import org.matrix.android.sdk.internal.session.openid.GetOpenIdTokenTask
-import org.matrix.android.sdk.internal.session.widgets.RegisterWidgetResponse
 import org.matrix.android.sdk.api.session.widgets.WidgetManagementFailure
 import org.matrix.android.sdk.internal.session.widgets.WidgetsAPI
 import org.matrix.android.sdk.internal.session.widgets.WidgetsAPIProvider
@@ -59,8 +58,8 @@ internal class DefaultGetScalarTokenTask @Inject constructor(private val widgets
 
     private suspend fun getNewScalarToken(widgetsAPI: WidgetsAPI, serverUrl: String): String {
         val openId = getOpenIdTokenTask.execute(Unit)
-        val registerWidgetResponse = executeRequest<RegisterWidgetResponse>(null) {
-            apiCall = widgetsAPI.register(openId, WIDGET_API_VERSION)
+        val registerWidgetResponse = executeRequest(null) {
+            widgetsAPI.register(openId, WIDGET_API_VERSION)
         }
         if (registerWidgetResponse.scalarToken == null) {
             // Should not happen
@@ -72,8 +71,8 @@ internal class DefaultGetScalarTokenTask @Inject constructor(private val widgets
 
     private suspend fun validateToken(widgetsAPI: WidgetsAPI, serverUrl: String, scalarToken: String): String {
         return try {
-            executeRequest<Unit>(null) {
-                apiCall = widgetsAPI.validateToken(scalarToken, WIDGET_API_VERSION)
+            executeRequest(null) {
+                widgetsAPI.validateToken(scalarToken, WIDGET_API_VERSION)
             }
             scalarToken
         } catch (failure: Throwable) {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/LogUtil.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/LogUtil.kt
index fe68b49a5c7712a6d85c7375f0e2d958cb71c4db..4656856bf788d50ecda8e47cecad840d65ab0a5f 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/LogUtil.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/LogUtil.kt
@@ -19,15 +19,27 @@ package org.matrix.android.sdk.internal.util
 import org.matrix.android.sdk.BuildConfig
 import timber.log.Timber
 
+internal fun <T> Collection<T>.logLimit(maxQuantity: Int = 5): String {
+    return buildString {
+        append(size)
+        append(" item(s)")
+        if (size > maxQuantity) {
+            append(", first $maxQuantity items")
+        }
+        append(": ")
+        append(this@logLimit.take(maxQuantity))
+    }
+}
+
 internal suspend fun <T> logDuration(message: String,
                                      block: suspend () -> T): T {
-    Timber.v("$message -- BEGIN")
+    Timber.d("$message -- BEGIN")
     val start = System.currentTimeMillis()
     val result = logRamUsage(message) {
         block()
     }
     val duration = System.currentTimeMillis() - start
-    Timber.v("$message -- END duration: $duration ms")
+    Timber.d("$message -- END duration: $duration ms")
 
     return result
 }
@@ -38,12 +50,12 @@ internal suspend fun <T> logRamUsage(message: String, block: suspend () -> T): T
         runtime.gc()
         val freeMemoryInMb = runtime.freeMemory() / 1048576L
         val usedMemInMBStart = runtime.totalMemory() / 1048576L - freeMemoryInMb
-        Timber.v("$message -- BEGIN (free memory: $freeMemoryInMb MB)")
+        Timber.d("$message -- BEGIN (free memory: $freeMemoryInMb MB)")
         val result = block()
         runtime.gc()
         val usedMemInMBEnd = (runtime.totalMemory() - runtime.freeMemory()) / 1048576L
         val usedMemInMBDiff = usedMemInMBEnd - usedMemInMBStart
-        Timber.v("$message -- END RAM usage: $usedMemInMBDiff MB")
+        Timber.d("$message -- END RAM usage: $usedMemInMBDiff MB")
         result
     } else {
         block()
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/MathUtils.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/MathUtils.kt
new file mode 100644
index 0000000000000000000000000000000000000000..c9c597e93d159131eac02bc8e4f8ce9d78e1591b
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/MathUtils.kt
@@ -0,0 +1,44 @@
+/*
+ * 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.util
+
+import kotlin.math.ceil
+
+internal data class BestChunkSize(
+        val numberOfChunks: Int,
+        val chunkSize: Int
+) {
+    fun shouldChunk() = numberOfChunks > 1
+}
+
+internal fun computeBestChunkSize(listSize: Int, limit: Int): BestChunkSize {
+    return if (listSize <= limit) {
+        BestChunkSize(
+                numberOfChunks = 1,
+                chunkSize = listSize
+        )
+    } else {
+        val numberOfChunks = ceil(listSize / limit.toDouble()).toInt()
+        // Round on next Int
+        val chunkSize = ceil(listSize / numberOfChunks.toDouble()).toInt()
+
+        BestChunkSize(
+                numberOfChunks = numberOfChunks,
+                chunkSize = chunkSize
+        )
+    }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/wellknown/GetWellknownTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/wellknown/GetWellknownTask.kt
index 3f0e27f4105bb1e5b2bbd3ac023e9ae42f5cddfc..7a9beac8c08e1d8db524a90cc0308ee6054dc967 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/wellknown/GetWellknownTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/wellknown/GetWellknownTask.kt
@@ -89,8 +89,8 @@ internal class DefaultGetWellknownTask @Inject constructor(
                 .create(WellKnownAPI::class.java)
 
         return try {
-            val wellKnown = executeRequest<WellKnown>(null) {
-                apiCall = wellKnownAPI.getWellKnown(domain)
+            val wellKnown = executeRequest(null) {
+                wellKnownAPI.getWellKnown(domain)
             }
 
             // Success
@@ -140,8 +140,8 @@ internal class DefaultGetWellknownTask @Inject constructor(
                 .create(CapabilitiesAPI::class.java)
 
         try {
-            executeRequest<Unit>(null) {
-                apiCall = capabilitiesAPI.ping()
+            executeRequest(null) {
+                capabilitiesAPI.ping()
             }
         } catch (throwable: Throwable) {
             return WellknownResult.FailError
@@ -178,8 +178,8 @@ internal class DefaultGetWellknownTask @Inject constructor(
                 .create(IdentityAuthAPI::class.java)
 
         return try {
-            executeRequest<Unit>(null) {
-                apiCall = identityPingApi.ping()
+            executeRequest(null) {
+                identityPingApi.ping()
             }
 
             true
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/wellknown/WellKnownAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/wellknown/WellKnownAPI.kt
index 981d013f49c994d216b2f00de7c13887d9c7a196..428f7f65c075b36760f4f23fb2bb6b9954e88b23 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/wellknown/WellKnownAPI.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/wellknown/WellKnownAPI.kt
@@ -16,11 +16,10 @@
 package org.matrix.android.sdk.internal.wellknown
 
 import org.matrix.android.sdk.api.auth.data.WellKnown
-import retrofit2.Call
 import retrofit2.http.GET
 import retrofit2.http.Path
 
 internal interface WellKnownAPI {
     @GET("https://{domain}/.well-known/matrix/client")
-    fun getWellKnown(@Path("domain") domain: String): Call<WellKnown>
+    suspend fun getWellKnown(@Path("domain") domain: String): WellKnown
 }
diff --git a/matrix-sdk-android/src/main/res/values-fi/strings_sas.xml b/matrix-sdk-android/src/main/res/values-fi/strings_sas.xml
index b690fee4ed4fe74d7f208927a370bc2dccc8bfe9..12edb39070eba41111744c38dee06ac1c1ad80b1 100644
--- a/matrix-sdk-android/src/main/res/values-fi/strings_sas.xml
+++ b/matrix-sdk-android/src/main/res/values-fi/strings_sas.xml
@@ -35,7 +35,7 @@
     <string name="verification_emoji_robot">Robotti</string>
     <string name="verification_emoji_hat">Hattu</string>
     <string name="verification_emoji_glasses">Silmälasit</string>
-    <string name="verification_emoji_spanner">Mutteriavain</string>
+    <string name="verification_emoji_spanner">Kiintoavain</string>
     <string name="verification_emoji_santa">Joulupukki</string>
     <string name="verification_emoji_thumbs_up">Peukalo ylös</string>
     <string name="verification_emoji_umbrella">Sateenvarjo</string>
diff --git a/matrix-sdk-android/src/main/res/values-ja/strings_sas.xml b/matrix-sdk-android/src/main/res/values-ja/strings_sas.xml
index 618302eb4f9f20c61e078bbcb8fa1d87c0a87ac6..12f90e316d63fcc817be47c8e0755bbfbb1ebf2a 100644
--- a/matrix-sdk-android/src/main/res/values-ja/strings_sas.xml
+++ b/matrix-sdk-android/src/main/res/values-ja/strings_sas.xml
@@ -3,18 +3,66 @@
     <!-- Generated file, do not edit -->
     <string name="verification_emoji_dog">犬</string>
     <string name="verification_emoji_cat">猫</string>
+    <string name="verification_emoji_lion">ライオン</string>
     <string name="verification_emoji_horse">馬</string>
+    <string name="verification_emoji_unicorn">ユニコーン</string>
+    <string name="verification_emoji_pig">ブタ</string>
+    <string name="verification_emoji_elephant">ゾウ</string>
+    <string name="verification_emoji_rabbit">うさぎ</string>
+    <string name="verification_emoji_panda">パンダ</string>
+    <string name="verification_emoji_rooster">ニワトリ</string>
+    <string name="verification_emoji_penguin">ペンギン</string>
+    <string name="verification_emoji_turtle">亀</string>
+    <string name="verification_emoji_fish">é­š</string>
     <string name="verification_emoji_octopus">たこ</string>
+    <string name="verification_emoji_butterfly">ちょうちょ</string>
     <string name="verification_emoji_flower">花</string>
     <string name="verification_emoji_tree">木</string>
+    <string name="verification_emoji_cactus">サボテン</string>
     <string name="verification_emoji_mushroom">きのこ</string>
+    <string name="verification_emoji_globe">地球</string>
     <string name="verification_emoji_moon">月</string>
+    <string name="verification_emoji_cloud">雲</string>
+    <string name="verification_emoji_fire">ç‚Ž</string>
+    <string name="verification_emoji_banana">バナナ</string>
     <string name="verification_emoji_apple">リンゴ</string>
+    <string name="verification_emoji_strawberry">いちご</string>
+    <string name="verification_emoji_corn">とうもろこし</string>
+    <string name="verification_emoji_pizza">ピザ</string>
     <string name="verification_emoji_cake">ケーキ</string>
+    <string name="verification_emoji_heart">ハート</string>
+    <string name="verification_emoji_smiley">スマイル</string>
     <string name="verification_emoji_robot">ロボと</string>
+    <string name="verification_emoji_hat">帽子</string>
     <string name="verification_emoji_glasses">めがね</string>
+    <string name="verification_emoji_spanner">スパナ</string>
+    <string name="verification_emoji_santa">サンタ</string>
+    <string name="verification_emoji_thumbs_up">いいね</string>
+    <string name="verification_emoji_umbrella">傘</string>
+    <string name="verification_emoji_hourglass">砂時計</string>
+    <string name="verification_emoji_clock">時計</string>
+    <string name="verification_emoji_gift">ギフト</string>
+    <string name="verification_emoji_light_bulb">電球</string>
     <string name="verification_emoji_book">本</string>
+    <string name="verification_emoji_pencil">鉛筆</string>
+    <string name="verification_emoji_paperclip">クリップ</string>
+    <string name="verification_emoji_scissors">はさみ</string>
+    <string name="verification_emoji_lock">錠前</string>
+    <string name="verification_emoji_key">鍵</string>
+    <string name="verification_emoji_hammer">金槌</string>
     <string name="verification_emoji_telephone">電話機</string>
+    <string name="verification_emoji_flag">æ——</string>
     <string name="verification_emoji_train">電車</string>
     <string name="verification_emoji_bicycle">自転車</string>
+    <string name="verification_emoji_aeroplane">飛行機</string>
+    <string name="verification_emoji_rocket">ロケット</string>
+    <string name="verification_emoji_trophy">トロフィー</string>
+    <string name="verification_emoji_ball">ボール</string>
+    <string name="verification_emoji_guitar">ギター</string>
+    <string name="verification_emoji_trumpet">トランペット</string>
+    <string name="verification_emoji_bell">ベル</string>
+    <string name="verification_emoji_anchor">いかり</string>
+    <string name="verification_emoji_headphones">ヘッドホン</string>
+    <string name="verification_emoji_folder">フォルダ</string>
+    <string name="verification_emoji_pin">ピン</string>
 </resources>
diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/util/MathUtilTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/util/MathUtilTest.kt
new file mode 100644
index 0000000000000000000000000000000000000000..ade811f9b76aa9b270908c931a5e9377c797e1dc
--- /dev/null
+++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/util/MathUtilTest.kt
@@ -0,0 +1,71 @@
+/*
+ * 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.util
+
+import org.amshove.kluent.shouldBeEqualTo
+import org.amshove.kluent.shouldHaveSize
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runners.MethodSorters
+import org.matrix.android.sdk.MatrixTest
+
+@FixMethodOrder(MethodSorters.JVM)
+class MathUtilTest : MatrixTest {
+
+    @Test
+    fun testComputeBestChunkSize0() = doTest(0, 100, 1, 0)
+
+    @Test
+    fun testComputeBestChunkSize1to99() {
+        for (i in 1..99) {
+            doTest(i, 100, 1, i)
+        }
+    }
+
+    @Test
+    fun testComputeBestChunkSize100() = doTest(100, 100, 1, 100)
+
+    @Test
+    fun testComputeBestChunkSize101() = doTest(101, 100, 2, 51)
+
+    @Test
+    fun testComputeBestChunkSize199() = doTest(199, 100, 2, 100)
+
+    @Test
+    fun testComputeBestChunkSize200() = doTest(200, 100, 2, 100)
+
+    @Test
+    fun testComputeBestChunkSize201() = doTest(201, 100, 3, 67)
+
+    @Test
+    fun testComputeBestChunkSize240() = doTest(240, 100, 3, 80)
+
+    private fun doTest(listSize: Int, limit: Int, expectedNumberOfChunks: Int, expectedChunkSize: Int) {
+        val result = computeBestChunkSize(listSize, limit)
+
+        result.numberOfChunks shouldBeEqualTo expectedNumberOfChunks
+        result.chunkSize shouldBeEqualTo expectedChunkSize
+
+        // Test that the result make sense, when we use chunked()
+        if (result.chunkSize > 0) {
+            generateSequence { "a" }
+                    .take(listSize)
+                    .chunked(result.chunkSize)
+                    .shouldHaveSize(result.numberOfChunks)
+        }
+    }
+}