diff --git a/.idea/misc.xml b/.idea/misc.xml
index 860da66a5ea990f8e3c36fcdb0e2e05dacadf880..ef61796f57624d99df9ef9b2479763aed4ad62eb 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <project version="4">
-  <component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
+  <component name="ProjectRootManager" version="2" languageLevel="JDK_11" project-jdk-name="1.8" project-jdk-type="JavaSDK">
     <output url="file://$PROJECT_DIR$/build/classes" />
   </component>
   <component name="ProjectType">
diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml
deleted file mode 100644
index e497da999824b5c5629039f2e74794dd96b6c419..0000000000000000000000000000000000000000
--- a/.idea/runConfigurations.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<project version="4">
-  <component name="RunConfigurationProducerService">
-    <option name="ignoredProducers">
-      <set>
-        <option value="com.android.tools.idea.compose.preview.runconfiguration.ComposePreviewRunConfigurationProducer" />
-        <option value="org.jetbrains.plugins.gradle.execution.test.runner.AllInPackageGradleConfigurationProducer" />
-        <option value="org.jetbrains.plugins.gradle.execution.test.runner.TestClassGradleConfigurationProducer" />
-        <option value="org.jetbrains.plugins.gradle.execution.test.runner.TestMethodGradleConfigurationProducer" />
-      </set>
-    </option>
-  </component>
-</project>
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
index 22f247b6a7d440171c5033c0e9a34f013be83074..c8a95ea2eca6b0a5d1dab09b98e7defe539bc64c 100644
--- a/build.gradle
+++ b/build.gradle
@@ -2,7 +2,7 @@
 
 buildscript {
     // Ref: https://kotlinlang.org/releases.html
-    ext.kotlin_version = '1.5.10'
+    ext.kotlin_version = '1.5.21'
     ext.kotlin_coroutines_version = "1.5.0"
     repositories {
         google()
@@ -12,7 +12,7 @@ buildscript {
         }
     }
     dependencies {
-        classpath 'com.android.tools.build:gradle:4.2.1'
+        classpath 'com.android.tools.build:gradle:7.0.2'
         classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
 
         // NOTE: Do not place your application dependencies here; they belong
@@ -38,7 +38,8 @@ allprojects {
 
     tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
         // Warnings are potential errors, so stop ignoring them
-        kotlinOptions.allWarningsAsErrors = true
+        // Ignore on the SDK for the moment
+        kotlinOptions.allWarningsAsErrors = false
     }
 
 }
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index e1e2fd2c75e69ac5748b9c767a2b01d8e45f8b30..17ba19021b848c06cb45184d10d07ac47964d70d 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
 distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
-distributionSha256Sum=13bf8d3cf8eeeb5770d19741a59bde9bd966dd78d17f1bbad787a05ef19d1c2d
-distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip
+distributionSha256Sum=9bb8bc05f562f2d42bdf1ba8db62f6b6fa1c3bf6c392228802cc7cb0578fe7e0
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.1.1-all.zip
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle
index fbd7f57ccdace83068848f2e894e9daf338f3f46..23e087b7f1b6e9f58225db7572ff03c9b3013b55 100644
--- a/matrix-sdk-android/build.gradle
+++ b/matrix-sdk-android/build.gradle
@@ -10,7 +10,7 @@ buildscript {
         mavenCentral()
     }
     dependencies {
-        classpath "io.realm:realm-gradle-plugin:10.5.0"
+        classpath "io.realm:realm-gradle-plugin:10.6.1"
     }
 }
 
