diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle
index fbd7f57ccdace83068848f2e894e9daf338f3f46..391e4087689c7397ce749dff3c20fe6063124dd0 100644
--- a/matrix-sdk-android/build.gradle
+++ b/matrix-sdk-android/build.gradle
@@ -3,14 +3,13 @@ apply plugin: 'kotlin-android'
 apply plugin: 'kotlin-kapt'
 apply plugin: 'kotlin-parcelize'
 apply plugin: 'realm-android'
-apply plugin: 'maven-publish'
 
 buildscript {
     repositories {
         mavenCentral()
     }
     dependencies {
-        classpath "io.realm:realm-gradle-plugin:10.5.0"
+        classpath "io.realm:realm-gradle-plugin:10.6.1"
     }
 }
 
@@ -22,7 +21,7 @@ android {
         minSdkVersion 21
         targetSdkVersion 30
         versionCode 1
-        versionName "1.1.9"
+        versionName "1.1.13"
         // Multidex is useful for tests
         multiDexEnabled true
         testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
@@ -113,7 +112,7 @@ dependencies {
     def lifecycle_version = '2.2.0'
     def arch_version = '2.1.0'
     def markwon_version = '3.1.0'
-    def daggerVersion = '2.36'
+    def daggerVersion = '2.37'
     def work_version = '2.5.0'
     def retrofit_version = '2.9.0'
 
@@ -122,7 +121,7 @@ dependencies {
     implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_version"
 
     implementation "androidx.appcompat:appcompat:1.3.0"
-    implementation "androidx.core:core-ktx:1.5.0"
+    implementation "androidx.core:core-ktx:1.6.0"
 
     implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"
     implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version"
@@ -170,40 +169,30 @@ dependencies {
     implementation 'com.otaliastudios:transcoder:0.10.3'
 
     // Phone number https://github.com/google/libphonenumber
-    implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.24'
+    implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.27'
 
     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.11.0'
-    testImplementation 'org.amshove.kluent:kluent-android:1.65'
+    testImplementation 'io.mockk:mockk:1.12.0'
+    testImplementation 'org.amshove.kluent:kluent-android:1.68'
     testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_version"
     // Plant Timber tree for test
     testImplementation 'net.lachlanmckee:timber-junit-rule:1.0.1'
 
     kaptAndroidTest "com.google.dagger:dagger-compiler:$daggerVersion"
-    androidTestImplementation 'androidx.test:core:1.3.0'
-    androidTestImplementation 'androidx.test:runner:1.3.0'
-    androidTestImplementation 'androidx.test:rules:1.3.0'
-    androidTestImplementation 'androidx.test.ext:junit:1.1.2'
-    androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
-    androidTestImplementation 'org.amshove.kluent:kluent-android:1.65'
-    androidTestImplementation 'io.mockk:mockk-android:1.11.0'
+    androidTestImplementation 'androidx.test:core:1.4.0'
+    androidTestImplementation 'androidx.test:runner:1.4.0'
+    androidTestImplementation 'androidx.test:rules:1.4.0'
+    androidTestImplementation 'androidx.test.ext:junit:1.1.3'
+    androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
+    androidTestImplementation 'org.amshove.kluent:kluent-android:1.68'
+    androidTestImplementation 'io.mockk:mockk-android:1.12.0'
     androidTestImplementation "androidx.arch.core:core-testing:$arch_version"
     androidTestImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_version"
     // Plant Timber tree for test
     androidTestImplementation 'net.lachlanmckee:timber-junit-rule:1.0.1'
 
-    androidTestUtil 'androidx.test:orchestrator:1.3.0'
-}
-
-project.afterEvaluate {
-    publishing {
-        publications {
-            release(MavenPublication) {
-                from components.release
-            }
-        }
-    }
+    androidTestUtil 'androidx.test:orchestrator:1.4.0'
 }
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/SpaceOrderTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/SpaceOrderTest.kt
new file mode 100644
index 0000000000000000000000000000000000000000..3270dfb7574246c85ecf1a7b6a5b64a171c6d86c
--- /dev/null
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/SpaceOrderTest.kt
@@ -0,0 +1,260 @@
+/*
+ * 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
+
+import org.amshove.kluent.internal.assertEquals
+import org.junit.Assert
+import org.junit.Test
+import org.matrix.android.sdk.api.session.space.SpaceOrderUtils
+
+class SpaceOrderTest {
+
+    @Test
+    fun testOrderBetweenNodesWithOrder() {
+        val orderedSpaces = listOf(
+                "roomId1" to "a",
+                "roomId2" to "m",
+                "roomId3" to "z"
+        ).assertSpaceOrdered()
+
+        val orderCommand = SpaceOrderUtils.orderCommandsForMove(orderedSpaces, "roomId1", 1)
+
+        Assert.assertTrue("Only one order should be changed", orderCommand.size == 1)
+        Assert.assertTrue("Moved space order should change", orderCommand.first().spaceId == "roomId1")
+
+        Assert.assertTrue("m" < orderCommand[0].order)
+        Assert.assertTrue(orderCommand[0].order < "z")
+    }
+
+    @Test
+    fun testMoveLastBetweenNodesWithOrder() {
+        val orderedSpaces = listOf(
+                "roomId1" to "a",
+                "roomId2" to "m",
+                "roomId3" to "z"
+        ).assertSpaceOrdered()
+
+        val orderCommand = SpaceOrderUtils.orderCommandsForMove(orderedSpaces, "roomId1", 2)
+
+        Assert.assertTrue("Only one order should be changed", orderCommand.size == 1)
+        Assert.assertTrue("Moved space order should change", orderCommand.first().spaceId == "roomId1")
+
+        Assert.assertTrue("z" < orderCommand[0].order)
+    }
+
+    @Test
+    fun testMoveUpNoOrder() {
+        val orderedSpaces = listOf(
+                "roomId1" to null,
+                "roomId2" to null,
+                "roomId3" to null
+        ).assertSpaceOrdered()
+
+        val orderCommand = SpaceOrderUtils.orderCommandsForMove(orderedSpaces, "roomId1", 1)
+
+        Assert.assertTrue("2 orders change should be needed", orderCommand.size == 2)
+
+        val reOrdered = reOrderWithCommands(orderedSpaces, orderCommand)
+
+        Assert.assertEquals("roomId2", reOrdered[0].first)
+        Assert.assertEquals("roomId1", reOrdered[1].first)
+        Assert.assertEquals("roomId3", reOrdered[2].first)
+    }
+
+    @Test
+    fun testMoveUpNotEnoughSpace() {
+        val orderedSpaces = listOf(
+                "roomId1" to "a",
+                "roomId2" to "j",
+                "roomId3" to "k"
+        ).assertSpaceOrdered()
+
+        val orderCommand = SpaceOrderUtils.orderCommandsForMove(orderedSpaces, "roomId1", 1)
+
+        Assert.assertTrue("more orders change should be needed", orderCommand.size > 1)
+
+        val reOrdered = reOrderWithCommands(orderedSpaces, orderCommand)
+
+        Assert.assertEquals("roomId2", reOrdered[0].first)
+        Assert.assertEquals("roomId1", reOrdered[1].first)
+        Assert.assertEquals("roomId3", reOrdered[2].first)
+    }
+
+    @Test
+    fun testMoveEndNoOrder() {
+        val orderedSpaces = listOf(
+                "roomId1" to null,
+                "roomId2" to null,
+                "roomId3" to null
+        ).assertSpaceOrdered()
+
+        val orderCommand = SpaceOrderUtils.orderCommandsForMove(orderedSpaces, "roomId1", 2)
+
+        // Actually 2 could be enough... as it's last it can stays with null
+        Assert.assertEquals("3 orders change should be needed", 3, orderCommand.size)
+
+        val reOrdered = reOrderWithCommands(orderedSpaces, orderCommand)
+
+        Assert.assertEquals("roomId2", reOrdered[0].first)
+        Assert.assertEquals("roomId3", reOrdered[1].first)
+        Assert.assertEquals("roomId1", reOrdered[2].first)
+    }
+
+    @Test
+    fun testMoveUpBiggerOrder() {
+        val orderedSpaces = listOf(
+                "roomId1" to "aaaa",
+                "roomId2" to "ffff",
+                "roomId3" to "pppp",
+                "roomId4" to null,
+                "roomId5" to null,
+                "roomId6" to null
+        ).assertSpaceOrdered()
+
+        val orderCommand = SpaceOrderUtils.orderCommandsForMove(orderedSpaces, "roomId2", 3)
+
+        // Actually 2 could be enough... as it's last it can stays with null
+        Assert.assertEquals("3 orders change should be needed", 3, orderCommand.size)
+
+        val reOrdered = reOrderWithCommands(orderedSpaces, orderCommand)
+
+        Assert.assertEquals("roomId1", reOrdered[0].first)
+        Assert.assertEquals("roomId3", reOrdered[1].first)
+        Assert.assertEquals("roomId4", reOrdered[2].first)
+        Assert.assertEquals("roomId5", reOrdered[3].first)
+        Assert.assertEquals("roomId2", reOrdered[4].first)
+        Assert.assertEquals("roomId6", reOrdered[5].first)
+    }
+
+    @Test
+    fun testMoveDownBetweenNodesWithOrder() {
+        val orderedSpaces = listOf(
+                "roomId1" to "a",
+                "roomId2" to "m",
+                "roomId3" to "z"
+        ).assertSpaceOrdered()
+
+        val orderCommand = SpaceOrderUtils.orderCommandsForMove(orderedSpaces, "roomId3", -1)
+
+        Assert.assertTrue("Only one order should be changed", orderCommand.size == 1)
+        Assert.assertTrue("Moved space order should change", orderCommand.first().spaceId == "roomId3")
+
+        val reOrdered = reOrderWithCommands(orderedSpaces, orderCommand)
+
+        Assert.assertEquals("roomId1", reOrdered[0].first)
+        Assert.assertEquals("roomId3", reOrdered[1].first)
+        Assert.assertEquals("roomId2", reOrdered[2].first)
+    }
+
+    @Test
+    fun testMoveDownNoOrder() {
+        val orderedSpaces = listOf(
+                "roomId1" to null,
+                "roomId2" to null,
+                "roomId3" to null
+        ).assertSpaceOrdered()
+
+        val orderCommand = SpaceOrderUtils.orderCommandsForMove(orderedSpaces, "roomId3", -1)
+
+        Assert.assertTrue("2 orders change should be needed", orderCommand.size == 2)
+
+        val reOrdered = reOrderWithCommands(orderedSpaces, orderCommand)
+
+        Assert.assertEquals("roomId1", reOrdered[0].first)
+        Assert.assertEquals("roomId3", reOrdered[1].first)
+        Assert.assertEquals("roomId2", reOrdered[2].first)
+    }
+
+    @Test
+    fun testMoveDownBiggerOrder() {
+        val orderedSpaces = listOf(
+                "roomId1" to "aaaa",
+                "roomId2" to "ffff",
+                "roomId3" to "pppp",
+                "roomId4" to null,
+                "roomId5" to null,
+                "roomId6" to null
+        ).assertSpaceOrdered()
+
+        val orderCommand = SpaceOrderUtils.orderCommandsForMove(orderedSpaces, "roomId5", -4)
+
+        Assert.assertEquals("1 order change should be needed", 1, orderCommand.size)
+
+        val reOrdered = reOrderWithCommands(orderedSpaces, orderCommand)
+
+        Assert.assertEquals("roomId5", reOrdered[0].first)
+        Assert.assertEquals("roomId1", reOrdered[1].first)
+        Assert.assertEquals("roomId2", reOrdered[2].first)
+        Assert.assertEquals("roomId3", reOrdered[3].first)
+        Assert.assertEquals("roomId4", reOrdered[4].first)
+        Assert.assertEquals("roomId6", reOrdered[5].first)
+    }
+
+    @Test
+    fun testMultipleMoveOrder() {
+        val orderedSpaces = listOf(
+                "roomId1" to null,
+                "roomId2" to null,
+                "roomId3" to null,
+                "roomId4" to null,
+                "roomId5" to null,
+                "roomId6" to null
+        ).assertSpaceOrdered()
+
+        // move 5 to Top
+        val fiveToTop = SpaceOrderUtils.orderCommandsForMove(orderedSpaces, "roomId5", -4)
+
+        val fiveTopReOrder = reOrderWithCommands(orderedSpaces, fiveToTop)
+
+        // now move 4 to second
+        val orderCommand = SpaceOrderUtils.orderCommandsForMove(fiveTopReOrder, "roomId4", -3)
+
+        val reOrdered = reOrderWithCommands(fiveTopReOrder, orderCommand)
+        // second order should cost 1 re-order
+        Assert.assertEquals(1, orderCommand.size)
+
+        Assert.assertEquals("roomId5", reOrdered[0].first)
+        Assert.assertEquals("roomId4", reOrdered[1].first)
+        Assert.assertEquals("roomId1", reOrdered[2].first)
+        Assert.assertEquals("roomId2", reOrdered[3].first)
+        Assert.assertEquals("roomId3", reOrdered[4].first)
+        Assert.assertEquals("roomId6", reOrdered[5].first)
+    }
+
+    @Test
+    fun testComparator() {
+        listOf(
+                "roomId2" to "a",
+                "roomId1" to "b",
+                "roomId3" to null,
+                "roomId4" to null
+        ).assertSpaceOrdered()
+    }
+
+    private fun reOrderWithCommands(orderedSpaces: List<Pair<String, String?>>, orderCommand: List<SpaceOrderUtils.SpaceReOrderCommand>) =
+            orderedSpaces.map { orderInfo ->
+                orderInfo.first to (orderCommand.find { it.spaceId == orderInfo.first }?.order ?: orderInfo.second)
+            }
+                    .sortedWith(testSpaceComparator)
+
+    private fun List<Pair<String, String?>>.assertSpaceOrdered(): List<Pair<String, String?>> {
+        assertEquals(this, this.sortedWith(testSpaceComparator))
+        return this
+    }
+
+    private val testSpaceComparator = compareBy<Pair<String, String?>, String?>(nullsLast()) { it.second }.thenBy { it.first }
+}
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/StringOrderTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/StringOrderTest.kt
new file mode 100644
index 0000000000000000000000000000000000000000..a625362c0413d766bdc4f9567c5f755afc315378
--- /dev/null
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/StringOrderTest.kt
@@ -0,0 +1,110 @@
+/*
+ * 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
+
+import org.amshove.kluent.internal.assertEquals
+import org.junit.Assert
+import org.junit.Test
+import org.matrix.android.sdk.api.MatrixPatterns
+import org.matrix.android.sdk.api.util.StringOrderUtils
+
+class StringOrderTest {
+
+    @Test
+    fun testbasing() {
+        assertEquals("a", StringOrderUtils.baseToString(StringOrderUtils.stringToBase("a", StringOrderUtils.DEFAULT_ALPHABET), StringOrderUtils.DEFAULT_ALPHABET))
+        assertEquals("element", StringOrderUtils.baseToString(StringOrderUtils.stringToBase("element", StringOrderUtils.DEFAULT_ALPHABET), StringOrderUtils.DEFAULT_ALPHABET))
+        assertEquals("matrix", StringOrderUtils.baseToString(StringOrderUtils.stringToBase("matrix", StringOrderUtils.DEFAULT_ALPHABET), StringOrderUtils.DEFAULT_ALPHABET))
+    }
+
+    @Test
+    fun testValid() {
+        println(StringOrderUtils.DEFAULT_ALPHABET.joinToString(","))
+
+        assert(MatrixPatterns.isValidOrderString("a"))
+        assert(MatrixPatterns.isValidOrderString(" "))
+        assert(MatrixPatterns.isValidOrderString("abc"))
+        assert(!MatrixPatterns.isValidOrderString("abcê"))
+        assert(!MatrixPatterns.isValidOrderString(""))
+        assert(MatrixPatterns.isValidOrderString("!"))
+        assert(MatrixPatterns.isValidOrderString("!\"#\$%&'()*+,012"))
+        assert(!MatrixPatterns.isValidOrderString(Char(' '.code - 1).toString()))
+
+        assert(!MatrixPatterns.isValidOrderString(
+                buildString {
+                    for (i in 0..49) {
+                        append(StringOrderUtils.DEFAULT_ALPHABET.random())
+                    }
+                }
+        ))
+
+        assert(MatrixPatterns.isValidOrderString(
+                buildString {
+                    for (i in 0..48) {
+                        append(StringOrderUtils.DEFAULT_ALPHABET.random())
+                    }
+                }
+        ))
+    }
+
+    @Test
+    fun testAverage() {
+        assertAverage("${StringOrderUtils.DEFAULT_ALPHABET.first()}", "m")
+        assertAverage("aa", "aab")
+        assertAverage("matrix", "element")
+        assertAverage("mmm", "mmmmm")
+        assertAverage("aab", "aa")
+        assertAverage("", "aa")
+        assertAverage("a", "z")
+        assertAverage("ground", "sky")
+    }
+
+    @Test
+    fun testMidPoints() {
+        val orders = StringOrderUtils.midPoints("element", "matrix", 4)
+        assertEquals(4, orders!!.size)
+        assert("element" < orders[0])
+        assert(orders[0] < orders[1])
+        assert(orders[1] < orders[2])
+        assert(orders[2] < orders[3])
+        assert(orders[3] < "matrix")
+
+        println("element < ${orders.joinToString(" < ") { "[$it]" }} < matrix")
+
+        val orders2 = StringOrderUtils.midPoints("a", "d", 4)
+        assertEquals(null, orders2)
+    }
+
+    @Test
+    fun testRenumberNeeded() {
+        assertEquals(null, StringOrderUtils.average("a", "a"))
+        assertEquals(null, StringOrderUtils.average("", ""))
+        assertEquals(null, StringOrderUtils.average("a", "b"))
+        assertEquals(null, StringOrderUtils.average("b", "a"))
+        assertEquals(null, StringOrderUtils.average("mmmm", "mmmm"))
+        assertEquals(null, StringOrderUtils.average("a${Char(0)}", "a"))
+    }
+
+    private fun assertAverage(first: String, second: String) {
+        val left = first.takeIf { first < second } ?: second
+        val right = first.takeIf { first > second } ?: second
+        val av1 = StringOrderUtils.average(left, right)!!
+        println("[$left] < [$av1] < [$right]")
+        Assert.assertTrue(left < av1)
+        Assert.assertTrue(av1 < right)
+    }
+}
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CommonTestHelper.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CommonTestHelper.kt
index 287a4233d990715176a0655c767a60a30cbf3bfc..6e07223ac7105c1598520d0e97c12b099d624210 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CommonTestHelper.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CommonTestHelper.kt
@@ -78,7 +78,7 @@ class CommonTestHelper(context: Context) {
     }
 
     /**
-     * Create a Home server configuration, with Http connection allowed for test
+     * Create a homeserver configuration, with Http connection allowed for test
      */
     fun createHomeServerConfig(): HomeServerConnectionConfig {
         return HomeServerConnectionConfig.Builder()
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupTest.kt
index eb8b8b97303c37570ea6107f2a5de381df67258c..89d297c5928b816bce535ee35a841ece3eee762e 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupTest.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupTest.kt
@@ -816,7 +816,7 @@ class KeysBackupTest : InstrumentedTest {
         // - Do an e2e backup to the homeserver
         mKeysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup)
 
-        // Get key backup version from the home server
+        // Get key backup version from the homeserver
         val keysVersionResult = mTestHelper.doSync<KeysVersionResult?> {
             keysBackup.getCurrentVersion(it)
         }
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 25c22bca57ddf74d1bc29b2326a99706ca4f1d7b..d14de30c9007560fbdeaee89146be2ccf5c5e10c 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
@@ -33,7 +33,7 @@ import org.matrix.android.sdk.common.TestConstants
 import org.matrix.android.sdk.internal.crypto.SSSS_ALGORITHM_AES_HMAC_SHA2
 import org.matrix.android.sdk.internal.crypto.crosssigning.toBase64NoPadding
 import org.matrix.android.sdk.internal.crypto.secrets.DefaultSharedSecretStorageService
-import org.matrix.android.sdk.api.session.accountdata.AccountDataEvent
+import org.matrix.android.sdk.api.session.accountdata.UserAccountDataEvent
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.GlobalScope
 import kotlinx.coroutines.launch
@@ -73,12 +73,12 @@ class QuadSTests : InstrumentedTest {
 
         // Assert Account data is updated
         val accountDataLock = CountDownLatch(1)
-        var accountData: AccountDataEvent? = null
+        var accountData: UserAccountDataEvent? = null
 
         val liveAccountData = runBlocking(Dispatchers.Main) {
-            aliceSession.userAccountDataService().getLiveAccountDataEvent("${DefaultSharedSecretStorageService.KEY_ID_BASE}.$TEST_KEY_ID")
+            aliceSession.accountDataService().getLiveUserAccountDataEvent("${DefaultSharedSecretStorageService.KEY_ID_BASE}.$TEST_KEY_ID")
         }
-        val accountDataObserver = Observer<Optional<AccountDataEvent>?> { t ->
+        val accountDataObserver = Observer<Optional<UserAccountDataEvent>?> { t ->
             if (t?.getOrNull()?.type == "${DefaultSharedSecretStorageService.KEY_ID_BASE}.$TEST_KEY_ID") {
                 accountData = t.getOrNull()
                 accountDataLock.countDown()
@@ -100,13 +100,13 @@ class QuadSTests : InstrumentedTest {
             quadS.setDefaultKey(TEST_KEY_ID)
         }
 
-        var defaultKeyAccountData: AccountDataEvent? = null
+        var defaultKeyAccountData: UserAccountDataEvent? = null
         val defaultDataLock = CountDownLatch(1)
 
         val liveDefAccountData = runBlocking(Dispatchers.Main) {
-            aliceSession.userAccountDataService().getLiveAccountDataEvent(DefaultSharedSecretStorageService.DEFAULT_KEY_ID)
+            aliceSession.accountDataService().getLiveUserAccountDataEvent(DefaultSharedSecretStorageService.DEFAULT_KEY_ID)
         }
-        val accountDefDataObserver = Observer<Optional<AccountDataEvent>?> { t ->
+        val accountDefDataObserver = Observer<Optional<UserAccountDataEvent>?> { t ->
             if (t?.getOrNull()?.type == DefaultSharedSecretStorageService.DEFAULT_KEY_ID) {
                 defaultKeyAccountData = t.getOrNull()!!
                 defaultDataLock.countDown()
@@ -206,7 +206,7 @@ class QuadSTests : InstrumentedTest {
             )
         }
 
-        val accountDataEvent = aliceSession.userAccountDataService().getAccountDataEvent("my.secret")
+        val accountDataEvent = aliceSession.accountDataService().getUserAccountDataEvent("my.secret")
         val encryptedContent = accountDataEvent?.content?.get("encrypted") as? Map<*, *>
 
         assertEquals("Content should contains two encryptions", 2, encryptedContent?.keys?.size ?: 0)
@@ -275,14 +275,14 @@ class QuadSTests : InstrumentedTest {
         mTestHelper.signOutAndClose(aliceSession)
     }
 
-    private fun assertAccountData(session: Session, type: String): AccountDataEvent {
+    private fun assertAccountData(session: Session, type: String): UserAccountDataEvent {
         val accountDataLock = CountDownLatch(1)
-        var accountData: AccountDataEvent? = null
+        var accountData: UserAccountDataEvent? = null
 
         val liveAccountData = runBlocking(Dispatchers.Main) {
-            session.userAccountDataService().getLiveAccountDataEvent(type)
+            session.accountDataService().getLiveUserAccountDataEvent(type)
         }
-        val accountDataObserver = Observer<Optional<AccountDataEvent>?> { t ->
+        val accountDataObserver = Observer<Optional<UserAccountDataEvent>?> { t ->
             if (t?.getOrNull()?.type == type) {
                 accountData = t.getOrNull()
                 accountDataLock.countDown()
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/MatrixPatterns.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/MatrixPatterns.kt
index d8532c77c839b446efec20a53c016ee5e560f86d..9a5e40ffeba3b8c4a0fc1c1ba1dc8ffefed9d1b0 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/MatrixPatterns.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/MatrixPatterns.kt
@@ -16,8 +16,12 @@
 
 package org.matrix.android.sdk.api
 
+import org.matrix.android.sdk.BuildConfig
+import timber.log.Timber
+
 /**
  * This class contains pattern to match the different Matrix ids
+ * Ref: https://matrix.org/docs/spec/appendices#identifier-grammar
  */
 object MatrixPatterns {
 
@@ -25,7 +29,7 @@ object MatrixPatterns {
     private const val DOMAIN_REGEX = ":[A-Z0-9.-]+(:[0-9]{2,5})?"
 
     // regex pattern to find matrix user ids in a string.
-    // See https://matrix.org/speculator/spec/HEAD/appendices.html#historical-user-ids
+    // See https://matrix.org/docs/spec/appendices#historical-user-ids
     private const val MATRIX_USER_IDENTIFIER_REGEX = "@[A-Z0-9\\x21-\\x39\\x3B-\\x7F]+$DOMAIN_REGEX"
     val PATTERN_CONTAIN_MATRIX_USER_IDENTIFIER = MATRIX_USER_IDENTIFIER_REGEX.toRegex(RegexOption.IGNORE_CASE)
 
@@ -71,6 +75,9 @@ object MatrixPatterns {
     private const val LINK_TO_APP_ROOM_ALIAS_REGEXP = APP_BASE_REGEX + MATRIX_ROOM_ALIAS_REGEX + SEP_REGEX + MATRIX_EVENT_IDENTIFIER_REGEX
     private val PATTERN_CONTAIN_APP_LINK_PERMALINK_ROOM_ALIAS = LINK_TO_APP_ROOM_ALIAS_REGEXP.toRegex(RegexOption.IGNORE_CASE)
 
+    // ascii characters in the range \x20 (space) to \x7E (~)
+    val ORDER_STRING_REGEX = "[ -~]+".toRegex()
+
     // list of patterns to find some matrix item.
     val MATRIX_PATTERNS = listOf(
             PATTERN_CONTAIN_MATRIX_TO_PERMALINK_ROOM_ID,
@@ -146,4 +153,32 @@ object MatrixPatterns {
     fun extractServerNameFromId(matrixId: String?): String? {
         return matrixId?.substringAfter(":", missingDelimiterValue = "")?.takeIf { it.isNotEmpty() }
     }
+
+    /**
+     * Orders which are not strings, or do not consist solely of ascii characters in the range \x20 (space) to \x7E (~),
+     * or consist of more than 50 characters, are forbidden and the field should be ignored if received.
+     */
+    fun isValidOrderString(order: String?): Boolean {
+        return order != null && order.length < 50 && order matches ORDER_STRING_REGEX
+    }
+
+    fun candidateAliasFromRoomName(name: String): String {
+        return Regex("\\s").replace(name.lowercase(), "_").let {
+            "[^a-z0-9._%#@=+-]".toRegex().replace(it, "")
+        }
+    }
+
+    /**
+     * Return the domain form a userId
+     * Examples:
+     * - "@alice:domain.org".getDomain() will return "domain.org"
+     * - "@bob:domain.org:3455".getDomain() will return "domain.org:3455"
+     */
+    fun String.getDomain(): String {
+        if (BuildConfig.DEBUG && !isUserId(this)) {
+            // They are some invalid userId localpart in the wild, but the domain part should be there anyway
+            Timber.w("Not a valid user ID: $this")
+        }
+        return substringAfter(":")
+    }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/HomeServerConnectionConfig.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/HomeServerConnectionConfig.kt
index e1c5171bfc50fdc5835e9461f77c410c5978b244..215f0a0351a9442d3d52d43685273ed3aa4ee5b4 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/HomeServerConnectionConfig.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/HomeServerConnectionConfig.kt
@@ -18,11 +18,11 @@ package org.matrix.android.sdk.api.auth.data
 
 import android.net.Uri
 import com.squareup.moshi.JsonClass
+import okhttp3.CipherSuite
+import okhttp3.TlsVersion
 import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig.Builder
 import org.matrix.android.sdk.internal.network.ssl.Fingerprint
 import org.matrix.android.sdk.internal.util.ensureTrailingSlash
-import okhttp3.CipherSuite
-import okhttp3.TlsVersion
 
 /**
  * This data class holds how to connect to a specific Homeserver.
@@ -31,7 +31,12 @@ import okhttp3.TlsVersion
  */
 @JsonClass(generateAdapter = true)
 data class HomeServerConnectionConfig(
+        // This is the homeserver URL entered by the user
         val homeServerUri: Uri,
+        // This is the homeserver base URL for the client-server API. Default to homeServerUri,
+        // but can be updated with data from .Well-Known before login, and/or with the data
+        // included in the login response
+        val homeServerUriBase: Uri = homeServerUri,
         val identityServerUri: Uri? = null,
         val antiVirusServerUri: Uri? = null,
         val allowedFingerprints: List<Fingerprint> = emptyList(),
@@ -47,7 +52,6 @@ data class HomeServerConnectionConfig(
      * This builder should be use to create a [HomeServerConnectionConfig] instance.
      */
     class Builder {
-
         private lateinit var homeServerUri: Uri
         private var identityServerUri: Uri? = null
         private var antiVirusServerUri: Uri? = null
@@ -69,14 +73,14 @@ data class HomeServerConnectionConfig(
          */
         fun withHomeServerUri(hsUri: Uri): Builder {
             if (hsUri.scheme != "http" && hsUri.scheme != "https") {
-                throw RuntimeException("Invalid home server URI: $hsUri")
+                throw RuntimeException("Invalid homeserver URI: $hsUri")
             }
             // ensure trailing /
             val hsString = hsUri.toString().ensureTrailingSlash()
             homeServerUri = try {
                 Uri.parse(hsString)
             } catch (e: Exception) {
-                throw RuntimeException("Invalid home server URI: $hsUri")
+                throw RuntimeException("Invalid homeserver URI: $hsUri")
             }
             return this
         }
@@ -134,7 +138,7 @@ data class HomeServerConnectionConfig(
         }
 
         /**
-         * Add an accepted TLS version for TLS connections with the home server.
+         * Add an accepted TLS version for TLS connections with the homeserver.
          *
          * @param tlsVersion the tls version to add to the set of TLS versions accepted.
          * @return this builder
@@ -156,7 +160,7 @@ data class HomeServerConnectionConfig(
         }
 
         /**
-         * Add a TLS cipher suite to the list of accepted TLS connections with the home server.
+         * Add a TLS cipher suite to the list of accepted TLS connections with the homeserver.
          *
          * @param tlsCipherSuite the tls cipher suite to add.
          * @return this builder
@@ -234,16 +238,16 @@ data class HomeServerConnectionConfig(
          */
         fun build(): HomeServerConnectionConfig {
             return HomeServerConnectionConfig(
-                    homeServerUri,
-                    identityServerUri,
-                    antiVirusServerUri,
-                    allowedFingerprints,
-                    shouldPin,
-                    tlsVersions,
-                    tlsCipherSuites,
-                    shouldAcceptTlsExtensions,
-                    allowHttpExtension,
-                    forceUsageTlsVersions
+                    homeServerUri = homeServerUri,
+                    identityServerUri = identityServerUri,
+                    antiVirusServerUri = antiVirusServerUri,
+                    allowedFingerprints = allowedFingerprints,
+                    shouldPin = shouldPin,
+                    tlsVersions = tlsVersions,
+                    tlsCipherSuites = tlsCipherSuites,
+                    shouldAcceptTlsExtensions = shouldAcceptTlsExtensions,
+                    allowHttpExtension = allowHttpExtension,
+                    forceUsageTlsVersions = forceUsageTlsVersions
             )
         }
     }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/SessionParams.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/SessionParams.kt
index b2a57c7f5ce39e093eb50be09797d46f775e474a..b490ac877ee4e455532d5566631f7753b169fa51 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/SessionParams.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/SessionParams.kt
@@ -51,13 +51,18 @@ data class SessionParams(
     val deviceId = credentials.deviceId
 
     /**
-     * The current homeserver Url. It can be different that the homeserver url entered
-     * during login phase, because a redirection may have occurred
+     * The homeserver Url entered by the user during the login phase.
      */
     val homeServerUrl = homeServerConnectionConfig.homeServerUri.toString()
 
     /**
-     * The current homeserver host
+     * The current homeserver Url for client-server API. It can be different that the homeserver url entered
+     * during login phase, because a redirection may have occurred
+     */
+    val homeServerUrlBase = homeServerConnectionConfig.homeServerUriBase.toString()
+
+    /**
+     * The current homeserver host, using what has been entered by the user during login phase
      */
     val homeServerHost = homeServerConnectionConfig.homeServerUri.host
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/registration/RegistrationFlowResponse.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/registration/RegistrationFlowResponse.kt
index 2b1c1c09b3d7b466d752ff3f7c89fbe4e1131a41..ac740ddab719a936933f87a1be69388547c3e2e1 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/registration/RegistrationFlowResponse.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/registration/RegistrationFlowResponse.kt
@@ -38,7 +38,7 @@ data class RegistrationFlowResponse(
         val completedStages: List<String>? = null,
 
         /**
-         * The session identifier that the client must pass back to the home server, if one is provided,
+         * The session identifier that the client must pass back to the homeserver, if one is provided,
          * in subsequent attempts to authenticate in the same API call.
          */
         @Json(name = "session")
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/wellknown/WellknownResult.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/wellknown/WellknownResult.kt
index c68a9e9699aaa4e9226895818f8d4d4bffa7f89a..56257db79c42f2438639bed8eb0dd0cc36fe97b3 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/wellknown/WellknownResult.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/wellknown/WellknownResult.kt
@@ -22,11 +22,6 @@ import org.matrix.android.sdk.api.auth.data.WellKnown
  * Ref: https://matrix.org/docs/spec/client_server/latest#well-known-uri
  */
 sealed class WellknownResult {
-    /**
-     * The provided matrixId is no valid. Unable to extract a domain name.
-     */
-    object InvalidMatrixId : WellknownResult()
-
     /**
      * Retrieve the specific piece of information from the user in a way which fits within the existing client user experience,
      * if the client is inclined to do so. Failure can take place instead if no good user experience for this is possible at this point.
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/MatrixIdFailure.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/MatrixIdFailure.kt
new file mode 100644
index 0000000000000000000000000000000000000000..8f7bca803dfa6f00877ae4066e3ac1afe8931178
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/MatrixIdFailure.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.failure
+
+sealed class MatrixIdFailure : Failure.FeatureFailure() {
+    object InvalidMatrixId : MatrixIdFailure()
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/raw/RawService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/raw/RawService.kt
index f1722b2189a324a8294f79a318ff0c08b95eb9dc..3366d040f7fa1227a6b72cc9668fa411681fea88 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/raw/RawService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/raw/RawService.kt
@@ -29,8 +29,10 @@ interface RawService {
 
     /**
      * Specific case for the well-known file. Cache validity is 8 hours
+     * @param domain the domain to get the .well-known file, for instance "matrix.org".
+     * The URL will be "https://{domain}/.well-known/matrix/client"
      */
-    suspend fun getWellknown(userId: String): String
+    suspend fun getWellknown(domain: String): String
 
     /**
      * Clear all the cache data
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 e888e5d2de748027166540353478115574d7ca8a..2f981ffbbedfbe7f58c9f51547816c46b1a2894d 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
@@ -24,7 +24,7 @@ import org.matrix.android.sdk.api.failure.GlobalError
 import org.matrix.android.sdk.api.federation.FederationService
 import org.matrix.android.sdk.api.pushrules.PushRuleService
 import org.matrix.android.sdk.api.session.account.AccountService
-import org.matrix.android.sdk.api.session.accountdata.AccountDataService
+import org.matrix.android.sdk.api.session.accountdata.SessionAccountDataService
 import org.matrix.android.sdk.api.session.cache.CacheService
 import org.matrix.android.sdk.api.session.call.CallSignalingService
 import org.matrix.android.sdk.api.session.content.ContentUploadStateTracker
@@ -239,9 +239,9 @@ interface Session :
     fun openIdService(): OpenIdService
 
     /**
-     * Returns the user account data service associated with the session
+     * Returns the account data service associated with the session
      */
-    fun userAccountDataService(): AccountDataService
+    fun accountDataService(): SessionAccountDataService
 
     /**
      * Add a listener to the session.
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/accountdata/SessionAccountDataService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/accountdata/SessionAccountDataService.kt
new file mode 100644
index 0000000000000000000000000000000000000000..2ffb9112d1e8ee56bfaab92a491643bf8c15b96e
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/accountdata/SessionAccountDataService.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2020 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.api.session.accountdata
+
+import androidx.lifecycle.LiveData
+import org.matrix.android.sdk.api.session.events.model.Content
+import org.matrix.android.sdk.api.session.room.accountdata.RoomAccountDataEvent
+import org.matrix.android.sdk.api.util.Optional
+
+/**
+ * This service is attached globally to the session.
+ */
+interface SessionAccountDataService {
+    /**
+     * Retrieve the account data with the provided type or null if not found
+     */
+    fun getUserAccountDataEvent(type: String): UserAccountDataEvent?
+
+    /**
+     * Observe the account data with the provided type
+     */
+    fun getLiveUserAccountDataEvent(type: String): LiveData<Optional<UserAccountDataEvent>>
+
+    /**
+     * Retrieve the account data with the provided types. The return list can have a different size that
+     * the size of the types set, because some AccountData may not exist.
+     * If an empty set is provided, all the AccountData are retrieved
+     */
+    fun getUserAccountDataEvents(types: Set<String>): List<UserAccountDataEvent>
+
+    /**
+     * Observe the account data with the provided types. If an empty set is provided, all the AccountData are observed
+     */
+    fun getLiveUserAccountDataEvents(types: Set<String>): LiveData<List<UserAccountDataEvent>>
+
+    /**
+     * Retrieve the room account data with the provided types. The return list can have a different size that
+     * the size of the types set, because some AccountData may not exist.
+     * If an empty set is provided, all the room AccountData are retrieved
+     */
+    fun getRoomAccountDataEvents(types: Set<String>): List<RoomAccountDataEvent>
+
+    /**
+     * Observe the room account data with the provided types. If an empty set is provided, AccountData of every room are observed
+     */
+    fun getLiveRoomAccountDataEvents(types: Set<String>): LiveData<List<RoomAccountDataEvent>>
+
+    /**
+     * Update the account data with the provided type and the provided account data content
+     */
+    suspend fun updateUserAccountData(type: String, content: Content)
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/accountdata/AccountDataEvent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/accountdata/UserAccountDataEvent.kt
similarity index 91%
rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/accountdata/AccountDataEvent.kt
rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/accountdata/UserAccountDataEvent.kt
index e5cbd07aafdeb3bab7b3790e6c5a9871b6933424..77381a28c43c8c78e44397cf5c6c82c2e904785e 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/accountdata/AccountDataEvent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/accountdata/UserAccountDataEvent.kt
@@ -22,10 +22,10 @@ import org.matrix.android.sdk.api.session.events.model.Content
 
 /**
  * This is a simplified Event with just a type and a content.
- * Currently used types are defined in [UserAccountDataTypes].
+ * Currently used types are defined in [UserAccountDataTypes]
  */
 @JsonClass(generateAdapter = true)
-data class AccountDataEvent(
+data class UserAccountDataEvent(
         @Json(name = "type") val type: String,
         @Json(name = "content") val content: Content
 )
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/call/CallListener.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/call/CallListener.kt
index 303add747f1715fb538978ea3998e45c3e1e8675..d17be59cd4d4cd5556b59b94dd7718dc1771b530 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/call/CallListener.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/call/CallListener.kt
@@ -17,6 +17,7 @@
 package org.matrix.android.sdk.api.session.call
 
 import org.matrix.android.sdk.api.session.room.model.call.CallAnswerContent
+import org.matrix.android.sdk.api.session.room.model.call.CallAssertedIdentityContent
 import org.matrix.android.sdk.api.session.room.model.call.CallCandidatesContent
 import org.matrix.android.sdk.api.session.room.model.call.CallHangupContent
 import org.matrix.android.sdk.api.session.room.model.call.CallInviteContent
@@ -61,4 +62,9 @@ interface CallListener {
      * Called when the call has been managed by an other session
      */
     fun onCallManagedByOtherSession(callId: String)
+
+    /**
+     * Called when an asserted identity event is received
+     */
+    fun onCallAssertedIdentityReceived(callAssertedIdentityContent: CallAssertedIdentityContent)
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt
index 1b7a5243e2ba00d191f21f6849f399ce164d9ef0..e3f00a24b669c6aebad3ff375700e8c26f37506f 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt
@@ -42,7 +42,6 @@ import org.matrix.android.sdk.internal.crypto.model.event.RoomKeyWithHeldContent
 import org.matrix.android.sdk.internal.crypto.model.rest.DeviceInfo
 import org.matrix.android.sdk.internal.crypto.model.rest.DevicesListResponse
 import org.matrix.android.sdk.internal.crypto.model.rest.RoomKeyRequestBody
-import kotlin.jvm.Throws
 
 interface CryptoService {
 
@@ -82,9 +81,11 @@ interface CryptoService {
 
     fun getDeviceTrackingStatus(userId: String): Int
 
-    fun importRoomKeys(roomKeysAsArray: ByteArray, password: String, progressListener: ProgressListener?, callback: MatrixCallback<ImportRoomKeysResult>)
+    suspend fun importRoomKeys(roomKeysAsArray: ByteArray,
+                               password: String,
+                               progressListener: ProgressListener?): ImportRoomKeysResult
 
-    fun exportRoomKeys(password: String, callback: MatrixCallback<ByteArray>)
+    suspend fun exportRoomKeys(password: String): ByteArray
 
     fun setRoomBlacklistUnverifiedDevices(roomId: String)
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/KeysBackupService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/KeysBackupService.kt
index 465d00168fd804b4cbeb7126e554a0b17e824eba..4464427b903ae748b91effc15043bd01656387a8 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/KeysBackupService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/KeysBackupService.kt
@@ -28,7 +28,7 @@ import org.matrix.android.sdk.internal.crypto.store.SavedKeyBackupKeyInfo
 
 interface KeysBackupService {
     /**
-     * Retrieve the current version of the backup from the home server
+     * Retrieve the current version of the backup from the homeserver
      *
      * It can be different than keysBackupVersion.
      * @param callback onSuccess(null) will be called if there is no backup on the server
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/KeysBackupState.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/KeysBackupState.kt
index 7d0f04ebcfc8147e636264622f87a22abc907e73..a4cc133398203ae4af23b47cc2d8343e4afd09db 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/KeysBackupState.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/KeysBackupState.kt
@@ -54,7 +54,7 @@ enum class KeysBackupState {
     // Need to check the current backup version on the homeserver
     Unknown,
 
-    // Checking if backup is enabled on home server
+    // Checking if backup is enabled on homeserver
     CheckingBackUpOnHomeserver,
 
     // Backup has been stopped because a new backup version has been detected on the homeserver
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 6400dd644408f59b01bd2858bb0f4613e485fe57..3d82846e7e98fd9c2370dcfac5969267e7638b88 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
@@ -104,7 +104,7 @@ data class Event(
 
     /**
      * The `age` value transcoded in a timestamp based on the device clock when the SDK received
-     * the event from the home server.
+     * the event from the homeserver.
      * Unlike `age`, this value is static.
      */
     @Transient
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/EventType.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/EventType.kt
index 229a53fa9d71f231afe873a806f7d4fc6e72ab66..9c3fdd57daa4fd2c4ff7d851a53dd4a1fa535520 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/EventType.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/EventType.kt
@@ -76,6 +76,8 @@ object EventType {
     const val CALL_NEGOTIATE = "m.call.negotiate"
     const val CALL_REJECT = "m.call.reject"
     const val CALL_HANGUP = "m.call.hangup"
+    const val CALL_ASSERTED_IDENTITY = "m.call.asserted_identity"
+    const val CALL_ASSERTED_IDENTITY_PREFIX = "org.matrix.call.asserted_identity"
 
     // This type is not processed by the client, just sent to the server
     const val CALL_REPLACES = "m.call.replaces"
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/homeserver/HomeServerCapabilities.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/homeserver/HomeServerCapabilities.kt
index da99ab8d54e070ed4017b18adecc93bd30f0a0da..10c2db45357c850fe455870961c22d1d2420f8b5 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/homeserver/HomeServerCapabilities.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/homeserver/HomeServerCapabilities.kt
@@ -32,7 +32,13 @@ data class HomeServerCapabilities(
         /**
          * Default identity server url, provided in Wellknown
          */
-        val defaultIdentityServerUrl: String? = null
+        val defaultIdentityServerUrl: String? = null,
+        /**
+         * Room versions supported by the server
+         * This capability describes the default and available room versions a server supports, and at what level of stability.
+         * Clients should make use of this capability to determine if users need to be encouraged to upgrade their rooms.
+         */
+        val roomVersions: RoomVersionCapabilities? = null
 ) {
     companion object {
         const val MAX_UPLOAD_FILE_SIZE_UNKNOWN = -1L
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/homeserver/RoomVersionModel.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/homeserver/RoomVersionModel.kt
new file mode 100644
index 0000000000000000000000000000000000000000..7798b4cc6388ad9313b643b515cbb9dd1e4234e8
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/homeserver/RoomVersionModel.kt
@@ -0,0 +1,32 @@
+/*
+ * 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.homeserver
+
+data class RoomVersionCapabilities(
+        val defaultRoomVersion: String,
+        val supportedVersion: List<RoomVersionInfo>
+)
+
+data class RoomVersionInfo(
+        val version: String,
+        val status: RoomVersionStatus
+)
+
+enum class RoomVersionStatus {
+    STABLE,
+    UNSTABLE
+}
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 8f8967e8fb25994d35339f50b09fe348e8662d39..ae546b6cece199cb2cfc5adc847098b3a74f181f 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
@@ -35,7 +35,7 @@ interface IdentityService {
     /**
      * Check if the identity server is valid
      * See https://matrix.org/docs/spec/identity_service/latest#status-check
-     * RiotX SDK only supports identity server API v2
+     * Matrix Android SDK2 only supports identity server API v2
      */
     suspend fun isValidIdentityServer(url: String)
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/AliasAvailabilityResult.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/AliasAvailabilityResult.kt
new file mode 100644
index 0000000000000000000000000000000000000000..6f607569c0816e71dd57cd4a2041c59ab4450d9d
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/AliasAvailabilityResult.kt
@@ -0,0 +1,24 @@
+/*
+ * 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
+
+import org.matrix.android.sdk.api.session.room.alias.RoomAliasError
+
+sealed class AliasAvailabilityResult {
+    object Available: AliasAvailabilityResult()
+    data class NotAvailable(val roomAliasError: RoomAliasError) : AliasAvailabilityResult()
+}
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 41bcbf8ff6f7541974e6ce377cde5cb2cbf3705d..ebe96b638298096ae360f11f0c46e6a512d1ced2 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,7 @@
 package org.matrix.android.sdk.api.session.room
 
 import androidx.lifecycle.LiveData
-import org.matrix.android.sdk.api.session.accountdata.AccountDataService
+import org.matrix.android.sdk.api.session.room.accountdata.RoomAccountDataService
 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
@@ -34,6 +34,7 @@ import org.matrix.android.sdk.api.session.room.tags.TagsService
 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.room.version.RoomVersionService
 import org.matrix.android.sdk.api.session.search.SearchResult
 import org.matrix.android.sdk.api.session.space.Space
 import org.matrix.android.sdk.api.util.Optional
@@ -57,7 +58,8 @@ interface Room :
         RelationService,
         RoomCryptoService,
         RoomPushRuleService,
-        AccountDataService {
+        RoomAccountDataService,
+        RoomVersionService {
 
     /**
      * The roomId of this room
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomDirectoryService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomDirectoryService.kt
index 176de8e408be3108d8307c5736a8e0c49bdacf4f..f3e3913bc124729dee05a6366d20069114c172fc 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomDirectoryService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomDirectoryService.kt
@@ -40,4 +40,6 @@ interface RoomDirectoryService {
      * Set the visibility of a room in the directory
      */
     suspend fun setRoomDirectoryVisibility(roomId: String, roomDirectoryVisibility: RoomDirectoryVisibility)
+
+    suspend fun checkAliasAvailability(aliasLocalPart: String?) : AliasAvailabilityResult
 }
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 871c5378a6c19fcbbc76f3fdc403e55441d984d5..b7377df1b3da4e3e7c153524116b42fbf5f37d52 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
@@ -125,6 +125,12 @@ interface RoomService {
      */
     suspend fun deleteRoomAlias(roomAlias: String)
 
+    /**
+     * Return the current local changes membership for the given room.
+     * see [getChangeMembershipsLive] for more details.
+     */
+    fun getChangeMemberships(roomIdOrAlias: String): ChangeMembershipState
+
     /**
      * Return a live data of all local changes membership that happened since the session has been opened.
      * It allows you to track this in your client to known what is currently being processed by the SDK.
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/accountdata/RoomAccountDataEvent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/accountdata/RoomAccountDataEvent.kt
new file mode 100644
index 0000000000000000000000000000000000000000..eb676ab5e7666c52c9fb9583ad8b53d6ca33d6fd
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/accountdata/RoomAccountDataEvent.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2020 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.api.session.room.accountdata
+
+import org.matrix.android.sdk.api.session.events.model.Content
+
+/**
+ * This is a simplified Event with just a roomId, a type and a content.
+ * Currently used types are defined in [RoomAccountDataTypes].
+ */
+data class RoomAccountDataEvent(
+        val roomId: String,
+        val type: String,
+        val content: Content
+)
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/room/accountdata/RoomAccountDataService.kt
similarity index 81%
rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/accountdata/AccountDataService.kt
rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/accountdata/RoomAccountDataService.kt
index 77f3eb0cd92f673619fe6fe8b528cc78e78b0111..190749c85cee92957fe16e1729a898d51b0da7f2 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/room/accountdata/RoomAccountDataService.kt
@@ -14,37 +14,37 @@
  * limitations under the License.
  */
 
-package org.matrix.android.sdk.api.session.accountdata
+package org.matrix.android.sdk.api.session.room.accountdata
 
 import androidx.lifecycle.LiveData
 import org.matrix.android.sdk.api.session.events.model.Content
 import org.matrix.android.sdk.api.util.Optional
 
 /**
- * This service can be attached globally to the session so it represents user data or attached to a single room.
+ * This service is attached to a single room.
  */
-interface AccountDataService {
+interface RoomAccountDataService {
     /**
      * Retrieve the account data with the provided type or null if not found
      */
-    fun getAccountDataEvent(type: String): AccountDataEvent?
+    fun getAccountDataEvent(type: String): RoomAccountDataEvent?
 
     /**
      * Observe the account data with the provided type
      */
-    fun getLiveAccountDataEvent(type: String): LiveData<Optional<AccountDataEvent>>
+    fun getLiveAccountDataEvent(type: String): LiveData<Optional<RoomAccountDataEvent>>
 
     /**
      * Retrieve the account data with the provided types. The return list can have a different size that
      * the size of the types set, because some AccountData may not exist.
      * If an empty set is provided, all the AccountData are retrieved
      */
-    fun getAccountDataEvents(types: Set<String>): List<AccountDataEvent>
+    fun getAccountDataEvents(types: Set<String>): List<RoomAccountDataEvent>
 
     /**
      * Observe the account data with the provided types. If an empty set is provided, all the AccountData are observed
      */
-    fun getLiveAccountDataEvents(types: Set<String>): LiveData<List<AccountDataEvent>>
+    fun getLiveAccountDataEvents(types: Set<String>): LiveData<List<RoomAccountDataEvent>>
 
     /**
      * Update the account data with the provided type and the provided account data content
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/accountdata/RoomAccountDataTypes.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/accountdata/RoomAccountDataTypes.kt
index 0e80c307b4f9a7fb9bfb94101642fcc75eb9c849..96eb86c0d65c7946a9f4a35efc57a40751bc4815 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/accountdata/RoomAccountDataTypes.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/accountdata/RoomAccountDataTypes.kt
@@ -20,4 +20,5 @@ object RoomAccountDataTypes {
     const val EVENT_TYPE_VIRTUAL_ROOM = "im.vector.is_virtual_room"
     const val EVENT_TYPE_TAG = "m.tag"
     const val EVENT_TYPE_FULLY_READ = "m.fully_read"
+    const val EVENT_TYPE_SPACE_ORDER = "org.matrix.msc3230.space_order" // m.space_order
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallAssertedIdentityContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallAssertedIdentityContent.kt
new file mode 100644
index 0000000000000000000000000000000000000000..4c5413425fc84a51bb4b74b8b68bb217ddd12b9a
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallAssertedIdentityContent.kt
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2021 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.api.session.room.model.call
+
+import com.squareup.moshi.Json
+import com.squareup.moshi.JsonClass
+
+/**
+ * This event is sent by the callee when they wish to answer the call.
+ */
+@JsonClass(generateAdapter = true)
+data class CallAssertedIdentityContent(
+        /**
+         * Required. The ID of the call this event relates to.
+         */
+        @Json(name = "call_id") override val callId: String,
+        /**
+         * Required. ID to let user identify remote echo of their own events
+         */
+        @Json(name = "party_id") override val partyId: String? = null,
+        /**
+         * Required. The version of the VoIP specification this messages adheres to.
+         */
+        @Json(name = "version") override val version: String?,
+
+        /**
+         * Optional. Used to inform the transferee who they're now speaking to.
+         */
+        @Json(name = "asserted_identity") val assertedIdentity: AssertedIdentity? = null
+) : CallSignalingContent {
+
+    /**
+     *  A user ID may be included if relevant, but unlike target_user, it is purely informational.
+     *  The asserted identity may not represent a matrix user at all,
+     *  in which case just a display_name may be given, or a perhaps a display_name and avatar_url.
+     */
+    @JsonClass(generateAdapter = true)
+    data class AssertedIdentity(
+            @Json(name = "id") val id: String? = null,
+            @Json(name = "display_name") val displayName: String? = null,
+            @Json(name = "avatar_url") val avatarUrl: String? = null
+    )
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageAudioContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageAudioContent.kt
index fcf3d73cbea13dcedd4deeef9ec0263c2c68af74..b4ba5c0a66e93561fd0b613ffb56ac6c10445f72 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageAudioContent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageAudioContent.kt
@@ -54,5 +54,5 @@ data class MessageAudioContent(
 ) : MessageWithAttachmentContent {
 
     override val mimeType: String?
-        get() = encryptedFileInfo?.mimetype ?: audioInfo?.mimeType
+        get() = audioInfo?.mimeType
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageFileContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageFileContent.kt
index d93f115322acc5acc0efa4e977c125bc015e001e..96877b4d9f12cce11be1ae623ab45a6806894c99 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageFileContent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageFileContent.kt
@@ -60,8 +60,7 @@ data class MessageFileContent(
 ) : MessageWithAttachmentContent {
 
     override val mimeType: String?
-        get() = encryptedFileInfo?.mimetype
-                ?: info?.mimeType
+        get() = info?.mimeType
                 ?: MimeTypeMap.getFileExtensionFromUrl(filename ?: body)?.let { extension ->
                     MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension)
                 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageImageContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageImageContent.kt
index 73e27b64e31925bcbae609535c7339f59f8873b2..73fd1eab56522bf0a83278ba66ace436b1b3b5ca 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageImageContent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageImageContent.kt
@@ -20,7 +20,6 @@ import com.squareup.moshi.Json
 import com.squareup.moshi.JsonClass
 import org.matrix.android.sdk.api.session.events.model.Content
 import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent
-import org.matrix.android.sdk.api.util.MimeTypes
 import org.matrix.android.sdk.internal.crypto.model.rest.EncryptedFileInfo
 
 @JsonClass(generateAdapter = true)
@@ -55,5 +54,5 @@ data class MessageImageContent(
         @Json(name = "file") override val encryptedFileInfo: EncryptedFileInfo? = null
 ) : MessageImageInfoContent {
     override val mimeType: String?
-        get() = encryptedFileInfo?.mimetype ?: info?.mimeType ?: MimeTypes.Images
+        get() = info?.mimeType
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageStickerContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageStickerContent.kt
index 280316d4b5208ee1280cbb5740d9826460b80b93..8e1d4d3d7589f286643740faddec1b1220720f04 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageStickerContent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageStickerContent.kt
@@ -55,5 +55,5 @@ data class MessageStickerContent(
         @Json(name = "file") override val encryptedFileInfo: EncryptedFileInfo? = null
 ) : MessageImageInfoContent {
     override val mimeType: String?
-        get() = encryptedFileInfo?.mimetype ?: info?.mimeType
+        get() = info?.mimeType
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageVideoContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageVideoContent.kt
index b7581c9fbf8fcf81be490eb84fef0b89b23b9f10..3f5d2dab2e770d189b538b71d16d07fef5c1627c 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageVideoContent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageVideoContent.kt
@@ -53,5 +53,5 @@ data class MessageVideoContent(
         @Json(name = "file") override val encryptedFileInfo: EncryptedFileInfo? = null
 ) : MessageWithAttachmentContent {
     override val mimeType: String?
-        get() = encryptedFileInfo?.mimetype ?: videoInfo?.mimeType
+        get() = videoInfo?.mimeType
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/version/RoomVersionService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/version/RoomVersionService.kt
new file mode 100644
index 0000000000000000000000000000000000000000..ea67b55174c1fa9609d9932724266dd869b6e6fd
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/version/RoomVersionService.kt
@@ -0,0 +1,45 @@
+/*
+ * 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.version
+
+interface RoomVersionService {
+    /**
+     * Return the room version of this room
+     */
+    fun getRoomVersion(): String
+
+    /**
+     * Upgrade to the given room version
+     * @return the replacement room id
+     */
+    suspend fun upgradeToVersion(version: String): String
+
+    /**
+     * Get the recommended room version for the current homeserver
+     */
+    fun getRecommendedVersion() : String
+
+    /**
+     * Ask if the user has enough power level to upgrade the room
+     */
+    fun userMayUpgradeRoom(userId: String): Boolean
+
+    /**
+     * Return true if the current room version is declared unstable by the homeserver
+     */
+    fun isUsingUnstableRoomVersion(): Boolean
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/Space.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/Space.kt
index db25762c2f2b7436e71a9f25754e1bc1b7b1d22e..3bae6126e0722e5c12be00c82664865a2bdacd6a 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/Space.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/Space.kt
@@ -18,6 +18,7 @@ package org.matrix.android.sdk.api.session.space
 
 import org.matrix.android.sdk.api.session.room.Room
 import org.matrix.android.sdk.api.session.room.model.RoomSummary
+import org.matrix.android.sdk.api.session.space.model.SpaceChildContent
 
 interface Space {
 
@@ -38,6 +39,8 @@ interface Space {
                             autoJoin: Boolean = false,
                             suggested: Boolean? = false)
 
+    fun getChildInfo(roomId: String): SpaceChildContent?
+
     suspend fun removeChildren(roomId: String)
 
     @Throws
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/SpaceOrderUtils.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/SpaceOrderUtils.kt
new file mode 100644
index 0000000000000000000000000000000000000000..844a5adcb4ae2a49c6cbf8bdb9cc77b0049c9fa5
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/SpaceOrderUtils.kt
@@ -0,0 +1,105 @@
+/*
+ * 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.space
+
+import org.matrix.android.sdk.api.util.StringOrderUtils
+
+/**
+ * Adds some utilities to compute correct string orders when ordering spaces.
+ * After moving a space (e.g via DnD), client should limit the number of room account data update.
+ * For example if the space is moved between two other spaces with orders, just update the moved space order by computing
+ * a mid point between the surrounding orders.
+ * If the space is moved after a space with no order, all the previous spaces should be then ordered,
+ * and the computed orders should be chosen so that there is enough gaps in between them to facilitate future re-order.
+ * Re numbering (i.e change all spaces m.space.order account data) should be avoided as much as possible,
+ * as the updates might not be atomic for other clients and would makes spaces jump around.
+ */
+object SpaceOrderUtils {
+
+    data class SpaceReOrderCommand(
+            val spaceId: String,
+            val order: String
+    )
+
+    /**
+     * Returns a minimal list of order change in order to re order the space list as per given move.
+     */
+    fun orderCommandsForMove(orderedSpacesToOrderMap: List<Pair<String, String?>>, movedSpaceId: String, delta: Int): List<SpaceReOrderCommand> {
+        val movedIndex = orderedSpacesToOrderMap.indexOfFirst { it.first == movedSpaceId }
+        if (movedIndex == -1) return emptyList()
+        if (delta == 0) return emptyList()
+
+        val targetIndex = if (delta > 0) movedIndex + delta else (movedIndex + delta - 1)
+
+        val nodesToReNumber = mutableListOf<String>()
+        var lowerBondOrder: String? = null
+        var index = targetIndex
+        while (index >= 0 && lowerBondOrder == null) {
+            val node = orderedSpacesToOrderMap.getOrNull(index)
+            if (node != null /*null when adding at the end*/) {
+                val nodeOrder = node.second
+                if (node.first == movedSpaceId) break
+                if (nodeOrder == null) {
+                    nodesToReNumber.add(0, node.first)
+                } else {
+                    lowerBondOrder = nodeOrder
+                }
+            }
+            index--
+        }
+        nodesToReNumber.add(movedSpaceId)
+        val afterSpace: Pair<String, String?>? = if (orderedSpacesToOrderMap.indices.contains(targetIndex + 1)) {
+            orderedSpacesToOrderMap[targetIndex + 1]
+        } else null
+
+        val defaultMaxOrder = CharArray(4) { StringOrderUtils.DEFAULT_ALPHABET.last() }
+                .joinToString("")
+
+        val defaultMinOrder = CharArray(4) { StringOrderUtils.DEFAULT_ALPHABET.first() }
+                .joinToString("")
+
+        val afterOrder = afterSpace?.second ?: defaultMaxOrder
+
+        val beforeOrder = lowerBondOrder ?: defaultMinOrder
+
+        val newOrder = StringOrderUtils.midPoints(beforeOrder, afterOrder, nodesToReNumber.size)
+
+        if (newOrder.isNullOrEmpty()) {
+            // re order all?
+            val expectedList = orderedSpacesToOrderMap.toMutableList()
+            expectedList.removeAt(movedIndex).let {
+                expectedList.add(movedIndex + delta, it)
+            }
+
+            return StringOrderUtils.midPoints(defaultMinOrder, defaultMaxOrder, orderedSpacesToOrderMap.size)?.let { orders ->
+                expectedList.mapIndexed { index, pair ->
+                    SpaceReOrderCommand(
+                            pair.first,
+                            orders[index]
+                    )
+                }
+            } ?: emptyList()
+        } else {
+            return nodesToReNumber.mapIndexed { i, s ->
+                SpaceReOrderCommand(
+                        s,
+                        newOrder[i]
+                )
+            }
+        }
+    }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/SpaceService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/SpaceService.kt
index fedf38fe065053b30f22f9e0487889aa9595813b..e5288e4045bd2316f4f6897a0367e1b9d7e5cb3f 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/SpaceService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/SpaceService.kt
@@ -36,7 +36,11 @@ interface SpaceService {
     /**
      * Just a shortcut for space creation for ease of use
      */
-    suspend fun createSpace(name: String, topic: String?, avatarUri: Uri?, isPublic: Boolean): String
+    suspend fun createSpace(name: String,
+                            topic: String?,
+                            avatarUri: Uri?,
+                            isPublic: Boolean,
+                            roomAliasLocalPart: String? = null): String
 
     /**
      * Get a space from a roomId
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/model/SpaceOrderContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/model/SpaceOrderContent.kt
new file mode 100644
index 0000000000000000000000000000000000000000..a8578347c87de3d05f0a5d7619175e08fa14b880
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/model/SpaceOrderContent.kt
@@ -0,0 +1,37 @@
+/*
+ * 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.space.model
+
+import com.squareup.moshi.JsonClass
+import org.matrix.android.sdk.api.MatrixPatterns
+
+/**
+ * {
+ * "type": "m.space_order",
+ *   "content": {
+ *       "order": "..."
+ *   }
+ * }
+ */
+@JsonClass(generateAdapter = true)
+data class SpaceOrderContent(
+        val order: String? = null
+) {
+    fun safeOrder(): String? {
+        return order?.takeIf { MatrixPatterns.isValidOrderString(it) }
+    }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/model/TopLevelSpaceComparator.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/model/TopLevelSpaceComparator.kt
new file mode 100644
index 0000000000000000000000000000000000000000..8af4f3a149ae9ae89fa3206f75c6ad8f5ffd32c1
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/model/TopLevelSpaceComparator.kt
@@ -0,0 +1,44 @@
+/*
+ * 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.space.model
+
+import org.matrix.android.sdk.api.session.room.model.RoomSummary
+
+// Can't use regular compare by because Null is considered less than any value, and for space order it's the opposite
+class TopLevelSpaceComparator(val orders: Map<String, String?>) : Comparator<RoomSummary> {
+
+    override fun compare(left: RoomSummary?, right: RoomSummary?): Int {
+        val leftOrder = left?.roomId?.let { orders[it] }
+        val rightOrder = right?.roomId?.let { orders[it] }
+        return if (leftOrder != null && rightOrder != null) {
+            leftOrder.compareTo(rightOrder)
+        } else {
+            if (leftOrder == null) {
+                if (rightOrder == null) {
+                    compareValues(left?.roomId, right?.roomId)
+                } else {
+                    1
+                }
+            } else {
+                -1
+            }
+        }
+//                .also {
+//            Timber.w("VAL: compare(${left?.displayName} | $leftOrder ,${right?.displayName} | $rightOrder) = $it")
+//        }
+    }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/StringOrderUtils.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/StringOrderUtils.kt
new file mode 100644
index 0000000000000000000000000000000000000000..83c858594167aa658d166b268c1830dc76fc4152
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/StringOrderUtils.kt
@@ -0,0 +1,87 @@
+/*
+ * 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.util
+
+import java.math.BigInteger
+
+object StringOrderUtils {
+
+    val DEFAULT_ALPHABET = buildString {
+        for (i in 0x20..0x7E) {
+            append(Char(i))
+        }
+    }.toCharArray()
+
+    // /=Range(0x20, 0x7E)
+
+    fun average(left: String, right: String, alphabet: CharArray = DEFAULT_ALPHABET): String? {
+        return midPoints(left, right, 1, alphabet)?.firstOrNull()
+    }
+
+    fun midPoints(left: String, right: String, count: Int, alphabet: CharArray = DEFAULT_ALPHABET): List<String>? {
+        if (left == right) return null // no space in between..
+        if (left > right) return midPoints(right, left, count, alphabet)
+        val size = maxOf(left.length, right.length)
+        val leftPadded = pad(left, size, alphabet.first())
+        val rightPadded = pad(right, size, alphabet.first())
+        val b1 = stringToBase(leftPadded, alphabet)
+        val b2 = stringToBase(rightPadded, alphabet)
+        val step = (b2.minus(b1)).div(BigInteger("${count + 1}"))
+        val orders = mutableListOf<String>()
+        var previous = left
+        for (i in 0 until count) {
+            val newOrder = baseToString(b1.add(step.multiply(BigInteger("${i + 1}"))), alphabet)
+            orders.add(newOrder)
+            // ensure there was enought precision
+            if (previous >= newOrder) return null
+            previous = newOrder
+        }
+        return orders.takeIf { orders.last() < right }
+    }
+
+    private fun pad(string: String, size: Int, padding: Char): String {
+        val raw = string.toCharArray()
+        return CharArray(size).also {
+            for (index in it.indices) {
+                if (index < raw.size) {
+                    it[index] = raw[index]
+                } else {
+                    it[index] = padding
+                }
+            }
+        }.joinToString("")
+    }
+
+    fun baseToString(x: BigInteger, alphabet: CharArray): String {
+        val len = alphabet.size.toBigInteger()
+        if (x < len) {
+            return alphabet[x.toInt()].toString()
+        } else {
+            return baseToString(x.div(len), alphabet) + alphabet[x.rem(len).toInt()].toString()
+        }
+    }
+
+    fun stringToBase(x: String, alphabet: CharArray): BigInteger {
+        if (x.isEmpty()) throw IllegalArgumentException()
+        val len = alphabet.size.toBigInteger()
+        var result = BigInteger("0")
+        x.reversed().forEachIndexed { index, c ->
+            result += (alphabet.indexOf(c).toBigInteger() * len.pow(index))
+        }
+        return result
+    }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/SessionManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/SessionManager.kt
index 441232f57f375598b64ac2e88437f7c53bb84e92..c746ad863a276fe189cb684fea9688cdd5f1a72e 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/SessionManager.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/SessionManager.kt
@@ -51,7 +51,7 @@ internal class SessionManager @Inject constructor(private val matrixComponent: M
         }
     }
 
-    private fun getOrCreateSessionComponent(sessionParams: SessionParams): SessionComponent {
+    fun getOrCreateSessionComponent(sessionParams: SessionParams): SessionComponent {
         return sessionComponents.getOrPut(sessionParams.credentials.sessionId()) {
             DaggerSessionComponent
                     .factory()
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 5a9fa9edf65263338e8ba47845099cba1914d3bd..50d9e5a06c88a875824eba715b445d04e24bf85a 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
@@ -21,8 +21,8 @@ import org.matrix.android.sdk.api.util.JsonDict
 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
 import org.matrix.android.sdk.internal.auth.data.TokenLoginParams
+import org.matrix.android.sdk.internal.auth.data.WebClientConfig
 import org.matrix.android.sdk.internal.auth.login.ResetPasswordMailConfirmed
 import org.matrix.android.sdk.internal.auth.registration.AddThreePidRegistrationParams
 import org.matrix.android.sdk.internal.auth.registration.AddThreePidRegistrationResponse
@@ -44,16 +44,16 @@ import retrofit2.http.Url
  */
 internal interface AuthAPI {
     /**
-     * Get a Riot config file, using the name including the domain
+     * Get a Web client config file, using the name including the domain
      */
     @GET("config.{domain}.json")
-    suspend fun getRiotConfigDomain(@Path("domain") domain: String): RiotConfig
+    suspend fun getWebClientConfigDomain(@Path("domain") domain: String): WebClientConfig
 
     /**
-     * Get a Riot config file
+     * Get a Web client default config file
      */
     @GET("config.json")
-    suspend fun getRiotConfig(): RiotConfig
+    suspend fun getWebClientConfig(): WebClientConfig
 
     /**
      * Get the version information of the homeserver
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 20ce438d8e8a44fb4fbb1a1201f95b0d43d1cad7..e76dc28734ae7bd43b67129c103876eab0d52368 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
@@ -19,6 +19,8 @@ package org.matrix.android.sdk.internal.auth
 import android.net.Uri
 import dagger.Lazy
 import okhttp3.OkHttpClient
+import org.matrix.android.sdk.api.MatrixPatterns
+import org.matrix.android.sdk.api.MatrixPatterns.getDomain
 import org.matrix.android.sdk.api.auth.AuthenticationService
 import org.matrix.android.sdk.api.auth.data.Credentials
 import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
@@ -28,10 +30,11 @@ import org.matrix.android.sdk.api.auth.login.LoginWizard
 import org.matrix.android.sdk.api.auth.registration.RegistrationWizard
 import org.matrix.android.sdk.api.auth.wellknown.WellknownResult
 import org.matrix.android.sdk.api.failure.Failure
+import org.matrix.android.sdk.api.failure.MatrixIdFailure
 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.RiotConfig
+import org.matrix.android.sdk.internal.auth.data.WebClientConfig
 import org.matrix.android.sdk.internal.auth.db.PendingSessionData
 import org.matrix.android.sdk.internal.auth.login.DefaultLoginWizard
 import org.matrix.android.sdk.internal.auth.login.DirectLoginTask
@@ -122,7 +125,7 @@ internal class DefaultAuthenticationService @Inject constructor(
     private fun getHomeServerUrlBase(): String? {
         return pendingSessionData
                 ?.homeServerConnectionConfig
-                ?.homeServerUri
+                ?.homeServerUriBase
                 ?.toString()
                 ?.trim { it == '/' }
     }
@@ -143,9 +146,9 @@ internal class DefaultAuthenticationService @Inject constructor(
         return result.fold(
                 {
                     // The homeserver exists and up to date, keep the config
-                    // Homeserver url may have been changed, if it was a Riot url
+                    // Homeserver url may have been changed, if it was a Web client url
                     val alteredHomeServerConnectionConfig = homeServerConnectionConfig.copy(
-                            homeServerUri = Uri.parse(it.homeServerUrl)
+                            homeServerUriBase = Uri.parse(it.homeServerUrl)
                     )
 
                     pendingSessionData = PendingSessionData(alteredHomeServerConnectionConfig)
@@ -154,7 +157,7 @@ internal class DefaultAuthenticationService @Inject constructor(
                 },
                 {
                     if (it is UnrecognizedCertificateException) {
-                        throw Failure.UnrecognizedCertificateFailure(homeServerConnectionConfig.homeServerUri.toString(), it.fingerprint)
+                        throw Failure.UnrecognizedCertificateFailure(homeServerConnectionConfig.homeServerUriBase.toString(), it.fingerprint)
                     } else {
                         throw it
                     }
@@ -165,46 +168,57 @@ internal class DefaultAuthenticationService @Inject constructor(
     private suspend fun getLoginFlowInternal(homeServerConnectionConfig: HomeServerConnectionConfig): LoginFlowResult {
         val authAPI = buildAuthAPI(homeServerConnectionConfig)
 
-        // First check the homeserver version
-        return runCatching {
-            executeRequest(null) {
-                authAPI.versions()
-            }
-        }
-                .map { versions ->
-                    // Ok, it seems that the homeserver url is valid
-                    getLoginFlowResult(authAPI, versions, homeServerConnectionConfig.homeServerUri.toString())
+        // First check if there is a well-known file
+        return try {
+            getWellknownLoginFlowInternal(homeServerConnectionConfig)
+        } catch (failure: Throwable) {
+            if (failure is Failure.OtherServerError
+                    && failure.httpCode == HttpsURLConnection.HTTP_NOT_FOUND /* 404 */) {
+                // 404, no well-known data, try direct access to the API
+                // First check the homeserver version
+                return runCatching {
+                    executeRequest(null) {
+                        authAPI.versions()
+                    }
                 }
-                .fold(
-                        {
-                            it
-                        },
-                        {
-                            if (it is Failure.OtherServerError
-                                    && it.httpCode == HttpsURLConnection.HTTP_NOT_FOUND /* 404 */) {
-                                // It's maybe a Riot url?
-                                getRiotDomainLoginFlowInternal(homeServerConnectionConfig)
-                            } else {
-                                throw it
-                            }
+                        .map { versions ->
+                            // Ok, it seems that the homeserver url is valid
+                            getLoginFlowResult(authAPI, versions, homeServerConnectionConfig.homeServerUriBase.toString())
                         }
-                )
+                        .fold(
+                                {
+                                    it
+                                },
+                                {
+                                    if (it is Failure.OtherServerError
+                                            && it.httpCode == HttpsURLConnection.HTTP_NOT_FOUND /* 404 */) {
+                                        // It's maybe a Web client url?
+                                        getWebClientDomainLoginFlowInternal(homeServerConnectionConfig)
+                                    } else {
+                                        throw it
+                                    }
+                                }
+                        )
+            } else {
+                throw failure
+            }
+        }
     }
 
-    private suspend fun getRiotDomainLoginFlowInternal(homeServerConnectionConfig: HomeServerConnectionConfig): LoginFlowResult {
+    private suspend fun getWebClientDomainLoginFlowInternal(homeServerConnectionConfig: HomeServerConnectionConfig): LoginFlowResult {
         val authAPI = buildAuthAPI(homeServerConnectionConfig)
 
         val domain = homeServerConnectionConfig.homeServerUri.host
-                ?: return getRiotLoginFlowInternal(homeServerConnectionConfig)
+                ?: return getWebClientLoginFlowInternal(homeServerConnectionConfig)
 
-        // Ok, try to get the config.domain.json file of a RiotWeb client
+        // Ok, try to get the config.domain.json file of a Web client
         return runCatching {
             executeRequest(null) {
-                authAPI.getRiotConfigDomain(domain)
+                authAPI.getWebClientConfigDomain(domain)
             }
         }
-                .map { riotConfig ->
-                    onRiotConfigRetrieved(homeServerConnectionConfig, riotConfig)
+                .map { webClientConfig ->
+                    onWebClientConfigRetrieved(homeServerConnectionConfig, webClientConfig)
                 }
                 .fold(
                         {
@@ -214,7 +228,7 @@ internal class DefaultAuthenticationService @Inject constructor(
                             if (it is Failure.OtherServerError
                                     && it.httpCode == HttpsURLConnection.HTTP_NOT_FOUND /* 404 */) {
                                 // Try with config.json
-                                getRiotLoginFlowInternal(homeServerConnectionConfig)
+                                getWebClientLoginFlowInternal(homeServerConnectionConfig)
                             } else {
                                 throw it
                             }
@@ -222,40 +236,24 @@ internal class DefaultAuthenticationService @Inject constructor(
                 )
     }
 
-    private suspend fun getRiotLoginFlowInternal(homeServerConnectionConfig: HomeServerConnectionConfig): LoginFlowResult {
+    private suspend fun getWebClientLoginFlowInternal(homeServerConnectionConfig: HomeServerConnectionConfig): LoginFlowResult {
         val authAPI = buildAuthAPI(homeServerConnectionConfig)
 
-        // Ok, try to get the config.json file of a RiotWeb client
-        return runCatching {
-            executeRequest(null) {
-                authAPI.getRiotConfig()
-            }
+        // Ok, try to get the config.json file of a Web client
+        return executeRequest(null) {
+            authAPI.getWebClientConfig()
         }
-                .map { riotConfig ->
-                    onRiotConfigRetrieved(homeServerConnectionConfig, riotConfig)
+                .let { webClientConfig ->
+                    onWebClientConfigRetrieved(homeServerConnectionConfig, webClientConfig)
                 }
-                .fold(
-                        {
-                            it
-                        },
-                        {
-                            if (it is Failure.OtherServerError
-                                    && it.httpCode == HttpsURLConnection.HTTP_NOT_FOUND /* 404 */) {
-                                // Try with wellknown
-                                getWellknownLoginFlowInternal(homeServerConnectionConfig)
-                            } else {
-                                throw it
-                            }
-                        }
-                )
     }
 
-    private suspend fun onRiotConfigRetrieved(homeServerConnectionConfig: HomeServerConnectionConfig, riotConfig: RiotConfig): LoginFlowResult {
-        val defaultHomeServerUrl = riotConfig.getPreferredHomeServerUrl()
+    private suspend fun onWebClientConfigRetrieved(homeServerConnectionConfig: HomeServerConnectionConfig, webClientConfig: WebClientConfig): LoginFlowResult {
+        val defaultHomeServerUrl = webClientConfig.getPreferredHomeServerUrl()
         if (defaultHomeServerUrl?.isNotEmpty() == true) {
             // Ok, good sign, we got a default hs url
             val newHomeServerConnectionConfig = homeServerConnectionConfig.copy(
-                    homeServerUri = Uri.parse(defaultHomeServerUrl)
+                    homeServerUriBase = Uri.parse(defaultHomeServerUrl)
             )
 
             val newAuthAPI = buildAuthAPI(newHomeServerConnectionConfig)
@@ -275,15 +273,13 @@ internal class DefaultAuthenticationService @Inject constructor(
         val domain = homeServerConnectionConfig.homeServerUri.host
                 ?: throw Failure.OtherServerError("", HttpsURLConnection.HTTP_NOT_FOUND /* 404 */)
 
-        // Create a fake userId, for the getWellknown task
-        val fakeUserId = "@alice:$domain"
-        val wellknownResult = getWellknownTask.execute(GetWellknownTask.Params(fakeUserId, homeServerConnectionConfig))
+        val wellknownResult = getWellknownTask.execute(GetWellknownTask.Params(domain, homeServerConnectionConfig))
 
         return when (wellknownResult) {
             is WellknownResult.Prompt -> {
                 val newHomeServerConnectionConfig = homeServerConnectionConfig.copy(
-                        homeServerUri = Uri.parse(wellknownResult.homeServerUrl),
-                        identityServerUri = wellknownResult.identityServerUrl?.let { Uri.parse(it) }
+                        homeServerUriBase = Uri.parse(wellknownResult.homeServerUrl),
+                        identityServerUri = wellknownResult.identityServerUrl?.let { Uri.parse(it) } ?: homeServerConnectionConfig.identityServerUri
                 )
 
                 val newAuthAPI = buildAuthAPI(newHomeServerConnectionConfig)
@@ -379,7 +375,14 @@ internal class DefaultAuthenticationService @Inject constructor(
 
     override suspend fun getWellKnownData(matrixId: String,
                                           homeServerConnectionConfig: HomeServerConnectionConfig?): WellknownResult {
-        return getWellknownTask.execute(GetWellknownTask.Params(matrixId, homeServerConnectionConfig))
+        if (!MatrixPatterns.isUserId(matrixId)) {
+            throw MatrixIdFailure.InvalidMatrixId
+        }
+
+        return getWellknownTask.execute(GetWellknownTask.Params(
+                domain = matrixId.getDomain(),
+                homeServerConnectionConfig = homeServerConnectionConfig)
+        )
     }
 
     override suspend fun directAuthentication(homeServerConnectionConfig: HomeServerConnectionConfig,
@@ -390,7 +393,7 @@ internal class DefaultAuthenticationService @Inject constructor(
     }
 
     private fun buildAuthAPI(homeServerConnectionConfig: HomeServerConnectionConfig): AuthAPI {
-        val retrofit = retrofitFactory.create(buildClient(homeServerConnectionConfig), homeServerConnectionConfig.homeServerUri.toString())
+        val retrofit = retrofitFactory.create(buildClient(homeServerConnectionConfig), homeServerConnectionConfig.homeServerUriBase.toString())
         return retrofit.create(AuthAPI::class.java)
     }
 
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 867cf46b8dbef8fd47e4601da74345c3bda21ad8..bc3d8870007f32b6bf98761d28baaedb0e47052c 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
@@ -42,7 +42,7 @@ internal class DefaultIsValidClientServerApiTask @Inject constructor(
 
     override suspend fun execute(params: IsValidClientServerApiTask.Params): Boolean {
         val client = buildClient(params.homeServerConnectionConfig)
-        val homeServerUrl = params.homeServerConnectionConfig.homeServerUri.toString()
+        val homeServerUrl = params.homeServerConnectionConfig.homeServerUriBase.toString()
 
         val authAPI = retrofitFactory.create(client, homeServerUrl)
                 .create(AuthAPI::class.java)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/SessionCreator.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/SessionCreator.kt
index 7c4a0c38ec5d3aed2cbdbe36ca2a6ccd899c8049..160fd2d5567eb5e0f66d75a21f1401e52e358db4 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/SessionCreator.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/SessionCreator.kt
@@ -38,7 +38,7 @@ internal class DefaultSessionCreator @Inject constructor(
 ) : SessionCreator {
 
     /**
-     * Credentials can affect the homeServerConnectionConfig, override home server url and/or
+     * Credentials can affect the homeServerConnectionConfig, override homeserver url and/or
      * identity server url if provided in the credentials
      */
     override suspend fun createSession(credentials: Credentials, homeServerConnectionConfig: HomeServerConnectionConfig): Session {
@@ -56,7 +56,7 @@ internal class DefaultSessionCreator @Inject constructor(
                     tryOrNull {
                         isValidClientServerApiTask.execute(
                                 IsValidClientServerApiTask.Params(
-                                        homeServerConnectionConfig.copy(homeServerUri = it)
+                                        homeServerConnectionConfig.copy(homeServerUriBase = it)
                                 )
                         )
                                 .also { Timber.d("Overriding homeserver url: $it") }
@@ -66,7 +66,7 @@ internal class DefaultSessionCreator @Inject constructor(
         val sessionParams = SessionParams(
                 credentials = credentials,
                 homeServerConnectionConfig = homeServerConnectionConfig.copy(
-                        homeServerUri = overriddenUrl ?: homeServerConnectionConfig.homeServerUri,
+                        homeServerUriBase = overriddenUrl ?: homeServerConnectionConfig.homeServerUriBase,
                         identityServerUri = credentials.discoveryInformation?.identityServer?.baseURL
                                 // remove trailing "/"
                                 ?.trim { it == '/' }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/RiotConfig.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/WebClientConfig.kt
similarity index 84%
rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/RiotConfig.kt
rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/WebClientConfig.kt
index e61358a67b2ec2ae2f3352be8fc9a8aa21e20732..65c3dc64a69cbaeea8cfe2005bdac9fc38ac4beb 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/RiotConfig.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/WebClientConfig.kt
@@ -20,7 +20,7 @@ import com.squareup.moshi.Json
 import com.squareup.moshi.JsonClass
 
 @JsonClass(generateAdapter = true)
-internal data class RiotConfig(
+internal data class WebClientConfig(
         /**
          * This is now deprecated, but still used first, rather than value from "default_server_config"
          */
@@ -28,7 +28,7 @@ internal data class RiotConfig(
         val defaultHomeServerUrl: String?,
 
         @Json(name = "default_server_config")
-        val defaultServerConfig: RiotConfigDefaultServerConfig?
+        val defaultServerConfig: WebClientConfigDefaultServerConfig?
 ) {
     fun getPreferredHomeServerUrl(): String? {
         return defaultHomeServerUrl
@@ -38,13 +38,13 @@ internal data class RiotConfig(
 }
 
 @JsonClass(generateAdapter = true)
-internal data class RiotConfigDefaultServerConfig(
+internal data class WebClientConfigDefaultServerConfig(
         @Json(name = "m.homeserver")
-        val homeServer: RiotConfigBaseConfig? = null
+        val homeServer: WebClientConfigBaseConfig? = null
 )
 
 @JsonClass(generateAdapter = true)
-internal data class RiotConfigBaseConfig(
+internal data class WebClientConfigBaseConfig(
         @Json(name = "base_url")
         val baseURL: String? = null
 )
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/db/AuthRealmMigration.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/db/AuthRealmMigration.kt
index bb2667228bb10a101b22bce1d66c7f30ce1e2660..c2104690b32dfc187ff7d16c71cf44f79f9f9438 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/db/AuthRealmMigration.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/db/AuthRealmMigration.kt
@@ -16,17 +16,19 @@
 
 package org.matrix.android.sdk.internal.auth.db
 
+import android.net.Uri
+import io.realm.DynamicRealm
+import io.realm.RealmMigration
 import org.matrix.android.sdk.api.auth.data.Credentials
+import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
 import org.matrix.android.sdk.api.auth.data.sessionId
 import org.matrix.android.sdk.internal.di.MoshiProvider
-import io.realm.DynamicRealm
-import io.realm.RealmMigration
 import timber.log.Timber
 
 internal object AuthRealmMigration : RealmMigration {
 
     // Current schema version
-    const val SCHEMA_VERSION = 3L
+    const val SCHEMA_VERSION = 4L
 
     override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) {
         Timber.d("Migrating Auth Realm from $oldVersion to $newVersion")
@@ -34,6 +36,7 @@ internal object AuthRealmMigration : RealmMigration {
         if (oldVersion <= 0) migrateTo1(realm)
         if (oldVersion <= 1) migrateTo2(realm)
         if (oldVersion <= 2) migrateTo3(realm)
+        if (oldVersion <= 3) migrateTo4(realm)
     }
 
     private fun migrateTo1(realm: DynamicRealm) {
@@ -81,4 +84,34 @@ internal object AuthRealmMigration : RealmMigration {
                 }
                 ?.addPrimaryKey(SessionParamsEntityFields.SESSION_ID)
     }
+
+    private fun migrateTo4(realm: DynamicRealm) {
+        Timber.d("Step 3 -> 4")
+        Timber.d("Update SessionParamsEntity to add HomeServerConnectionConfig.homeServerUriBase value")
+
+        val adapter = MoshiProvider.providesMoshi()
+                .adapter(HomeServerConnectionConfig::class.java)
+
+        realm.schema.get("SessionParamsEntity")
+                ?.transform {
+                    val homeserverConnectionConfigJson = it.getString(SessionParamsEntityFields.HOME_SERVER_CONNECTION_CONFIG_JSON)
+
+                    val homeserverConnectionConfig = adapter
+                            .fromJson(homeserverConnectionConfigJson)
+
+                    val homeserverUrl = homeserverConnectionConfig?.homeServerUri?.toString()
+                    // Special case for matrix.org. Old session may use "https://matrix.org", newer one may use
+                    // "https://matrix-client.matrix.org". So fix that here
+                    val alteredHomeserverConnectionConfig =
+                            if (homeserverUrl == "https://matrix.org" || homeserverUrl == "https://matrix-client.matrix.org") {
+                                homeserverConnectionConfig.copy(
+                                        homeServerUri = Uri.parse("https://matrix.org"),
+                                        homeServerUriBase = Uri.parse("https://matrix-client.matrix.org")
+                                )
+                            } else {
+                                homeserverConnectionConfig
+                            }
+                    it.set(SessionParamsEntityFields.HOME_SERVER_CONNECTION_CONFIG_JSON, adapter.toJson(alteredHomeserverConnectionConfig))
+                }
+    }
 }
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 77bbb8096fe943c1a5e7b4cbb8f5bf5f1beb638c..3888633723bd2fbe8eabb122c633ef62a0901ddf 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
@@ -50,7 +50,7 @@ internal class DefaultDirectLoginTask @Inject constructor(
 
     override suspend fun execute(params: DirectLoginTask.Params): Session {
         val client = buildClient(params.homeServerConnectionConfig)
-        val homeServerUrl = params.homeServerConnectionConfig.homeServerUri.toString()
+        val homeServerUrl = params.homeServerConnectionConfig.homeServerUriBase.toString()
 
         val authAPI = retrofitFactory.create(client, homeServerUrl)
                 .create(AuthAPI::class.java)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoModule.kt
index e114f86a9956c614497337875f91df73eb6cf452..84d4fef5afdf0609b0c28d5880b5fdb26bd2666f 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoModule.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoModule.kt
@@ -112,7 +112,6 @@ internal abstract class CryptoModule {
         @SessionScope
         fun providesRealmConfiguration(@SessionFilesDirectory directory: File,
                                        @UserMd5 userMd5: String,
-                                       realmCryptoStoreMigration: RealmCryptoStoreMigration,
                                        realmKeysUtils: RealmKeysUtils): RealmConfiguration {
             return RealmConfiguration.Builder()
                     .directory(directory)
@@ -123,7 +122,7 @@ internal abstract class CryptoModule {
                     .modules(RealmCryptoStoreModule())
                     .allowWritesOnUiThread(true)
                     .schemaVersion(RealmCryptoStoreMigration.CRYPTO_STORE_SCHEMA_VERSION)
-                    .migration(realmCryptoStoreMigration)
+                    .migration(RealmCryptoStoreMigration)
                     .build()
         }
 
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 7f5cfe8df103cfa036eacae1bd760855dd2006eb..d170ae3dd306d5cbe654bc4c6b5b1646117302b3 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
@@ -388,7 +388,7 @@ internal class DefaultCryptoService @Inject constructor(
         cryptoStore.close()
     }
 
-    // Aways enabled on RiotX
+    // Always enabled on Matrix Android SDK2
     override fun isCryptoEnabled() = true
 
     /**
@@ -928,14 +928,10 @@ internal class DefaultCryptoService @Inject constructor(
      * Export the crypto keys
      *
      * @param password the password
-     * @param callback the exported keys
+     * @return the exported keys
      */
-    override fun exportRoomKeys(password: String, callback: MatrixCallback<ByteArray>) {
-        cryptoCoroutineScope.launch(coroutineDispatchers.main) {
-            runCatching {
-                exportRoomKeys(password, MXMegolmExportEncryption.DEFAULT_ITERATION_COUNT)
-            }.foldToCallback(callback)
-        }
+    override suspend fun exportRoomKeys(password: String): ByteArray {
+        return exportRoomKeys(password, MXMegolmExportEncryption.DEFAULT_ITERATION_COUNT)
     }
 
     /**
@@ -963,42 +959,37 @@ internal class DefaultCryptoService @Inject constructor(
      * @param roomKeysAsArray  the room keys as array.
      * @param password         the password
      * @param progressListener the progress listener
-     * @param callback         the asynchronous callback.
+     * @return the result ImportRoomKeysResult
      */
-    override fun importRoomKeys(roomKeysAsArray: ByteArray,
-                                password: String,
-                                progressListener: ProgressListener?,
-                                callback: MatrixCallback<ImportRoomKeysResult>) {
-        cryptoCoroutineScope.launch(coroutineDispatchers.main) {
-            runCatching {
-                withContext(coroutineDispatchers.crypto) {
-                    Timber.v("## CRYPTO | importRoomKeys starts")
+    override suspend fun importRoomKeys(roomKeysAsArray: ByteArray,
+                                        password: String,
+                                        progressListener: ProgressListener?): ImportRoomKeysResult {
+        return withContext(coroutineDispatchers.crypto) {
+            Timber.v("## CRYPTO | importRoomKeys starts")
 
-                    val t0 = System.currentTimeMillis()
-                    val roomKeys = MXMegolmExportEncryption.decryptMegolmKeyFile(roomKeysAsArray, password)
-                    val t1 = System.currentTimeMillis()
+            val t0 = System.currentTimeMillis()
+            val roomKeys = MXMegolmExportEncryption.decryptMegolmKeyFile(roomKeysAsArray, password)
+            val t1 = System.currentTimeMillis()
 
-                    Timber.v("## CRYPTO | importRoomKeys : decryptMegolmKeyFile done in ${t1 - t0} ms")
+            Timber.v("## CRYPTO | importRoomKeys : decryptMegolmKeyFile done in ${t1 - t0} ms")
 
-                    val importedSessions = MoshiProvider.providesMoshi()
-                            .adapter<List<MegolmSessionData>>(Types.newParameterizedType(List::class.java, MegolmSessionData::class.java))
-                            .fromJson(roomKeys)
+            val importedSessions = MoshiProvider.providesMoshi()
+                    .adapter<List<MegolmSessionData>>(Types.newParameterizedType(List::class.java, MegolmSessionData::class.java))
+                    .fromJson(roomKeys)
 
-                    val t2 = System.currentTimeMillis()
+            val t2 = System.currentTimeMillis()
 
-                    Timber.v("## CRYPTO | importRoomKeys : JSON parsing ${t2 - t1} ms")
+            Timber.v("## CRYPTO | importRoomKeys : JSON parsing ${t2 - t1} ms")
 
-                    if (importedSessions == null) {
-                        throw Exception("Error")
-                    }
+            if (importedSessions == null) {
+                throw Exception("Error")
+            }
 
-                    megolmSessionDataImporter.handle(
-                            megolmSessionsData = importedSessions,
-                            fromBackup = false,
-                            progressListener = progressListener
-                    )
-                }
-            }.foldToCallback(callback)
+            megolmSessionDataImporter.handle(
+                    megolmSessionsData = importedSessions,
+                    fromBackup = false,
+                    progressListener = progressListener
+            )
         }
     }
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt
index a29ac457fb956178d2be73f9b13c53b112f0fc5e..70d202229932fbae5fd89994abb22ee8805ed993 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt
@@ -155,7 +155,7 @@ internal class MXMegolmDecryption(private val userId: String,
                                                 withHeldInfo.code?.value ?: "",
                                                 withHeldInfo.reason)
                                     } else {
-                                        // This is un-used in riotX SDK, not sure if needed
+                                        // This is un-used in Matrix Android SDK2, not sure if needed
                                         // addEventToPendingList(event, timeline)
                                         if (requestKeysOnFail) {
                                             requestKeysForEvent(event, false)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/attachments/MXEncryptedAttachments.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/attachments/MXEncryptedAttachments.kt
index 5a9852b6db71742355f051008ffe82415059cbd8..70730326da12d90fa0397e12a9adf0777b571a3e 100755
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/attachments/MXEncryptedAttachments.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/attachments/MXEncryptedAttachments.kt
@@ -39,7 +39,9 @@ internal object MXEncryptedAttachments {
     private const val SECRET_KEY_SPEC_ALGORITHM = "AES"
     private const val MESSAGE_DIGEST_ALGORITHM = "SHA-256"
 
-    fun encrypt(clearStream: InputStream, mimetype: String?, outputFile: File, progress: ((current: Int, total: Int) -> Unit)): EncryptedFileInfo {
+    fun encrypt(clearStream: InputStream,
+                outputFile: File,
+                progress: ((current: Int, total: Int) -> Unit)): EncryptedFileInfo {
         val t0 = System.currentTimeMillis()
         val secureRandom = SecureRandom()
         val initVectorBytes = ByteArray(16) { 0.toByte() }
@@ -86,7 +88,6 @@ internal object MXEncryptedAttachments {
 
         return EncryptedFileInfo(
                 url = null,
-                mimetype = mimetype,
                 key = EncryptedFileKey(
                         alg = "A256CTR",
                         ext = true,
@@ -155,10 +156,9 @@ internal object MXEncryptedAttachments {
      * Encrypt an attachment stream.
      * DO NOT USE for big files, it will load all in memory
      * @param attachmentStream the attachment stream. Will be closed after this method call.
-     * @param mimetype the mime type
      * @return the encryption file info
      */
-    fun encryptAttachment(attachmentStream: InputStream, mimetype: String?): EncryptionResult {
+    fun encryptAttachment(attachmentStream: InputStream): EncryptionResult {
         val t0 = System.currentTimeMillis()
         val secureRandom = SecureRandom()
 
@@ -207,7 +207,6 @@ internal object MXEncryptedAttachments {
         return EncryptionResult(
                 encryptedFileInfo = EncryptedFileInfo(
                         url = null,
-                        mimetype = mimetype,
                         key = EncryptedFileKey(
                                 alg = "A256CTR",
                                 ext = true,
@@ -232,7 +231,9 @@ internal object MXEncryptedAttachments {
      * @param outputStream     the outputStream where the decrypted attachment will be write.
      * @return true in case of success, false in case of error
      */
-    fun decryptAttachment(attachmentStream: InputStream?, elementToDecrypt: ElementToDecrypt?, outputStream: OutputStream): Boolean {
+    fun decryptAttachment(attachmentStream: InputStream?,
+                          elementToDecrypt: ElementToDecrypt?,
+                          outputStream: OutputStream): Boolean {
         // sanity checks
         if (null == attachmentStream || elementToDecrypt == null) {
             Timber.e("## decryptAttachment() : null stream")
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/MXDeviceInfo.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/MXDeviceInfo.kt
index 00b8bde5d999aea1a843e537bfedab6ca7ce048f..68cc41005eff82afab592e3a1695a4d4db9e11cc 100755
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/MXDeviceInfo.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/MXDeviceInfo.kt
@@ -56,7 +56,7 @@ data class MXDeviceInfo(
         val signatures: Map<String, Map<String, String>>? = null,
 
         /*
-         * Additional data from the home server.
+         * Additional data from the homeserver.
          */
         @Json(name = "unsigned")
         val unsigned: JsonDict? = null,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/EncryptedFileInfo.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/EncryptedFileInfo.kt
index 0ed6c0dac938882028a90fc1d53849ed0d8ab613..4fc3adb42cc43de630d10c250a3063d4261b3e3e 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/EncryptedFileInfo.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/EncryptedFileInfo.kt
@@ -29,12 +29,6 @@ data class EncryptedFileInfo(
         @Json(name = "url")
         val url: String? = null,
 
-        /**
-         * Not documented
-         */
-        @Json(name = "mimetype")
-        val mimetype: String? = null,
-
         /**
          * Required. A JSON Web Key object.
          */
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 1f80ce2c812ab1b359fad9dd355c7a252060534e..fb10cf4482b90bc64724b037db262dac9e41b0de 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
@@ -18,7 +18,7 @@ package org.matrix.android.sdk.internal.crypto.secrets
 
 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
+import org.matrix.android.sdk.api.session.accountdata.SessionAccountDataService
 import org.matrix.android.sdk.api.session.events.model.toContent
 import org.matrix.android.sdk.api.session.securestorage.EncryptedSecretContent
 import org.matrix.android.sdk.api.session.securestorage.IntegrityResult
@@ -56,7 +56,7 @@ import kotlin.experimental.and
 
 internal class DefaultSharedSecretStorageService @Inject constructor(
         @UserId private val userId: String,
-        private val accountDataService: AccountDataService,
+        private val accountDataService: SessionAccountDataService,
         private val outgoingGossipingRequestManager: OutgoingGossipingRequestManager,
         private val coroutineDispatchers: MatrixCoroutineDispatchers,
         private val cryptoCoroutineScope: CoroutineScope
@@ -84,7 +84,7 @@ internal class DefaultSharedSecretStorageService @Inject constructor(
                 )
             } ?: storageKeyContent
 
-            accountDataService.updateAccountData("$KEY_ID_BASE.$keyId", signedContent.toContent())
+            accountDataService.updateUserAccountData("$KEY_ID_BASE.$keyId", signedContent.toContent())
             SsssKeyCreationInfo(
                     keyId = keyId,
                     content = storageKeyContent,
@@ -113,7 +113,7 @@ internal class DefaultSharedSecretStorageService @Inject constructor(
                 )
             } ?: storageKeyContent
 
-            accountDataService.updateAccountData(
+            accountDataService.updateUserAccountData(
                     "$KEY_ID_BASE.$keyId",
                     signedContent.toContent()
             )
@@ -127,11 +127,11 @@ internal class DefaultSharedSecretStorageService @Inject constructor(
     }
 
     override fun hasKey(keyId: String): Boolean {
-        return accountDataService.getAccountDataEvent("$KEY_ID_BASE.$keyId") != null
+        return accountDataService.getUserAccountDataEvent("$KEY_ID_BASE.$keyId") != null
     }
 
     override fun getKey(keyId: String): KeyInfoResult {
-        val accountData = accountDataService.getAccountDataEvent("$KEY_ID_BASE.$keyId")
+        val accountData = accountDataService.getUserAccountDataEvent("$KEY_ID_BASE.$keyId")
                 ?: return KeyInfoResult.Error(SharedSecretStorageError.UnknownKey(keyId))
         return SecretStorageKeyContent.fromJson(accountData.content)?.let {
             KeyInfoResult.Success(
@@ -143,14 +143,14 @@ internal class DefaultSharedSecretStorageService @Inject constructor(
     override suspend fun setDefaultKey(keyId: String) {
         val existingKey = getKey(keyId)
         if (existingKey is KeyInfoResult.Success) {
-            accountDataService.updateAccountData(DEFAULT_KEY_ID, mapOf("key" to keyId))
+            accountDataService.updateUserAccountData(DEFAULT_KEY_ID, mapOf("key" to keyId))
         } else {
             throw SharedSecretStorageError.UnknownKey(keyId)
         }
     }
 
     override fun getDefaultKey(): KeyInfoResult {
-        val accountData = accountDataService.getAccountDataEvent(DEFAULT_KEY_ID)
+        val accountData = accountDataService.getUserAccountDataEvent(DEFAULT_KEY_ID)
                 ?: return KeyInfoResult.Error(SharedSecretStorageError.UnknownKey(DEFAULT_KEY_ID))
         val keyId = accountData.content["key"] as? String
                 ?: return KeyInfoResult.Error(SharedSecretStorageError.UnknownKey(DEFAULT_KEY_ID))
@@ -178,7 +178,7 @@ internal class DefaultSharedSecretStorageService @Inject constructor(
                 }
             }
 
-            accountDataService.updateAccountData(
+            accountDataService.updateUserAccountData(
                     type = name,
                     content = mapOf("encrypted" to encryptedContents)
             )
@@ -288,7 +288,7 @@ internal class DefaultSharedSecretStorageService @Inject constructor(
     }
 
     override fun getAlgorithmsForSecret(name: String): List<KeyInfoResult> {
-        val accountData = accountDataService.getAccountDataEvent(name)
+        val accountData = accountDataService.getUserAccountDataEvent(name)
                 ?: return listOf(KeyInfoResult.Error(SharedSecretStorageError.UnknownSecret(name)))
         val encryptedContent = accountData.content[ENCRYPTED] as? Map<*, *>
                 ?: return listOf(KeyInfoResult.Error(SharedSecretStorageError.SecretNotEncrypted(name)))
@@ -303,7 +303,7 @@ internal class DefaultSharedSecretStorageService @Inject constructor(
     }
 
     override suspend fun getSecret(name: String, keyId: String?, secretKey: SsssKeySpec): String {
-        val accountData = accountDataService.getAccountDataEvent(name) ?: throw SharedSecretStorageError.UnknownSecret(name)
+        val accountData = accountDataService.getUserAccountDataEvent(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)
@@ -368,7 +368,7 @@ internal class DefaultSharedSecretStorageService @Inject constructor(
         }
 
         secretNames.forEach { secretName ->
-            val secretEvent = accountDataService.getAccountDataEvent(secretName)
+            val secretEvent = accountDataService.getUserAccountDataEvent(secretName)
                     ?: return IntegrityResult.Error(SharedSecretStorageError.UnknownSecret(secretName))
             if ((secretEvent.content["encrypted"] as? Map<*, *>)?.get(keyInfo.id) == null) {
                 return IntegrityResult.Error(SharedSecretStorageError.SecretNotEncryptedWithKey(secretName, keyInfo.id))
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStoreMigration.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStoreMigration.kt
index bca79143885373c71a286b2b5cd5c276551a1b39..8e77f5b82329468b34d39d1591f31e152ef07f74 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStoreMigration.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStoreMigration.kt
@@ -18,6 +18,9 @@ package org.matrix.android.sdk.internal.crypto.store.db
 
 import com.squareup.moshi.Moshi
 import com.squareup.moshi.Types
+import io.realm.DynamicRealm
+import io.realm.RealmMigration
+import io.realm.RealmObjectSchema
 import org.matrix.android.sdk.api.extensions.tryOrNull
 import org.matrix.android.sdk.api.util.JsonDict
 import org.matrix.android.sdk.internal.crypto.model.MXDeviceInfo
@@ -35,29 +38,24 @@ import org.matrix.android.sdk.internal.crypto.store.db.model.KeysBackupDataEntit
 import org.matrix.android.sdk.internal.crypto.store.db.model.MyDeviceLastSeenInfoEntityFields
 import org.matrix.android.sdk.internal.crypto.store.db.model.OlmInboundGroupSessionEntityFields
 import org.matrix.android.sdk.internal.crypto.store.db.model.OlmSessionEntityFields
+import org.matrix.android.sdk.internal.crypto.store.db.model.OutboundGroupSessionInfoEntityFields
 import org.matrix.android.sdk.internal.crypto.store.db.model.OutgoingGossipingRequestEntityFields
 import org.matrix.android.sdk.internal.crypto.store.db.model.SharedSessionEntityFields
 import org.matrix.android.sdk.internal.crypto.store.db.model.TrustLevelEntityFields
 import org.matrix.android.sdk.internal.crypto.store.db.model.UserEntityFields
 import org.matrix.android.sdk.internal.crypto.store.db.model.WithHeldSessionEntityFields
+import org.matrix.android.sdk.internal.di.MoshiProvider
 import org.matrix.android.sdk.internal.di.SerializeNulls
-import io.realm.DynamicRealm
-import io.realm.RealmMigration
-import io.realm.RealmObjectSchema
-import org.matrix.android.sdk.internal.crypto.store.db.model.OutboundGroupSessionInfoEntityFields
 import org.matrix.androidsdk.crypto.data.MXOlmInboundGroupSession2
 import timber.log.Timber
-import javax.inject.Inject
 import org.matrix.androidsdk.crypto.data.MXDeviceInfo as LegacyMXDeviceInfo
 
-internal class RealmCryptoStoreMigration @Inject constructor(private val crossSigningKeysMapper: CrossSigningKeysMapper) : RealmMigration {
+internal object RealmCryptoStoreMigration : RealmMigration {
 
-    companion object {
-        // 0, 1, 2: legacy Riot-Android
-        // 3: migrate to RiotX schema
-        // 4, 5, 6, 7, 8, 9: migrations from RiotX (which was previously 1, 2, 3, 4, 5, 6)
-        const val CRYPTO_STORE_SCHEMA_VERSION = 12L
-    }
+    // 0, 1, 2: legacy Riot-Android
+    // 3: migrate to RiotX schema
+    // 4, 5, 6, 7, 8, 9: migrations from RiotX (which was previously 1, 2, 3, 4, 5, 6)
+    const val CRYPTO_STORE_SCHEMA_VERSION = 12L
 
     private fun RealmObjectSchema.addFieldIfNotExists(fieldName: String, fieldType: Class<*>): RealmObjectSchema {
         if (!hasField(fieldName)) {
@@ -384,6 +382,8 @@ internal class RealmCryptoStoreMigration @Inject constructor(private val crossSi
     private fun migrateTo7(realm: DynamicRealm) {
         Timber.d("Step 6 -> 7")
         Timber.d("Updating KeyInfoEntity table")
+        val crossSigningKeysMapper = CrossSigningKeysMapper(MoshiProvider.providesMoshi())
+
         val keyInfoEntities = realm.where("KeyInfoEntity").findAll()
         try {
             keyInfoEntities.forEach {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/InitializeCrossSigningTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/InitializeCrossSigningTask.kt
index f8a8354e482e0c4ad2aa3b5c144c8e767545d541..1d40e5defd3b02f70fc31479b776b85558b3bacd 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/InitializeCrossSigningTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/InitializeCrossSigningTask.kt
@@ -97,7 +97,7 @@ internal class DefaultInitializeCrossSigningTask @Inject constructor(
 
             Timber.v("## CrossSigning - sskPublicKey:$sskPublicKey")
 
-            // Sign userSigningKey with master
+            // Sign selfSigningKey with master
             val signedSSK = CryptoCrossSigningKey.Builder(userId, KeyUsage.SELF_SIGNING)
                     .key(sskPublicKey)
                     .build()
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 2b3c3b28eebfca2f6da87efd999ca7cfadc51398..28ae4d8bfd48aa8b1f14eb06c6b72398625e65db 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
@@ -19,9 +19,10 @@ 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.VersioningState
 import org.matrix.android.sdk.api.session.events.model.EventType
+import org.matrix.android.sdk.api.session.room.model.Membership
 import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesContent
+import org.matrix.android.sdk.api.session.room.model.VersioningState
 import org.matrix.android.sdk.api.session.room.model.create.RoomCreateContent
 import org.matrix.android.sdk.api.session.room.model.tag.RoomTag
 import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntityFields
@@ -40,14 +41,12 @@ import org.matrix.android.sdk.internal.database.model.SpaceChildSummaryEntityFie
 import org.matrix.android.sdk.internal.database.model.SpaceParentSummaryEntityFields
 import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields
 import org.matrix.android.sdk.internal.di.MoshiProvider
+import org.matrix.android.sdk.internal.query.process
 import timber.log.Timber
-import javax.inject.Inject
 
-class RealmSessionStoreMigration @Inject constructor() : RealmMigration {
+internal object RealmSessionStoreMigration : RealmMigration {
 
-    companion object {
-        const val SESSION_STORE_SCHEMA_VERSION = 14L
-    }
+    const val SESSION_STORE_SCHEMA_VERSION = 16L
 
     override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) {
         Timber.v("Migrating Realm Session from $oldVersion to $newVersion")
@@ -66,6 +65,8 @@ class RealmSessionStoreMigration @Inject constructor() : RealmMigration {
         if (oldVersion <= 11) migrateTo12(realm)
         if (oldVersion <= 12) migrateTo13(realm)
         if (oldVersion <= 13) migrateTo14(realm)
+        if (oldVersion <= 14) migrateTo15(realm)
+        if (oldVersion <= 15) migrateTo16(realm)
     }
 
     private fun migrateTo1(realm: DynamicRealm) {
@@ -292,7 +293,7 @@ class RealmSessionStoreMigration @Inject constructor() : RealmMigration {
         Timber.d("Step 13 -> 14")
         val roomAccountDataSchema = realm.schema.create("RoomAccountDataEntity")
                 .addField(RoomAccountDataEntityFields.CONTENT_STR, String::class.java)
-                .addField(RoomAccountDataEntityFields.TYPE, String::class.java,  FieldAttribute.INDEXED)
+                .addField(RoomAccountDataEntityFields.TYPE, String::class.java, FieldAttribute.INDEXED)
 
         realm.schema.get("RoomEntity")
                 ?.addRealmListField(RoomEntityFields.ACCOUNT_DATA.`$`, roomAccountDataSchema)
@@ -306,4 +307,27 @@ class RealmSessionStoreMigration @Inject constructor() : RealmMigration {
 
         roomAccountDataSchema.isEmbedded = true
     }
+
+    private fun migrateTo15(realm: DynamicRealm) {
+        Timber.d("Step 14 -> 15")
+        // fix issue with flattenParentIds on DM that kept growing with duplicate
+        // so we reset it, will be updated next sync
+        realm.where("RoomSummaryEntity")
+                .process(RoomSummaryEntityFields.MEMBERSHIP_STR, Membership.activeMemberships())
+                .equalTo(RoomSummaryEntityFields.IS_DIRECT, true)
+                .findAll()
+                .onEach {
+                    it.setString(RoomSummaryEntityFields.FLATTEN_PARENT_IDS, null)
+                }
+    }
+
+    private fun migrateTo16(realm: DynamicRealm) {
+        Timber.d("Step 15 -> 16")
+        realm.schema.get("HomeServerCapabilitiesEntity")
+                ?.addField(HomeServerCapabilitiesEntityFields.ROOM_VERSIONS_JSON, String::class.java)
+                ?.transform { obj ->
+                    // Schedule a refresh of the capabilities
+                    obj.setLong(HomeServerCapabilitiesEntityFields.LAST_UPDATED_TIMESTAMP, 0)
+                }
+    }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/SessionRealmConfigurationFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/SessionRealmConfigurationFactory.kt
index 244fe3432acf6c26e3c8944e7ad0fbdbca9a0c95..1771c5b202f8363fdbf79c6e1820fb5f072b771d 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/SessionRealmConfigurationFactory.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/SessionRealmConfigurationFactory.kt
@@ -43,7 +43,6 @@ internal class SessionRealmConfigurationFactory @Inject constructor(
         @SessionFilesDirectory val directory: File,
         @SessionId val sessionId: String,
         @UserMd5 val userMd5: String,
-        val migration: RealmSessionStoreMigration,
         context: Context) {
 
     // Keep legacy preferences name for compatibility reason
@@ -72,7 +71,7 @@ internal class SessionRealmConfigurationFactory @Inject constructor(
                 .allowWritesOnUiThread(true)
                 .modules(SessionRealmModule())
                 .schemaVersion(RealmSessionStoreMigration.SESSION_STORE_SCHEMA_VERSION)
-                .migration(migration)
+                .migration(RealmSessionStoreMigration)
                 .build()
 
         // Try creating a realm instance and if it succeeds we can clear the flag
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/AccountDataMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/AccountDataMapper.kt
index 4edfdad8970e9a855eacf8aee26e5cca123f1c64..dca0f927ad08e811271aa3c92b36259fc3cfb217 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/AccountDataMapper.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/AccountDataMapper.kt
@@ -17,7 +17,8 @@
 package org.matrix.android.sdk.internal.database.mapper
 
 import com.squareup.moshi.Moshi
-import org.matrix.android.sdk.api.session.accountdata.AccountDataEvent
+import org.matrix.android.sdk.api.session.accountdata.UserAccountDataEvent
+import org.matrix.android.sdk.api.session.room.accountdata.RoomAccountDataEvent
 import org.matrix.android.sdk.api.util.JSON_DICT_PARAMETERIZED_TYPE
 import org.matrix.android.sdk.internal.database.model.RoomAccountDataEntity
 import org.matrix.android.sdk.internal.database.model.UserAccountDataEntity
@@ -27,15 +28,16 @@ internal class AccountDataMapper @Inject constructor(moshi: Moshi) {
 
     private val adapter = moshi.adapter<Map<String, Any>>(JSON_DICT_PARAMETERIZED_TYPE)
 
-    fun map(entity: UserAccountDataEntity): AccountDataEvent {
-        return AccountDataEvent(
+    fun map(entity: UserAccountDataEntity): UserAccountDataEvent {
+        return UserAccountDataEvent(
                 type = entity.type ?: "",
                 content = entity.contentStr?.let { adapter.fromJson(it) }.orEmpty()
         )
     }
 
-    fun map(entity: RoomAccountDataEntity): AccountDataEvent {
-        return AccountDataEvent(
+    fun map(roomId: String, entity: RoomAccountDataEntity): RoomAccountDataEvent {
+        return RoomAccountDataEvent(
+                roomId = roomId,
                 type = entity.type ?: "",
                 content = entity.contentStr?.let { adapter.fromJson(it) }.orEmpty()
         )
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/HomeServerCapabilitiesMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/HomeServerCapabilitiesMapper.kt
index b18c67294fdd31528fa33dde16aa28b0007546f0..2575cdef2683d4d1706ba1c082d4f80030672d03 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/HomeServerCapabilitiesMapper.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/HomeServerCapabilitiesMapper.kt
@@ -16,8 +16,15 @@
 
 package org.matrix.android.sdk.internal.database.mapper
 
+import org.matrix.android.sdk.api.extensions.tryOrNull
 import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities
+import org.matrix.android.sdk.api.session.homeserver.RoomVersionCapabilities
+import org.matrix.android.sdk.api.session.homeserver.RoomVersionInfo
+import org.matrix.android.sdk.api.session.homeserver.RoomVersionStatus
 import org.matrix.android.sdk.internal.database.model.HomeServerCapabilitiesEntity
+import org.matrix.android.sdk.internal.di.MoshiProvider
+import org.matrix.android.sdk.internal.session.homeserver.RoomVersions
+import org.matrix.android.sdk.internal.session.room.version.DefaultRoomVersionService
 
 /**
  * HomeServerCapabilitiesEntity -> HomeSeverCapabilities
@@ -29,7 +36,30 @@ internal object HomeServerCapabilitiesMapper {
                 canChangePassword = entity.canChangePassword,
                 maxUploadFileSize = entity.maxUploadFileSize,
                 lastVersionIdentityServerSupported = entity.lastVersionIdentityServerSupported,
-                defaultIdentityServerUrl = entity.defaultIdentityServerUrl
+                defaultIdentityServerUrl = entity.defaultIdentityServerUrl,
+                roomVersions = mapRoomVersion(entity.roomVersionsJson)
         )
     }
+
+    private fun mapRoomVersion(roomVersionsJson: String?): RoomVersionCapabilities? {
+        roomVersionsJson ?: return null
+
+        return tryOrNull {
+            MoshiProvider.providesMoshi().adapter(RoomVersions::class.java).fromJson(roomVersionsJson)?.let {
+                RoomVersionCapabilities(
+                        defaultRoomVersion = it.default ?: DefaultRoomVersionService.DEFAULT_ROOM_VERSION,
+                        supportedVersion = it.available.entries.map { entry ->
+                            RoomVersionInfo(
+                                    version = entry.key,
+                                    status = if (entry.value == "stable") {
+                                        RoomVersionStatus.STABLE
+                                    } else {
+                                        RoomVersionStatus.UNSTABLE
+                                    }
+                            )
+                        }
+                )
+            }
+        }
+    }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/HomeServerCapabilitiesEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/HomeServerCapabilitiesEntity.kt
index 763dcf80a20996bb5350956fdf442c67275ee53d..980449ddfbe4235c60044370ea6c43da42eac691 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/HomeServerCapabilitiesEntity.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/HomeServerCapabilitiesEntity.kt
@@ -21,6 +21,7 @@ import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities
 
 internal open class HomeServerCapabilitiesEntity(
         var canChangePassword: Boolean = true,
+        var roomVersionsJson: String? = null,
         var maxUploadFileSize: Long = HomeServerCapabilities.MAX_UPLOAD_FILE_SIZE_UNKNOWN,
         var lastVersionIdentityServerSupported: Boolean = false,
         var defaultIdentityServerUrl: String? = null,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/UserAccountDataEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/UserAccountDataEntity.kt
index cfdb84d0335304484bab4d5611361ea004c9f6e5..e258c20df5aa238c201d445ba6e716080716d0c8 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/UserAccountDataEntity.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/UserAccountDataEntity.kt
@@ -20,7 +20,7 @@ import io.realm.RealmObject
 import io.realm.annotations.Index
 
 /**
- * Clients can store custom config data for their account on their HomeServer.
+ * Clients can store custom config data for their account on their homeserver.
  * This account data will be synced between different devices and can persist across installations on a particular device.
  * Users may only view the account data for their own account.
  * The account_data may be either global or scoped to a particular rooms.
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/federation/FederationModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/federation/FederationModule.kt
index 320bf1d4454df3ec98294597e50191480671d707..a4eef80c58eb0e63f9da9723c04c11b12514df90 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/federation/FederationModule.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/federation/FederationModule.kt
@@ -37,7 +37,8 @@ internal abstract class FederationModule {
         fun providesFederationAPI(@Unauthenticated okHttpClient: Lazy<OkHttpClient>,
                                   sessionParams: SessionParams,
                                   retrofitFactory: RetrofitFactory): FederationAPI {
-            return retrofitFactory.create(okHttpClient, sessionParams.homeServerUrl).create(FederationAPI::class.java)
+            return retrofitFactory.create(okHttpClient, sessionParams.homeServerUrlBase)
+                    .create(FederationAPI::class.java)
         }
     }
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/legacy/DefaultLegacySessionImporter.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/legacy/DefaultLegacySessionImporter.kt
index e0e2f96fa92f547eda719aa6c08dfa278194db03..ad2aff4c9d94d922054986ffbd656d03d217d29e 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/legacy/DefaultLegacySessionImporter.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/legacy/DefaultLegacySessionImporter.kt
@@ -42,7 +42,6 @@ import org.matrix.android.sdk.internal.legacy.riot.HomeServerConnectionConfig as
 internal class DefaultLegacySessionImporter @Inject constructor(
         private val context: Context,
         private val sessionParamsStore: SessionParamsStore,
-        private val realmCryptoStoreMigration: RealmCryptoStoreMigration,
         private val realmKeysUtils: RealmKeysUtils
 ) : LegacySessionImporter {
 
@@ -153,7 +152,7 @@ internal class DefaultLegacySessionImporter @Inject constructor(
     }
 
     private fun importCryptoDb(legacyConfig: LegacyHomeServerConnectionConfig) {
-        // Here we migrate the DB, we copy the crypto DB to the location specific to RiotX, and we encrypt it.
+        // Here we migrate the DB, we copy the crypto DB to the location specific to Matrix SDK2, and we encrypt it.
         val userMd5 = legacyConfig.credentials.userId.md5()
 
         val sessionId = legacyConfig.credentials.let { (if (it.deviceId.isNullOrBlank()) it.userId else "${it.userId}|${it.deviceId}").md5() }
@@ -172,17 +171,17 @@ internal class DefaultLegacySessionImporter @Inject constructor(
                 .name("crypto_store.realm")
                 .modules(RealmCryptoStoreModule())
                 .schemaVersion(RealmCryptoStoreMigration.CRYPTO_STORE_SCHEMA_VERSION)
-                .migration(realmCryptoStoreMigration)
+                .migration(RealmCryptoStoreMigration)
                 .build()
 
         Timber.d("Migration: copy DB to encrypted DB")
         Realm.getInstance(realmConfiguration).use {
-            // Move the DB to the new location, handled by RiotX
+            // Move the DB to the new location, handled by Matrix SDK2
             it.writeEncryptedCopyTo(File(newLocation, realmConfiguration.realmFileName), realmKeysUtils.getRealmEncryptionKey(keyAlias))
         }
     }
 
-    // Delete all the files created by Riot Android which will not be used anymore by RiotX
+    // Delete all the files created by Riot Android which will not be used anymore by Element
     private fun clearFileSystem(legacyConfig: LegacyHomeServerConnectionConfig) {
         val cryptoFolder = legacyConfig.credentials.userId.md5()
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/legacy/riot/Credentials.java b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/legacy/riot/Credentials.java
index 6844744044e562c4ba32d892f60c46e044d98ff1..0ca0c7db85f622ac132ffc9c533572c42c8bd7ff 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/legacy/riot/Credentials.java
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/legacy/riot/Credentials.java
@@ -41,7 +41,7 @@ public class Credentials {
 
     public String deviceId;
 
-    // Optional data that may contain info to override home server and/or identity server
+    // Optional data that may contain info to override homeserver and/or identity server
     public WellKnown wellKnown;
 
     public JSONObject toJson() throws JSONException {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/legacy/riot/HomeServerConnectionConfig.java b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/legacy/riot/HomeServerConnectionConfig.java
index 21d069f2957977f460a778dde8c6e37e2717755a..75fc187c45814da75484ef703817f6363101ee77 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/legacy/riot/HomeServerConnectionConfig.java
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/legacy/riot/HomeServerConnectionConfig.java
@@ -44,7 +44,7 @@ import timber.log.Timber;
  */
 public class HomeServerConnectionConfig {
 
-    // the home server URI
+    // the homeserver URI
     private Uri mHomeServerUri;
     // the jitsi server URI. Can be null
     @Nullable
@@ -82,7 +82,7 @@ public class HomeServerConnectionConfig {
     }
 
     /**
-     * Update the home server URI.
+     * Update the homeserver URI.
      *
      * @param uri the new HS uri
      */
@@ -91,7 +91,7 @@ public class HomeServerConnectionConfig {
     }
 
     /**
-     * @return the home server uri
+     * @return the homeserver uri
      */
     public Uri getHomeserverUri() {
         return mHomeServerUri;
@@ -145,7 +145,7 @@ public class HomeServerConnectionConfig {
     public void setCredentials(Credentials credentials) {
         mCredentials = credentials;
 
-        // Override home server url and/or identity server url if provided
+        // Override homeserver url and/or identity server url if provided
         if (credentials.wellKnown != null) {
             if (credentials.wellKnown.homeServer != null) {
                 String homeServerUrl = credentials.wellKnown.homeServer.baseURL;
@@ -200,7 +200,7 @@ public class HomeServerConnectionConfig {
     }
 
     /**
-     * TLS versions accepted for TLS connections with the home server.
+     * TLS versions accepted for TLS connections with the homeserver.
      */
     @Nullable
     public List<TlsVersion> getAcceptedTlsVersions() {
@@ -208,7 +208,7 @@ public class HomeServerConnectionConfig {
     }
 
     /**
-     * TLS cipher suites accepted for TLS connections with the home server.
+     * TLS cipher suites accepted for TLS connections with the homeserver.
      */
     @Nullable
     public List<CipherSuite> getAcceptedTlsCipherSuites() {
@@ -426,7 +426,7 @@ public class HomeServerConnectionConfig {
          */
         public Builder withHomeServerUri(final Uri homeServerUri) {
             if (homeServerUri == null || (!"http".equals(homeServerUri.getScheme()) && !"https".equals(homeServerUri.getScheme()))) {
-                throw new RuntimeException("Invalid home server URI: " + homeServerUri);
+                throw new RuntimeException("Invalid homeserver URI: " + homeServerUri);
             }
 
             // remove trailing /
@@ -435,7 +435,7 @@ public class HomeServerConnectionConfig {
                     String url = homeServerUri.toString();
                     mHomeServerConnectionConfig.mHomeServerUri = Uri.parse(url.substring(0, url.length() - 1));
                 } catch (Exception e) {
-                    throw new RuntimeException("Invalid home server URI: " + homeServerUri);
+                    throw new RuntimeException("Invalid homeserver URI: " + homeServerUri);
                 }
             } else {
                 mHomeServerConnectionConfig.mHomeServerUri = homeServerUri;
@@ -549,7 +549,7 @@ public class HomeServerConnectionConfig {
         }
 
         /**
-         * Add an accepted TLS version for TLS connections with the home server.
+         * Add an accepted TLS version for TLS connections with the homeserver.
          *
          * @param tlsVersion the tls version to add to the set of TLS versions accepted.
          * @return this builder
@@ -577,7 +577,7 @@ public class HomeServerConnectionConfig {
         }
 
         /**
-         * Add a TLS cipher suite to the list of accepted TLS connections with the home server.
+         * Add a TLS cipher suite to the list of accepted TLS connections with the homeserver.
          *
          * @param tlsCipherSuite the tls cipher suite to add.
          * @return this builder
@@ -666,7 +666,7 @@ public class HomeServerConnectionConfig {
         public HomeServerConnectionConfig build() {
             // Check mandatory parameters
             if (mHomeServerConnectionConfig.mHomeServerUri == null) {
-                throw new RuntimeException("Home server URI not set");
+                throw new RuntimeException("Homeserver URI not set");
             }
 
             return mHomeServerConnectionConfig;
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/legacy/riot/LoginStorage.java b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/legacy/riot/LoginStorage.java
index 516007524e5f4af2cd23f30fa98d7bb8382cffaa..2820b6688617a236a627f6709f959294ca94c90c 100755
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/legacy/riot/LoginStorage.java
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/legacy/riot/LoginStorage.java
@@ -38,7 +38,7 @@ import timber.log.Timber;
 public class LoginStorage {
     private static final String PREFS_LOGIN = "Vector.LoginStorage";
 
-    // multi accounts + home server config
+    // multi accounts + homeserver config
     private static final String PREFS_KEY_CONNECTION_CONFIGS = "PREFS_KEY_CONNECTION_CONFIGS";
 
     private final Context mContext;
@@ -49,7 +49,7 @@ public class LoginStorage {
     }
 
     /**
-     * @return the list of home server configurations.
+     * @return the list of homeserver configurations.
      */
     public List<HomeServerConnectionConfig> getCredentialsList() {
         SharedPreferences prefs = mContext.getSharedPreferences(PREFS_LOGIN, Context.MODE_PRIVATE);
@@ -85,7 +85,7 @@ public class LoginStorage {
     /**
      * Add a credentials to the credentials list
      *
-     * @param config the home server config to add.
+     * @param config the homeserver config to add.
      */
     public void addCredentials(HomeServerConnectionConfig config) {
         if (null != config && config.getCredentials() != null) {
@@ -203,4 +203,4 @@ public class LoginStorage {
         //Need to commit now because called before forcing an app restart
         editor.commit();
     }
-}
\ No newline at end of file
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/UserAgentHolder.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/UserAgentHolder.kt
index 973c120f59abd4204920352cd24b501466d20e71..1a884041280c3ca872b191587591dcba593d81d1 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/UserAgentHolder.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/UserAgentHolder.kt
@@ -36,7 +36,7 @@ internal class UserAgentHolder @Inject constructor(private val context: Context,
 
     /**
      * Create an user agent with the application version.
-     * Ex: RiotX/1.0.0 (Linux; U; Android 6.0.1; SM-A510F Build/MMB29; Flavour GPlay; MatrixAndroidSDK_X 1.0)
+     * Ex: Element/1.0.0 (Linux; U; Android 6.0.1; SM-A510F Build/MMB29; Flavour GPlay; MatrixAndroidSDK_X 1.0)
      *
      * @param flavorDescription the flavor description
      */
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/ssl/CertUtil.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/ssl/CertUtil.kt
index 9d7263f56a2db108c6835d29d53433b09563a3c9..976751446b1afad754df909b06b518adab48d28b 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/ssl/CertUtil.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/ssl/CertUtil.kt
@@ -253,7 +253,7 @@ internal object CertUtil {
         val list = ArrayList<ConnectionSpec>()
         list.add(builder.build())
         // TODO: we should display a warning if user enter an http url
-        if (hsConfig.allowHttpExtension || hsConfig.homeServerUri.toString().startsWith("http://")) {
+        if (hsConfig.allowHttpExtension || hsConfig.homeServerUriBase.toString().startsWith("http://")) {
             list.add(ConnectionSpec.CLEARTEXT)
         }
         return list
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/DefaultRawService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/DefaultRawService.kt
index 42b826de16240918edc9443a93a239c812da2d7d..bca1e498de029df928eb8b386b16316497f75ead 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/DefaultRawService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/DefaultRawService.kt
@@ -29,10 +29,9 @@ internal class DefaultRawService @Inject constructor(
         return getUrlTask.execute(GetUrlTask.Params(url, cacheStrategy))
     }
 
-    override suspend fun getWellknown(userId: String): String {
-        val homeServerDomain = userId.substringAfter(":")
+    override suspend fun getWellknown(domain: String): String {
         return getUrl(
-                "https://$homeServerDomain/.well-known/matrix/client",
+                "https://$domain/.well-known/matrix/client",
                 CacheStrategy.TtlCache(TimeUnit.HOURS.toMillis(8), false)
         )
     }
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 1f4797819892a8455d862832d00b416b25323cf9..c2bd1e24edea5dd2f9148fb72c3966283b137ae3 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
@@ -29,7 +29,7 @@ import org.matrix.android.sdk.api.pushrules.PushRuleService
 import org.matrix.android.sdk.api.session.Session
 import org.matrix.android.sdk.api.session.SessionLifecycleObserver
 import org.matrix.android.sdk.api.session.account.AccountService
-import org.matrix.android.sdk.api.session.accountdata.AccountDataService
+import org.matrix.android.sdk.api.session.accountdata.SessionAccountDataService
 import org.matrix.android.sdk.api.session.cache.CacheService
 import org.matrix.android.sdk.api.session.call.CallSignalingService
 import org.matrix.android.sdk.api.session.content.ContentUploadStateTracker
@@ -74,7 +74,6 @@ import org.matrix.android.sdk.internal.session.identity.DefaultIdentityService
 import org.matrix.android.sdk.internal.session.sync.SyncTokenStore
 import org.matrix.android.sdk.internal.session.sync.job.SyncThread
 import org.matrix.android.sdk.internal.session.sync.job.SyncWorker
-import org.matrix.android.sdk.internal.session.user.accountdata.UserAccountDataService
 import org.matrix.android.sdk.internal.util.createUIHandler
 import timber.log.Timber
 import javax.inject.Inject
@@ -118,7 +117,7 @@ internal class DefaultSession @Inject constructor(
         private val contentDownloadStateTracker: ContentDownloadStateTracker,
         private val initialSyncProgressService: Lazy<InitialSyncProgressService>,
         private val homeServerCapabilitiesService: Lazy<HomeServerCapabilitiesService>,
-        private val accountDataService: Lazy<UserAccountDataService>,
+        private val accountDataService: Lazy<SessionAccountDataService>,
         private val _sharedSecretStorageService: Lazy<SharedSecretStorageService>,
         private val accountService: Lazy<AccountService>,
         private val eventService: Lazy<EventService>,
@@ -293,7 +292,7 @@ internal class DefaultSession @Inject constructor(
 
     override fun openIdService(): OpenIdService = openIdService.get()
 
-    override fun userAccountDataService(): AccountDataService = accountDataService.get()
+    override fun accountDataService(): SessionAccountDataService = accountDataService.get()
 
     override fun getOkHttpClient(): OkHttpClient {
         return unauthenticatedWithCertificateOkHttpClient.get()
@@ -314,7 +313,7 @@ internal class DefaultSession @Inject constructor(
 
     override fun getUiaSsoFallbackUrl(authenticationSessionId: String): String {
         val hsBas = sessionParams.homeServerConnectionConfig
-                .homeServerUri
+                .homeServerUriBase
                 .toString()
                 .trim { it == '/' }
         return buildString {
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 49ce92372e2381a984ab4516fc3efea1d811021f..cb29cb4819c07ca7a2c779b5156773b5f97bfe80 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
@@ -34,7 +34,7 @@ import org.matrix.android.sdk.api.auth.data.sessionId
 import org.matrix.android.sdk.api.crypto.MXCryptoConfig
 import org.matrix.android.sdk.api.session.Session
 import org.matrix.android.sdk.api.session.SessionLifecycleObserver
-import org.matrix.android.sdk.api.session.accountdata.AccountDataService
+import org.matrix.android.sdk.api.session.accountdata.SessionAccountDataService
 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
@@ -93,7 +93,7 @@ import org.matrix.android.sdk.internal.session.room.send.queue.EventSenderProces
 import org.matrix.android.sdk.internal.session.room.tombstone.RoomTombstoneEventProcessor
 import org.matrix.android.sdk.internal.session.securestorage.DefaultSecureStorageService
 import org.matrix.android.sdk.internal.session.typing.DefaultTypingUsersTracker
-import org.matrix.android.sdk.internal.session.user.accountdata.UserAccountDataService
+import org.matrix.android.sdk.internal.session.user.accountdata.DefaultSessionAccountDataService
 import org.matrix.android.sdk.internal.session.widgets.DefaultWidgetURLFormatter
 import org.matrix.android.sdk.internal.util.md5
 import retrofit2.Retrofit
@@ -261,7 +261,7 @@ internal abstract class SessionModule {
                              sessionParams: SessionParams,
                              retrofitFactory: RetrofitFactory): Retrofit {
             return retrofitFactory
-                    .create(okHttpClient, sessionParams.homeServerConnectionConfig.homeServerUri.toString())
+                    .create(okHttpClient, sessionParams.homeServerConnectionConfig.homeServerUriBase.toString())
         }
 
         @JvmStatic
@@ -364,7 +364,7 @@ internal abstract class SessionModule {
     abstract fun bindHomeServerCapabilitiesService(service: DefaultHomeServerCapabilitiesService): HomeServerCapabilitiesService
 
     @Binds
-    abstract fun bindAccountDataService(service: UserAccountDataService): AccountDataService
+    abstract fun bindSessionAccountDataService(service: DefaultSessionAccountDataService): SessionAccountDataService
 
     @Binds
     abstract fun bindEventService(service: DefaultEventService): EventService
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 a190ff62acebd4687b8e2b47dd5bff3d57215d5c..473adeb8d268eac74e9a63ada3d52082508e4e90 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
@@ -37,7 +37,9 @@ internal class CallEventProcessor @Inject constructor(private val callSignalingH
             EventType.CALL_CANDIDATES,
             EventType.CALL_INVITE,
             EventType.CALL_HANGUP,
-            EventType.ENCRYPTED
+            EventType.ENCRYPTED,
+            EventType.CALL_ASSERTED_IDENTITY,
+            EventType.CALL_ASSERTED_IDENTITY_PREFIX
     )
 
     private val eventsToPostProcess = mutableListOf<Event>()
@@ -57,9 +59,8 @@ internal class CallEventProcessor @Inject constructor(private val callSignalingH
         return eventType == EventType.CALL_INVITE
     }
 
-    suspend fun processFastLane(event: Event) {
-        eventsToPostProcess.add(event)
-        onPostProcess()
+    fun processFastLane(event: Event) {
+        dispatchToCallSignalingHandlerIfNeeded(event)
     }
 
     override suspend fun onPostProcess() {
@@ -71,13 +72,12 @@ 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/answered) in the same event batch?
         event.roomId ?: return Unit.also {
             Timber.w("Event with no room id ${event.eventId}")
         }
         val age = now - (event.ageLocalTs ?: now)
         if (age > 40_000) {
-            // To old to ring?
+            // Too old to ring?
             return
         }
         callSignalingHandler.onCallEvent(event)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallListenersDispatcher.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallListenersDispatcher.kt
index 1de2d8a106e66beeb21fec80d3abc7d85d88e2dd..dad17f4ac933627c8190225c9206c3afbf0593f9 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallListenersDispatcher.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallListenersDispatcher.kt
@@ -20,6 +20,7 @@ import org.matrix.android.sdk.api.extensions.tryOrNull
 import org.matrix.android.sdk.api.session.call.CallListener
 import org.matrix.android.sdk.api.session.call.MxCall
 import org.matrix.android.sdk.api.session.room.model.call.CallAnswerContent
+import org.matrix.android.sdk.api.session.room.model.call.CallAssertedIdentityContent
 import org.matrix.android.sdk.api.session.room.model.call.CallCandidatesContent
 import org.matrix.android.sdk.api.session.room.model.call.CallHangupContent
 import org.matrix.android.sdk.api.session.room.model.call.CallInviteContent
@@ -64,6 +65,10 @@ internal class CallListenersDispatcher(private val listeners: Set<CallListener>)
         it.onCallNegotiateReceived(callNegotiateContent)
     }
 
+    override fun onCallAssertedIdentityReceived(callAssertedIdentityContent: CallAssertedIdentityContent) = dispatch {
+        it.onCallAssertedIdentityReceived(callAssertedIdentityContent)
+    }
+
     private fun dispatch(lambda: (CallListener) -> Unit) {
         listeners.toList().forEach {
             tryOrNull {
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 61ea660b6045313708ca783a80078f9e74bb68ee..b0901af7196fc211ba23b4f247edb5afda383000 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
@@ -23,6 +23,7 @@ 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.call.CallAnswerContent
+import org.matrix.android.sdk.api.session.room.model.call.CallAssertedIdentityContent
 import org.matrix.android.sdk.api.session.room.model.call.CallCandidatesContent
 import org.matrix.android.sdk.api.session.room.model.call.CallHangupContent
 import org.matrix.android.sdk.api.session.room.model.call.CallInviteContent
@@ -40,6 +41,7 @@ internal class CallSignalingHandler @Inject constructor(private val activeCallHa
                                                         private val mxCallFactory: MxCallFactory,
                                                         @UserId private val userId: String) {
 
+    private val invitedCallIds = mutableSetOf<String>()
     private val callListeners = mutableSetOf<CallListener>()
     private val callListenersDispatcher = CallListenersDispatcher(callListeners)
 
@@ -53,28 +55,42 @@ 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 -> {
+            EventType.CALL_SELECT_ANSWER            -> {
                 handleCallSelectAnswerEvent(event)
             }
-            EventType.CALL_NEGOTIATE     -> {
+            EventType.CALL_NEGOTIATE                -> {
                 handleCallNegotiateEvent(event)
             }
+            EventType.CALL_ASSERTED_IDENTITY,
+            EventType.CALL_ASSERTED_IDENTITY_PREFIX -> {
+                handleCallAssertedIdentityEvent(event)
+            }
+        }
+    }
+
+    private fun handleCallAssertedIdentityEvent(event: Event) {
+        val content = event.getClearContent().toModel<CallAssertedIdentityContent>() ?: return
+        val call = content.getCall() ?: return
+        if (call.ourPartyId == content.partyId) {
+            // Ignore remote echo (not that we send asserted identity, but still...)
+            return
         }
+        callListenersDispatcher.onCallAssertedIdentityReceived(content)
     }
 
     private fun handleCallNegotiateEvent(event: Event) {
@@ -167,17 +183,17 @@ internal class CallSignalingHandler @Inject constructor(private val activeCallHa
         val content = event.getClearContent().toModel<CallInviteContent>() ?: return
 
         content.callId ?: return
-        if (activeCallHandler.getCallWithId(content.callId) != null) {
+        if (invitedCallIds.contains(content.callId)) {
             // 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,
                 content = content
         ) ?: return
+        invitedCallIds.add(content.callId)
         activeCallHandler.addCall(incomingCall)
         callListenersDispatcher.onCallInviteReceived(incomingCall, content)
     }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/cleanup/CleanupSession.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/cleanup/CleanupSession.kt
index b5c1e6a282742813f47dbadb6b5f13fd966c74aa..8870d92a35795d7f8e5a4a6bee104eb151a31744 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/cleanup/CleanupSession.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/cleanup/CleanupSession.kt
@@ -51,15 +51,12 @@ internal class CleanupSession @Inject constructor(
         @UserMd5 private val userMd5: String
 ) {
     suspend fun handle() {
-        Timber.d("Cleanup: release session...")
-        sessionManager.releaseSession(sessionId)
+        Timber.d("Cleanup: delete session params...")
+        sessionParamsStore.delete(sessionId)
 
         Timber.d("Cleanup: cancel pending works...")
         workManagerProvider.cancelAllWorks()
 
-        Timber.d("Cleanup: delete session params...")
-        sessionParamsStore.delete(sessionId)
-
         Timber.d("Cleanup: clear session data...")
         clearSessionDataTask.execute(Unit)
 
@@ -74,6 +71,9 @@ internal class CleanupSession @Inject constructor(
         realmKeysUtils.clear(SessionModule.getKeyAlias(userMd5))
         realmKeysUtils.clear(CryptoModule.getKeyAlias(userMd5))
 
+        Timber.d("Cleanup: release session...")
+        sessionManager.releaseSession(sessionId)
+
         // Sanity check
         if (BuildConfig.DEBUG) {
             Realm.getGlobalInstanceCount(realmSessionConfiguration)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/DefaultContentUrlResolver.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/DefaultContentUrlResolver.kt
index f765056496f4fe020678ce8cbf6bbf131617035f..e4efdaa254f2909437eb7050170d1f9b45925eea 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/DefaultContentUrlResolver.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/DefaultContentUrlResolver.kt
@@ -26,7 +26,7 @@ private const val MATRIX_CONTENT_URI_SCHEME = "mxc://"
 
 internal class DefaultContentUrlResolver @Inject constructor(homeServerConnectionConfig: HomeServerConnectionConfig) : ContentUrlResolver {
 
-    private val baseUrl = homeServerConnectionConfig.homeServerUri.toString().ensureTrailingSlash()
+    private val baseUrl = homeServerConnectionConfig.homeServerUriBase.toString().ensureTrailingSlash()
 
     override val uploadUrl = baseUrl + NetworkConstants.URI_API_MEDIA_PREFIX_PATH_R0 + "upload"
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/UploadContentWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/UploadContentWorker.kt
index 237411db53d72f614b887eb30af833a5a000706e..f14c85cf804c9f06122947f014cc777ab5ee7ac6 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/UploadContentWorker.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/UploadContentWorker.kt
@@ -234,7 +234,7 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter
                             .also { filesToDelete.add(it) }
 
                     uploadedFileEncryptedFileInfo =
-                            MXEncryptedAttachments.encrypt(fileToUpload.inputStream(), attachment.getSafeMimeType(), encryptedFile) { read, total ->
+                            MXEncryptedAttachments.encrypt(fileToUpload.inputStream(), encryptedFile) { read, total ->
                                 notifyTracker(params) {
                                     contentUploadStateTracker.setEncrypting(it, read.toLong(), total.toLong())
                                 }
@@ -315,7 +315,7 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter
                         if (params.isEncrypted) {
                             Timber.v("Encrypt thumbnail")
                             notifyTracker(params) { contentUploadStateTracker.setEncryptingThumbnail(it) }
-                            val encryptionResult = MXEncryptedAttachments.encryptAttachment(thumbnailData.bytes.inputStream(), thumbnailData.mimeType)
+                            val encryptionResult = MXEncryptedAttachments.encryptAttachment(thumbnailData.bytes.inputStream())
                             val contentUploadResponse = fileUploader.uploadByteArray(
                                     byteArray = encryptionResult.encryptedByteArray,
                                     filename = null,
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
index d7e9ef2ee04b1801ee883e3b987a055a2484e988..9d80f27e0196eca61d2f04a1546b7911e0abb0b4 100644
--- 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
@@ -29,7 +29,7 @@ internal class DefaultEventService @Inject constructor(
 
     override suspend fun getEvent(roomId: String, eventId: String): Event {
         val event = getEventTask.execute(GetEventTask.Params(roomId, eventId))
-
+        event.ageLocalTs = event.unsignedData?.age?.let { System.currentTimeMillis() - it }
         // Fast lane to the call event processors: try to make the incoming call ring faster
         if (callEventProcessor.shouldProcessFastLane(event.getClearType())) {
             callEventProcessor.processFastLane(event)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/DefaultHomeServerCapabilitiesService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/DefaultHomeServerCapabilitiesService.kt
index 0ed690d972e829f85798daa7b27f8efc84e85e57..4c755b54b514cf8f8fd2d50c412e2b5439114814 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/DefaultHomeServerCapabilitiesService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/DefaultHomeServerCapabilitiesService.kt
@@ -16,18 +16,12 @@
 
 package org.matrix.android.sdk.internal.session.homeserver
 
-import com.zhuinden.monarchy.Monarchy
-import io.realm.Realm
 import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities
 import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilitiesService
-import org.matrix.android.sdk.internal.database.mapper.HomeServerCapabilitiesMapper
-import org.matrix.android.sdk.internal.database.model.HomeServerCapabilitiesEntity
-import org.matrix.android.sdk.internal.database.query.get
-import org.matrix.android.sdk.internal.di.SessionDatabase
 import javax.inject.Inject
 
 internal class DefaultHomeServerCapabilitiesService @Inject constructor(
-        @SessionDatabase private val monarchy: Monarchy,
+        private val homeServerCapabilitiesDataSource: HomeServerCapabilitiesDataSource,
         private val getHomeServerCapabilitiesTask: GetHomeServerCapabilitiesTask
 ) : HomeServerCapabilitiesService {
 
@@ -36,11 +30,7 @@ internal class DefaultHomeServerCapabilitiesService @Inject constructor(
     }
 
     override fun getHomeServerCapabilities(): HomeServerCapabilities {
-        return Realm.getInstance(monarchy.realmConfiguration).use { realm ->
-            HomeServerCapabilitiesEntity.get(realm)?.let {
-                HomeServerCapabilitiesMapper.map(it)
-            }
-        }
+        return homeServerCapabilitiesDataSource.getHomeServerCapabilities()
                 ?: HomeServerCapabilities()
     }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetCapabilitiesResult.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetCapabilitiesResult.kt
index ab029a0fce013e4a8a0eb8de1516766c6bb6e07f..c4bc09a233b8237ebd29e26ff1182d1635f3b34e 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetCapabilitiesResult.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetCapabilitiesResult.kt
@@ -19,6 +19,7 @@ package org.matrix.android.sdk.internal.session.homeserver
 import com.squareup.moshi.Json
 import com.squareup.moshi.JsonClass
 import org.matrix.android.sdk.api.extensions.orTrue
+import org.matrix.android.sdk.api.util.JsonDict
 
 /**
  * Ref: https://matrix.org/docs/spec/client_server/latest#get-matrix-client-r0-capabilities
@@ -38,9 +39,14 @@ internal data class Capabilities(
          * Capability to indicate if the user can change their password.
          */
         @Json(name = "m.change_password")
-        val changePassword: ChangePassword? = null
+        val changePassword: ChangePassword? = null,
 
-        // No need for m.room_versions for the moment
+        /**
+         * This capability describes the default and available room versions a server supports, and at what level of stability.
+         * Clients should make use of this capability to determine if users need to be encouraged to upgrade their rooms.
+         */
+        @Json(name = "m.room_versions")
+        val roomVersions: RoomVersions? = null
 )
 
 @JsonClass(generateAdapter = true)
@@ -52,6 +58,21 @@ internal data class ChangePassword(
         val enabled: Boolean?
 )
 
+@JsonClass(generateAdapter = true)
+internal data class RoomVersions(
+        /**
+         * Required. The default room version the server is using for new rooms.
+         */
+        @Json(name = "default")
+        val default: String?,
+
+        /**
+         * Required. A detailed description of the room versions the server supports.
+         */
+        @Json(name = "available")
+        val available: JsonDict
+)
+
 // The spec says: If not present, the client should assume that password changes are possible via the API
 internal fun GetCapabilitiesResult.canChangePassword(): Boolean {
     return capabilities?.changePassword?.enabled.orTrue()
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 740370123f877d92340e322b6a50c83eb4dd0803..612b98f8638d50cda4273328fea4d21a07061e17 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetHomeServerCapabilitiesTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetHomeServerCapabilitiesTask.kt
@@ -17,6 +17,7 @@
 package org.matrix.android.sdk.internal.session.homeserver
 
 import com.zhuinden.monarchy.Monarchy
+import org.matrix.android.sdk.api.MatrixPatterns.getDomain
 import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
 import org.matrix.android.sdk.api.auth.wellknown.WellknownResult
 import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities
@@ -24,6 +25,7 @@ import org.matrix.android.sdk.internal.auth.version.Versions
 import org.matrix.android.sdk.internal.auth.version.isLoginAndRegistrationSupportedBySdk
 import org.matrix.android.sdk.internal.database.model.HomeServerCapabilitiesEntity
 import org.matrix.android.sdk.internal.database.query.getOrCreate
+import org.matrix.android.sdk.internal.di.MoshiProvider
 import org.matrix.android.sdk.internal.di.SessionDatabase
 import org.matrix.android.sdk.internal.di.UserId
 import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
@@ -89,7 +91,10 @@ internal class DefaultGetHomeServerCapabilitiesTask @Inject constructor(
         }.getOrNull()
 
         val wellknownResult = runCatching {
-            getWellknownTask.execute(GetWellknownTask.Params(userId, homeServerConnectionConfig))
+            getWellknownTask.execute(GetWellknownTask.Params(
+                    domain = userId.getDomain(),
+                    homeServerConnectionConfig = homeServerConnectionConfig
+            ))
         }.getOrNull()
 
         insertInDb(capabilities, mediaConfig, versions, wellknownResult)
@@ -104,6 +109,10 @@ internal class DefaultGetHomeServerCapabilitiesTask @Inject constructor(
 
             if (getCapabilitiesResult != null) {
                 homeServerCapabilitiesEntity.canChangePassword = getCapabilitiesResult.canChangePassword()
+
+                homeServerCapabilitiesEntity.roomVersionsJson = getCapabilitiesResult.capabilities?.roomVersions?.let {
+                    MoshiProvider.providesMoshi().adapter(RoomVersions::class.java).toJson(it)
+                }
             }
 
             if (getMediaConfigResult != null) {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/HomeServerCapabilitiesDataSource.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/HomeServerCapabilitiesDataSource.kt
new file mode 100644
index 0000000000000000000000000000000000000000..6c913fa41ee6b16feb8e9e0da0ed73f294cb422b
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/HomeServerCapabilitiesDataSource.kt
@@ -0,0 +1,38 @@
+/*
+ * 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.homeserver
+
+import com.zhuinden.monarchy.Monarchy
+import io.realm.Realm
+import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities
+import org.matrix.android.sdk.internal.database.mapper.HomeServerCapabilitiesMapper
+import org.matrix.android.sdk.internal.database.model.HomeServerCapabilitiesEntity
+import org.matrix.android.sdk.internal.database.query.get
+import org.matrix.android.sdk.internal.di.SessionDatabase
+import javax.inject.Inject
+
+internal class HomeServerCapabilitiesDataSource @Inject constructor(
+        @SessionDatabase private val monarchy: Monarchy
+) {
+    fun getHomeServerCapabilities(): HomeServerCapabilities? {
+        return Realm.getInstance(monarchy.realmConfiguration).use { realm ->
+            HomeServerCapabilitiesEntity.get(realm)?.let {
+                HomeServerCapabilitiesMapper.map(it)
+            }
+        }
+    }
+}
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 4f88d8eb9507b1ca1eccd9cea549a90d74f8efe6..48870b86b79cb46bb968ee3c7af36e8871a633bc 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
@@ -191,7 +191,7 @@ internal class DefaultIdentityService @Inject constructor(
         } 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
+            // 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") }
 
@@ -241,7 +241,7 @@ internal class DefaultIdentityService @Inject constructor(
 
     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
+        // to the homeserver, and not emails and phone numbers from the contact book of the user
 
         if (threePids.isEmpty()) {
             return emptyMap()
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 9d990d4d8f9340a91bbb07dd8a792c8eb216a4ef..f77eb296aa9e48438206662fe1dee09d85718e77 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
@@ -42,7 +42,7 @@ internal interface IdentityAuthAPI {
     suspend fun ping()
 
     /**
-     * Ping v1 will be used to check outdated Identity server
+     * Ping v1 will be used to check outdated identity server
      */
     @GET("_matrix/identity/api/v1")
     suspend fun pingV1()
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityModule.kt
index 7a39a333a59bd7b039ddf51915c807d5993f12e8..4d664b76bed8578403731bd209a13f41ee1f3946 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityModule.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityModule.kt
@@ -60,7 +60,6 @@ internal abstract class IdentityModule {
         @SessionScope
         fun providesIdentityRealmConfiguration(realmKeysUtils: RealmKeysUtils,
                                                @SessionFilesDirectory directory: File,
-                                               migration: RealmIdentityStoreMigration,
                                                @UserMd5 userMd5: String): RealmConfiguration {
             return RealmConfiguration.Builder()
                     .directory(directory)
@@ -69,7 +68,7 @@ internal abstract class IdentityModule {
                         realmKeysUtils.configureEncryption(this, SessionModule.getKeyAlias(userMd5))
                     }
                     .schemaVersion(RealmIdentityStoreMigration.IDENTITY_STORE_SCHEMA_VERSION)
-                    .migration(migration)
+                    .migration(RealmIdentityStoreMigration)
                     .allowWritesOnUiThread(true)
                     .modules(IdentityRealmModule())
                     .build()
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/db/RealmIdentityStoreMigration.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/db/RealmIdentityStoreMigration.kt
index 6081dbab12e5a59fa77e667dec09f35c2cca1770..21c0f8eb9e6cf64c8bb7af563ad7bced77157d67 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/db/RealmIdentityStoreMigration.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/db/RealmIdentityStoreMigration.kt
@@ -19,13 +19,10 @@ package org.matrix.android.sdk.internal.session.identity.db
 import io.realm.DynamicRealm
 import io.realm.RealmMigration
 import timber.log.Timber
-import javax.inject.Inject
 
-internal class RealmIdentityStoreMigration @Inject constructor() : RealmMigration {
+internal object RealmIdentityStoreMigration : RealmMigration {
 
-    companion object {
-        const val IDENTITY_STORE_SCHEMA_VERSION = 1L
-    }
+    const val IDENTITY_STORE_SCHEMA_VERSION = 1L
 
     override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) {
         Timber.v("Migrating Realm Identity from $oldVersion to $newVersion")
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/integrationmanager/IntegrationManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/integrationmanager/IntegrationManager.kt
index f79f8084a8e501d0f4a8bb1908d481ef18a9896e..b654b8610def5f3cce043fd7f4002e32fc6d4f82 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/integrationmanager/IntegrationManager.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/integrationmanager/IntegrationManager.kt
@@ -33,7 +33,7 @@ import org.matrix.android.sdk.internal.extensions.observeNotNull
 import org.matrix.android.sdk.api.session.SessionLifecycleObserver
 import org.matrix.android.sdk.internal.session.SessionScope
 import org.matrix.android.sdk.api.session.accountdata.UserAccountDataTypes
-import org.matrix.android.sdk.api.session.accountdata.AccountDataEvent
+import org.matrix.android.sdk.api.session.accountdata.UserAccountDataEvent
 import org.matrix.android.sdk.internal.session.user.accountdata.UserAccountDataDataSource
 import org.matrix.android.sdk.internal.session.user.accountdata.UpdateUserAccountDataTask
 import org.matrix.android.sdk.internal.session.widgets.helper.WidgetFactory
@@ -43,8 +43,8 @@ import javax.inject.Inject
 
 /**
  * The integration manager allows to
- *  - Get the Integration Manager that a user has explicitly set for its account (via account data)
- *  - Get the recommended/preferred Integration Manager list as defined by the HomeServer (via wellknown)
+ *  - Get the integration manager that a user has explicitly set for its account (via account data)
+ *  - Get the recommended/preferred integration manager list as defined by the homeserver (via wellknown)
  *  - Check if the user has disabled the integration manager feature
  *  - Allow / Disallow Integration manager (propagated to other riot clients)
  *
@@ -240,7 +240,7 @@ internal class IntegrationManager @Inject constructor(matrixConfiguration: Matri
         )
     }
 
-    private fun AccountDataEvent.asIntegrationManagerWidgetContent(): WidgetContent? {
+    private fun UserAccountDataEvent.asIntegrationManagerWidgetContent(): WidgetContent? {
         return extractWidgetSequence(widgetFactory)
                 .filter {
                     WidgetType.IntegrationManager == it.type
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/ViaParameterFinder.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/ViaParameterFinder.kt
index 72fbfcced5ee6f6dbf608d623bebb8f238380a32..82565d8118612d63dcc92cb67a5fe98f5dca60f9 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/ViaParameterFinder.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/ViaParameterFinder.kt
@@ -16,6 +16,7 @@
 
 package org.matrix.android.sdk.internal.session.permalinks
 
+import org.matrix.android.sdk.api.MatrixPatterns.getDomain
 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.internal.di.UserId
@@ -47,9 +48,9 @@ internal class ViaParameterFinder @Inject constructor(
     }
 
     fun computeViaParams(userId: String, roomId: String, max: Int): List<String> {
-        val userHomeserver = userId.substringAfter(":")
+        val userHomeserver = userId.getDomain()
         return getUserIdsOfJoinedMembers(roomId)
-                .map { it.substringAfter(":") }
+                .map { it.getDomain() }
                 .groupBy { it }
                 .mapValues { it.value.size }
                 .toMutableMap()
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 485a4973cab0f838e566ff57f3ee4092bd0ad769..4b56db9f1330cf999d5bcf6715c5bcbb1820d268 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
@@ -86,7 +86,7 @@ internal interface ProfileAPI {
     suspend fun addMsisdn(@Body body: AddMsisdnBody): AddMsisdnResponse
 
     /**
-     * Validate Msisdn code (same model than for Identity server API)
+     * Validate Msisdn code (same model than for identity server API)
      */
     @POST
     suspend fun validateMsisdn(@Url url: String,
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 5a2eef7e8a3dbe5fa23a93cfb9bea6a29026f95d..8afd690f642d82fcf62dcf175dfdd11f90b57d15 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,10 +17,10 @@
 package org.matrix.android.sdk.internal.session.room
 
 import androidx.lifecycle.LiveData
-import org.matrix.android.sdk.api.session.accountdata.AccountDataService
 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
+import org.matrix.android.sdk.api.session.room.accountdata.RoomAccountDataService
 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.members.MembershipService
@@ -37,12 +37,12 @@ import org.matrix.android.sdk.api.session.room.tags.TagsService
 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.room.version.RoomVersionService
 import org.matrix.android.sdk.api.session.search.SearchResult
 import org.matrix.android.sdk.api.session.space.Space
 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.permalinks.ViaParameterFinder
-import org.matrix.android.sdk.internal.session.room.accountdata.RoomAccountDataService
 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
@@ -68,9 +68,11 @@ internal class DefaultRoom(override val roomId: String,
                            private val roomMembersService: MembershipService,
                            private val roomPushRuleService: RoomPushRuleService,
                            private val roomAccountDataService: RoomAccountDataService,
+                           private val roomVersionService: RoomVersionService,
                            private val sendStateTask: SendStateTask,
                            private val viaParameterFinder: ViaParameterFinder,
-                           private val searchTask: SearchTask) :
+                           private val searchTask: SearchTask
+) :
         Room,
         TimelineService by timelineService,
         SendService by sendService,
@@ -86,7 +88,8 @@ internal class DefaultRoom(override val roomId: String,
         RelationService by relationService,
         MembershipService by roomMembersService,
         RoomPushRuleService by roomPushRuleService,
-        AccountDataService by roomAccountDataService {
+        RoomAccountDataService by roomAccountDataService,
+        RoomVersionService by roomVersionService {
 
     override fun getRoomSummaryLive(): LiveData<Optional<RoomSummary>> {
         return roomSummaryDataSource.getRoomSummaryLive(roomId)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomDirectoryService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomDirectoryService.kt
index 218d846afb5cf3e9452826f7512c02177093eec7..7330c91c202d0f088f87b7bbcaf0217e98369292 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomDirectoryService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomDirectoryService.kt
@@ -16,10 +16,13 @@
 
 package org.matrix.android.sdk.internal.session.room
 
+import org.matrix.android.sdk.api.session.room.AliasAvailabilityResult
 import org.matrix.android.sdk.api.session.room.RoomDirectoryService
+import org.matrix.android.sdk.api.session.room.alias.RoomAliasError
 import org.matrix.android.sdk.api.session.room.model.RoomDirectoryVisibility
 import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoomsParams
 import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoomsResponse
+import org.matrix.android.sdk.internal.session.room.alias.RoomAliasAvailabilityChecker
 import org.matrix.android.sdk.internal.session.room.directory.GetPublicRoomTask
 import org.matrix.android.sdk.internal.session.room.directory.GetRoomDirectoryVisibilityTask
 import org.matrix.android.sdk.internal.session.room.directory.SetRoomDirectoryVisibilityTask
@@ -28,7 +31,8 @@ import javax.inject.Inject
 internal class DefaultRoomDirectoryService @Inject constructor(
         private val getPublicRoomTask: GetPublicRoomTask,
         private val getRoomDirectoryVisibilityTask: GetRoomDirectoryVisibilityTask,
-        private val setRoomDirectoryVisibilityTask: SetRoomDirectoryVisibilityTask
+        private val setRoomDirectoryVisibilityTask: SetRoomDirectoryVisibilityTask,
+        private val roomAliasAvailabilityChecker: RoomAliasAvailabilityChecker
 ) : RoomDirectoryService {
 
     override suspend fun getPublicRooms(server: String?,
@@ -43,4 +47,13 @@ internal class DefaultRoomDirectoryService @Inject constructor(
     override suspend fun setRoomDirectoryVisibility(roomId: String, roomDirectoryVisibility: RoomDirectoryVisibility) {
         setRoomDirectoryVisibilityTask.execute(SetRoomDirectoryVisibilityTask.Params(roomId, roomDirectoryVisibility))
     }
+
+    override suspend fun checkAliasAvailability(aliasLocalPart: String?): AliasAvailabilityResult {
+        return try {
+            roomAliasAvailabilityChecker.check(aliasLocalPart)
+            AliasAvailabilityResult.Available
+        } catch (failure: RoomAliasError) {
+            AliasAvailabilityResult.NotAvailable(failure)
+        }
+    }
 }
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 d9fe1288e29d0a538321ed390d9125773d3945e2..632ea4c45042ec2fa8e0b3c2c72c784ba8aa5772 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
@@ -134,6 +134,10 @@ internal class DefaultRoomService @Inject constructor(
         deleteRoomAliasTask.execute(DeleteRoomAliasTask.Params(roomAlias))
     }
 
+    override fun getChangeMemberships(roomIdOrAlias: String): ChangeMembershipState {
+        return roomChangeMembershipStateDataSource.getState(roomIdOrAlias)
+    }
+
     override fun getChangeMembershipsLive(): LiveData<Map<String, ChangeMembershipState>> {
         return roomChangeMembershipStateDataSource.getLiveStates()
     }
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 4f1260403938071011519252c458d94d53a2323e..18ece6062913c63e7edae097b36f1d55cd064539 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
@@ -369,4 +369,15 @@ internal interface RoomAPI {
                                    @Path("roomId") roomId: String,
                                    @Path("type") type: String,
                                    @Body content: JsonDict)
+
+    /**
+     * Upgrades the given room to a particular room version.
+     * Errors:
+     * 400, The request was invalid. One way this can happen is if the room version requested is not supported by the homeserver
+     * (M_UNSUPPORTED_ROOM_VERSION)
+     * 403: The user is not permitted to upgrade the room.(M_FORBIDDEN)
+     */
+    @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/upgrade")
+    suspend fun upgradeRoom(@Path("roomId") roomId: String,
+                            @Body body: RoomUpgradeBody): RoomUpgradeResponse
 }
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 8efbf2360a27ad1bfed07660816ccffcb1d8c60a..d44eb32529b3954fb8d66938aaf15299550a30f2 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
@@ -19,8 +19,8 @@ package org.matrix.android.sdk.internal.session.room
 import org.matrix.android.sdk.api.session.crypto.CryptoService
 import org.matrix.android.sdk.api.session.room.Room
 import org.matrix.android.sdk.internal.session.SessionScope
-import org.matrix.android.sdk.internal.session.room.accountdata.RoomAccountDataService
 import org.matrix.android.sdk.internal.session.permalinks.ViaParameterFinder
+import org.matrix.android.sdk.internal.session.room.accountdata.DefaultRoomAccountDataService
 import org.matrix.android.sdk.internal.session.room.alias.DefaultAliasService
 import org.matrix.android.sdk.internal.session.room.call.DefaultRoomCallService
 import org.matrix.android.sdk.internal.session.room.draft.DefaultDraftService
@@ -37,6 +37,7 @@ import org.matrix.android.sdk.internal.session.room.tags.DefaultTagsService
 import org.matrix.android.sdk.internal.session.room.timeline.DefaultTimelineService
 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.room.version.DefaultRoomVersionService
 import org.matrix.android.sdk.internal.session.search.SearchTask
 import javax.inject.Inject
 
@@ -61,7 +62,8 @@ 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 roomAccountDataServiceFactory: RoomAccountDataService.Factory,
+                                                      private val roomVersionServiceFactory: DefaultRoomVersionService.Factory,
+                                                      private val roomAccountDataServiceFactory: DefaultRoomAccountDataService.Factory,
                                                       private val sendStateTask: SendStateTask,
                                                       private val viaParameterFinder: ViaParameterFinder,
                                                       private val searchTask: SearchTask) :
@@ -87,6 +89,7 @@ internal class DefaultRoomFactory @Inject constructor(private val cryptoService:
                 roomMembersService = membershipServiceFactory.create(roomId),
                 roomPushRuleService = roomPushRuleServiceFactory.create(roomId),
                 roomAccountDataService = roomAccountDataServiceFactory.create(roomId),
+                roomVersionService = roomVersionServiceFactory.create(roomId),
                 sendStateTask = sendStateTask,
                 searchTask = searchTask,
                 viaParameterFinder = viaParameterFinder
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 d88c1950560d3928a682b70f8694d165fcd4eefc..c04c899e18f2c01cbb4be11d387ac4419c0e3fb1 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
@@ -92,6 +92,8 @@ import org.matrix.android.sdk.internal.session.room.typing.DefaultSendTypingTask
 import org.matrix.android.sdk.internal.session.room.typing.SendTypingTask
 import org.matrix.android.sdk.internal.session.room.uploads.DefaultGetUploadsTask
 import org.matrix.android.sdk.internal.session.room.uploads.GetUploadsTask
+import org.matrix.android.sdk.internal.session.room.version.DefaultRoomVersionUpgradeTask
+import org.matrix.android.sdk.internal.session.room.version.RoomVersionUpgradeTask
 import org.matrix.android.sdk.internal.session.space.DefaultSpaceService
 import retrofit2.Retrofit
 
@@ -243,4 +245,7 @@ internal abstract class RoomModule {
 
     @Binds
     abstract fun bindGetEventTask(task: DefaultGetEventTask): GetEventTask
+
+    @Binds
+    abstract fun bindRoomVersionUpgradeTask(task: DefaultRoomVersionUpgradeTask): RoomVersionUpgradeTask
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomUpgradeBody.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomUpgradeBody.kt
new file mode 100644
index 0000000000000000000000000000000000000000..4629f6e409751a6108e93b172480eeb251d8d030
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomUpgradeBody.kt
@@ -0,0 +1,26 @@
+/*
+ * 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.session.room
+
+import com.squareup.moshi.Json
+import com.squareup.moshi.JsonClass
+
+@JsonClass(generateAdapter = true)
+internal data class RoomUpgradeBody(
+        @Json(name = "new_version")
+        val newVersion: String
+)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomUpgradeResponse.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomUpgradeResponse.kt
new file mode 100644
index 0000000000000000000000000000000000000000..1cca2c572bb794ea69fe45063ccc3d0365a49d3c
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomUpgradeResponse.kt
@@ -0,0 +1,26 @@
+/*
+ * 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.session.room
+
+import com.squareup.moshi.Json
+import com.squareup.moshi.JsonClass
+
+@JsonClass(generateAdapter = true)
+internal data class RoomUpgradeResponse(
+        @Json(name = "replacement_room")
+        val replacementRoomId: String
+)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/accountdata/RoomAccountDataService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/accountdata/DefaultRoomAccountDataService.kt
similarity index 72%
rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/accountdata/RoomAccountDataService.kt
rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/accountdata/DefaultRoomAccountDataService.kt
index 9e9e9dc322a4534c6d2b651f1f36a6f303a84a54..caeeb3bf5375aeea24618a76028edbe7a61a1d23 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/accountdata/RoomAccountDataService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/accountdata/DefaultRoomAccountDataService.kt
@@ -20,34 +20,34 @@ import androidx.lifecycle.LiveData
 import dagger.assisted.Assisted
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
-import org.matrix.android.sdk.api.session.accountdata.AccountDataEvent
-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.session.room.accountdata.RoomAccountDataEvent
+import org.matrix.android.sdk.api.session.room.accountdata.RoomAccountDataService
 import org.matrix.android.sdk.api.util.Optional
 
-internal class RoomAccountDataService @AssistedInject constructor(@Assisted private val roomId: String,
-                                                                  private val dataSource: RoomAccountDataDataSource,
-                                                                  private val updateRoomAccountDataTask: UpdateRoomAccountDataTask
-) : AccountDataService {
+internal class DefaultRoomAccountDataService @AssistedInject constructor(@Assisted private val roomId: String,
+                                                                         private val dataSource: RoomAccountDataDataSource,
+                                                                         private val updateRoomAccountDataTask: UpdateRoomAccountDataTask
+) : RoomAccountDataService {
 
     @AssistedFactory
     interface Factory {
-        fun create(roomId: String): RoomAccountDataService
+        fun create(roomId: String): DefaultRoomAccountDataService
     }
 
-    override fun getAccountDataEvent(type: String): AccountDataEvent? {
+    override fun getAccountDataEvent(type: String): RoomAccountDataEvent? {
         return dataSource.getAccountDataEvent(roomId, type)
     }
 
-    override fun getLiveAccountDataEvent(type: String): LiveData<Optional<AccountDataEvent>> {
+    override fun getLiveAccountDataEvent(type: String): LiveData<Optional<RoomAccountDataEvent>> {
         return dataSource.getLiveAccountDataEvent(roomId, type)
     }
 
-    override fun getAccountDataEvents(types: Set<String>): List<AccountDataEvent> {
+    override fun getAccountDataEvents(types: Set<String>): List<RoomAccountDataEvent> {
         return dataSource.getAccountDataEvents(roomId, types)
     }
 
-    override fun getLiveAccountDataEvents(types: Set<String>): LiveData<List<AccountDataEvent>> {
+    override fun getLiveAccountDataEvents(types: Set<String>): LiveData<List<RoomAccountDataEvent>> {
         return dataSource.getLiveAccountDataEvents(roomId, types)
     }
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/accountdata/RoomAccountDataDataSource.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/accountdata/RoomAccountDataDataSource.kt
index 0bcf9d7f386088e3c0624cfc3d712e4b7cbdc3a0..0e4493846c17072ce5b90def8405c81148364df5 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/accountdata/RoomAccountDataDataSource.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/accountdata/RoomAccountDataDataSource.kt
@@ -20,14 +20,16 @@ import androidx.lifecycle.LiveData
 import androidx.lifecycle.MediatorLiveData
 import androidx.lifecycle.Transformations
 import com.zhuinden.monarchy.Monarchy
-import org.matrix.android.sdk.api.session.accountdata.AccountDataEvent
+import io.realm.Realm
+import io.realm.RealmQuery
+import org.matrix.android.sdk.api.session.room.accountdata.RoomAccountDataEvent
 import org.matrix.android.sdk.api.util.Optional
 import org.matrix.android.sdk.api.util.toOptional
 import org.matrix.android.sdk.internal.database.RealmSessionProvider
 import org.matrix.android.sdk.internal.database.mapper.AccountDataMapper
 import org.matrix.android.sdk.internal.database.model.RoomAccountDataEntityFields
 import org.matrix.android.sdk.internal.database.model.RoomEntity
-import org.matrix.android.sdk.internal.database.query.where
+import org.matrix.android.sdk.internal.database.model.RoomEntityFields
 import org.matrix.android.sdk.internal.di.SessionDatabase
 import javax.inject.Inject
 
@@ -35,43 +37,62 @@ internal class RoomAccountDataDataSource @Inject constructor(@SessionDatabase pr
                                                              private val realmSessionProvider: RealmSessionProvider,
                                                              private val accountDataMapper: AccountDataMapper) {
 
-    fun getAccountDataEvent(roomId: String, type: String): AccountDataEvent? {
+    fun getAccountDataEvent(roomId: String, type: String): RoomAccountDataEvent? {
         return getAccountDataEvents(roomId, setOf(type)).firstOrNull()
     }
 
-    fun getLiveAccountDataEvent(roomId: String, type: String): LiveData<Optional<AccountDataEvent>> {
+    fun getLiveAccountDataEvent(roomId: String, type: String): LiveData<Optional<RoomAccountDataEvent>> {
         return Transformations.map(getLiveAccountDataEvents(roomId, setOf(type))) {
             it.firstOrNull()?.toOptional()
         }
     }
 
-    fun getAccountDataEvents(roomId: String, types: Set<String>): List<AccountDataEvent> {
+    /**
+     * @param roomId the roomId to search for account data event. If null will check in every room.
+     * @param types the types to filter. If empty will return all account data event in given room (or every room if roomId is null)
+     *
+     */
+    fun getAccountDataEvents(roomId: String?, types: Set<String>): List<RoomAccountDataEvent> {
         return realmSessionProvider.withRealm { realm ->
-            val roomEntity = RoomEntity.where(realm, roomId).findFirst() ?: return@withRealm emptyList()
+            val roomEntity = buildRoomQuery(realm, roomId, types).findFirst() ?: return@withRealm emptyList()
             roomEntity.accountDataEvents(types)
         }
     }
 
-    fun getLiveAccountDataEvents(roomId: String, types: Set<String>): LiveData<List<AccountDataEvent>> {
-        val liveRoomEntity = monarchy.findAllManagedWithChanges { RoomEntity.where(it, roomId) }
-        val resultLiveData = MediatorLiveData<List<AccountDataEvent>>()
-        resultLiveData.addSource(liveRoomEntity) {
-            val roomEntity = it.realmResults.firstOrNull()
-            if (roomEntity == null) {
-                resultLiveData.postValue(emptyList())
-            } else {
-                val mappedResult = roomEntity.accountDataEvents(types)
-                resultLiveData.postValue(mappedResult)
-            }
+    /**
+     * @param roomId the roomId to search for account data event. If null will check in every room.
+     * @param types the types to filter. If empty will return all account data event in the given room (or every room if roomId is null).
+     *
+     */
+    fun getLiveAccountDataEvents(roomId: String?, types: Set<String>): LiveData<List<RoomAccountDataEvent>> {
+        val liveRoomEntity = monarchy.findAllManagedWithChanges {
+            buildRoomQuery(it, roomId, types)
+        }
+        val resultLiveData = MediatorLiveData<List<RoomAccountDataEvent>>()
+        resultLiveData.addSource(liveRoomEntity) { changeSet ->
+            val mappedResult = changeSet.realmResults.flatMap { it.accountDataEvents(types) }
+            resultLiveData.postValue(mappedResult)
         }
         return resultLiveData
     }
 
-    private fun RoomEntity.accountDataEvents(types: Set<String>): List<AccountDataEvent> {
+    private fun buildRoomQuery(realm: Realm, roomId: String?, types: Set<String>): RealmQuery<RoomEntity> {
+        val query = realm.where(RoomEntity::class.java)
+        if (roomId != null) {
+            query.equalTo(RoomEntityFields.ROOM_ID, roomId)
+        }
+        query.isNotEmpty(RoomEntityFields.ACCOUNT_DATA.`$`)
+        if (types.isNotEmpty()) {
+            query.`in`(RoomEntityFields.ACCOUNT_DATA.TYPE, types.toTypedArray())
+        }
+        return query
+    }
+
+    private fun RoomEntity.accountDataEvents(types: Set<String>): List<RoomAccountDataEvent> {
         val query = accountData.where()
         if (types.isNotEmpty()) {
             query.`in`(RoomAccountDataEntityFields.TYPE, types.toTypedArray())
         }
-        return query.findAll().map { accountDataMapper.map(it) }
+        return query.findAll().map { accountDataMapper.map(roomId, it) }
     }
 }
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 b39cbaa5825c69ecfda9656115e8827542626fa8..7c137a810275a1146ecb6ea5fa6e6a3531ed1713 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/RoomAliasAvailabilityChecker.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/RoomAliasAvailabilityChecker.kt
@@ -16,6 +16,7 @@
 
 package org.matrix.android.sdk.internal.session.room.alias
 
+import org.matrix.android.sdk.api.MatrixPatterns.getDomain
 import org.matrix.android.sdk.api.failure.Failure
 import org.matrix.android.sdk.api.session.room.alias.RoomAliasError
 import org.matrix.android.sdk.internal.di.UserId
@@ -51,19 +52,19 @@ internal class RoomAliasAvailabilityChecker @Inject constructor(
         } catch (throwable: Throwable) {
             if (throwable is Failure.ServerError && throwable.httpCode == 404) {
                 // This is a 404, so the alias is available: nominal case
-                null
+                return
             } else {
                 // Other error, propagate it
                 throw throwable
             }
         }
-                ?.let {
+                .let {
                     // Alias already exists: error case
                     throw RoomAliasError.AliasNotAvailable
                 }
     }
 
     companion object {
-        internal fun String.toFullLocalAlias(userId: String) = "#" + this + ":" + userId.substringAfter(":")
+        internal fun String.toFullLocalAlias(userId: String) = "#" + this + ":" + userId.getDomain()
     }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomBodyBuilder.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomBodyBuilder.kt
index 018b8653886ac861ffc7ca791cdda340af72af20..86d2b70da1a87e83dcebb491e19afb7da2a42534 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomBodyBuilder.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomBodyBuilder.kt
@@ -59,7 +59,7 @@ internal class CreateRoomBodyBuilder @Inject constructor(
         val invite3pids = params.invite3pids
                 .takeIf { it.isNotEmpty() }
                 ?.let { invites ->
-                    // This can throw Exception if Identity server is not configured
+                    // This can throw an exception if identity server is not configured
                     ensureIdentityTokenTask.execute(Unit)
 
                     val identityServerUrlWithoutProtocol = identityStore.getIdentityServerUrlWithoutProtocol()
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomChangeMembershipStateDataSource.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomChangeMembershipStateDataSource.kt
index b9c547d4fb8c6b2f911174084eac88bf1b84bcd2..35d8cb08af62e62a75b796ce158b251c033c373e 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomChangeMembershipStateDataSource.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomChangeMembershipStateDataSource.kt
@@ -21,6 +21,7 @@ import androidx.lifecycle.MutableLiveData
 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.session.SessionScope
+import java.util.concurrent.ConcurrentHashMap
 import javax.inject.Inject
 
 /**
@@ -30,7 +31,7 @@ import javax.inject.Inject
 internal class RoomChangeMembershipStateDataSource @Inject constructor() {
 
     private val mutableLiveStates = MutableLiveData<Map<String, ChangeMembershipState>>(emptyMap())
-    private val states = HashMap<String, ChangeMembershipState>()
+    private val states = ConcurrentHashMap<String, ChangeMembershipState>()
 
     /**
      * This will update local states to be synced with the server.
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 33776e4f6ece17d41a1e1910167425093ff6d291..562b25683b1e47f780eb07c48f92514e5d8190fc 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
@@ -54,6 +54,10 @@ internal class DefaultJoinRoomTask @Inject constructor(
 ) : JoinRoomTask {
 
     override suspend fun execute(params: JoinRoomTask.Params) {
+        val currentState = roomChangeMembershipStateDataSource.getState(params.roomIdOrAlias)
+        if (currentState.isInProgress() || currentState == ChangeMembershipState.Joined) {
+            return
+        }
         roomChangeMembershipStateDataSource.updateState(params.roomIdOrAlias, ChangeMembershipState.Joining)
         val joinRoomResponse = try {
             executeRequest(globalErrorReceiver) {
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 449189e6b56cb0ef723e5f8bf8e67511b24c4f86..092fec4d72217f01e0b5611d519035c6478c5efc 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
@@ -142,7 +142,7 @@ internal class DefaultSendService @AssistedInject constructor(
                     // The image has not yet been sent
                     val attachmentData = ContentAttachmentData(
                             size = messageContent.info!!.size,
-                            mimeType = messageContent.info.mimeType!!,
+                            mimeType = messageContent.mimeType,
                             width = messageContent.info.width.toLong(),
                             height = messageContent.info.height.toLong(),
                             name = messageContent.body,
@@ -169,7 +169,7 @@ internal class DefaultSendService @AssistedInject constructor(
                 is MessageFileContent  -> {
                     val attachmentData = ContentAttachmentData(
                             size = messageContent.info!!.size,
-                            mimeType = messageContent.info.mimeType!!,
+                            mimeType = messageContent.mimeType,
                             name = messageContent.getFileName(),
                             queryUri = Uri.parse(messageContent.url),
                             type = ContentAttachmentData.Type.FILE
@@ -181,7 +181,7 @@ internal class DefaultSendService @AssistedInject constructor(
                     val attachmentData = ContentAttachmentData(
                             size = messageContent.audioInfo?.size ?: 0,
                             duration = messageContent.audioInfo?.duration?.toLong() ?: 0L,
-                            mimeType = messageContent.audioInfo?.mimeType,
+                            mimeType = messageContent.mimeType,
                             name = messageContent.body,
                             queryUri = Uri.parse(messageContent.url),
                             type = ContentAttachmentData.Type.AUDIO
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 80bfd02b0eef8bcec3f29cde2d5d2f2b4451c673..3be01762e77cc57e65df3c986d11b83286ba33f8 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
@@ -46,7 +46,7 @@ private const val MAX_RETRY_COUNT = 3
 
 /**
  * This class is responsible for sending events in order in each room. It uses the QueuedTask.queueIdentifier to execute tasks sequentially.
- * Each send is retried 3 times, if there is no network (e.g if cannot ping home server) it will wait and
+ * Each send is retried 3 times, if there is no network (e.g if cannot ping homeserver) it will wait and
  * periodically test reachability before resume (does not count as a retry)
  *
  * If the app is killed before all event were sent, on next wakeup the scheduled events will be re posted
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/EventSenderProcessorThread.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/EventSenderProcessorThread.kt
index 9db7cc90394fbb7378231dc963314e58d7ddac30..f32890f3fb42cd912e68f83b7a91aa7536e2345b 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/EventSenderProcessorThread.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/EventSenderProcessorThread.kt
@@ -42,7 +42,7 @@ import kotlin.concurrent.schedule
 
 /**
  * A simple ever running thread unique for that session responsible of sending events in order.
- * Each send is retried 3 times, if there is no network (e.g if cannot ping home server) it will wait and
+ * Each send is retried 3 times, if there is no network (e.g if cannot ping homeserver) it will wait and
  * periodically test reachability before resume (does not count as a retry)
  *
  * If the app is killed before all event were sent, on next wakeup the scheduled events will be re posted
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/HomeServerAvailabilityChecker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/HomeServerAvailabilityChecker.kt
index 2d53699917b03c819fd0ac04882a61edf7fea8ca..1d7ce587f9938e088942ab3e31f4b3619d2104f5 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/HomeServerAvailabilityChecker.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/HomeServerAvailabilityChecker.kt
@@ -26,8 +26,8 @@ import java.net.Socket
 internal class HomeServerAvailabilityChecker(val sessionParams: SessionParams) {
 
     fun check(): Boolean {
-        val host = sessionParams.homeServerConnectionConfig.homeServerUri.host ?: return false
-        val port = sessionParams.homeServerConnectionConfig.homeServerUri.port.takeIf { it != -1 } ?: 80
+        val host = sessionParams.homeServerConnectionConfig.homeServerUriBase.host ?: return false
+        val port = sessionParams.homeServerConnectionConfig.homeServerUriBase.port.takeIf { it != -1 } ?: 80
         val timeout = 30_000
         try {
             Socket().use { socket ->
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 7cbcfee713565ecdd840f3c08d26201ef896ce4b..842c9d3abadfbf41d7f92a3e10c87633ccd8c98c 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
@@ -199,7 +199,6 @@ internal class RoomSummaryUpdater @Inject constructor(
         measureTimeMillis {
             val lookupMap = realm.where(RoomSummaryEntity::class.java)
                     .process(RoomSummaryEntityFields.MEMBERSHIP_STR, Membership.activeMemberships())
-                    .equalTo(RoomSummaryEntityFields.IS_DIRECT, false)
                     // we order by roomID to be consistent when breaking parent/child cycles
                     .sort(RoomSummaryEntityFields.ROOM_ID)
                     .findAll().map {
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 e230599f8f6d1d013cfd4d03ae6d720be6bcff18..8cc5d943b7e291ba0c4afdfd11c096e88874276c 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
@@ -24,7 +24,6 @@ import io.realm.RealmQuery
 import io.realm.RealmResults
 import io.realm.Sort
 import org.matrix.android.sdk.api.MatrixCallback
-import org.matrix.android.sdk.api.NoOpMatrixCallback
 import org.matrix.android.sdk.api.extensions.orFalse
 import org.matrix.android.sdk.api.extensions.tryOrNull
 import org.matrix.android.sdk.api.session.events.model.EventType
@@ -168,9 +167,7 @@ internal class DefaultTimeline(
                 timelineEvents.addChangeListener(eventsChangeListener)
                 handleInitialLoad()
                 loadRoomMembersTask
-                        .configureWith(LoadRoomMembersTask.Params(roomId)) {
-                            this.callback = NoOpMatrixCallback()
-                        }
+                        .configureWith(LoadRoomMembersTask.Params(roomId))
                         .executeBy(taskExecutor)
 
                 // Ensure ReadReceipt from init sync are loaded
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/typing/DefaultTypingService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/typing/DefaultTypingService.kt
index 39b7967bc1de69314497a86d90e8d0cbeaa03e55..b76829e8937eda5fce2a22b5a446bab224855cbd 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/typing/DefaultTypingService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/typing/DefaultTypingService.kt
@@ -18,13 +18,13 @@ package org.matrix.android.sdk.internal.session.room.typing
 
 import android.os.SystemClock
 import dagger.assisted.Assisted
-import dagger.assisted.AssistedInject
 import dagger.assisted.AssistedFactory
-import org.matrix.android.sdk.api.MatrixCallback
+import dagger.assisted.AssistedInject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
 import org.matrix.android.sdk.api.session.room.typing.TypingService
-import org.matrix.android.sdk.api.util.Cancelable
-import org.matrix.android.sdk.internal.task.TaskExecutor
-import org.matrix.android.sdk.internal.task.configureWith
 import timber.log.Timber
 
 /**
@@ -35,7 +35,6 @@ import timber.log.Timber
  */
 internal class DefaultTypingService @AssistedInject constructor(
         @Assisted private val roomId: String,
-        private val taskExecutor: TaskExecutor,
         private val sendTypingTask: SendTypingTask
 ) : TypingService {
 
@@ -44,8 +43,8 @@ internal class DefaultTypingService @AssistedInject constructor(
         fun create(roomId: String): DefaultTypingService
     }
 
-    private var currentTask: Cancelable? = null
-    private var currentAutoStopTask: Cancelable? = null
+    private val coroutineScope = CoroutineScope(Job())
+    private var currentTask: Job? = null
 
     // What the homeserver knows
     private var userIsTyping = false
@@ -53,26 +52,24 @@ internal class DefaultTypingService @AssistedInject constructor(
     // Last time the user is typing event has been sent
     private var lastRequestTimestamp: Long = 0
 
+    /**
+     * Notify to the server that the user is typing and schedule the auto typing off
+     */
     override fun userIsTyping() {
-        scheduleAutoStop()
-
         val now = SystemClock.elapsedRealtime()
-
-        if (userIsTyping && now < lastRequestTimestamp + MIN_DELAY_BETWEEN_TWO_USER_IS_TYPING_REQUESTS_MILLIS) {
-            Timber.d("Typing: Skip start request")
-            return
-        }
-
-        Timber.d("Typing: Send start request")
-        userIsTyping = true
-        lastRequestTimestamp = now
-
         currentTask?.cancel()
-
-        val params = SendTypingTask.Params(roomId, true)
-        currentTask = sendTypingTask
-                .configureWith(params)
-                .executeBy(taskExecutor)
+        currentTask = coroutineScope.launch {
+            if (userIsTyping && now < lastRequestTimestamp + MIN_DELAY_BETWEEN_TWO_USER_IS_TYPING_REQUESTS_MILLIS) {
+                Timber.d("Typing: Skip start request")
+            } else {
+                Timber.d("Typing: Send start request")
+                lastRequestTimestamp = now
+                sendRequest(true)
+            }
+            delay(MIN_DELAY_TO_SEND_STOP_TYPING_REQUEST_WHEN_NO_USER_ACTIVITY_MILLIS)
+            Timber.d("Typing: auto stop")
+            sendRequest(false)
+        }
     }
 
     override fun userStopsTyping() {
@@ -82,35 +79,22 @@ internal class DefaultTypingService @AssistedInject constructor(
         }
 
         Timber.d("Typing: Send stop request")
-        userIsTyping = false
         lastRequestTimestamp = 0
 
-        currentAutoStopTask?.cancel()
         currentTask?.cancel()
-
-        val params = SendTypingTask.Params(roomId, false)
-        currentTask = sendTypingTask
-                .configureWith(params)
-                .executeBy(taskExecutor)
+        currentTask = coroutineScope.launch {
+            sendRequest(false)
+        }
     }
 
-    private fun scheduleAutoStop() {
-        Timber.d("Typing: Schedule auto stop")
-        currentAutoStopTask?.cancel()
-
-        val params = SendTypingTask.Params(
-                roomId,
-                false,
-                delay = MIN_DELAY_TO_SEND_STOP_TYPING_REQUEST_WHEN_NO_USER_ACTIVITY_MILLIS)
-        currentAutoStopTask = sendTypingTask
-                .configureWith(params) {
-                    callback = object : MatrixCallback<Unit> {
-                        override fun onSuccess(data: Unit) {
-                            userIsTyping = false
-                        }
-                    }
-                }
-                .executeBy(taskExecutor)
+    private suspend fun sendRequest(isTyping: Boolean) {
+        try {
+            sendTypingTask.execute(SendTypingTask.Params(roomId, isTyping))
+            userIsTyping = isTyping
+        } catch (failure: Throwable) {
+            // Ignore network error, etc...
+            Timber.w(failure, "Unable to send typing request")
+        }
     }
 
     companion object {
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 0b0df743113285d432cc586afbef88cb5237e0a6..0bdceb9adebddc70ed3612b5ff125227ba9a66f0 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
@@ -17,11 +17,10 @@
 package org.matrix.android.sdk.internal.session.room.typing
 
 import org.matrix.android.sdk.internal.di.UserId
+import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import org.matrix.android.sdk.internal.network.executeRequest
 import org.matrix.android.sdk.internal.session.room.RoomAPI
 import org.matrix.android.sdk.internal.task.Task
-import kotlinx.coroutines.delay
-import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import javax.inject.Inject
 
 internal interface SendTypingTask : Task<SendTypingTask.Params, Unit> {
@@ -29,9 +28,7 @@ internal interface SendTypingTask : Task<SendTypingTask.Params, Unit> {
     data class Params(
             val roomId: String,
             val isTyping: Boolean,
-            val typingTimeoutMillis: Int? = 30_000,
-            // Optional delay before sending the request to the homeserver
-            val delay: Long? = null
+            val typingTimeoutMillis: Int? = 30_000
     )
 }
 
@@ -42,8 +39,6 @@ internal class DefaultSendTypingTask @Inject constructor(
 ) : SendTypingTask {
 
     override suspend fun execute(params: SendTypingTask.Params) {
-        delay(params.delay ?: -1)
-
         executeRequest(globalErrorReceiver) {
             roomAPI.sendTypingState(
                     params.roomId,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/version/DefaultRoomVersionService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/version/DefaultRoomVersionService.kt
new file mode 100644
index 0000000000000000000000000000000000000000..dc12c3209b2811398a41c35ec2009ac0074b5f08
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/version/DefaultRoomVersionService.kt
@@ -0,0 +1,84 @@
+/*
+ * 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.session.room.version
+
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import org.matrix.android.sdk.api.query.QueryStringValue
+import org.matrix.android.sdk.api.session.events.model.EventType
+import org.matrix.android.sdk.api.session.events.model.toModel
+import org.matrix.android.sdk.api.session.homeserver.RoomVersionStatus
+import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent
+import org.matrix.android.sdk.api.session.room.model.create.RoomCreateContent
+import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper
+import org.matrix.android.sdk.api.session.room.version.RoomVersionService
+import org.matrix.android.sdk.internal.session.homeserver.HomeServerCapabilitiesDataSource
+import org.matrix.android.sdk.internal.session.room.state.StateEventDataSource
+
+internal class DefaultRoomVersionService @AssistedInject constructor(
+        @Assisted private val roomId: String,
+        private val homeServerCapabilitiesDataSource: HomeServerCapabilitiesDataSource,
+        private val stateEventDataSource: StateEventDataSource,
+        private val roomVersionUpgradeTask: RoomVersionUpgradeTask
+) : RoomVersionService {
+
+    @AssistedFactory
+    interface Factory {
+        fun create(roomId: String): DefaultRoomVersionService
+    }
+
+    override fun getRoomVersion(): String {
+        return stateEventDataSource.getStateEvent(roomId, EventType.STATE_ROOM_CREATE, QueryStringValue.IsEmpty)
+                ?.content
+                ?.toModel<RoomCreateContent>()
+                ?.roomVersion
+        // as per spec -> Defaults to "1" if the key does not exist.
+                ?: DEFAULT_ROOM_VERSION
+    }
+
+    override suspend fun upgradeToVersion(version: String): String {
+        return roomVersionUpgradeTask.execute(
+                RoomVersionUpgradeTask.Params(
+                        roomId = roomId,
+                        newVersion = version
+                )
+        )
+    }
+
+    override fun getRecommendedVersion(): String {
+        return homeServerCapabilitiesDataSource.getHomeServerCapabilities()?.roomVersions?.defaultRoomVersion ?: DEFAULT_ROOM_VERSION
+    }
+
+    override fun isUsingUnstableRoomVersion(): Boolean {
+        val versionCaps = homeServerCapabilitiesDataSource.getHomeServerCapabilities()?.roomVersions
+        val currentVersion = getRoomVersion()
+        return versionCaps?.supportedVersion?.firstOrNull { it.version == currentVersion }?.status == RoomVersionStatus.UNSTABLE
+    }
+
+    override fun userMayUpgradeRoom(userId: String): Boolean {
+        val powerLevelsHelper = stateEventDataSource.getStateEvent(roomId, EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.NoCondition)
+                ?.content?.toModel<PowerLevelsContent>()
+                ?.let { PowerLevelsHelper(it) }
+
+        return powerLevelsHelper?.isUserAllowedToSend(userId, true, EventType.STATE_ROOM_TOMBSTONE) ?: false
+    }
+
+    companion object {
+        const val DEFAULT_ROOM_VERSION = "1"
+    }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/version/RoomVersionUpgradeTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/version/RoomVersionUpgradeTask.kt
new file mode 100644
index 0000000000000000000000000000000000000000..457bb3e948bbd96d3ec28dd184fbcbab5a753772
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/version/RoomVersionUpgradeTask.kt
@@ -0,0 +1,66 @@
+/*
+ * 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.session.room.version
+
+import io.realm.RealmConfiguration
+import org.matrix.android.sdk.api.extensions.tryOrNull
+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.RoomSummaryEntity
+import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields
+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.RoomUpgradeBody
+import org.matrix.android.sdk.internal.task.Task
+import java.util.concurrent.TimeUnit
+import javax.inject.Inject
+
+internal interface RoomVersionUpgradeTask : Task<RoomVersionUpgradeTask.Params, String> {
+    data class Params(
+            val roomId: String,
+            val newVersion: String
+    )
+}
+
+internal class DefaultRoomVersionUpgradeTask @Inject constructor(
+        private val roomAPI: RoomAPI,
+        private val globalErrorReceiver: GlobalErrorReceiver,
+        @SessionDatabase
+        private val realmConfiguration: RealmConfiguration
+) : RoomVersionUpgradeTask {
+
+    override suspend fun execute(params: RoomVersionUpgradeTask.Params): String {
+        val replacementRoomId = executeRequest(globalErrorReceiver) {
+            roomAPI.upgradeRoom(
+                    roomId = params.roomId,
+                    body = RoomUpgradeBody(params.newVersion)
+            )
+        }.replacementRoomId
+
+        // Wait for room to come back from the sync (but it can maybe be in the DB if the sync response is received before)
+        tryOrNull {
+            awaitNotEmptyResult(realmConfiguration, TimeUnit.MINUTES.toMillis(1L)) { realm ->
+                realm.where(RoomSummaryEntity::class.java)
+                        .equalTo(RoomSummaryEntityFields.ROOM_ID, replacementRoomId)
+                        .equalTo(RoomSummaryEntityFields.MEMBERSHIP_STR, Membership.JOIN.name)
+            }
+        }
+        return replacementRoomId
+    }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpace.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpace.kt
index 70c52bf4aec8169596a706a6929af844e774b65c..233eef45f8d1f074e43fa2e0872f42ccf2d7d288 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpace.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpace.kt
@@ -86,6 +86,12 @@ internal class DefaultSpace(
         )
     }
 
+    override fun getChildInfo(roomId: String): SpaceChildContent? {
+        return room.getStateEvents(setOf(EventType.STATE_SPACE_CHILD), QueryStringValue.Equals(roomId))
+                .firstOrNull()
+                ?.content.toModel<SpaceChildContent>()
+    }
+
     override suspend fun setChildrenOrder(roomId: String, order: String?) {
         val existing = room.getStateEvents(setOf(EventType.STATE_SPACE_CHILD), QueryStringValue.Equals(roomId))
                 .firstOrNull()
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpaceService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpaceService.kt
index 9c6153b34969ce07357c80c73173c462b6ecc326..0c5c0416f968aecb8f911a5a45bf9a6d8f613f9f 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpaceService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpaceService.kt
@@ -66,12 +66,13 @@ internal class DefaultSpaceService @Inject constructor(
         return createRoomTask.executeRetry(params, 3)
     }
 
-    override suspend fun createSpace(name: String, topic: String?, avatarUri: Uri?, isPublic: Boolean): String {
+    override suspend fun createSpace(name: String, topic: String?, avatarUri: Uri?, isPublic: Boolean, roomAliasLocalPart: String?): String {
         return createSpace(CreateSpaceParams().apply {
             this.name = name
             this.topic = topic
             this.avatarUri = avatarUri
             if (isPublic) {
+                this.roomAliasName = roomAliasLocalPart
                 this.powerLevelContentOverride = (powerLevelContentOverride ?: PowerLevelsContent()).copy(
                         invite = 0
                 )
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 83a2ffc4466cd7a90e4ef890413562f8de4c122c..c80fbe60c1b7e52d3f856a27645bde819021e262 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
@@ -94,7 +94,7 @@ internal class DefaultSyncTask @Inject constructor(
             userStore.createOrUpdate(userId)
             initialSyncProgressService.startRoot(InitSyncStep.ImportingAccount, 100)
         }
-        // Maybe refresh the home server capabilities data we know
+        // Maybe refresh the homeserver capabilities data we know
         getHomeServerCapabilitiesTask.execute(GetHomeServerCapabilitiesTask.Params(forceRefresh = false))
 
         val readTimeOut = (params.timeout + TIMEOUT_MARGIN).coerceAtLeast(TimeOutInterceptor.DEFAULT_LONG_TIMEOUT)
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 3aebd90ed26d045c6a6b1d006959629d4c8766be..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
@@ -23,7 +23,7 @@ import io.realm.kotlin.where
 import org.matrix.android.sdk.api.pushrules.RuleScope
 import org.matrix.android.sdk.api.pushrules.RuleSetKey
 import org.matrix.android.sdk.api.pushrules.rest.GetPushRulesResponse
-import org.matrix.android.sdk.api.session.accountdata.AccountDataEvent
+import org.matrix.android.sdk.api.session.accountdata.UserAccountDataEvent
 import org.matrix.android.sdk.api.session.accountdata.UserAccountDataTypes
 import org.matrix.android.sdk.api.session.events.model.Content
 import org.matrix.android.sdk.api.session.events.model.toModel
@@ -113,7 +113,7 @@ internal class UserAccountDataSyncHandler @Inject constructor(
         }
     }
 
-    private fun handlePushRules(realm: Realm, event: AccountDataEvent) {
+    private fun handlePushRules(realm: Realm, event: UserAccountDataEvent) {
         val pushRules = event.content.toModel<GetPushRulesResponse>() ?: return
         realm.where(PushRulesEntity::class.java)
                 .findAll()
@@ -155,7 +155,7 @@ internal class UserAccountDataSyncHandler @Inject constructor(
         realm.insertOrUpdate(underrides)
     }
 
-    private fun handleDirectChatRooms(realm: Realm, event: AccountDataEvent) {
+    private fun handleDirectChatRooms(realm: Realm, event: UserAccountDataEvent) {
         val content = event.content.toModel<DirectMessagesContent>() ?: return
         content.forEach { (userId, roomIds) ->
             roomIds.forEach { roomId ->
@@ -181,7 +181,7 @@ internal class UserAccountDataSyncHandler @Inject constructor(
                 }
     }
 
-    private fun handleIgnoredUsers(realm: Realm, event: AccountDataEvent) {
+    private fun handleIgnoredUsers(realm: Realm, event: UserAccountDataEvent) {
         val userIds = event.content.toModel<IgnoredUsersContent>()?.ignoredUsers?.keys ?: return
         realm.where(IgnoredUserEntity::class.java)
                 .findAll()
@@ -191,7 +191,7 @@ internal class UserAccountDataSyncHandler @Inject constructor(
         // TODO If not initial sync, we should execute a init sync
     }
 
-    private fun handleBreadcrumbs(realm: Realm, event: AccountDataEvent) {
+    private fun handleBreadcrumbs(realm: Realm, event: UserAccountDataEvent) {
         val recentRoomIds = event.content.toModel<BreadcrumbsContent>()?.recentRoomIds ?: return
         val entity = BreadcrumbsEntity.getOrCreate(realm)
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/model/accountdata/UserAccountDataSync.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/model/accountdata/UserAccountDataSync.kt
index ddb71cd19f4344733f23b1a53ef800d9034fdb25..05b50ab2c5ee00be2f069b7c008721ea67c7d4d2 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/model/accountdata/UserAccountDataSync.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/model/accountdata/UserAccountDataSync.kt
@@ -18,9 +18,9 @@ package org.matrix.android.sdk.internal.session.sync.model.accountdata
 
 import com.squareup.moshi.Json
 import com.squareup.moshi.JsonClass
-import org.matrix.android.sdk.api.session.accountdata.AccountDataEvent
+import org.matrix.android.sdk.api.session.accountdata.UserAccountDataEvent
 
 @JsonClass(generateAdapter = true)
 internal data class UserAccountDataSync(
-        @Json(name = "events") val list: List<AccountDataEvent> = emptyList()
+        @Json(name = "events") val list: List<UserAccountDataEvent> = emptyList()
 )
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/parsing/RoomSyncAccountDataHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/parsing/RoomSyncAccountDataHandler.kt
index c8aab586a01048cfb19a5507552bd1f7ff2b5293..8bf9ad5b908f2af0a78d1bb7f23df514a4467a52 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/parsing/RoomSyncAccountDataHandler.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/parsing/RoomSyncAccountDataHandler.kt
@@ -56,7 +56,6 @@ internal class RoomSyncAccountDataHandler @Inject constructor(private val roomTa
     private fun handleGeneric(roomEntity: RoomEntity, content: JsonDict?, eventType: String) {
         val existing = roomEntity.accountData.where().equalTo(RoomAccountDataEntityFields.TYPE, eventType).findFirst()
         if (existing != null) {
-            // Update current value
             existing.contentStr = ContentMapper.map(content)
         } else {
             val roomAccountData = RoomAccountDataEntity(
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/UserAccountDataService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/DefaultSessionAccountDataService.kt
similarity index 55%
rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/UserAccountDataService.kt
rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/DefaultSessionAccountDataService.kt
index b15d1d0f8bbe8f2cd737b36c57ecea11e616e134..ff1750ce8e4ffa57f8ab39c1d175421b53b10c95 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/UserAccountDataService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/DefaultSessionAccountDataService.kt
@@ -18,42 +18,53 @@ package org.matrix.android.sdk.internal.session.user.accountdata
 
 import androidx.lifecycle.LiveData
 import com.zhuinden.monarchy.Monarchy
-import org.matrix.android.sdk.api.session.accountdata.AccountDataService
+import org.matrix.android.sdk.api.session.accountdata.SessionAccountDataService
 import org.matrix.android.sdk.api.session.events.model.Content
 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.AccountDataEvent
+import org.matrix.android.sdk.api.session.accountdata.UserAccountDataEvent
+import org.matrix.android.sdk.api.session.room.accountdata.RoomAccountDataEvent
+import org.matrix.android.sdk.internal.session.room.accountdata.RoomAccountDataDataSource
 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 UserAccountDataService @Inject constructor(
+internal class DefaultSessionAccountDataService @Inject constructor(
         @SessionDatabase private val monarchy: Monarchy,
         private val updateUserAccountDataTask: UpdateUserAccountDataTask,
         private val userAccountDataSyncHandler: UserAccountDataSyncHandler,
-        private val accountDataDataSource: UserAccountDataDataSource,
+        private val userAccountDataDataSource: UserAccountDataDataSource,
+        private val roomAccountDataDataSource: RoomAccountDataDataSource,
         private val taskExecutor: TaskExecutor
-) : AccountDataService {
+) : SessionAccountDataService {
 
-    override fun getAccountDataEvent(type: String): AccountDataEvent? {
-        return accountDataDataSource.getAccountDataEvent(type)
+    override fun getUserAccountDataEvent(type: String): UserAccountDataEvent? {
+        return userAccountDataDataSource.getAccountDataEvent(type)
     }
 
-    override fun getLiveAccountDataEvent(type: String): LiveData<Optional<AccountDataEvent>> {
-        return accountDataDataSource.getLiveAccountDataEvent(type)
+    override fun getLiveUserAccountDataEvent(type: String): LiveData<Optional<UserAccountDataEvent>> {
+        return userAccountDataDataSource.getLiveAccountDataEvent(type)
     }
 
-    override fun getAccountDataEvents(types: Set<String>): List<AccountDataEvent> {
-        return accountDataDataSource.getAccountDataEvents(types)
+    override fun getUserAccountDataEvents(types: Set<String>): List<UserAccountDataEvent> {
+        return userAccountDataDataSource.getAccountDataEvents(types)
     }
 
-    override fun getLiveAccountDataEvents(types: Set<String>): LiveData<List<AccountDataEvent>> {
-        return accountDataDataSource.getLiveAccountDataEvents(types)
+    override fun getLiveUserAccountDataEvents(types: Set<String>): LiveData<List<UserAccountDataEvent>> {
+        return userAccountDataDataSource.getLiveAccountDataEvents(types)
     }
 
-    override suspend fun updateAccountData(type: String, content: Content) {
+    override fun getRoomAccountDataEvents(types: Set<String>): List<RoomAccountDataEvent> {
+        return roomAccountDataDataSource.getAccountDataEvents(null, types)
+    }
+
+    override fun getLiveRoomAccountDataEvents(types: Set<String>): LiveData<List<RoomAccountDataEvent>> {
+        return roomAccountDataDataSource.getLiveAccountDataEvents(null, types)
+    }
+
+    override suspend fun updateUserAccountData(type: String, content: Content) {
         val params = UpdateUserAccountDataTask.AnyParams(type = type, any = content)
         awaitCallback<Unit> { callback ->
             updateUserAccountDataTask.configureWith(params) {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/DirectChatsHelper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/DirectChatsHelper.kt
index e297f59b969a3622d47da201382df6b7f6a2bb14..a9e5089774076564400a016d61c1672fbb927933 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/DirectChatsHelper.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/DirectChatsHelper.kt
@@ -31,9 +31,11 @@ internal class DirectChatsHelper @Inject constructor(@SessionDatabase
      */
     fun getLocalUserAccount(filterRoomId: String? = null): MutableMap<String, MutableList<String>> {
         return Realm.getInstance(realmConfiguration).use { realm ->
+            // Makes sure we have the latest realm updates, this is important as we sent this information to the server.
+            realm.refresh()
             RoomSummaryEntity.getDirectRooms(realm)
                     .asSequence()
-                    .filter { it.roomId != filterRoomId && it.directUserId != null }
+                    .filter { it.roomId != filterRoomId && it.directUserId != null && it.membership.isActive() }
                     .groupByTo(mutableMapOf(), { it.directUserId!! }, { it.roomId })
         }
     }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/UserAccountDataDataSource.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/UserAccountDataDataSource.kt
index f64b1bdd2ecc7db38fb446b96104f52a52b6f978..b36bdc80f889be918a36a413d87123a252428886 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/UserAccountDataDataSource.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/UserAccountDataDataSource.kt
@@ -21,7 +21,7 @@ import androidx.lifecycle.Transformations
 import com.zhuinden.monarchy.Monarchy
 import io.realm.Realm
 import io.realm.RealmQuery
-import org.matrix.android.sdk.api.session.accountdata.AccountDataEvent
+import org.matrix.android.sdk.api.session.accountdata.UserAccountDataEvent
 import org.matrix.android.sdk.api.util.Optional
 import org.matrix.android.sdk.api.util.toOptional
 import org.matrix.android.sdk.internal.database.RealmSessionProvider
@@ -35,23 +35,23 @@ internal class UserAccountDataDataSource @Inject constructor(@SessionDatabase pr
                                                              private val realmSessionProvider: RealmSessionProvider,
                                                              private val accountDataMapper: AccountDataMapper) {
 
-    fun getAccountDataEvent(type: String): AccountDataEvent? {
+    fun getAccountDataEvent(type: String): UserAccountDataEvent? {
         return getAccountDataEvents(setOf(type)).firstOrNull()
     }
 
-    fun getLiveAccountDataEvent(type: String): LiveData<Optional<AccountDataEvent>> {
+    fun getLiveAccountDataEvent(type: String): LiveData<Optional<UserAccountDataEvent>> {
         return Transformations.map(getLiveAccountDataEvents(setOf(type))) {
             it.firstOrNull()?.toOptional()
         }
     }
 
-    fun getAccountDataEvents(types: Set<String>): List<AccountDataEvent> {
+    fun getAccountDataEvents(types: Set<String>): List<UserAccountDataEvent> {
         return realmSessionProvider.withRealm {
             accountDataEventsQuery(it, types).findAll().map(accountDataMapper::map)
         }
     }
 
-    fun getLiveAccountDataEvents(types: Set<String>): LiveData<List<AccountDataEvent>> {
+    fun getLiveAccountDataEvents(types: Set<String>): LiveData<List<UserAccountDataEvent>> {
         return monarchy.findAllMappedWithChanges(
                 { accountDataEventsQuery(it, types) },
                 accountDataMapper::map
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 ca1a129da79e262486fe5926df4bb9c847fb0f0f..e0f43a11c570bceb4b94c61a004291d3048c0f83 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
@@ -23,7 +23,7 @@ import androidx.lifecycle.LiveData
 import androidx.lifecycle.Transformations
 import org.matrix.android.sdk.api.query.QueryStringValue
 import org.matrix.android.sdk.api.session.Session
-import org.matrix.android.sdk.api.session.accountdata.AccountDataEvent
+import org.matrix.android.sdk.api.session.accountdata.UserAccountDataEvent
 import org.matrix.android.sdk.api.session.accountdata.UserAccountDataTypes
 import org.matrix.android.sdk.api.session.events.model.Content
 import org.matrix.android.sdk.api.session.events.model.Event
@@ -47,7 +47,7 @@ import javax.inject.Inject
 
 @SessionScope
 internal class WidgetManager @Inject constructor(private val integrationManager: IntegrationManager,
-                                                 private val accountDataDataSource: UserAccountDataDataSource,
+                                                 private val userAccountDataDataSource: UserAccountDataDataSource,
                                                  private val stateEventDataSource: StateEventDataSource,
                                                  private val createWidgetTask: CreateWidgetTask,
                                                  private val widgetFactory: WidgetFactory,
@@ -136,7 +136,7 @@ internal class WidgetManager @Inject constructor(private val integrationManager:
             widgetTypes: Set<String>? = null,
             excludedTypes: Set<String>? = null
     ): LiveData<List<Widget>> {
-        val widgetsAccountData = accountDataDataSource.getLiveAccountDataEvent(UserAccountDataTypes.TYPE_WIDGETS)
+        val widgetsAccountData = userAccountDataDataSource.getLiveAccountDataEvent(UserAccountDataTypes.TYPE_WIDGETS)
         return Transformations.map(widgetsAccountData) {
             it.getOrNull()?.mapToWidgets(widgetTypes, excludedTypes).orEmpty()
         }
@@ -146,12 +146,12 @@ internal class WidgetManager @Inject constructor(private val integrationManager:
             widgetTypes: Set<String>? = null,
             excludedTypes: Set<String>? = null
     ): List<Widget> {
-        val widgetsAccountData = accountDataDataSource.getAccountDataEvent(UserAccountDataTypes.TYPE_WIDGETS) ?: return emptyList()
+        val widgetsAccountData = userAccountDataDataSource.getAccountDataEvent(UserAccountDataTypes.TYPE_WIDGETS) ?: return emptyList()
         return widgetsAccountData.mapToWidgets(widgetTypes, excludedTypes)
     }
 
-    private fun AccountDataEvent.mapToWidgets(widgetTypes: Set<String>? = null,
-                                              excludedTypes: Set<String>? = null): List<Widget> {
+    private fun UserAccountDataEvent.mapToWidgets(widgetTypes: Set<String>? = null,
+                                                  excludedTypes: Set<String>? = null): List<Widget> {
         return extractWidgetSequence(widgetFactory)
                 .filter {
                     val widgetType = it.widgetContent.type ?: return@filter false
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/helper/UserAccountWidgets.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/helper/UserAccountWidgets.kt
index 5aa32d5a31751f5f928e6be47cab0d1da907340d..6f423b38a09ed42f3f4ed74f69761bf6ad869d29 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/helper/UserAccountWidgets.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/helper/UserAccountWidgets.kt
@@ -19,10 +19,10 @@ package org.matrix.android.sdk.internal.session.widgets.helper
 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.util.JsonDict
-import org.matrix.android.sdk.api.session.accountdata.AccountDataEvent
+import org.matrix.android.sdk.api.session.accountdata.UserAccountDataEvent
 import org.matrix.android.sdk.api.session.widgets.model.Widget
 
-internal fun AccountDataEvent.extractWidgetSequence(widgetFactory: WidgetFactory): Sequence<Widget> {
+internal fun UserAccountDataEvent.extractWidgetSequence(widgetFactory: WidgetFactory): Sequence<Widget> {
     return content.asSequence()
             .mapNotNull {
                 @Suppress("UNCHECKED_CAST")
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 7a9beac8c08e1d8db524a90cc0308ee6054dc967..f11e87e1e72bdf551191bc8d7ae3fdb456e91077 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
@@ -18,7 +18,6 @@ package org.matrix.android.sdk.internal.wellknown
 
 import android.util.MalformedJsonException
 import dagger.Lazy
-import org.matrix.android.sdk.api.MatrixPatterns
 import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
 import org.matrix.android.sdk.api.auth.data.WellKnown
 import org.matrix.android.sdk.api.auth.wellknown.WellknownResult
@@ -39,7 +38,11 @@ import javax.net.ssl.HttpsURLConnection
 
 internal interface GetWellknownTask : Task<GetWellknownTask.Params, WellknownResult> {
     data class Params(
-            val matrixId: String,
+            /**
+             * domain, for instance "matrix.org"
+             * the URL will be https://{domain}/.well-known/matrix/client
+             */
+            val domain: String,
             val homeServerConnectionConfig: HomeServerConnectionConfig?
     )
 }
@@ -54,14 +57,8 @@ internal class DefaultGetWellknownTask @Inject constructor(
 ) : GetWellknownTask {
 
     override suspend fun execute(params: GetWellknownTask.Params): WellknownResult {
-        if (!MatrixPatterns.isUserId(params.matrixId)) {
-            return WellknownResult.InvalidMatrixId
-        }
-
-        val homeServerDomain = params.matrixId.substringAfter(":")
-
         val client = buildClient(params.homeServerConnectionConfig)
-        return findClientConfig(homeServerDomain, client)
+        return findClientConfig(params.domain, client)
     }
 
     private fun buildClient(homeServerConnectionConfig: HomeServerConnectionConfig?): OkHttpClient {
@@ -133,7 +130,7 @@ internal class DefaultGetWellknownTask @Inject constructor(
     }
 
     /**
-     * Return true if home server is valid, and (if applicable) if identity server is pingable
+     * Return true if homeserver is valid, and (if applicable) if identity server is pingable
      */
     private suspend fun validateHomeServer(homeServerBaseUrl: String, wellKnown: WellKnown, client: OkHttpClient): WellknownResult {
         val capabilitiesAPI = retrofitFactory.create(client, homeServerBaseUrl)
@@ -189,7 +186,7 @@ internal class DefaultGetWellknownTask @Inject constructor(
     }
 
     /**
-     * Try to get an identity server URL from a home server URL, using a .wellknown request
+     * Try to get an identity server URL from a homeserver URL, using a .wellknown request
      */
     /*
     fun getIdentityServer(homeServerUrl: String, callback: ApiCallback<String?>) {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/worker/WorkerParamsFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/worker/WorkerParamsFactory.kt
index 7aed74d2ca64dd898f6402bee40aba097196126e..7a47270efd3460489f78add0608b2b932680335e 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/worker/WorkerParamsFactory.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/worker/WorkerParamsFactory.kt
@@ -46,7 +46,7 @@ internal object WorkerParamsFactory {
 
     inline fun <reified T> fromData(data: Data) = fromData(T::class.java, data)
 
-    fun <T> fromData(clazz: Class<T>, data: Data): T? = tryOrNull("Unable to parse work parameters") {
+    fun <T> fromData(clazz: Class<T>, data: Data): T? = tryOrNull<T?>("Unable to parse work parameters") {
         val json = data.getString(KEY)
         return if (json == null) {
             null
diff --git a/matrix-sdk-android/src/main/java/org/matrix/androidsdk/crypto/data/MXDeviceInfo.java b/matrix-sdk-android/src/main/java/org/matrix/androidsdk/crypto/data/MXDeviceInfo.java
index 393c633c6aaee6e0e63109dfa989c8412345b649..1014ceda0e56a5988cc4abaa84cb24d0c9588683 100755
--- a/matrix-sdk-android/src/main/java/org/matrix/androidsdk/crypto/data/MXDeviceInfo.java
+++ b/matrix-sdk-android/src/main/java/org/matrix/androidsdk/crypto/data/MXDeviceInfo.java
@@ -66,7 +66,7 @@ public class MXDeviceInfo implements Serializable {
     public Map<String, Map<String, String>> signatures;
 
     /*
-     * Additional data from the home server.
+     * Additional data from the homeserver.
      */
     public Map<String, Object> unsigned;
 
@@ -81,4 +81,4 @@ public class MXDeviceInfo implements Serializable {
     public MXDeviceInfo() {
         mVerified = DEVICE_VERIFICATION_UNKNOWN;
     }
-}
\ No newline at end of file
+}
diff --git a/matrix-sdk-android/src/main/res/values-fr/strings_sas.xml b/matrix-sdk-android/src/main/res/values-fr/strings_sas.xml
index af9d797542edaf6ad53ee88a923c036f7bb1a35b..e7d6ef8058f188a4c33a7f609e5be3c1d25f321c 100644
--- a/matrix-sdk-android/src/main/res/values-fr/strings_sas.xml
+++ b/matrix-sdk-android/src/main/res/values-fr/strings_sas.xml
@@ -33,11 +33,11 @@
     <string name="verification_emoji_heart">CÅ“ur</string>
     <string name="verification_emoji_smiley">Sourire</string>
     <string name="verification_emoji_robot">Robot</string>
-    <string name="verification_emoji_hat">Châpeau</string>
+    <string name="verification_emoji_hat">Chapeau</string>
     <string name="verification_emoji_glasses">Lunettes</string>
     <string name="verification_emoji_spanner">Clé à molette</string>
     <string name="verification_emoji_santa">Père Noël</string>
-    <string name="verification_emoji_thumbs_up">Pouce en l\'air</string>
+    <string name="verification_emoji_thumbs_up">Pouce en l’air</string>
     <string name="verification_emoji_umbrella">Parapluie</string>
     <string name="verification_emoji_hourglass">Sablier</string>
     <string name="verification_emoji_clock">Réveil</string>
diff --git a/matrix-sdk-android/src/main/res/values-hu/strings_sas.xml b/matrix-sdk-android/src/main/res/values-hu/strings_sas.xml
new file mode 100644
index 0000000000000000000000000000000000000000..5b5e0e0205f1b943ae3860fb5c8a70025a38a09a
--- /dev/null
+++ b/matrix-sdk-android/src/main/res/values-hu/strings_sas.xml
@@ -0,0 +1,68 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <!-- Generated file, do not edit -->
+    <string name="verification_emoji_dog">Kutya</string>
+    <string name="verification_emoji_cat">Macska</string>
+    <string name="verification_emoji_lion">Oroszlán</string>
+    <string name="verification_emoji_horse">Ló</string>
+    <string name="verification_emoji_unicorn">Egyszarvú</string>
+    <string name="verification_emoji_pig">Malac</string>
+    <string name="verification_emoji_elephant">Elefánt</string>
+    <string name="verification_emoji_rabbit">Nyúl</string>
+    <string name="verification_emoji_panda">Panda</string>
+    <string name="verification_emoji_rooster">Kakas</string>
+    <string name="verification_emoji_penguin">Pingvin</string>
+    <string name="verification_emoji_turtle">Teknős</string>
+    <string name="verification_emoji_fish">Hal</string>
+    <string name="verification_emoji_octopus">Polip</string>
+    <string name="verification_emoji_butterfly">Pillangó</string>
+    <string name="verification_emoji_flower">Virág</string>
+    <string name="verification_emoji_tree">Fa</string>
+    <string name="verification_emoji_cactus">Kaktusz</string>
+    <string name="verification_emoji_mushroom">Gomba</string>
+    <string name="verification_emoji_globe">Földgömb</string>
+    <string name="verification_emoji_moon">Hold</string>
+    <string name="verification_emoji_cloud">Felhő</string>
+    <string name="verification_emoji_fire">Tűz</string>
+    <string name="verification_emoji_banana">Banán</string>
+    <string name="verification_emoji_apple">Alma</string>
+    <string name="verification_emoji_strawberry">Eper</string>
+    <string name="verification_emoji_corn">Kukorica</string>
+    <string name="verification_emoji_pizza">Pizza</string>
+    <string name="verification_emoji_cake">Süti</string>
+    <string name="verification_emoji_heart">Szív</string>
+    <string name="verification_emoji_smiley">Mosoly</string>
+    <string name="verification_emoji_robot">Robot</string>
+    <string name="verification_emoji_hat">Kalap</string>
+    <string name="verification_emoji_glasses">Szemüveg</string>
+    <string name="verification_emoji_spanner">Csavarkulcs</string>
+    <string name="verification_emoji_santa">Télapó</string>
+    <string name="verification_emoji_thumbs_up">Hüvelykujj fel</string>
+    <string name="verification_emoji_umbrella">Esernyő</string>
+    <string name="verification_emoji_hourglass">Homokóra</string>
+    <string name="verification_emoji_clock">Óra</string>
+    <string name="verification_emoji_gift">Ajándék</string>
+    <string name="verification_emoji_light_bulb">Égő</string>
+    <string name="verification_emoji_book">Könyv</string>
+    <string name="verification_emoji_pencil">Ceruza</string>
+    <string name="verification_emoji_paperclip">Gémkapocs</string>
+    <string name="verification_emoji_scissors">Olló</string>
+    <string name="verification_emoji_lock">Lakat</string>
+    <string name="verification_emoji_key">Kulcs</string>
+    <string name="verification_emoji_hammer">Kalapács</string>
+    <string name="verification_emoji_telephone">Telefon</string>
+    <string name="verification_emoji_flag">Zászló</string>
+    <string name="verification_emoji_train">Vonat</string>
+    <string name="verification_emoji_bicycle">Kerékpár</string>
+    <string name="verification_emoji_aeroplane">Repülő</string>
+    <string name="verification_emoji_rocket">Rakáta</string>
+    <string name="verification_emoji_trophy">Trófea</string>
+    <string name="verification_emoji_ball">Labda</string>
+    <string name="verification_emoji_guitar">Gitár</string>
+    <string name="verification_emoji_trumpet">Trombita</string>
+    <string name="verification_emoji_bell">Harang</string>
+    <string name="verification_emoji_anchor">Horgony</string>
+    <string name="verification_emoji_headphones">Fejhallgató</string>
+    <string name="verification_emoji_folder">Mappa</string>
+    <string name="verification_emoji_pin">Rajszeg</string>
+</resources>
diff --git a/matrix-sdk-android/src/main/res/values-tzm/strings_sas.xml b/matrix-sdk-android/src/main/res/values-tzm/strings_sas.xml
new file mode 100644
index 0000000000000000000000000000000000000000..bebf64e5abdb3b59d8b65875ff95328a31fbe56c
--- /dev/null
+++ b/matrix-sdk-android/src/main/res/values-tzm/strings_sas.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <!-- Generated file, do not edit -->
+    <string name="verification_emoji_dog">Aydi</string>
+    <string name="verification_emoji_cat">Amuc</string>
+    <string name="verification_emoji_lion">Izem</string>
+    <string name="verification_emoji_horse">Ayyis</string>
+    <string name="verification_emoji_pig">Ilef</string>
+    <string name="verification_emoji_elephant">Ilu</string>
+    <string name="verification_emoji_rabbit">Agnin</string>
+    <string name="verification_emoji_rooster">Ayaẓiḍ</string>
+    <string name="verification_emoji_turtle">Ifker</string>
+    <string name="verification_emoji_fish">Aselm</string>
+    <string name="verification_emoji_tree">Aseklu</string>
+    <string name="verification_emoji_mushroom">Agursel</string>
+    <string name="verification_emoji_moon">Ayyur</string>
+    <string name="verification_emoji_fire">Timessi</string>
+    <string name="verification_emoji_banana">Tabanant</string>
+    <string name="verification_emoji_apple">Tadeffuyt</string>
+    <string name="verification_emoji_heart">Ul</string>
+    <string name="verification_emoji_robot">Aá¹›ubu</string>
+    <string name="verification_emoji_hat">Taraza</string>
+    <string name="verification_emoji_book">Adlis</string>
+    <string name="verification_emoji_key">Tasarut</string>
+    <string name="verification_emoji_telephone">Atilifun</string>
+    <string name="verification_emoji_flag">Acenyal</string>
+    <string name="verification_emoji_ball">Tcama</string>
+    <string name="verification_emoji_guitar">Agiá¹­aá¹›</string>
+    <string name="verification_emoji_folder">Asdaw</string>
+</resources>