@@ -22,7 +22,7 @@ android {
         minSdkVersion 21
         targetSdkVersion 30
         versionCode 1
-        versionName "1.1.9"
+        versionName "1.2.0"
         // Multidex is useful for tests
         multiDexEnabled true
         testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
@@ -73,12 +73,12 @@ android {
     }
 
     compileOptions {
-        sourceCompatibility JavaVersion.VERSION_1_8
-        targetCompatibility JavaVersion.VERSION_1_8
+        sourceCompatibility JavaVersion.VERSION_11
+        targetCompatibility JavaVersion.VERSION_11
     }
 
     kotlinOptions {
-        jvmTarget = "1.8"
+        jvmTarget = "11"
     }
 
     sourceSets {
@@ -113,7 +113,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.38'
     def work_version = '2.5.0'
     def retrofit_version = '2.9.0'
 
@@ -121,8 +121,8 @@ dependencies {
     implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlin_coroutines_version"
     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.appcompat:appcompat:1.3.1"
+    implementation "androidx.core:core-ktx:1.6.0"
 
     implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"
     implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version"
@@ -163,39 +163,39 @@ dependencies {
     kapt "com.google.dagger:dagger-compiler:$daggerVersion"
 
     // Logging
-    implementation 'com.jakewharton.timber:timber:4.7.1'
+    implementation 'com.jakewharton.timber:timber:5.0.1'
     implementation 'com.facebook.stetho:stetho-okhttp3:1.6.0'
 
     // Video compression
     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.28'
 
     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'
+    androidTestUtil 'androidx.test:orchestrator:1.4.0'
 }
 
 project.afterEvaluate {
diff --git a/matrix-sdk-android/src/androidTest/AndroidManifest.xml b/matrix-sdk-android/src/androidTest/AndroidManifest.xml
new file mode 100644
index 0000000000000000000000000000000000000000..274bd8c87b14e39bc108254a879aff19425b5bf3
--- /dev/null
+++ b/matrix-sdk-android/src/androidTest/AndroidManifest.xml
@@ -0,0 +1,18 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    package="org.matrix.android.sdk">
+
+    <application>
+
+        <!--
+        This is mandatory to run integration tests
+        -->
+        <provider
+            android:name="androidx.work.impl.WorkManagerInitializer"
+            android:authorities="${applicationId}.workmanager-init"
+            android:exported="false"
+            tools:node="remove" />
+
+    </application>
+
+</manifest>
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/AndroidManifest.xml b/matrix-sdk-android/src/main/AndroidManifest.xml
index 220a168f60ef25659b96f50b9d4a91cb7c09b916..de0731422c11426399db25c4e274d733ba8096e5 100644
--- a/matrix-sdk-android/src/main/AndroidManifest.xml
+++ b/matrix-sdk-android/src/main/AndroidManifest.xml
@@ -10,15 +10,6 @@
 
     <application android:networkSecurityConfig="@xml/network_security_config">
 
-        <!--
-        This is mandatory to run integration tests
-        -->
-        <provider
-            android:name="androidx.work.impl.WorkManagerInitializer"
-            android:authorities="${applicationId}.workmanager-init"
-            android:exported="false"
-            tools:node="remove" />
-
         <!--
          The SDK offers a secured File provider to access downloaded files.
          Access to these file will be given via the FileService, with a temporary
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/logger/LoggerTag.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/logger/LoggerTag.kt
new file mode 100644
index 0000000000000000000000000000000000000000..51f9b50699b6437f7e3e5a2349e024aa38db03b8
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/logger/LoggerTag.kt
@@ -0,0 +1,34 @@
+/*
+ * 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.logger
+
+/**
+ * Parent class for custom logger tags. Can be used with Timber :
+ *
+ * val loggerTag = LoggerTag("MyTag", LoggerTag.VOIP)
+ * Timber.tag(loggerTag.value).v("My log message")
+ */
+open class LoggerTag(_value: String, parentTag: LoggerTag? = null) {
+
+    object VOIP : LoggerTag("VOIP")
+
+    val value: String = if (parentTag == null) {
+        _value
+    } else {
+        "${parentTag.value}/$_value"
+    }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/PushRuleService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/PushRuleService.kt
index d9bf5cfd13b2b0bb3123df5943b82d5d1f30f46a..45343686798e21d4123d2f3f95834a5bf90bd05d 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/PushRuleService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/PushRuleService.kt
@@ -31,7 +31,13 @@ interface PushRuleService {
 
     suspend fun addPushRule(kind: RuleKind, pushRule: PushRule)
 
-    suspend fun updatePushRuleActions(kind: RuleKind, oldPushRule: PushRule, newPushRule: PushRule)
+    /**
+     * Enables/Disables a push rule and updates the actions if necessary
+     * @param enable Enables/Disables the rule
+     * @param actions Actions to update if not null
+     */
+
+    suspend fun updatePushRuleActions(kind: RuleKind, ruleId: String, enable: Boolean, actions: List<Action>?)
 
     suspend fun removePushRule(kind: RuleKind, pushRule: PushRule)
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/rest/PushRule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/rest/PushRule.kt
index 3a9fc4fb834a89e0e738842cc7e3eb3c6ce23bce..31d7770a9f4a62193825f39e81f97dc9f85ac6a7 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/rest/PushRule.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/rest/PushRule.kt
@@ -18,6 +18,7 @@ package org.matrix.android.sdk.api.pushrules.rest
 
 import com.squareup.moshi.Json
 import com.squareup.moshi.JsonClass
+import org.matrix.android.sdk.api.extensions.orFalse
 import org.matrix.android.sdk.api.pushrules.Action
 import org.matrix.android.sdk.api.pushrules.getActions
 import org.matrix.android.sdk.api.pushrules.toJson
@@ -100,6 +101,13 @@ data class PushRule(
         )
     }
 
+    /**
+     * Get the highlight status. As spec mentions assume false if no tweak present.
+     */
+    fun getHighlight(): Boolean {
+        return getActions().filterIsInstance<Action.Highlight>().firstOrNull()?.highlight.orFalse()
+    }
+
     /**
      * Set the notification status.
      *
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/call/CallState.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/call/CallState.kt
index 2dbd1c9b01bf03bac7a6176552e69cb279fc8c46..47a63b4a251a48d260c8c4d081858adc1f729107 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/call/CallState.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/call/CallState.kt
@@ -16,6 +16,8 @@
 
 package org.matrix.android.sdk.api.session.call
 
+import org.matrix.android.sdk.api.session.room.model.call.EndCallReason
+
 sealed class CallState {
 
     /** Idle, setting up objects */
@@ -42,6 +44,6 @@ sealed class CallState {
      * */
     data class Connected(val iceConnectionState: MxPeerConnectionState) : CallState()
 
-    /** Terminated.  Incoming/Outgoing call, the call is terminated */
-    object Terminated : CallState()
+    /** Ended.  Incoming/Outgoing call, the call is terminated */
+    data class Ended(val reason: EndCallReason? = null) : CallState()
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/call/MxCall.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/call/MxCall.kt
index fcc9f7072dee4ae863648e7418bde20a633a3a16..dd23e81cc6a062697aa29bf1322727b6e6ed2697 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/call/MxCall.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/call/MxCall.kt
@@ -18,7 +18,7 @@ package org.matrix.android.sdk.api.session.call
 
 import org.matrix.android.sdk.api.session.room.model.call.CallCandidate
 import org.matrix.android.sdk.api.session.room.model.call.CallCapabilities
-import org.matrix.android.sdk.api.session.room.model.call.CallHangupContent
+import org.matrix.android.sdk.api.session.room.model.call.EndCallReason
 import org.matrix.android.sdk.api.session.room.model.call.SdpType
 import org.matrix.android.sdk.api.util.Optional
 
@@ -69,7 +69,7 @@ interface MxCall : MxCallDetail {
     /**
      * End the call
      */
-    fun hangUp(reason: CallHangupContent.Reason? = null)
+    fun hangUp(reason: EndCallReason? = null)
 
     /**
      * Start a call
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/content/ContentAttachmentData.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/content/ContentAttachmentData.kt
index 98a84b8b662176a71685f37e52fd8cfe83e18d37..7ee26de8db3063903fce7d1f282f560963b4a46d 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/content/ContentAttachmentData.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/content/ContentAttachmentData.kt
@@ -35,7 +35,8 @@ data class ContentAttachmentData(
         val name: String? = null,
         val queryUri: Uri,
         val mimeType: String?,
-        val type: Type
+        val type: Type,
+        val waveform: List<Int>? = null
 ) : Parcelable {
 
     @JsonClass(generateAdapter = false)
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..b49236c3382eba41deb76eef8a3c3e4c026d8d9d 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,9 +32,71 @@ 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
 ) {
+
+    enum class RoomCapabilitySupport {
+        SUPPORTED,
+        SUPPORTED_UNSTABLE,
+        UNSUPPORTED,
+        UNKNOWN
+    }
+
+    /**
+     * Check if a feature is supported by the homeserver.
+     * @return
+     *  UNKNOWN if the server does not implement room caps
+     *  UNSUPPORTED if this feature is not supported
+     *  SUPPORTED if this feature is supported by a stable version
+     *  SUPPORTED_UNSTABLE if this feature is supported by an unstable version
+     *  (unstable version should only be used for dev/experimental purpose)
+     */
+    fun isFeatureSupported(feature: String): RoomCapabilitySupport {
+        if (roomVersions?.capabilities == null) return RoomCapabilitySupport.UNKNOWN
+        val info = roomVersions.capabilities[feature] ?: return RoomCapabilitySupport.UNSUPPORTED
+
+        val preferred = info.preferred ?: info.support.lastOrNull()
+        val versionCap = roomVersions.supportedVersion.firstOrNull { it.version == preferred }
+
+        return when {
+            versionCap == null                            -> {
+                RoomCapabilitySupport.UNKNOWN
+            }
+            versionCap.status == RoomVersionStatus.STABLE -> {
+                RoomCapabilitySupport.SUPPORTED
+            }
+            else                                          -> {
+                RoomCapabilitySupport.SUPPORTED_UNSTABLE
+            }
+        }
+    }
+    fun isFeatureSupported(feature: String, byRoomVersion: String): Boolean {
+        if (roomVersions?.capabilities == null) return false
+        val info = roomVersions.capabilities[feature] ?: return false
+
+        return info.preferred == byRoomVersion || info.support.contains(byRoomVersion)
+    }
+
+    /**
+     * Use this method to know if you should force a version when creating
+     * a room that requires this feature.
+     * You can also use #isFeatureSupported prior to this call to check if the
+     * feature is supported and report some feedback to user.
+     */
+    fun versionOverrideForFeature(feature: String) : String? {
+        val cap = roomVersions?.capabilities?.get(feature)
+        return cap?.preferred ?: cap?.support?.lastOrNull()
+    }
+
     companion object {
         const val MAX_UPLOAD_FILE_SIZE_UNKNOWN = -1L
+        const val ROOM_CAP_KNOCK = "knock"
+        const val ROOM_CAP_RESTRICTED = "restricted"
     }
 }
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..9f8e9aa1d122a89423f440b5263fcb0773ed93cf
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/homeserver/RoomVersionModel.kt
@@ -0,0 +1,39 @@
+/*
+ * 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>,
+        // Keys are capabilities defined per spec, as for now knock or restricted
+        val capabilities: Map<String, RoomCapabilitySupport>?
+)
+
+data class RoomVersionInfo(
+        val version: String,
+        val status: RoomVersionStatus
+)
+
+data class RoomCapabilitySupport(
+        val preferred: String?,
+        val support: List<String>
+)
+
+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/RoomSummaryQueryParams.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomSummaryQueryParams.kt
index 88ec2de768a4774e7ac19b8ae7de9d8db34eb067..b4408575187b8ec5d362980ada7d9e0aa135cef4 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomSummaryQueryParams.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomSummaryQueryParams.kt
@@ -39,12 +39,6 @@ fun spaceSummaryQueryParams(init: (RoomSummaryQueryParams.Builder.() -> Unit) =
             .build()
 }
 
-enum class RoomCategoryFilter {
-    ONLY_DM,
-    ONLY_ROOMS,
-    ALL
-}
-
 /**
  * This class can be used to filter room summaries to use with:
  * [org.matrix.android.sdk.api.session.room.Room] and [org.matrix.android.sdk.api.session.room.RoomService]
@@ -59,11 +53,10 @@ data class RoomSummaryQueryParams(
         val excludeType: List<String?>?,
         val includeType: List<String?>?,
         val activeSpaceFilter: ActiveSpaceFilter?,
-        var activeGroupId: String? = null
+        val activeGroupId: String? = null
 ) {
 
     class Builder {
-
         var roomId: QueryStringValue = QueryStringValue.IsNotEmpty
         var displayName: QueryStringValue = QueryStringValue.IsNotEmpty
         var canonicalAlias: QueryStringValue = QueryStringValue.NoCondition
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/call/CallHangupContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallHangupContent.kt
index 9d6e1a7eaeb09e39308235fdb543827a45694c70..31f801dd6f7b1e8615515920fbfef6c3bd451109 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallHangupContent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallHangupContent.kt
@@ -43,29 +43,5 @@ data class CallHangupContent(
          * or `invite_timeout` for when the other party did not answer in time.
          * One of: ["ice_failed", "invite_timeout"]
          */
-        @Json(name = "reason") val reason: Reason? = null
-) : CallSignalingContent {
-    @JsonClass(generateAdapter = false)
-    enum class Reason {
-        @Json(name = "ice_failed")
-        ICE_FAILED,
-
-        @Json(name = "ice_timeout")
-        ICE_TIMEOUT,
-
-        @Json(name = "user_hangup")
-        USER_HANGUP,
-
-        @Json(name = "replaced")
-        REPLACED,
-
-        @Json(name = "user_media_failed")
-        USER_MEDIA_FAILED,
-
-        @Json(name = "invite_timeout")
-        INVITE_TIMEOUT,
-
-        @Json(name = "unknown_error")
-        UNKWOWN_ERROR
-    }
-}
+        @Json(name = "reason") val reason: EndCallReason? = null
+) : CallSignalingContent
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallRejectContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallRejectContent.kt
index ea412fbe3eba6fd353c671e6d3486020b79dfefd..1b9a7186e2d54c76aa821bce90c0df21821dc4f0 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallRejectContent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallRejectContent.kt
@@ -36,5 +36,10 @@ data class CallRejectContent(
         /**
          * Required. The version of the VoIP specification this message adheres to.
          */
-        @Json(name = "version") override val version: String?
+        @Json(name = "version") override val version: String?,
+
+        /**
+         * Optional error reason for the reject.
+         */
+        @Json(name = "reason") val reason: EndCallReason? = null
 ) : CallSignalingContent
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/EndCallReason.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/EndCallReason.kt
new file mode 100644
index 0000000000000000000000000000000000000000..60e038b2f939e8a2a13b73615a298dbad9b871ad
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/EndCallReason.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2021 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.api.session.room.model.call
+
+import com.squareup.moshi.Json
+import com.squareup.moshi.JsonClass
+
+@JsonClass(generateAdapter = false)
+enum class EndCallReason {
+    @Json(name = "ice_failed")
+    ICE_FAILED,
+
+    @Json(name = "ice_timeout")
+    ICE_TIMEOUT,
+
+    @Json(name = "user_hangup")
+    USER_HANGUP,
+
+    @Json(name = "replaced")
+    REPLACED,
+
+    @Json(name = "user_media_failed")
+    USER_MEDIA_FAILED,
+
+    @Json(name = "invite_timeout")
+    INVITE_TIMEOUT,
+
+    @Json(name = "unknown_error")
+    UNKWOWN_ERROR,
+
+    @Json(name = "user_busy")
+    USER_BUSY,
+
+    @Json(name = "answered_elsewhere")
+    ANSWERED_ELSEWHERE
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/create/CreateRoomParams.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/create/CreateRoomParams.kt
index ca8c66bb3b5b9115a4811c2ab11255e05a139612..566790600036cd34616faeea7261b9148e367e4b 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/create/CreateRoomParams.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/create/CreateRoomParams.kt
@@ -22,10 +22,8 @@ import org.matrix.android.sdk.api.session.room.model.GuestAccess
 import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent
 import org.matrix.android.sdk.api.session.room.model.RoomDirectoryVisibility
 import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility
-import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesAllowEntry
 import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM
 
-// TODO Give a way to include other initial states
 open class CreateRoomParams {
     /**
      * A public visibility indicates that the room will be shown in the published room list.
@@ -103,6 +101,13 @@ open class CreateRoomParams {
      */
     val creationContent = mutableMapOf<String, Any>()
 
+    /**
+     * A list of state events to set in the new room. This allows the user to override the default state events
+     * set in the new room. The expected format of the state events are an object with type, state_key and content keys set.
+     * Takes precedence over events set by preset, but gets overridden by name and topic keys.
+     */
+    val initialStates = mutableListOf<CreateRoomStateEvent>()
+
     /**
      * Set to true to disable federation of this room.
      * Default: false
@@ -156,7 +161,7 @@ open class CreateRoomParams {
 
     var roomVersion: String? = null
 
-    var joinRuleRestricted: List<RoomJoinRulesAllowEntry>? = null
+    var featurePreset: RoomFeaturePreset? = null
 
     companion object {
         private const val CREATION_CONTENT_KEY_M_FEDERATE = "m.federate"
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/create/CreateRoomStateEvent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/create/CreateRoomStateEvent.kt
new file mode 100644
index 0000000000000000000000000000000000000000..fcfdc3e33369bca09e0eb728fc4097dd32b39ee6
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/create/CreateRoomStateEvent.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2021 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.api.session.room.model.create
+
+import org.matrix.android.sdk.api.session.events.model.Content
+
+data class CreateRoomStateEvent(
+        /**
+         * Required. The type of event to send.
+         */
+        val type: String,
+
+        /**
+         * Required. The content of the event.
+         */
+        val content: Content,
+
+        /**
+         * The state_key of the state event. Defaults to an empty string.
+         */
+        val stateKey: String = ""
+)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/create/RoomFeaturePreset.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/create/RoomFeaturePreset.kt
new file mode 100644
index 0000000000000000000000000000000000000000..f5f722d7833059eaa3d2d81d00f287249df0641b
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/create/RoomFeaturePreset.kt
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2020 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.api.session.room.model.create
+
+import org.matrix.android.sdk.api.session.events.model.Event
+import org.matrix.android.sdk.api.session.events.model.EventType
+import org.matrix.android.sdk.api.session.events.model.toContent
+import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities
+import org.matrix.android.sdk.api.session.room.model.GuestAccess
+import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility
+import org.matrix.android.sdk.api.session.room.model.RoomJoinRules
+import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesAllowEntry
+import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesContent
+
+interface RoomFeaturePreset {
+
+    fun updateRoomParams(params: CreateRoomParams)
+
+    fun setupInitialStates(): List<Event>?
+}
+
+class RestrictedRoomPreset(val homeServerCapabilities: HomeServerCapabilities, val restrictedList: List<RoomJoinRulesAllowEntry>) : RoomFeaturePreset {
+
+    override fun updateRoomParams(params: CreateRoomParams) {
+        params.historyVisibility = params.historyVisibility ?: RoomHistoryVisibility.SHARED
+        params.guestAccess = params.guestAccess ?: GuestAccess.Forbidden
+        params.roomVersion = homeServerCapabilities.versionOverrideForFeature(HomeServerCapabilities.ROOM_CAP_RESTRICTED)
+    }
+
+    override fun setupInitialStates(): List<Event>? {
+        return listOf(
+                Event(
+                        type = EventType.STATE_ROOM_JOIN_RULES,
+                        stateKey = "",
+                        content = RoomJoinRulesContent(
+                                _joinRules = RoomJoinRules.RESTRICTED.value,
+                                allowList = restrictedList
+                        ).toContent()
+                )
+        )
+    }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/AudioInfo.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/AudioInfo.kt
index b72c8e5dfd07f8e360cb18af295fb7b7da929b28..ea6bdfafadce97842964c8f2f30b105f85ad3c13 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/AudioInfo.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/AudioInfo.kt
@@ -24,15 +24,15 @@ data class AudioInfo(
         /**
          * The mimetype of the audio e.g. "audio/aac".
          */
-        @Json(name = "mimetype") val mimeType: String?,
+        @Json(name = "mimetype") val mimeType: String? = null,
 
         /**
          * The size of the audio clip in bytes.
          */
-        @Json(name = "size") val size: Long = 0,
+        @Json(name = "size") val size: Long? = null,
 
         /**
          * The duration of the audio in milliseconds.
          */
-        @Json(name = "duration") val duration: Int = 0
+        @Json(name = "duration") val duration: Int? = null
 )
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/AudioWaveformInfo.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/AudioWaveformInfo.kt
new file mode 100644
index 0000000000000000000000000000000000000000..d576f1057a29fa8a42e4b66f375b6f7acc5a62a4
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/AudioWaveformInfo.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2020 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.api.session.room.model.message
+
+import com.squareup.moshi.Json
+import com.squareup.moshi.JsonClass
+
+/**
+ * See https://github.com/matrix-org/matrix-doc/blob/travis/msc/audio-waveform/proposals/3246-audio-waveform.md
+ */
+@JsonClass(generateAdapter = true)
+data class AudioWaveformInfo(
+        @Json(name = "duration")
+        val duration: Int? = null,
+
+        /**
+         * The array should have no less than 30 elements and no more than 120.
+         * List of integers between zero and 1024, inclusive.
+         */
+        @Json(name = "waveform")
+        val waveform: List<Int>? = 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..1bcb10d88cf753987af8c51e7acf373df5833737 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
@@ -20,6 +20,7 @@ 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.JsonDict
 import org.matrix.android.sdk.internal.crypto.model.rest.EncryptedFileInfo
 
 @JsonClass(generateAdapter = true)
@@ -50,9 +51,19 @@ data class MessageAudioContent(
         /**
          * Required if the file is encrypted. Information on the encrypted file, as specified in End-to-end encryption.
          */
-        @Json(name = "file") override val encryptedFileInfo: EncryptedFileInfo? = null
+        @Json(name = "file") override val encryptedFileInfo: EncryptedFileInfo? = null,
+
+        /**
+         * Encapsulates waveform and duration of the audio.
+         */
+        @Json(name = "org.matrix.msc1767.audio") val audioWaveformInfo: AudioWaveformInfo? = null,
+
+        /**
+         * Indicates that is a voice message.
+         */
+        @Json(name = "org.matrix.msc3245.voice") val voiceMessageIndicator: JsonDict? = null
 ) : 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/state/StateService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/state/StateService.kt
index e614ea91d649b8cd712926d78c0a1a6176bdb2a8..4d3f95233d05d54e8202c1a48084b9913ecb001b 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/state/StateService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/state/StateService.kt
@@ -23,6 +23,7 @@ import org.matrix.android.sdk.api.session.events.model.Event
 import org.matrix.android.sdk.api.session.room.model.GuestAccess
 import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility
 import org.matrix.android.sdk.api.session.room.model.RoomJoinRules
+import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesAllowEntry
 import org.matrix.android.sdk.api.util.JsonDict
 import org.matrix.android.sdk.api.util.Optional
 
@@ -53,7 +54,7 @@ interface StateService {
     /**
      * Update the join rule and/or the guest access
      */
-    suspend fun updateJoinRule(joinRules: RoomJoinRules?, guestAccess: GuestAccess?)
+    suspend fun updateJoinRule(joinRules: RoomJoinRules?, guestAccess: GuestAccess?, allowList: List<RoomJoinRulesAllowEntry>? = null)
 
     /**
      * Update the avatar of the room
@@ -91,4 +92,8 @@ interface StateService {
      * @param eventTypes Set of eventType to observe. If empty, all state events will be observed
      */
     fun getStateEventsLive(eventTypes: Set<String>, stateKey: QueryStringValue = QueryStringValue.NoCondition): LiveData<List<Event>>
+
+    suspend fun setJoinRulePublic()
+    suspend fun setJoinRuleInviteOnly()
+    suspend fun setJoinRuleRestricted(allowList: List<String>)
 }
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/MimeTypes.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/MimeTypes.kt
index 182b37f2ad018e609a1d480fabb3659c427c59de..ef47775f1bec34033b2e42ab9207ed67de510c06 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/MimeTypes.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/MimeTypes.kt
@@ -31,6 +31,8 @@ object MimeTypes {
     const val Jpeg = "image/jpeg"
     const val Gif = "image/gif"
 
+    const val Ogg = "audio/ogg"
+
     fun String?.normalizeMimeType() = if (this == BadJpg) Jpeg else this
 
     fun String?.isMimeTypeImage() = this?.startsWith("image/").orFalse()
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..cc00c963ea1b2550254ccf5346a36f7f1cdbab4f 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 {
@@ -49,6 +49,8 @@ internal class DefaultSessionCreator @Inject constructor(
                 // remove trailing "/"
                 ?.trim { it == '/' }
                 ?.takeIf { it.isNotBlank() }
+                // It can be the same value, so in this case, do not check again the validity
+                ?.takeIf { it != homeServerConnectionConfig.homeServerUriBase.toString() }
                 ?.also { Timber.d("Overriding homeserver url to $it (will check if valid)") }
                 ?.let { Uri.parse(it) }
                 ?.takeIf {
@@ -56,7 +58,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 +68,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..563c89095040dc6c3a6d2b9bdae6140e547ace80 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
@@ -314,6 +314,12 @@ internal class DefaultCryptoService @Inject constructor(
         cryptoCoroutineScope.launchToCallback(coroutineDispatchers.crypto, NoOpMatrixCallback()) {
             // Open the store
             cryptoStore.open()
+
+            if (!cryptoStore.areDeviceKeysUploaded()) {
+                // Schedule upload of OTK
+                oneTimeKeysUploader.updateOneTimeKeyCount(0)
+            }
+
             // this can throw if no network
             tryOrNull {
                 uploadDeviceKeys()
@@ -388,7 +394,7 @@ internal class DefaultCryptoService @Inject constructor(
         cryptoStore.close()
     }
 
-    // Aways enabled on RiotX
+    // Always enabled on Matrix Android SDK2
     override fun isCryptoEnabled() = true
 
     /**
@@ -905,7 +911,7 @@ internal class DefaultCryptoService @Inject constructor(
      * Upload my user's device keys.
      */
     private suspend fun uploadDeviceKeys() {
-        if (cryptoStore.getDeviceKeysUploaded()) {
+        if (cryptoStore.areDeviceKeysUploaded()) {
             Timber.d("Keys already uploaded, nothing to do")
             return
         }
@@ -928,14 +934,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 +965,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/DeviceListManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt
index 63f15aaf6e6ca0ce3555b566746f282191194696..79910c6de29b9c40b6af67ea47be2a6a9c65422a 100755
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt
@@ -16,6 +16,7 @@
 
 package org.matrix.android.sdk.internal.crypto
 
+import kotlinx.coroutines.CancellationException
 import kotlinx.coroutines.launch
 import org.matrix.android.sdk.api.MatrixPatterns
 import org.matrix.android.sdk.api.auth.data.Credentials
@@ -336,7 +337,12 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
             downloadKeysForUsersTask.execute(params)
         } catch (throwable: Throwable) {
             Timber.e(throwable, "## CRYPTO | doKeyDownloadForUsers(): error")
-            onKeysDownloadFailed(filteredUsers)
+            if (throwable is CancellationException) {
+                // the crypto module is getting closed, so we cannot access the DB anymore
+                Timber.w("The crypto module is closed, ignoring this error")
+            } else {
+                onKeysDownloadFailed(filteredUsers)
+            }
             throw throwable
         }
         Timber.v("## CRYPTO | doKeyDownloadForUsers() : Got keys for " + filteredUsers.size + " users")
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OneTimeKeysUploader.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OneTimeKeysUploader.kt
index 6695234d621940d0dd24a7299b2077f07bf65837..c4b62fe9fee560c047786e10499b13eb817156ed 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OneTimeKeysUploader.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OneTimeKeysUploader.kt
@@ -16,6 +16,7 @@
 
 package org.matrix.android.sdk.internal.crypto
 
+import org.matrix.android.sdk.api.extensions.tryOrNull
 import org.matrix.android.sdk.internal.crypto.model.MXKey
 import org.matrix.android.sdk.internal.crypto.model.rest.KeysUploadResponse
 import org.matrix.android.sdk.internal.crypto.tasks.UploadKeysTask
@@ -77,6 +78,10 @@ internal class OneTimeKeysUploader @Inject constructor(
         // discard the oldest private keys first. This will eventually clean
         // out stale private keys that won't receive a message.
         val keyLimit = floor(maxOneTimeKeys / 2.0).toInt()
+        if (oneTimeKeyCount == null) {
+            // Ask the server how many otk he has
+            oneTimeKeyCount = fetchOtkCount()
+        }
         val oneTimeKeyCountFromSync = oneTimeKeyCount
         if (oneTimeKeyCountFromSync != null) {
             // We need to keep a pool of one time public keys on the server so that
@@ -90,17 +95,22 @@ internal class OneTimeKeysUploader @Inject constructor(
             // private keys clogging up our local storage.
             // So we need some kind of engineering compromise to balance all of
             // these factors.
-            try {
+            tryOrNull("Unable to upload OTK") {
                 val uploadedKeys = uploadOTK(oneTimeKeyCountFromSync, keyLimit)
                 Timber.v("## uploadKeys() : success, $uploadedKeys key(s) sent")
-            } finally {
-                oneTimeKeyCheckInProgress = false
             }
         } else {
             Timber.w("maybeUploadOneTimeKeys: waiting to know the number of OTK from the sync")
-            oneTimeKeyCheckInProgress = false
             lastOneTimeKeyCheck = 0
         }
+        oneTimeKeyCheckInProgress = false
+    }
+
+    private suspend fun fetchOtkCount(): Int? {
+        return tryOrNull("Unable to get OTK count") {
+            val result = uploadKeysTask.execute(UploadKeysTask.Params(null, null))
+            result.oneTimeKeyCountsForAlgorithm(MXKey.KEY_SIGNED_CURVE_25519_TYPE)
+        }
     }
 
     /**
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/CryptoDeviceInfo.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/CryptoDeviceInfo.kt
index 4004294d973da3fdfa47dda69d1e9055e29c7522..5e7744853aa464a9515fb1fb9c807e1c5321617d 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/CryptoDeviceInfo.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/CryptoDeviceInfo.kt
@@ -18,8 +18,6 @@ package org.matrix.android.sdk.internal.crypto.model
 import org.matrix.android.sdk.internal.crypto.crosssigning.DeviceTrustLevel
 import org.matrix.android.sdk.internal.crypto.model.rest.DeviceKeys
 import org.matrix.android.sdk.internal.crypto.model.rest.UnsignedDeviceInfo
-import org.matrix.android.sdk.internal.crypto.store.db.model.CryptoMapper
-import org.matrix.android.sdk.internal.crypto.store.db.model.DeviceInfoEntity
 
 data class CryptoDeviceInfo(
         val deviceId: String,
@@ -77,7 +75,3 @@ data class CryptoDeviceInfo(
 internal fun CryptoDeviceInfo.toRest(): DeviceKeys {
     return CryptoInfoMapper.map(this)
 }
-
-internal fun CryptoDeviceInfo.toEntity(): DeviceInfoEntity {
-    return CryptoMapper.mapToEntity(this)
-}
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/IMXCryptoStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt
index 181bd94cc7e832718a8aa3595108c03e092040cb..3d12e74fcdd061833de6a12d988ee5abab3e3432 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt
@@ -475,7 +475,7 @@ internal interface IMXCryptoStore {
     fun getGossipingEvents(): List<Event>
 
     fun setDeviceKeysUploaded(uploaded: Boolean)
-    fun getDeviceKeysUploaded(): Boolean
+    fun areDeviceKeysUploaded(): Boolean
     fun tidyUpDataBase()
     fun logDbUsageInfo()
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt
index 9ae93d61eb88be0b71cd6aeedf42a058ff123ff3..d99799883658e236bba62e3509a75d927f169a4f 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt
@@ -51,7 +51,6 @@ import org.matrix.android.sdk.internal.crypto.model.OutboundGroupSessionWrapper
 import org.matrix.android.sdk.internal.crypto.model.event.RoomKeyWithHeldContent
 import org.matrix.android.sdk.internal.crypto.model.rest.DeviceInfo
 import org.matrix.android.sdk.internal.crypto.model.rest.RoomKeyRequestBody
-import org.matrix.android.sdk.internal.crypto.model.toEntity
 import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
 import org.matrix.android.sdk.internal.crypto.store.PrivateKeysInfo
 import org.matrix.android.sdk.internal.crypto.store.SavedKeyBackupKeyInfo
@@ -280,24 +279,37 @@ internal class RealmCryptoStore @Inject constructor(
     override fun storeUserDevices(userId: String, devices: Map<String, CryptoDeviceInfo>?) {
         doRealmTransaction(realmConfiguration) { realm ->
             if (devices == null) {
+                Timber.d("Remove user $userId")
                 // Remove the user
                 UserEntity.delete(realm, userId)
             } else {
-                UserEntity.getOrCreate(realm, userId)
-                        .let { u ->
-                            // Add the devices
-                            val currentKnownDevices = u.devices.toList()
-                            val new = devices.map { entry -> entry.value.toEntity() }
-                            new.forEach { entity ->
-                                // Maintain first time seen
-                                val existing = currentKnownDevices.firstOrNull { it.deviceId == entity.deviceId && it.identityKey == entity.identityKey }
-                                entity.firstTimeSeenLocalTs = existing?.firstTimeSeenLocalTs ?: System.currentTimeMillis()
-                                realm.insertOrUpdate(entity)
-                            }
-                            // Ensure all other devices are deleted
-                            u.devices.clearWith { it.deleteOnCascade() }
-                            u.devices.addAll(new)
+                val userEntity = UserEntity.getOrCreate(realm, userId)
+                // First delete the removed devices
+                val deviceIds = devices.keys
+                userEntity.devices.toTypedArray().iterator().let {
+                    while (it.hasNext()) {
+                        val deviceInfoEntity = it.next()
+                        if (deviceInfoEntity.deviceId !in deviceIds) {
+                            Timber.d("Remove device ${deviceInfoEntity.deviceId} of user $userId")
+                            deviceInfoEntity.deleteOnCascade()
                         }
+                    }
+                }
+                // Then update existing devices or add new one
+                devices.values.forEach { cryptoDeviceInfo ->
+                    val existingDeviceInfoEntity = userEntity.devices.firstOrNull { it.deviceId == cryptoDeviceInfo.deviceId }
+                    if (existingDeviceInfoEntity == null) {
+                        // Add the device
+                        Timber.d("Add device ${cryptoDeviceInfo.deviceId} of user $userId")
+                        val newEntity = CryptoMapper.mapToEntity(cryptoDeviceInfo)
+                        newEntity.firstTimeSeenLocalTs = System.currentTimeMillis()
+                        userEntity.devices.add(newEntity)
+                    } else {
+                        // Update the device
+                        Timber.d("Update device ${cryptoDeviceInfo.deviceId} of user $userId")
+                        CryptoMapper.updateDeviceInfoEntity(existingDeviceInfoEntity, cryptoDeviceInfo)
+                    }
+                }
             }
         }
     }
@@ -937,7 +949,7 @@ internal class RealmCryptoStore @Inject constructor(
         }
     }
 
-    override fun getDeviceKeysUploaded(): Boolean {
+    override fun areDeviceKeysUploaded(): Boolean {
         return doWithRealm(realmConfiguration) {
             it.where<CryptoMetadataEntity>().findFirst()?.deviceKeysSentToServer
         } ?: false
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..2846be993255241c15d9f5279373581645acecad 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 = 13L
 
     private fun RealmObjectSchema.addFieldIfNotExists(fieldName: String, fieldType: Class<*>): RealmObjectSchema {
         if (!hasField(fieldName)) {
@@ -95,6 +93,7 @@ internal class RealmCryptoStoreMigration @Inject constructor(private val crossSi
         if (oldVersion <= 9) migrateTo10(realm)
         if (oldVersion <= 10) migrateTo11(realm)
         if (oldVersion <= 11) migrateTo12(realm)
+        if (oldVersion <= 12) migrateTo13(realm)
     }
 
     private fun migrateTo1Legacy(realm: DynamicRealm) {
@@ -384,6 +383,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 {
@@ -497,4 +498,60 @@ internal class RealmCryptoStoreMigration @Inject constructor(private val crossSi
         realm.schema.get("CryptoRoomEntity")
                 ?.addRealmObjectField(CryptoRoomEntityFields.OUTBOUND_SESSION_INFO.`$`, outboundEntitySchema)
     }
+
+    // Version 13L delete unreferenced TrustLevelEntity
+    private fun migrateTo13(realm: DynamicRealm) {
+        Timber.d("Step 12 -> 13")
+
+        // Use a trick to do that... Ref: https://stackoverflow.com/questions/55221366
+        val trustLevelEntitySchema = realm.schema.get("TrustLevelEntity")
+
+        /*
+        Creating a new temp field called isLinked which is set to true for those which are
+        references by other objects. Rest of them are set to false. Then removing all
+        those which are false and hence duplicate and unnecessary. Then removing the temp field
+        isLinked
+         */
+        var mainCounter = 0
+        var deviceInfoCounter = 0
+        var keyInfoCounter = 0
+        val deleteCounter: Int
+
+        trustLevelEntitySchema
+                ?.addField("isLinked", Boolean::class.java)
+                ?.transform { obj ->
+                    // Setting to false for all by default
+                    obj.set("isLinked", false)
+                    mainCounter++
+                }
+
+        realm.schema.get("DeviceInfoEntity")?.transform { obj ->
+            // Setting to true for those which are referenced in DeviceInfoEntity
+            deviceInfoCounter++
+            obj.getObject("trustLevelEntity")?.set("isLinked", true)
+        }
+
+        realm.schema.get("KeyInfoEntity")?.transform { obj ->
+            // Setting to true for those which are referenced in KeyInfoEntity
+            keyInfoCounter++
+            obj.getObject("trustLevelEntity")?.set("isLinked", true)
+        }
+
+        // Removing all those which are set as false
+        realm.where("TrustLevelEntity")
+                .equalTo("isLinked", false)
+                .findAll()
+                .also { deleteCounter = it.size }
+                .deleteAllFromRealm()
+
+        trustLevelEntitySchema?.removeField("isLinked")
+
+        Timber.w("TrustLevelEntity cleanup: $mainCounter entities")
+        Timber.w("TrustLevelEntity cleanup: $deviceInfoCounter entities referenced in DeviceInfoEntities")
+        Timber.w("TrustLevelEntity cleanup: $keyInfoCounter entities referenced in KeyInfoEntity")
+        Timber.w("TrustLevelEntity cleanup: $deleteCounter entities deleted!")
+        if (mainCounter != deviceInfoCounter + keyInfoCounter + deleteCounter) {
+            Timber.e("TrustLevelEntity cleanup: Something is not correct...")
+        }
+    }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/CryptoMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/CryptoMapper.kt
index 37d144169055cf3a30d9c195a882211c3f44212e..7ba986699ab60bf26acf5e2891225096a0fb6897 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/CryptoMapper.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/CryptoMapper.kt
@@ -44,23 +44,32 @@ object CryptoMapper {
     ))
 
     internal fun mapToEntity(deviceInfo: CryptoDeviceInfo): DeviceInfoEntity {
-        return DeviceInfoEntity(
-                primaryKey = DeviceInfoEntity.createPrimaryKey(deviceInfo.userId, deviceInfo.deviceId),
-                userId = deviceInfo.userId,
-                deviceId = deviceInfo.deviceId,
-                algorithmListJson = listMigrationAdapter.toJson(deviceInfo.algorithms),
-                keysMapJson = mapMigrationAdapter.toJson(deviceInfo.keys),
-                signatureMapJson = mapMigrationAdapter.toJson(deviceInfo.signatures),
-                isBlocked = deviceInfo.isBlocked,
-                trustLevelEntity = deviceInfo.trustLevel?.let {
-                    TrustLevelEntity(
-                            crossSignedVerified = it.crossSigningVerified,
-                            locallyVerified = it.locallyVerified
-                    )
-                },
-                // We store the device name if present now
-                unsignedMapJson = deviceInfo.unsigned?.deviceDisplayName
-        )
+        return DeviceInfoEntity(primaryKey = DeviceInfoEntity.createPrimaryKey(deviceInfo.userId, deviceInfo.deviceId))
+                .also { updateDeviceInfoEntity(it, deviceInfo) }
+    }
+
+    internal fun updateDeviceInfoEntity(entity: DeviceInfoEntity, deviceInfo: CryptoDeviceInfo) {
+        entity.userId = deviceInfo.userId
+        entity.deviceId = deviceInfo.deviceId
+        entity.algorithmListJson = listMigrationAdapter.toJson(deviceInfo.algorithms)
+        entity.keysMapJson = mapMigrationAdapter.toJson(deviceInfo.keys)
+        entity.signatureMapJson = mapMigrationAdapter.toJson(deviceInfo.signatures)
+        entity.isBlocked = deviceInfo.isBlocked
+        val deviceInfoTrustLevel = deviceInfo.trustLevel
+        if (deviceInfoTrustLevel == null) {
+            entity.trustLevelEntity?.deleteFromRealm()
+            entity.trustLevelEntity = null
+        } else {
+            if (entity.trustLevelEntity == null) {
+                // Create a new TrustLevelEntity object
+                entity.trustLevelEntity = TrustLevelEntity()
+            }
+            // Update the existing TrustLevelEntity object
+            entity.trustLevelEntity?.crossSignedVerified = deviceInfoTrustLevel.crossSigningVerified
+            entity.trustLevelEntity?.locallyVerified = deviceInfoTrustLevel.locallyVerified
+        }
+        // We store the device name if present now
+        entity.unsignedMapJson = deviceInfo.unsigned?.deviceDisplayName
     }
 
     internal fun mapToModel(deviceInfoEntity: DeviceInfoEntity): CryptoDeviceInfo {
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/crypto/verification/VerificationTransportToDevice.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationTransportToDevice.kt
index 0dbbe656c71bb7fac8e473c4e8ee8ab04bba1d7a..45f81439377b97a00ae26d332b86ecddbea8144c 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationTransportToDevice.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationTransportToDevice.kt
@@ -68,7 +68,7 @@ internal class VerificationTransportToDevice(
             contentMap.setObject(otherUserId, it, keyReq)
         }
         sendToDeviceTask
-                .configureWith(SendToDeviceTask.Params(MessageType.MSGTYPE_VERIFICATION_REQUEST, contentMap, localId)) {
+                .configureWith(SendToDeviceTask.Params(MessageType.MSGTYPE_VERIFICATION_REQUEST, contentMap)) {
                     this.callback = object : MatrixCallback<Unit> {
                         override fun onSuccess(data: Unit) {
                             Timber.v("## verification [$tx.transactionId] send toDevice request success")
@@ -124,7 +124,7 @@ internal class VerificationTransportToDevice(
         contentMap.setObject(tx.otherUserId, tx.otherDeviceId, toSendToDeviceObject)
 
         sendToDeviceTask
-                .configureWith(SendToDeviceTask.Params(type, contentMap, tx.transactionId)) {
+                .configureWith(SendToDeviceTask.Params(type, contentMap)) {
                     this.callback = object : MatrixCallback<Unit> {
                         override fun onSuccess(data: Unit) {
                             Timber.v("## SAS verification [$tx.transactionId] toDevice type '$type' success.")
@@ -155,7 +155,7 @@ internal class VerificationTransportToDevice(
         val contentMap = MXUsersDevicesMap<Any>()
         contentMap.setObject(otherUserId, otherUserDeviceId, cancelMessage)
         sendToDeviceTask
-                .configureWith(SendToDeviceTask.Params(EventType.KEY_VERIFICATION_DONE, contentMap, transactionId)) {
+                .configureWith(SendToDeviceTask.Params(EventType.KEY_VERIFICATION_DONE, contentMap)) {
                     this.callback = object : MatrixCallback<Unit> {
                         override fun onSuccess(data: Unit) {
                             onDone?.invoke()
@@ -176,7 +176,7 @@ internal class VerificationTransportToDevice(
         val contentMap = MXUsersDevicesMap<Any>()
         contentMap.setObject(otherUserId, otherUserDeviceId, cancelMessage)
         sendToDeviceTask
-                .configureWith(SendToDeviceTask.Params(EventType.KEY_VERIFICATION_CANCEL, contentMap, transactionId)) {
+                .configureWith(SendToDeviceTask.Params(EventType.KEY_VERIFICATION_CANCEL, contentMap)) {
                     this.callback = object : MatrixCallback<Unit> {
                         override fun onSuccess(data: Unit) {
                             Timber.v("## SAS verification [$transactionId] canceled for reason ${code.value}")
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..8b6d263f8c7e7bbf6ffe3bd4ed58e689d36451f6 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,16 @@
 
 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.RoomCapabilitySupport
+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 +37,39 @@ 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 { roomVersions ->
+                RoomVersionCapabilities(
+                        defaultRoomVersion = roomVersions.default ?: DefaultRoomVersionService.DEFAULT_ROOM_VERSION,
+                        supportedVersion = roomVersions.available?.entries?.map { entry ->
+                            RoomVersionInfo(entry.key, RoomVersionStatus.STABLE
+                                    .takeIf { entry.value == "stable" }
+                                    ?: RoomVersionStatus.UNSTABLE)
+                        }.orEmpty(),
+                        capabilities = roomVersions.roomCapabilities?.entries?.mapNotNull { entry ->
+                            (entry.value as? Map<*, *>)?.let {
+                                val preferred = it["preferred"] as? String ?: return@mapNotNull null
+                                val support = (it["support"] as? List<*>)?.filterIsInstance<String>()
+                                entry.key to RoomCapabilitySupport(preferred, support.orEmpty())
+                            }
+                        }?.toMap()
+                        // Just for debug purpose
+//                                ?: mapOf(
+//                                HomeServerCapabilities.ROOM_CAP_RESTRICTED to RoomCapabilitySupport(
+//                                        preferred = null,
+//                                        support = listOf("org.matrix.msc3083")
+//                                )
+//                                )
+                )
+            }
+        }
+    }
 }
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/DefaultFileService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt
index a284d976d0525187ce18e5306e488f7a29f2cd6f..414c018074dad12c116ad4c4594a47d1fd14dc5e 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt
@@ -25,6 +25,7 @@ import kotlinx.coroutines.completeWith
 import kotlinx.coroutines.withContext
 import okhttp3.OkHttpClient
 import okhttp3.Request
+import org.matrix.android.sdk.api.failure.Failure
 import org.matrix.android.sdk.api.session.content.ContentUrlResolver
 import org.matrix.android.sdk.api.session.file.FileService
 import org.matrix.android.sdk.internal.crypto.attachments.ElementToDecrypt
@@ -33,6 +34,7 @@ import org.matrix.android.sdk.internal.di.SessionDownloadsDirectory
 import org.matrix.android.sdk.internal.di.UnauthenticatedWithCertificateWithProgress
 import org.matrix.android.sdk.internal.session.download.DownloadProgressInterceptor.Companion.DOWNLOAD_PROGRESS_INTERCEPTOR_HEADER
 import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers
+import org.matrix.android.sdk.internal.util.file.AtomicFileCreator
 import org.matrix.android.sdk.internal.util.md5
 import org.matrix.android.sdk.internal.util.writeToFile
 import timber.log.Timber
@@ -96,6 +98,9 @@ internal class DefaultFileService @Inject constructor(
             }
         }
 
+        var atomicFileDownload: AtomicFileCreator? = null
+        var atomicFileDecrypt: AtomicFileCreator? = null
+
         if (existingDownload != null) {
             // FIXME If the first downloader cancels then we'll unfortunately be cancelled too.
             return existingDownload.await()
@@ -120,19 +125,30 @@ internal class DefaultFileService @Inject constructor(
                             .header(DOWNLOAD_PROGRESS_INTERCEPTOR_HEADER, url)
                             .build()
 
-                    val response = okHttpClient.newCall(request).execute()
+                    val response = try {
+                        okHttpClient.newCall(request).execute()
+                    } catch (failure: Throwable) {
+                        throw if (failure is IOException) {
+                            Failure.NetworkConnection(failure)
+                        } else {
+                            failure
+                        }
+                    }
 
                     if (!response.isSuccessful) {
-                        throw IOException()
+                        throw Failure.NetworkConnection(IOException())
                     }
 
-                    val source = response.body?.source() ?: throw IOException()
+                    val source = response.body?.source() ?: throw Failure.NetworkConnection(IOException())
 
                     Timber.v("Response size ${response.body?.contentLength()} - Stream available: ${!source.exhausted()}")
 
                     // Write the file to cache (encrypted version if the file is encrypted)
-                    writeToFile(source.inputStream(), cachedFiles.file)
+                    // Write to a part file first, so if we abort before done, we don't have a broken cached file
+                    val atomicFileCreator = AtomicFileCreator(cachedFiles.file).also { atomicFileDownload = it }
+                    writeToFile(source.inputStream(), atomicFileCreator.partFile)
                     response.close()
+                    atomicFileCreator.commit()
                 } else {
                     Timber.v("## FileService: cache hit for $url")
                 }
@@ -145,8 +161,10 @@ internal class DefaultFileService @Inject constructor(
                     Timber.v("## FileService: decrypt file")
                     // Ensure the parent folder exists
                     cachedFiles.decryptedFile.parentFile?.mkdirs()
+                    // Write to a part file first, so if we abort before done, we don't have a broken cached file
+                    val atomicFileCreator = AtomicFileCreator(cachedFiles.decryptedFile).also { atomicFileDecrypt = it }
                     val decryptSuccess = cachedFiles.file.inputStream().use { inputStream ->
-                        cachedFiles.decryptedFile.outputStream().buffered().use { outputStream ->
+                        atomicFileCreator.partFile.outputStream().buffered().use { outputStream ->
                             MXEncryptedAttachments.decryptAttachment(
                                     inputStream,
                                     elementToDecrypt,
@@ -154,6 +172,7 @@ internal class DefaultFileService @Inject constructor(
                             )
                         }
                     }
+                    atomicFileCreator.commit()
                     if (!decryptSuccess) {
                         throw IllegalStateException("Decryption error")
                     }
@@ -174,6 +193,11 @@ internal class DefaultFileService @Inject constructor(
         }
         toNotify?.completeWith(result)
 
+        result.onFailure {
+            atomicFileDownload?.cancel()
+            atomicFileDecrypt?.cancel()
+        }
+
         return result.getOrThrow()
     }
 
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..bdc254fc99cf20d4ffb75b650357e12fe6824d60 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
@@ -20,11 +20,14 @@ import io.realm.Realm
 import org.matrix.android.sdk.api.session.events.model.Event
 import org.matrix.android.sdk.api.session.events.model.EventType
 import org.matrix.android.sdk.internal.database.model.EventInsertType
+import org.matrix.android.sdk.api.logger.LoggerTag
 import org.matrix.android.sdk.internal.session.EventInsertLiveProcessor
 import org.matrix.android.sdk.internal.session.SessionScope
 import timber.log.Timber
 import javax.inject.Inject
 
+private val loggerTag = LoggerTag("CallEventProcessor", LoggerTag.VOIP)
+
 @SessionScope
 internal class CallEventProcessor @Inject constructor(private val callSignalingHandler: CallSignalingHandler)
     : EventInsertLiveProcessor {
@@ -37,7 +40,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 +62,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() {
@@ -70,15 +74,8 @@ 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?
-            return
+            Timber.tag(loggerTag.value).w("Event with no room id ${event.eventId}")
         }
         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..59058bf9767648cea08f3dea30bbfb7bfca60502 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
@@ -16,6 +16,7 @@
 
 package org.matrix.android.sdk.internal.session.call
 
+import org.matrix.android.sdk.api.logger.LoggerTag
 import org.matrix.android.sdk.api.session.call.CallListener
 import org.matrix.android.sdk.api.session.call.CallState
 import org.matrix.android.sdk.api.session.call.MxCall
@@ -23,6 +24,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
@@ -35,11 +37,15 @@ import org.matrix.android.sdk.internal.session.SessionScope
 import timber.log.Timber
 import javax.inject.Inject
 
+private val loggerTag = LoggerTag("CallSignalingHandler", LoggerTag.VOIP)
+private const val MAX_AGE_TO_RING = 40_000
+
 @SessionScope
 internal class CallSignalingHandler @Inject constructor(private val activeCallHandler: ActiveCallHandler,
                                                         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,30 +59,44 @@ 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) {
         val content = event.getClearContent().toModel<CallNegotiateContent>() ?: return
         val call = content.getCall() ?: return
@@ -95,12 +115,12 @@ internal class CallSignalingHandler @Inject constructor(private val activeCallHa
             return
         }
         if (call.isOutgoing) {
-            Timber.v("Got selectAnswer for an outbound call: ignoring")
+            Timber.tag(loggerTag.value).v("Got selectAnswer for an outbound call: ignoring")
             return
         }
         val selectedPartyId = content.selectedPartyId
         if (selectedPartyId == null) {
-            Timber.w("Got nonsensical select_answer with null selected_party_id: ignoring")
+            Timber.tag(loggerTag.value).w("Got nonsensical select_answer with null selected_party_id: ignoring")
             return
         }
         callListenersDispatcher.onCallSelectAnswerReceived(content)
@@ -114,7 +134,7 @@ internal class CallSignalingHandler @Inject constructor(private val activeCallHa
             return
         }
         if (call.opponentPartyId != null && !call.partyIdsMatches(content)) {
-            Timber.v("Ignoring candidates from party ID ${content.partyId} we have chosen party ID ${call.opponentPartyId}")
+            Timber.tag(loggerTag.value).v("Ignoring candidates from party ID ${content.partyId} we have chosen party ID ${call.opponentPartyId}")
             return
         }
         callListenersDispatcher.onCallIceCandidateReceived(call, content)
@@ -147,10 +167,10 @@ internal class CallSignalingHandler @Inject constructor(private val activeCallHa
         // party ID must match (our chosen partner hanging up the call) or be undefined (we haven't chosen
         // a partner yet but we're treating the hangup as a reject as per VoIP v0)
         if (call.opponentPartyId != null && !call.partyIdsMatches(content)) {
-            Timber.v("Ignoring hangup from party ID ${content.partyId} we have chosen party ID ${call.opponentPartyId}")
+            Timber.tag(loggerTag.value).v("Ignoring hangup from party ID ${content.partyId} we have chosen party ID ${call.opponentPartyId}")
             return
         }
-        if (call.state != CallState.Terminated) {
+        if (call.state !is CallState.Ended) {
             activeCallHandler.removeCall(content.callId)
             callListenersDispatcher.onCallHangupReceived(content)
         }
@@ -164,20 +184,26 @@ internal class CallSignalingHandler @Inject constructor(private val activeCallHa
         if (event.roomId == null || event.senderId == null) {
             return
         }
+        val now = System.currentTimeMillis()
+        val age = now - (event.ageLocalTs ?: now)
+        if (age > MAX_AGE_TO_RING) {
+            Timber.tag(loggerTag.value).w("Call invite is too old to ring.")
+            return
+        }
         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")
+            Timber.tag(loggerTag.value).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)
     }
@@ -198,7 +224,8 @@ internal class CallSignalingHandler @Inject constructor(private val activeCallHa
             callListenersDispatcher.onCallManagedByOtherSession(content.callId)
         } else {
             if (call.opponentPartyId != null) {
-                Timber.v("Ignoring answer from party ID ${content.partyId} we already have an answer from ${call.opponentPartyId}")
+                Timber.tag(loggerTag.value)
+                        .v("Ignoring answer from party ID ${content.partyId} we already have an answer from ${call.opponentPartyId}")
                 return
             }
             mxCallFactory.updateOutgoingCallWithOpponentData(call, event.senderId, content, content.capabilities)
@@ -215,7 +242,7 @@ internal class CallSignalingHandler @Inject constructor(private val activeCallHa
             activeCallHandler.getCallWithId(it)
         }
         if (currentCall == null) {
-            Timber.v("Call with id $callId is null")
+            Timber.tag(loggerTag.value).v("Call with id $callId is null")
         }
         return currentCall
     }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/DefaultCallSignalingService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/DefaultCallSignalingService.kt
index da1f84cc899f297a337de1c408af899f8c1ebf08..4a949e21a691c20fd8da9e51ee05e84f6b5ef882 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/DefaultCallSignalingService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/DefaultCallSignalingService.kt
@@ -21,7 +21,6 @@ import org.matrix.android.sdk.api.session.call.CallSignalingService
 import org.matrix.android.sdk.api.session.call.MxCall
 import org.matrix.android.sdk.api.session.call.TurnServerResponse
 import org.matrix.android.sdk.internal.session.SessionScope
-import timber.log.Timber
 import javax.inject.Inject
 
 @SessionScope
@@ -51,7 +50,6 @@ internal class DefaultCallSignalingService @Inject constructor(
     }
 
     override fun getCallWithId(callId: String): MxCall? {
-        Timber.v("## VOIP getCallWithId $callId all calls ${activeCallHandler.getActiveCallsLiveData().value?.map { it.callId }}")
         return activeCallHandler.getCallWithId(callId)
     }
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/model/MxCallImpl.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/model/MxCallImpl.kt
index f101685a4b46b6f6248d79e635956bf139d8dda6..9fc84e6fe534644e8daa4236be64acf0c88438ee 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/model/MxCallImpl.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/model/MxCallImpl.kt
@@ -17,6 +17,7 @@
 package org.matrix.android.sdk.internal.session.call.model
 
 import org.matrix.android.sdk.api.MatrixConfiguration
+import org.matrix.android.sdk.api.logger.LoggerTag
 import org.matrix.android.sdk.api.session.call.CallIdGenerator
 import org.matrix.android.sdk.api.session.call.CallState
 import org.matrix.android.sdk.api.session.call.MxCall
@@ -38,6 +39,7 @@ import org.matrix.android.sdk.api.session.room.model.call.CallRejectContent
 import org.matrix.android.sdk.api.session.room.model.call.CallReplacesContent
 import org.matrix.android.sdk.api.session.room.model.call.CallSelectAnswerContent
 import org.matrix.android.sdk.api.session.room.model.call.CallSignalingContent
+import org.matrix.android.sdk.api.session.room.model.call.EndCallReason
 import org.matrix.android.sdk.api.session.room.model.call.SdpType
 import org.matrix.android.sdk.api.util.Optional
 import org.matrix.android.sdk.internal.session.call.DefaultCallSignalingService
@@ -47,6 +49,8 @@ import org.matrix.android.sdk.internal.session.room.send.queue.EventSenderProces
 import timber.log.Timber
 import java.math.BigDecimal
 
+private val loggerTag = LoggerTag("MxCallImpl", LoggerTag.VOIP)
+
 internal class MxCallImpl(
         override val callId: String,
         override val isOutgoing: Boolean,
@@ -93,7 +97,7 @@ internal class MxCallImpl(
             try {
                 it.onStateUpdate(this)
             } catch (failure: Throwable) {
-                Timber.d("dispatchStateChange failed for call $callId : ${failure.localizedMessage}")
+                Timber.tag(loggerTag.value).d("dispatchStateChange failed for call $callId : ${failure.localizedMessage}")
             }
         }
     }
@@ -109,7 +113,7 @@ internal class MxCallImpl(
 
     override fun offerSdp(sdpString: String) {
         if (!isOutgoing) return
-        Timber.v("## VOIP offerSdp $callId")
+        Timber.tag(loggerTag.value).v("offerSdp $callId")
         state = CallState.Dialing
         CallInviteContent(
                 callId = callId,
@@ -124,7 +128,7 @@ internal class MxCallImpl(
     }
 
     override fun sendLocalCallCandidates(candidates: List<CallCandidate>) {
-        Timber.v("Send local call canditates $callId: $candidates")
+        Timber.tag(loggerTag.value).v("Send local call canditates $callId: $candidates")
         CallCandidatesContent(
                 callId = callId,
                 partyId = ourPartyId,
@@ -141,11 +145,11 @@ internal class MxCallImpl(
 
     override fun reject() {
         if (opponentVersion < 1) {
-            Timber.v("Opponent version is less than 1 ($opponentVersion): sending hangup instead of reject")
-            hangUp()
+            Timber.tag(loggerTag.value).v("Opponent version is less than 1 ($opponentVersion): sending hangup instead of reject")
+            hangUp(EndCallReason.USER_HANGUP)
             return
         }
-        Timber.v("## VOIP reject $callId")
+        Timber.tag(loggerTag.value).v("reject $callId")
         CallRejectContent(
                 callId = callId,
                 partyId = ourPartyId,
@@ -153,24 +157,24 @@ internal class MxCallImpl(
         )
                 .let { createEventAndLocalEcho(type = EventType.CALL_REJECT, roomId = roomId, content = it.toContent()) }
                 .also { eventSenderProcessor.postEvent(it) }
-        state = CallState.Terminated
+        state = CallState.Ended(reason = EndCallReason.USER_HANGUP)
     }
 
-    override fun hangUp(reason: CallHangupContent.Reason?) {
-        Timber.v("## VOIP hangup $callId")
+    override fun hangUp(reason: EndCallReason?) {
+        Timber.tag(loggerTag.value).v("hangup $callId")
         CallHangupContent(
                 callId = callId,
                 partyId = ourPartyId,
-                reason = reason ?: CallHangupContent.Reason.USER_HANGUP,
+                reason = reason,
                 version = MxCall.VOIP_PROTO_VERSION.toString()
         )
                 .let { createEventAndLocalEcho(type = EventType.CALL_HANGUP, roomId = roomId, content = it.toContent()) }
                 .also { eventSenderProcessor.postEvent(it) }
-        state = CallState.Terminated
+        state = CallState.Ended(reason)
     }
 
     override fun accept(sdpString: String) {
-        Timber.v("## VOIP accept $callId")
+        Timber.tag(loggerTag.value).v("accept $callId")
         if (isOutgoing) return
         state = CallState.Answering
         CallAnswerContent(
@@ -185,7 +189,7 @@ internal class MxCallImpl(
     }
 
     override fun negotiate(sdpString: String, type: SdpType) {
-        Timber.v("## VOIP negotiate $callId")
+        Timber.tag(loggerTag.value).v("negotiate $callId")
         CallNegotiateContent(
                 callId = callId,
                 partyId = ourPartyId,
@@ -198,7 +202,7 @@ internal class MxCallImpl(
     }
 
     override fun selectAnswer() {
-        Timber.v("## VOIP select answer $callId")
+        Timber.tag(loggerTag.value).v("select answer $callId")
         if (isOutgoing) return
         state = CallState.Answering
         CallSelectAnswerContent(
@@ -219,7 +223,7 @@ internal class MxCallImpl(
         val profileInfo = try {
             getProfileInfoTask.execute(profileInfoParams)
         } catch (failure: Throwable) {
-            Timber.v("Fail fetching profile info of $targetUserId while transferring call")
+            Timber.tag(loggerTag.value).v("Fail fetching profile info of $targetUserId while transferring call")
             null
         }
         CallReplacesContent(
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..1fe4f9d90a6f76c835fe5d13f9635af634163129 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,36 @@ 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? = null,
+
+        /**
+         *  "room_capabilities": {
+         *      "knock" : {
+         *              "preferred": "7",
+         *              "support" : ["7"]
+         *      },
+         *      "restricted" : {
+         *              "preferred": "9",
+         *              "support" : ["8", "9"]
+         *      }
+         * }
+         */
+        @Json(name = "room_capabilities")
+        val roomCapabilities: JsonDict? = null
+)
+
 // 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/notification/DefaultPushRuleService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/notification/DefaultPushRuleService.kt
index 38f6b08b43b39d9eba976a7820f0faaeb0f6652a..4e8abcf7840c38f5e314ab7770ae0ce42f6f508b 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/notification/DefaultPushRuleService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/notification/DefaultPushRuleService.kt
@@ -113,8 +113,8 @@ internal class DefaultPushRuleService @Inject constructor(
         addPushRuleTask.execute(AddPushRuleTask.Params(kind, pushRule))
     }
 
-    override suspend fun updatePushRuleActions(kind: RuleKind, oldPushRule: PushRule, newPushRule: PushRule) {
-        updatePushRuleActionsTask.execute(UpdatePushRuleActionsTask.Params(kind, oldPushRule, newPushRule))
+    override suspend fun updatePushRuleActions(kind: RuleKind, ruleId: String, enable: Boolean, actions: List<Action>?) {
+        updatePushRuleActionsTask.execute(UpdatePushRuleActionsTask.Params(kind, ruleId, enable, actions))
     }
 
     override suspend fun removePushRule(kind: RuleKind, pushRule: PushRule) {
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..21f55bbc42535b6da82316844e01155d36512504 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,17 +16,25 @@
 
 package org.matrix.android.sdk.internal.session.permalinks
 
+import org.matrix.android.sdk.api.MatrixPatterns.getDomain
+import org.matrix.android.sdk.api.query.QueryStringValue
+import org.matrix.android.sdk.api.session.events.model.EventType
+import org.matrix.android.sdk.api.session.events.model.toModel
 import org.matrix.android.sdk.api.session.room.members.roomMemberQueryParams
 import org.matrix.android.sdk.api.session.room.model.Membership
+import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent
+import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper
 import org.matrix.android.sdk.internal.di.UserId
 import org.matrix.android.sdk.internal.session.room.RoomGetter
+import org.matrix.android.sdk.internal.session.room.state.StateEventDataSource
 import java.net.URLEncoder
 import javax.inject.Inject
 import javax.inject.Provider
 
 internal class ViaParameterFinder @Inject constructor(
         @UserId private val userId: String,
-        private val roomGetterProvider: Provider<RoomGetter>
+        private val roomGetterProvider: Provider<RoomGetter>,
+        private val stateEventDataSource: StateEventDataSource
 ) {
 
     fun computeViaParams(roomId: String, max: Int): List<String> {
@@ -47,9 +55,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()
@@ -69,4 +77,28 @@ internal class ViaParameterFinder @Inject constructor(
                 .orEmpty()
                 .toSet()
     }
+
+    fun computeViaParamsForRestricted(roomId: String, max: Int): List<String> {
+        val userThatCanInvite = roomGetterProvider.get().getRoom(roomId)
+                ?.getRoomMembers(roomMemberQueryParams { memberships = listOf(Membership.JOIN) })
+                ?.map { it.userId }
+                ?.filter { userCanInvite(userId, roomId) }
+                .orEmpty()
+                .toSet()
+
+        return userThatCanInvite.map { it.getDomain() }
+                .groupBy { it }
+                .mapValues { it.value.size }
+                .toMutableMap()
+                .let { map -> map.keys.sortedByDescending { map[it] } }
+                .take(max)
+    }
+
+    fun userCanInvite(userId: String, roomId: String): Boolean {
+        val powerLevelsHelper = stateEventDataSource.getStateEvent(roomId, EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.NoCondition)
+                ?.content?.toModel<PowerLevelsContent>()
+                ?.let { PowerLevelsHelper(it) }
+
+        return powerLevelsHelper?.isUserAbleToInvite(userId) ?: false
+    }
 }
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/pushers/UpdatePushRuleActionsTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/UpdatePushRuleActionsTask.kt
index 2a24aee8929111559584efbf6fda0e3d97deeca3..b8dbabd09ebcd6eb1cfa5511164499546b2f7906 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/UpdatePushRuleActionsTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/UpdatePushRuleActionsTask.kt
@@ -15,8 +15,9 @@
  */
 package org.matrix.android.sdk.internal.session.pushers
 
+import org.matrix.android.sdk.api.pushrules.Action
 import org.matrix.android.sdk.api.pushrules.RuleKind
-import org.matrix.android.sdk.api.pushrules.rest.PushRule
+import org.matrix.android.sdk.api.pushrules.toJson
 import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import org.matrix.android.sdk.internal.network.executeRequest
 import org.matrix.android.sdk.internal.task.Task
@@ -25,8 +26,9 @@ import javax.inject.Inject
 internal interface UpdatePushRuleActionsTask : Task<UpdatePushRuleActionsTask.Params, Unit> {
     data class Params(
             val kind: RuleKind,
-            val oldPushRule: PushRule,
-            val newPushRule: PushRule
+            val ruleId: String,
+            val enable: Boolean,
+            val actions: List<Action>?
     )
 }
 
@@ -36,20 +38,14 @@ internal class DefaultUpdatePushRuleActionsTask @Inject constructor(
 ) : UpdatePushRuleActionsTask {
 
     override suspend fun execute(params: UpdatePushRuleActionsTask.Params) {
-        if (params.oldPushRule.enabled != params.newPushRule.enabled) {
-            // First change enabled state
             executeRequest(globalErrorReceiver) {
-                pushRulesApi.updateEnableRuleStatus(params.kind.value, params.newPushRule.ruleId, params.newPushRule.enabled)
+                pushRulesApi.updateEnableRuleStatus(params.kind.value, params.ruleId, enable = params.enable)
             }
-        }
-
-        if (params.newPushRule.enabled) {
-            // Also ensure the actions are up to date
-            val body = mapOf("actions" to params.newPushRule.actions)
-
-            executeRequest(globalErrorReceiver) {
-                pushRulesApi.updateRuleActions(params.kind.value, params.newPushRule.ruleId, body)
+            if (params.actions != null) {
+                val body = mapOf("actions" to params.actions.toJson())
+                executeRequest(globalErrorReceiver) {
+                    pushRulesApi.updateRuleActions(params.kind.value, params.ruleId, body)
+                }
             }
-        }
     }
 }
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..9bb3899f2fdc966b183379f1cc7c3798df9ce9cb 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
@@ -17,16 +17,10 @@
 package org.matrix.android.sdk.internal.session.room.create
 
 import org.matrix.android.sdk.api.extensions.tryOrNull
-import org.matrix.android.sdk.api.session.crypto.crosssigning.CrossSigningService
 import org.matrix.android.sdk.api.session.events.model.Event
 import org.matrix.android.sdk.api.session.events.model.EventType
-import org.matrix.android.sdk.api.session.events.model.toContent
 import org.matrix.android.sdk.api.session.identity.IdentityServiceError
 import org.matrix.android.sdk.api.session.identity.toMedium
-import org.matrix.android.sdk.api.session.room.model.GuestAccess
-import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility
-import org.matrix.android.sdk.api.session.room.model.RoomJoinRules
-import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesContent
 import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams
 import org.matrix.android.sdk.api.util.MimeTypes
 import org.matrix.android.sdk.internal.crypto.DeviceListManager
@@ -45,7 +39,6 @@ import javax.inject.Inject
 
 internal class CreateRoomBodyBuilder @Inject constructor(
         private val ensureIdentityTokenTask: EnsureIdentityTokenTask,
-        private val crossSigningService: CrossSigningService,
         private val deviceListManager: DeviceListManager,
         private val identityStore: IdentityStore,
         private val fileUploader: FileUploader,
@@ -59,7 +52,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()
@@ -76,18 +69,18 @@ internal class CreateRoomBodyBuilder @Inject constructor(
                     }
                 }
 
-        if (params.joinRuleRestricted != null) {
-            params.roomVersion = "org.matrix.msc3083"
-            params.historyVisibility = params.historyVisibility ?: RoomHistoryVisibility.SHARED
-            params.guestAccess = params.guestAccess ?: GuestAccess.Forbidden
-        }
-        val initialStates = listOfNotNull(
-                buildEncryptionWithAlgorithmEvent(params),
-                buildHistoryVisibilityEvent(params),
-                buildAvatarEvent(params),
-                buildGuestAccess(params),
-                buildJoinRulesRestricted(params)
-        )
+        params.featurePreset?.updateRoomParams(params)
+
+        val initialStates = (
+                listOfNotNull(
+                        buildEncryptionWithAlgorithmEvent(params),
+                        buildHistoryVisibilityEvent(params),
+                        buildAvatarEvent(params),
+                        buildGuestAccess(params)
+                )
+                        + params.featurePreset?.setupInitialStates().orEmpty()
+                        + buildCustomInitialStates(params)
+                )
                 .takeIf { it.isNotEmpty() }
 
         return CreateRoomBody(
@@ -95,7 +88,7 @@ internal class CreateRoomBodyBuilder @Inject constructor(
                 roomAliasName = params.roomAliasName,
                 name = params.name,
                 topic = params.topic,
-                invitedUserIds = params.invitedUserIds.filter { it != userId },
+                invitedUserIds = params.invitedUserIds.filter { it != userId }.takeIf { it.isNotEmpty() },
                 invite3pids = invite3pids,
                 creationContent = params.creationContent.takeIf { it.isNotEmpty() },
                 initialStates = initialStates,
@@ -103,10 +96,19 @@ internal class CreateRoomBodyBuilder @Inject constructor(
                 isDirect = params.isDirect,
                 powerLevelContentOverride = params.powerLevelContentOverride,
                 roomVersion = params.roomVersion
-
         )
     }
 
+    private fun buildCustomInitialStates(params: CreateRoomParams): List<Event> {
+        return params.initialStates.map {
+            Event(
+                    type = it.type,
+                    stateKey = it.stateKey,
+                    content = it.content
+            )
+        }
+    }
+
     private suspend fun buildAvatarEvent(params: CreateRoomParams): Event? {
         return params.avatarUri?.let { avatarUri ->
             // First upload the image, ignoring any error
@@ -148,20 +150,6 @@ internal class CreateRoomBodyBuilder @Inject constructor(
                 }
     }
 
-    private fun buildJoinRulesRestricted(params: CreateRoomParams): Event? {
-        return params.joinRuleRestricted
-                ?.let { allowList ->
-                    Event(
-                            type = EventType.STATE_ROOM_JOIN_RULES,
-                            stateKey = "",
-                            content = RoomJoinRulesContent(
-                                    _joinRules = RoomJoinRules.RESTRICTED.value,
-                                    allowList = allowList
-                            ).toContent()
-                    )
-                }
-    }
-
     /**
      * Add the crypto algorithm to the room creation parameters.
      */
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..a64b9039474a9f4c54dc113a55dd86de2735d257 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,10 +181,11 @@ 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
+                            type = ContentAttachmentData.Type.AUDIO,
+                            waveform = messageContent.audioWaveformInfo?.waveform
                     )
                     localEchoRepository.updateSendState(localEcho.eventId, roomId, SendState.UNSENT)
                     internalSendMedia(listOf(localEcho.root), attachmentData, true)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt
index f505b13b337ee1325f4e3eacd544d4e6ccf87292..c610326a9409625c91b0a7e965891f797462f0e2 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt
@@ -29,6 +29,7 @@ import org.matrix.android.sdk.api.session.events.model.RelationType
 import org.matrix.android.sdk.api.session.events.model.UnsignedData
 import org.matrix.android.sdk.api.session.events.model.toContent
 import org.matrix.android.sdk.api.session.room.model.message.AudioInfo
+import org.matrix.android.sdk.api.session.room.model.message.AudioWaveformInfo
 import org.matrix.android.sdk.api.session.room.model.message.FileInfo
 import org.matrix.android.sdk.api.session.room.model.message.ImageInfo
 import org.matrix.android.sdk.api.session.room.model.message.MessageAudioContent
@@ -74,6 +75,7 @@ internal class LocalEchoEventFactory @Inject constructor(
         private val markdownParser: MarkdownParser,
         private val textPillsUtils: TextPillsUtils,
         private val thumbnailExtractor: ThumbnailExtractor,
+        private val waveformSanitizer: WaveFormSanitizer,
         private val localEchoRepository: LocalEchoRepository,
         private val permalinkFactory: PermalinkFactory
 ) {
@@ -289,14 +291,21 @@ internal class LocalEchoEventFactory @Inject constructor(
     }
 
     private fun createAudioEvent(roomId: String, attachment: ContentAttachmentData): Event {
+        val isVoiceMessage = attachment.waveform != null
         val content = MessageAudioContent(
                 msgType = MessageType.MSGTYPE_AUDIO,
                 body = attachment.name ?: "audio",
                 audioInfo = AudioInfo(
+                        duration = attachment.duration?.toInt(),
                         mimeType = attachment.getSafeMimeType()?.takeIf { it.isNotBlank() },
                         size = attachment.size
                 ),
-                url = attachment.queryUri.toString()
+                url = attachment.queryUri.toString(),
+                audioWaveformInfo = if (!isVoiceMessage) null else AudioWaveformInfo(
+                        duration = attachment.duration?.toInt(),
+                        waveform = waveformSanitizer.sanitize(attachment.waveform)
+                ),
+                voiceMessageIndicator = if (!isVoiceMessage) null else emptyMap()
         )
         return createMessageEvent(roomId, content)
     }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/WaveFormSanitizer.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/WaveFormSanitizer.kt
new file mode 100644
index 0000000000000000000000000000000000000000..78a03f3775c6e7aa4f3ed5134ea26cef199b9986
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/WaveFormSanitizer.kt
@@ -0,0 +1,86 @@
+/*
+ * Copyright (c) 2021 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.internal.session.room.send
+
+import timber.log.Timber
+import javax.inject.Inject
+import kotlin.math.abs
+import kotlin.math.ceil
+
+internal class WaveFormSanitizer @Inject constructor() {
+    private companion object {
+        const val MIN_NUMBER_OF_VALUES = 30
+        const val MAX_NUMBER_OF_VALUES = 120
+
+        const val MAX_VALUE = 1024
+    }
+
+    /**
+     * The array should have no less than 30 elements and no more than 120.
+     * List of integers between zero and 1024, inclusive.
+     */
+    fun sanitize(waveForm: List<Int>?): List<Int>? {
+        if (waveForm.isNullOrEmpty()) {
+            return null
+        }
+
+        // Limit the number of items
+        val sizeInRangeList = mutableListOf<Int>()
+        when {
+            waveForm.size < MIN_NUMBER_OF_VALUES -> {
+                // Repeat the same value to have at least 30 items
+                val repeatTimes = ceil(MIN_NUMBER_OF_VALUES / waveForm.size.toDouble()).toInt()
+                waveForm.map { value ->
+                    repeat(repeatTimes) {
+                        sizeInRangeList.add(value)
+                    }
+                }
+            }
+            waveForm.size > MAX_NUMBER_OF_VALUES -> {
+                val keepOneOf = ceil(waveForm.size.toDouble() / MAX_NUMBER_OF_VALUES).toInt()
+                waveForm.mapIndexed { idx, value ->
+                    if (idx % keepOneOf == 0) {
+                        sizeInRangeList.add(value)
+                    }
+                }
+            }
+            else                                 -> {
+                sizeInRangeList.addAll(waveForm)
+            }
+        }
+
+        // OK, ensure all items are positive
+        val positiveList = sizeInRangeList.map {
+            abs(it)
+        }
+
+        // Ensure max is not above MAX_VALUE
+        val max = positiveList.maxOrNull() ?: MAX_VALUE
+
+        val finalList = if (max > MAX_VALUE) {
+            // Reduce the values
+            positiveList.map {
+                it * MAX_VALUE / max
+            }
+        } else {
+            positiveList
+        }
+
+        Timber.d("Sanitize from ${waveForm.size} items to ${finalList.size} items. Max value was $max")
+        return finalList
+    }
+}
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/state/DefaultStateService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/DefaultStateService.kt
index ff2afb5d61aff480092658ea0c8d9900e0de1f8b..7eed22f65fcb29af921bc454c79c55527135025b 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/DefaultStateService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/DefaultStateService.kt
@@ -19,8 +19,8 @@ package org.matrix.android.sdk.internal.session.room.state
 import android.net.Uri
 import androidx.lifecycle.LiveData
 import dagger.assisted.Assisted
-import dagger.assisted.AssistedInject
 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.Event
 import org.matrix.android.sdk.api.session.events.model.EventType
@@ -29,17 +29,20 @@ import org.matrix.android.sdk.api.session.room.model.GuestAccess
 import org.matrix.android.sdk.api.session.room.model.RoomCanonicalAliasContent
 import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility
 import org.matrix.android.sdk.api.session.room.model.RoomJoinRules
+import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesAllowEntry
+import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesContent
 import org.matrix.android.sdk.api.session.room.state.StateService
 import org.matrix.android.sdk.api.util.JsonDict
 import org.matrix.android.sdk.api.util.MimeTypes
 import org.matrix.android.sdk.api.util.Optional
 import org.matrix.android.sdk.internal.session.content.FileUploader
-import java.lang.UnsupportedOperationException
+import org.matrix.android.sdk.internal.session.permalinks.ViaParameterFinder
 
 internal class DefaultStateService @AssistedInject constructor(@Assisted private val roomId: String,
                                                                private val stateEventDataSource: StateEventDataSource,
                                                                private val sendStateTask: SendStateTask,
-                                                               private val fileUploader: FileUploader
+                                                               private val fileUploader: FileUploader,
+                                                               private val viaParameterFinder: ViaParameterFinder
 ) : StateService {
 
     @AssistedFactory
@@ -126,12 +129,19 @@ internal class DefaultStateService @AssistedInject constructor(@Assisted private
         )
     }
 
-    override suspend fun updateJoinRule(joinRules: RoomJoinRules?, guestAccess: GuestAccess?) {
+    override suspend fun updateJoinRule(joinRules: RoomJoinRules?, guestAccess: GuestAccess?, allowList: List<RoomJoinRulesAllowEntry>?) {
         if (joinRules != null) {
-            if (joinRules == RoomJoinRules.RESTRICTED) throw UnsupportedOperationException("No yet supported")
+            val body = if (joinRules == RoomJoinRules.RESTRICTED) {
+                RoomJoinRulesContent(
+                        _joinRules = RoomJoinRules.RESTRICTED.value,
+                        allowList = allowList
+                ).toContent()
+            } else {
+                mapOf("join_rule" to joinRules)
+            }
             sendStateEvent(
                     eventType = EventType.STATE_ROOM_JOIN_RULES,
-                    body = mapOf("join_rule" to joinRules),
+                    body = body,
                     stateKey = null
             )
         }
@@ -160,4 +170,20 @@ internal class DefaultStateService @AssistedInject constructor(@Assisted private
                 stateKey = null
         )
     }
+
+    override suspend fun setJoinRulePublic() {
+        updateJoinRule(RoomJoinRules.PUBLIC, null)
+    }
+
+    override suspend fun setJoinRuleInviteOnly() {
+        updateJoinRule(RoomJoinRules.INVITE, null)
+    }
+
+    override suspend fun setJoinRuleRestricted(allowList: List<String>) {
+        // we need to compute correct via parameters and check if PL are correct
+        val allowEntries = allowList.map { spaceId ->
+            RoomJoinRulesAllowEntry(spaceId, viaParameterFinder.computeViaParamsForRestricted(spaceId, 3))
+        }
+        updateJoinRule(RoomJoinRules.RESTRICTED, null, allowEntries)
+    }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt
index bff1af60ca9af4c1e8f3df7f489592ba76c4c908..0b8c6df8066c6274acaf8c257619a030b1f45ed3 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt
@@ -247,10 +247,10 @@ internal class RoomSummaryDataSource @Inject constructor(@SessionDatabase privat
 
         queryParams.roomCategoryFilter?.let {
             when (it) {
-                RoomCategoryFilter.ONLY_DM -> query.equalTo(RoomSummaryEntityFields.IS_DIRECT, true)
-                RoomCategoryFilter.ONLY_ROOMS -> query.equalTo(RoomSummaryEntityFields.IS_DIRECT, false)
+                RoomCategoryFilter.ONLY_DM                 -> query.equalTo(RoomSummaryEntityFields.IS_DIRECT, true)
+                RoomCategoryFilter.ONLY_ROOMS              -> query.equalTo(RoomSummaryEntityFields.IS_DIRECT, false)
                 RoomCategoryFilter.ONLY_WITH_NOTIFICATIONS -> query.greaterThan(RoomSummaryEntityFields.NOTIFICATION_COUNT, 0)
-                RoomCategoryFilter.ALL -> {
+                RoomCategoryFilter.ALL                     -> {
                     // nop
                 }
             }
@@ -274,15 +274,15 @@ internal class RoomSummaryDataSource @Inject constructor(@SessionDatabase privat
             query.equalTo(RoomSummaryEntityFields.ROOM_TYPE, it)
         }
         when (queryParams.roomCategoryFilter) {
-            RoomCategoryFilter.ONLY_DM -> query.equalTo(RoomSummaryEntityFields.IS_DIRECT, true)
-            RoomCategoryFilter.ONLY_ROOMS -> query.equalTo(RoomSummaryEntityFields.IS_DIRECT, false)
+            RoomCategoryFilter.ONLY_DM                 -> query.equalTo(RoomSummaryEntityFields.IS_DIRECT, true)
+            RoomCategoryFilter.ONLY_ROOMS              -> query.equalTo(RoomSummaryEntityFields.IS_DIRECT, false)
             RoomCategoryFilter.ONLY_WITH_NOTIFICATIONS -> query.greaterThan(RoomSummaryEntityFields.NOTIFICATION_COUNT, 0)
-            RoomCategoryFilter.ALL -> Unit // nop
+            RoomCategoryFilter.ALL                     -> Unit // nop
         }
 
         // Timber.w("VAL: activeSpaceId : ${queryParams.activeSpaceId}")
         when (queryParams.activeSpaceFilter) {
-            is ActiveSpaceFilter.ActiveSpace -> {
+            is ActiveSpaceFilter.ActiveSpace  -> {
                 // It's annoying but for now realm java does not support querying in primitive list :/
                 // https://github.com/realm/realm-java/issues/5361
                 if (queryParams.activeSpaceFilter.currentSpaceId == null) {
@@ -300,8 +300,8 @@ internal class RoomSummaryDataSource @Inject constructor(@SessionDatabase privat
             }
         }
 
-        if (queryParams.activeGroupId != null) {
-            query.contains(RoomSummaryEntityFields.GROUP_IDS, queryParams.activeGroupId!!)
+        queryParams.activeGroupId?.let { activeGroupId ->
+            query.contains(RoomSummaryEntityFields.GROUP_IDS, activeGroupId)
         }
         return query
     }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt
index 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/util/file/AtomicFileCreator.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/file/AtomicFileCreator.kt
new file mode 100644
index 0000000000000000000000000000000000000000..ca10c0ed0f85cecb5b098e3f80d758bfeca0a9e9
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/file/AtomicFileCreator.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2021 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.internal.util.file
+
+import timber.log.Timber
+import java.io.File
+
+internal class AtomicFileCreator(private val file: File) {
+    val partFile = File(file.parentFile, "${file.name}.part")
+
+    init {
+        if (file.exists()) {
+            Timber.w("## AtomicFileCreator: target file ${file.path} exists, it should not happen.")
+        }
+        if (partFile.exists()) {
+            Timber.d("## AtomicFileCreator: discard aborted part file ${partFile.path}")
+            // No need to delete the file, we will overwrite it
+        }
+    }
+
+    fun cancel() {
+        partFile.delete()
+    }
+
+    fun commit() {
+        partFile.renameTo(file)
+    }
+}
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>
diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/send/WaveFormSanitizerTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/send/WaveFormSanitizerTest.kt
new file mode 100644
index 0000000000000000000000000000000000000000..23c8aeb76bbc212909d6aacd2be5ef1592449561
--- /dev/null
+++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/send/WaveFormSanitizerTest.kt
@@ -0,0 +1,123 @@
+/*
+ * Copyright (c) 2021 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.internal.session.room.send
+
+import org.amshove.kluent.shouldBe
+import org.amshove.kluent.shouldBeInRange
+import org.junit.Test
+
+class WaveFormSanitizerTest {
+
+    private val waveFormSanitizer = WaveFormSanitizer()
+
+    @Test
+    fun sanitizeNull() {
+        waveFormSanitizer.sanitize(null) shouldBe null
+    }
+
+    @Test
+    fun sanitizeEmpty() {
+        waveFormSanitizer.sanitize(emptyList()) shouldBe null
+    }
+
+    @Test
+    fun sanitizeSingleton() {
+        val result = waveFormSanitizer.sanitize(listOf(1))!!
+        result.size shouldBe 30
+        checkResult(result)
+    }
+
+    @Test
+    fun sanitize29() {
+        val list = generateSequence { 1 }.take(29).toList()
+        val result = waveFormSanitizer.sanitize(list)!!
+        checkResult(result)
+    }
+
+    @Test
+    fun sanitize30() {
+        val list = generateSequence { 1 }.take(30).toList()
+        val result = waveFormSanitizer.sanitize(list)!!
+        result.size shouldBe 30
+        checkResult(result)
+    }
+
+    @Test
+    fun sanitize31() {
+        val list = generateSequence { 1 }.take(31).toList()
+        val result = waveFormSanitizer.sanitize(list)!!
+        checkResult(result)
+    }
+
+    @Test
+    fun sanitize119() {
+        val list = generateSequence { 1 }.take(119).toList()
+        val result = waveFormSanitizer.sanitize(list)!!
+        checkResult(result)
+    }
+
+    @Test
+    fun sanitize120() {
+        val list = generateSequence { 1 }.take(120).toList()
+        val result = waveFormSanitizer.sanitize(list)!!
+        result.size shouldBe 120
+        checkResult(result)
+    }
+
+    @Test
+    fun sanitize121() {
+        val list = generateSequence { 1 }.take(121).toList()
+        val result = waveFormSanitizer.sanitize(list)!!
+        checkResult(result)
+    }
+
+    @Test
+    fun sanitize1024() {
+        val list = generateSequence { 1 }.take(1024).toList()
+        val result = waveFormSanitizer.sanitize(list)!!
+        checkResult(result)
+    }
+
+    @Test
+    fun sanitizeNegative() {
+        val list = generateSequence { -1 }.take(30).toList()
+        val result = waveFormSanitizer.sanitize(list)!!
+        checkResult(result)
+    }
+
+    @Test
+    fun sanitizeMaxValue() {
+        val list = generateSequence { 1025 }.take(30).toList()
+        val result = waveFormSanitizer.sanitize(list)!!
+        checkResult(result)
+    }
+
+    @Test
+    fun sanitizeNegativeMaxValue() {
+        val list = generateSequence { -1025 }.take(30).toList()
+        val result = waveFormSanitizer.sanitize(list)!!
+        checkResult(result)
+    }
+
+    private fun checkResult(result: List<Int>) {
+        result.forEach {
+            it shouldBeInRange 0..1024
+        }
+
+        result.size shouldBeInRange 30..120
+    }
+}
diff --git a/tools/import_from_element.sh b/tools/import_from_element.sh
index 331df43c2d9c244266a5532f99b0478a0032d065..8d64b6db6b3ff6ae5d68d5b2c2b3c57793ec2128 100755
--- a/tools/import_from_element.sh
+++ b/tools/import_from_element.sh
@@ -42,7 +42,7 @@ git add -A
 read -p "Press enter to build the library (update the version first?)"
 
 # Build the library
-./gradlew clean assembleRelease
+./gradlew clean assembleRelease --stacktrace
 
 # Success