From 882e9adf50297587eb62028a0787a6f8adde1424 Mon Sep 17 00:00:00 2001
From: Benoit Marty <benoit@matrix.org>
Date: Wed, 8 Sep 2021 22:27:38 +0200
Subject: [PATCH] Import from Element 1.2.1

---
 matrix-sdk-android/build.gradle               |  14 +--
 matrix-sdk-android/lint.xml                   |  34 ------
 .../matrix/android/sdk/PermalinkParserTest.kt |  60 ++++++++++
 .../android/sdk/common/CommonTestHelper.kt    |   2 +
 .../android/sdk/common/CryptoTestHelper.kt    |   2 +
 .../sdk/internal/crypto/ssss/QuadSTests.kt    |   2 +
 .../timeline/TimelineForwardPaginationTest.kt |   2 +-
 .../TimelinePreviousLastForwardTest.kt        |   4 +-
 .../sdk/session/space/SpaceCreationTest.kt    |  33 +++---
 .../sdk/session/space/SpaceHierarchyTest.kt   |   9 +-
 .../android/sdk/api/failure/Extensions.kt     |   4 +-
 .../android/sdk/api/failure/GlobalError.kt    |   1 +
 .../android/sdk/api/failure/MatrixError.kt    |  25 ++++
 .../sdk/api/pushrules/PushRuleService.kt      |   5 +-
 .../android/sdk/api/pushrules/RuleIds.kt      |   5 +
 .../verification/VerificationService.kt       |   2 +-
 .../api/session/identity/IdentityService.kt   |  14 +++
 .../api/session/permalinks/PermalinkData.kt   |  21 ++++
 .../api/session/permalinks/PermalinkParser.kt |  79 +++++++++++--
 .../sdk/api/session/room/RoomService.kt       |  13 +++
 .../sdk/api/session/room/RoomSortOrder.kt     |   1 +
 .../room/model/RoomJoinRulesAllowEntry.kt     |  14 ++-
 .../room/model/RoomJoinRulesContent.kt        |   4 +-
 .../api/session/room/model/SpaceChildInfo.kt  |   2 +-
 .../room/model/message/AudioWaveformInfo.kt   |   2 +-
 .../android/sdk/api/session/space/Space.kt    |   6 +-
 .../api/session/space/SpaceHierarchyData.kt   |  28 +++++
 .../sdk/api/session/space/SpaceService.kt     |  11 +-
 .../session/space/model/SpaceChildContent.kt  |  12 +-
 .../crypto/CancelGossipRequestWorker.kt       |  13 ++-
 .../crypto/IncomingGossipingRequestManager.kt |   8 +-
 .../crypto/OutgoingGossipingRequestManager.kt |   9 +-
 .../crypto/SendGossipRequestWorker.kt         |  12 +-
 .../sdk/internal/crypto/SendGossipWorker.kt   |  12 +-
 .../crypto/store/db/RealmCryptoStore.kt       |   6 +-
 .../internal/crypto/tasks/SendToDeviceTask.kt |  17 ++-
 .../internal/crypto/util/RequestIdHelper.kt   |  23 ++++
 .../database/EventInsertLiveObserver.kt       |  31 +----
 .../database/RealmSessionStoreMigration.kt    |  10 +-
 .../database/mapper/RoomSummaryMapper.kt      |   2 +-
 .../internal/database/model/EventEntity.kt    |  10 +-
 .../database/model/EventInsertEntity.kt       |   7 +-
 .../database/query/EventEntityQueries.kt      |   4 +-
 .../sdk/internal/network/NetworkConstants.kt  |   1 +
 .../internal/network/RetrofitExtensions.kt    |  25 ++--
 .../internal/query/QueryRoomOrderProcessor.kt |  14 ++-
 .../homeserver/GetCapabilitiesResult.kt       |   2 +-
 .../identity/DefaultIdentityService.kt        |  10 ++
 .../internal/session/identity/IdentityAPI.kt  |  14 +++
 .../identity/Sign3pidInvitationTask.kt        |  49 ++++++++
 .../identity/model/SignInvitationBody.kt      |  29 +++++
 .../identity/model/SignInvitationResult.kt    |  31 +++++
 .../notification/DefaultPushRuleService.kt    |  22 +++-
 .../session/permalinks/ViaParameterFinder.kt  |   4 +
 .../internal/session/pushers/EnabledBody.kt   |  25 ++++
 .../session/pushers/GetPushersResponse.kt     |   2 +-
 .../internal/session/pushers/PushRulesApi.kt  |   2 +-
 .../session/pushers/RemovePushRuleTask.kt     |   5 +-
 .../pushers/UpdatePushRuleActionsTask.kt      |   6 +-
 .../pushers/UpdatePushRuleEnableStatusTask.kt |   6 +-
 .../session/room/DefaultRoomService.kt        |   7 ++
 .../sdk/internal/session/room/RoomAPI.kt      |   5 +-
 .../sdk/internal/session/room/RoomModule.kt   |   5 +
 .../session/room/create/CreateRoomTask.kt     |   2 +-
 .../room/membership/RoomMemberEventHandler.kt |  24 +++-
 .../room/membership/joining/JoinRoomTask.kt   |  11 +-
 .../SetRoomNotificationStateTask.kt           |   2 +-
 .../relationship/RoomChildRelationInfo.kt     |   4 +-
 .../session/room/send/DefaultSendService.kt   |   2 +-
 .../session/room/send/LocalEchoRepository.kt  |   2 +-
 .../session/room/state/DefaultStateService.kt |   2 +-
 .../room/summary/RoomSummaryUpdater.kt        |   2 +-
 .../room/timeline/TimelineEventDecryptor.kt   |   3 +-
 .../internal/session/space/DefaultSpace.kt    |  67 ++++++-----
 .../session/space/DefaultSpaceService.kt      | 107 ++++++++++--------
 .../internal/session/space/JoinSpaceTask.kt   |  65 ++++++-----
 .../session/space/ResolveSpaceInfoTask.kt     |  38 ++-----
 .../sdk/internal/session/space/SpaceApi.kt    |  32 +++---
 .../space/SpaceChildSummaryResponse.kt        |  18 ++-
 .../session/space/SpaceSummaryParams.kt       |  34 ------
 .../internal/session/space/SpacesResponse.kt  |   5 +-
 .../session/space/peeking/PeekSpaceTask.kt    |   6 +-
 .../session/space/peeking/SpacePeekResult.kt  |   6 +-
 .../internal/session/sync/RoomSyncHandler.kt  |  10 +-
 .../SyncResponsePostTreatmentAggregator.kt    |   2 +
 ...cResponsePostTreatmentAggregatorHandler.kt |  40 ++++++-
 .../sync/UserAccountDataSyncHandler.kt        |   3 +-
 .../accountdata/DirectMessagesContent.kt      |   9 ++
 .../user/accountdata/DirectChatsHelper.kt     |   3 +-
 .../session/widgets/DefaultWidgetService.kt   |   2 +-
 .../internal/task/CoroutineSequencersTest.kt  |   3 +
 91 files changed, 942 insertions(+), 401 deletions(-)
 delete mode 100644 matrix-sdk-android/lint.xml
 create mode 100644 matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/PermalinkParserTest.kt
 create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/SpaceHierarchyData.kt
 create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/util/RequestIdHelper.kt
 create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/Sign3pidInvitationTask.kt
 create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/model/SignInvitationBody.kt
 create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/model/SignInvitationResult.kt
 create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/EnabledBody.kt
 delete mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpaceSummaryParams.kt

diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle
index 23e087b7..81136f43 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.6.1"
+        classpath "io.realm:realm-gradle-plugin:10.8.0"
     }
 }
 
@@ -22,7 +22,7 @@ android {
         minSdkVersion 21
         targetSdkVersion 30
         versionCode 1
-        versionName "1.2.0"
+        versionName "1.2.1"
         // Multidex is useful for tests
         multiDexEnabled true
         testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
@@ -68,10 +68,6 @@ android {
         installOptions "-g"
     }
 
-    lintOptions {
-        lintConfig file("lint.xml")
-    }
-
     compileOptions {
         sourceCompatibility JavaVersion.VERSION_11
         targetCompatibility JavaVersion.VERSION_11
@@ -113,7 +109,7 @@ dependencies {
     def lifecycle_version = '2.2.0'
     def arch_version = '2.1.0'
     def markwon_version = '3.1.0'
-    def daggerVersion = '2.38'
+    def daggerVersion = '2.38.1'
     def work_version = '2.5.0'
     def retrofit_version = '2.9.0'
 
@@ -142,7 +138,7 @@ dependencies {
     implementation "ru.noties.markwon:core:$markwon_version"
 
     // Image
-    implementation 'androidx.exifinterface:exifinterface:1.3.2'
+    implementation 'androidx.exifinterface:exifinterface:1.3.3'
 
     // Database
     implementation 'com.github.Zhuinden:realm-monarchy:0.7.1'
@@ -170,7 +166,7 @@ dependencies {
     implementation 'com.otaliastudios:transcoder:0.10.3'
 
     // Phone number https://github.com/google/libphonenumber
-    implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.28'
+    implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.31'
 
     testImplementation 'junit:junit:4.13.2'
     testImplementation 'org.robolectric:robolectric:4.5.1'
diff --git a/matrix-sdk-android/lint.xml b/matrix-sdk-android/lint.xml
deleted file mode 100644
index 134aba82..00000000
--- a/matrix-sdk-android/lint.xml
+++ /dev/null
@@ -1,34 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<lint>
-    <!-- Modify some severity -->
-
-    <!-- Resource -->
-    <issue id="MissingTranslation" severity="warning" />
-    <issue id="TypographyEllipsis" severity="error" />
-    <issue id="ImpliedQuantity" severity="warning" />
-
-    <!-- UX -->
-    <issue id="ButtonOrder" severity="error" />
-
-    <!-- Layout -->
-    <issue id="UnknownIdInLayout" severity="error" />
-    <issue id="StringFormatCount" severity="error" />
-    <issue id="HardcodedText" severity="error" />
-    <issue id="SpUsage" severity="error" />
-    <issue id="ObsoleteLayoutParam" severity="error" />
-    <issue id="InefficientWeight" severity="error" />
-    <issue id="DisableBaselineAlignment" severity="error" />
-    <issue id="ScrollViewSize" severity="error" />
-
-    <!-- RTL -->
-    <issue id="RtlEnabled" severity="error" />
-    <issue id="RtlHardcoded" severity="error" />
-    <issue id="RtlSymmetry" severity="error" />
-
-    <!-- Code -->
-    <issue id="SetTextI18n" severity="error" />
-    <issue id="ViewConstructor" severity="error" />
-    <issue id="UseValueOf" severity="error" />
-    <issue id="ObsoleteSdkInt" severity="error" />
-
-</lint>
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/PermalinkParserTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/PermalinkParserTest.kt
new file mode 100644
index 00000000..b11a5389
--- /dev/null
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/PermalinkParserTest.kt
@@ -0,0 +1,60 @@
+/*
+ * 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.junit.Assert
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runners.MethodSorters
+import org.matrix.android.sdk.api.session.permalinks.PermalinkData
+import org.matrix.android.sdk.api.session.permalinks.PermalinkParser
+
+@FixMethodOrder(MethodSorters.JVM)
+class PermalinkParserTest {
+
+    @Test
+    fun testParseEmailInvite() {
+        val rawInvite = """
+            https://app.element.io/#/room/%21MRBNLPtFnMAazZVPMO%3Amatrix.org?email=bob%2Bspace%40example.com&signurl=https%3A%2F%2Fvector.im%2F_matrix%2Fidentity%2Fapi%2Fv1%2Fsign-ed25519%3Ftoken%3DXmOwRZnSFabCRhTywFbJWKXWVNPysOpXIbroMGaUymqkJSvHeVKRsjHajwjCYdBsvGSvHauxbKfJmOxtXldtyLnyBMLKpBQCMzyYggrdapbVIceWZBtmslOQrXLABRoe%26private_key%3DT2gq0c3kJB_8OroXVxl1pBnzHsN7V6Xn4bEBSeW1ep4&room_name=Team2&room_avatar_url=&inviter_name=hiphop5&guest_access_token=&guest_user_id=
+        """.trimIndent()
+                .replace("https://app.element.io/#/room/", "https://matrix.to/#/")
+
+        val parsedLink = PermalinkParser.parse(rawInvite)
+        Assert.assertTrue("Should be parsed as email invite but was ${parsedLink::class.java}", parsedLink is PermalinkData.RoomEmailInviteLink)
+        parsedLink as PermalinkData.RoomEmailInviteLink
+        Assert.assertEquals("!MRBNLPtFnMAazZVPMO:matrix.org", parsedLink.roomId)
+        Assert.assertEquals("XmOwRZnSFabCRhTywFbJWKXWVNPysOpXIbroMGaUymqkJSvHeVKRsjHajwjCYdBsvGSvHauxbKfJmOxtXldtyLnyBMLKpBQCMzyYggrdapbVIceWZBtmslOQrXLABRoe", parsedLink.token)
+        Assert.assertEquals("vector.im", parsedLink.identityServer)
+        Assert.assertEquals("Team2", parsedLink.roomName)
+        Assert.assertEquals("hiphop5", parsedLink.inviterName)
+    }
+
+    @Test
+    fun testParseLinkWIthEvent() {
+        val rawInvite = "https://matrix.to/#/!OGEhHVWSdvArJzumhm:matrix.org/\$xuvJUVDJnwEeVjPx029rAOZ50difpmU_5gZk_T0jGfc?via=matrix.org&via=libera.chat&via=matrix.example.io"
+
+        val parsedLink = PermalinkParser.parse(rawInvite)
+        Assert.assertTrue("Should be parsed as room link", parsedLink is PermalinkData.RoomLink)
+        parsedLink as PermalinkData.RoomLink
+        Assert.assertEquals("!OGEhHVWSdvArJzumhm:matrix.org", parsedLink.roomIdOrAlias)
+        Assert.assertEquals("\$xuvJUVDJnwEeVjPx029rAOZ50difpmU_5gZk_T0jGfc", parsedLink.eventId)
+        Assert.assertEquals(3, parsedLink.viaParameters.size)
+        Assert.assertTrue(parsedLink.viaParameters.contains("matrix.example.io"))
+        Assert.assertTrue(parsedLink.viaParameters.contains("matrix.org"))
+        Assert.assertTrue(parsedLink.viaParameters.contains("matrix.example.io"))
+    }
+}
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 6e07223a..7817351e 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
@@ -91,6 +91,7 @@ class CommonTestHelper(context: Context) {
      *
      * @param session    the session to sync
      */
+    @Suppress("EXPERIMENTAL_API_USAGE")
     fun syncSession(session: Session, timeout: Long = TestConstants.timeOutMillis) {
         val lock = CountDownLatch(1)
 
@@ -327,6 +328,7 @@ class CommonTestHelper(context: Context) {
         assertTrue(latch.await(timeout ?: TestConstants.timeOutMillis, TimeUnit.MILLISECONDS))
     }
 
+    @Suppress("EXPERIMENTAL_API_USAGE")
     fun retryPeriodicallyWithLatch(latch: CountDownLatch, condition: (() -> Boolean)) {
         GlobalScope.launch {
             while (true) {
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CryptoTestHelper.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CryptoTestHelper.kt
index da176491..a8cbc160 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CryptoTestHelper.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CryptoTestHelper.kt
@@ -84,6 +84,7 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) {
     /**
      * @return alice and bob sessions
      */
+    @Suppress("EXPERIMENTAL_API_USAGE")
     fun doE2ETestWithAliceAndBobInARoom(encryptedRoom: Boolean = true): CryptoTestData {
         val cryptoTestData = doE2ETestWithAliceInARoom(encryptedRoom)
         val aliceSession = cryptoTestData.firstSession
@@ -255,6 +256,7 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) {
         )
     }
 
+    @Suppress("EXPERIMENTAL_API_USAGE")
     fun createDM(alice: Session, bob: Session): String {
         val roomId = mTestHelper.runBlockingTest {
             alice.createDirectRoom(bob.myUserId)
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 d14de30c..74855b86 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
@@ -60,6 +60,7 @@ class QuadSTests : InstrumentedTest {
     }
 
     @Test
+    @Suppress("EXPERIMENTAL_API_USAGE")
     fun test_Generate4SKey() {
         val aliceSession = mTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true))
 
@@ -275,6 +276,7 @@ class QuadSTests : InstrumentedTest {
         mTestHelper.signOutAndClose(aliceSession)
     }
 
+    @Suppress("EXPERIMENTAL_API_USAGE")
     private fun assertAccountData(session: Session, type: String): UserAccountDataEvent {
         val accountDataLock = CountDownLatch(1)
         var accountData: UserAccountDataEvent? = null
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineForwardPaginationTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineForwardPaginationTest.kt
index f156a5eb..0fe341ca 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineForwardPaginationTest.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineForwardPaginationTest.kt
@@ -139,7 +139,7 @@ class TimelineForwardPaginationTest : InstrumentedTest {
                 // Alice can see the first event of the room (so Back pagination has worked)
                 snapshot.lastOrNull()?.root?.getClearType() == EventType.STATE_ROOM_CREATE
                         // 6 for room creation item (backward pagination), 1 for the context, and 50 for the forward pagination
-                        && snapshot.size == 6 + 1 + 50
+                        && snapshot.size == 57 // 6 + 1 + 50
             }
 
             aliceTimeline.addListener(aliceEventsListener)
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelinePreviousLastForwardTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelinePreviousLastForwardTest.kt
index 9ebac876..03a4d419 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelinePreviousLastForwardTest.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelinePreviousLastForwardTest.kt
@@ -189,7 +189,7 @@ class TimelinePreviousLastForwardTest : InstrumentedTest {
                     Timber.w(" event ${it.root}")
                 }
 
-                snapshot.size == 8 + 1 + 35
+                snapshot.size == 44 // 8 + 1 + 35
             }
 
             bobTimeline.addListener(eventsListener)
@@ -218,7 +218,7 @@ class TimelinePreviousLastForwardTest : InstrumentedTest {
                 // Bob can see the first event of the room (so Back pagination has worked)
                 snapshot.lastOrNull()?.root?.getClearType() == EventType.STATE_ROOM_CREATE
                         // 8 for room creation item 60 message from Alice
-                        && snapshot.size == 8 + 60
+                        && snapshot.size == 68 // 8 + 60
                         && snapshot.checkSendOrder(secondMessage, 30, 0)
                         && snapshot.checkSendOrder(firstMessage, 30, 30)
             }
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceCreationTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceCreationTest.kt
index a1744a0d..5911414c 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceCreationTest.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceCreationTest.kt
@@ -28,12 +28,9 @@ import org.junit.runner.RunWith
 import org.junit.runners.JUnit4
 import org.junit.runners.MethodSorters
 import org.matrix.android.sdk.InstrumentedTest
-import org.matrix.android.sdk.api.query.ActiveSpaceFilter
 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.RoomSummaryQueryParams
 import org.matrix.android.sdk.api.session.room.model.GuestAccess
-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.model.RoomGuestAccessContent
 import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility
@@ -42,7 +39,6 @@ import org.matrix.android.sdk.api.session.room.model.RoomType
 import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams
 import org.matrix.android.sdk.api.session.room.model.create.CreateRoomPreset
 import org.matrix.android.sdk.api.session.room.model.create.RoomCreateContent
-import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams
 import org.matrix.android.sdk.api.session.space.JoinSpaceResult
 import org.matrix.android.sdk.common.CommonTestHelper
 import org.matrix.android.sdk.common.SessionTestParams
@@ -54,6 +50,7 @@ class SpaceCreationTest : InstrumentedTest {
     private val commonTestHelper = CommonTestHelper(context())
 
     @Test
+    @Suppress("EXPERIMENTAL_API_USAGE")
     fun createSimplePublicSpace() {
         val session = commonTestHelper.createAccount("Hubble", SessionTestParams(true))
         val roomName = "My Space"
@@ -137,6 +134,7 @@ class SpaceCreationTest : InstrumentedTest {
     }
 
     @Test
+    @Suppress("EXPERIMENTAL_API_USAGE")
     fun testSimplePublicSpaceWithChildren() {
         val aliceSession = commonTestHelper.createAccount("alice", SessionTestParams(true))
         val bobSession = commonTestHelper.createAccount("bob", SessionTestParams(true))
@@ -162,7 +160,7 @@ class SpaceCreationTest : InstrumentedTest {
 
         commonTestHelper.waitWithLatch {
             GlobalScope.launch {
-                syncedSpace?.addChildren(firstChild!!, listOf(aliceSession.sessionParams.homeServerHost ?: ""), "a", true, suggested = true)
+                syncedSpace?.addChildren(firstChild!!, listOf(aliceSession.sessionParams.homeServerHost ?: ""), "a", suggested = true)
                 it.countDown()
             }
         }
@@ -181,7 +179,7 @@ class SpaceCreationTest : InstrumentedTest {
 
         commonTestHelper.waitWithLatch {
             GlobalScope.launch {
-                syncedSpace?.addChildren(secondChild!!, listOf(aliceSession.sessionParams.homeServerHost ?: ""), "b", false, suggested = true)
+                syncedSpace?.addChildren(secondChild!!, listOf(aliceSession.sessionParams.homeServerHost ?: ""), "b", suggested = true)
                 it.countDown()
             }
         }
@@ -202,19 +200,20 @@ class SpaceCreationTest : InstrumentedTest {
         assertEquals("Room name should be set", roomName, spaceBobPov?.asRoom()?.roomSummary()?.name)
         assertEquals("Room topic should be set", topic, spaceBobPov?.asRoom()?.roomSummary()?.topic)
 
+        // /!\ AUTO_JOIN has been descoped
         // check if bob has joined automatically the first room
 
-        val bobMembershipFirstRoom = bobSession.getRoomSummary(firstChild!!)?.membership
-        assertEquals("Bob should have joined this room", Membership.JOIN, bobMembershipFirstRoom)
-        RoomSummaryQueryParams.Builder()
-
-        val childCount = bobSession.getRoomSummaries(
-                roomSummaryQueryParams {
-                    activeSpaceFilter = ActiveSpaceFilter.ActiveSpace(spaceId)
-                }
-        ).size
-
-        assertEquals("Unexpected number of joined children", 1, childCount)
+//        val bobMembershipFirstRoom = bobSession.getRoomSummary(firstChild!!)?.membership
+//        assertEquals("Bob should have joined this room", Membership.JOIN, bobMembershipFirstRoom)
+//        RoomSummaryQueryParams.Builder()
+//
+//        val childCount = bobSession.getRoomSummaries(
+//                roomSummaryQueryParams {
+//                    activeSpaceFilter = ActiveSpaceFilter.ActiveSpace(spaceId)
+//                }
+//        ).size
+//
+//        assertEquals("Unexpected number of joined children", 1, childCount)
 
         commonTestHelper.signOutAndClose(aliceSession)
         commonTestHelper.signOutAndClose(bobSession)
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceHierarchyTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceHierarchyTest.kt
index 521b5805..301cdea4 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceHierarchyTest.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceHierarchyTest.kt
@@ -47,6 +47,7 @@ class SpaceHierarchyTest : InstrumentedTest {
     private val commonTestHelper = CommonTestHelper(context())
 
     @Test
+    @Suppress("EXPERIMENTAL_API_USAGE")
     fun createCanonicalChildRelation() {
         val session = commonTestHelper.createAccount("John", SessionTestParams(true))
         val spaceName = "My Space"
@@ -171,6 +172,7 @@ class SpaceHierarchyTest : InstrumentedTest {
 //    }
 
     @Test
+    @Suppress("EXPERIMENTAL_API_USAGE")
     fun testFilteringBySpace() {
         val session = commonTestHelper.createAccount("John", SessionTestParams(true))
 
@@ -179,7 +181,7 @@ class SpaceHierarchyTest : InstrumentedTest {
                 Triple("A2", true, true)
         ))
 
-        val spaceBInfo = createPublicSpace(session, "SpaceB", listOf(
+        /* val spaceBInfo = */ createPublicSpace(session, "SpaceB", listOf(
                 Triple("B1", true /*auto-join*/, true/*canonical*/),
                 Triple("B2", true, true),
                 Triple("B3", true, true)
@@ -254,6 +256,7 @@ class SpaceHierarchyTest : InstrumentedTest {
     }
 
     @Test
+    @Suppress("EXPERIMENTAL_API_USAGE")
     fun testBreakCycle() {
         val session = commonTestHelper.createAccount("John", SessionTestParams(true))
 
@@ -301,6 +304,7 @@ class SpaceHierarchyTest : InstrumentedTest {
     }
 
     @Test
+    @Suppress("EXPERIMENTAL_API_USAGE")
     fun testLiveFlatChildren() {
         val session = commonTestHelper.createAccount("John", SessionTestParams(true))
 
@@ -389,6 +393,7 @@ class SpaceHierarchyTest : InstrumentedTest {
             val roomIds: List<String>
     )
 
+    @Suppress("EXPERIMENTAL_API_USAGE")
     private fun createPublicSpace(session: Session,
                                   spaceName: String,
                                   childInfo: List<Triple<String, Boolean, Boolean?>>
@@ -433,7 +438,7 @@ class SpaceHierarchyTest : InstrumentedTest {
     fun testRootSpaces() {
         val session = commonTestHelper.createAccount("John", SessionTestParams(true))
 
-        val spaceAInfo = createPublicSpace(session, "SpaceA", listOf(
+        /* val spaceAInfo = */ createPublicSpace(session, "SpaceA", listOf(
                 Triple("A1", true /*auto-join*/, true/*canonical*/),
                 Triple("A2", true, true)
         ))
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/Extensions.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/Extensions.kt
index 0ba61e58..3149a021 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/Extensions.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/Extensions.kt
@@ -29,7 +29,9 @@ fun Throwable.is401() =
 
 fun Throwable.isTokenError() =
         this is Failure.ServerError
-                && (error.code == MatrixError.M_UNKNOWN_TOKEN || error.code == MatrixError.M_MISSING_TOKEN)
+                && (error.code == MatrixError.M_UNKNOWN_TOKEN
+                || error.code == MatrixError.M_MISSING_TOKEN
+                || error.code == MatrixError.ORG_MATRIX_EXPIRED_ACCOUNT)
 
 fun Throwable.shouldBeRetried(): Boolean {
     return this is Failure.NetworkConnection
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/GlobalError.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/GlobalError.kt
index ef770ea1..50c84da0 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/GlobalError.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/GlobalError.kt
@@ -23,4 +23,5 @@ sealed class GlobalError {
     data class InvalidToken(val softLogout: Boolean) : GlobalError()
     data class ConsentNotGivenError(val consentUri: String) : GlobalError()
     data class CertificateError(val fingerprint: Fingerprint) : GlobalError()
+    object ExpiredAccount : GlobalError()
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/MatrixError.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/MatrixError.kt
index 73b0fe0a..1bc86361 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/MatrixError.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/MatrixError.kt
@@ -182,6 +182,24 @@ data class MatrixError(
         /** (Not documented yet) */
         const val M_WEAK_PASSWORD = "M_WEAK_PASSWORD"
 
+        /** The provided password's length is shorter than the minimum length required by the server. */
+        const val M_PASSWORD_TOO_SHORT = "M_PASSWORD_TOO_SHORT"
+
+        /** The password doesn't contain any digit but the server requires at least one. */
+        const val M_PASSWORD_NO_DIGIT = "M_PASSWORD_NO_DIGIT"
+
+        /** The password doesn't contain any uppercase letter but the server requires at least one. */
+        const val M_PASSWORD_NO_UPPERCASE = "M_PASSWORD_NO_UPPERCASE"
+
+        /** The password doesn't contain any lowercase letter but the server requires at least one. */
+        const val M_PASSWORD_NO_LOWERCASE = "M_PASSWORD_NO_LOWERCASE"
+
+        /** The password doesn't contain any symbol but the server requires at least one. */
+        const val M_PASSWORD_NO_SYMBOL = "M_PASSWORD_NO_SYMBOL"
+
+        /** The password was found in a dictionary, and is not acceptable. */
+        const val M_PASSWORD_IN_DICTIONARY = "M_PASSWORD_IN_DICTIONARY"
+
         const val M_TERMS_NOT_SIGNED = "M_TERMS_NOT_SIGNED"
 
         // For identity service
@@ -189,5 +207,12 @@ data class MatrixError(
 
         // Possible value for "limit_type"
         const val LIMIT_TYPE_MAU = "monthly_active_user"
+
+        /**
+         * The user account has expired. It has to be renewed by clicking on an email or by sending a renewal token.
+         *
+         * More documentation can be found in the dedicated Synapse plugin module repository: https://github.com/matrix-org/synapse-email-account-validity
+         */
+        const val ORG_MATRIX_EXPIRED_ACCOUNT = "ORG_MATRIX_EXPIRED_ACCOUNT"
     }
 }
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 45343686..1d0acf38 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
@@ -15,6 +15,7 @@
  */
 package org.matrix.android.sdk.api.pushrules
 
+import androidx.lifecycle.LiveData
 import org.matrix.android.sdk.api.pushrules.rest.PushRule
 import org.matrix.android.sdk.api.pushrules.rest.RuleSet
 import org.matrix.android.sdk.api.session.events.model.Event
@@ -39,7 +40,7 @@ interface PushRuleService {
 
     suspend fun updatePushRuleActions(kind: RuleKind, ruleId: String, enable: Boolean, actions: List<Action>?)
 
-    suspend fun removePushRule(kind: RuleKind, pushRule: PushRule)
+    suspend fun removePushRule(kind: RuleKind, ruleId: String)
 
     fun addPushRuleListener(listener: PushRuleListener)
 
@@ -56,4 +57,6 @@ interface PushRuleService {
         fun onEventRedacted(redactedEventId: String)
         fun batchFinish()
     }
+
+    fun getKeywords(): LiveData<Set<String>>
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/RuleIds.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/RuleIds.kt
index 4c01588b..5b14e97d 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/RuleIds.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/RuleIds.kt
@@ -35,6 +35,11 @@ object RuleIds {
     // Default Content Rules
     const val RULE_ID_CONTAIN_USER_NAME = ".m.rule.contains_user_name"
 
+    // The keywords rule id is not a "real" id in that it does not exist server-side.
+    // It is used client-side as a placeholder for rendering the keyword push rule setting
+    // alongside the others. A similar approach and naming is used on Web and iOS.
+    const val RULE_ID_KEYWORDS = "_keywords"
+
     // Default Underride Rules
     const val RULE_ID_CALL = ".m.rule.call"
     const val RULE_ID_ONE_TO_ONE_ENCRYPTED_ROOM = ".m.rule.encrypted_room_one_to_one"
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/verification/VerificationService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/verification/VerificationService.kt
index 54a1e896..b9d0c0ad 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/verification/VerificationService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/verification/VerificationService.kt
@@ -52,7 +52,7 @@ interface VerificationService {
                              transactionId: String?): String?
 
     /**
-     * Request a key verification from another user using toDevice events.
+     * Request key verification with another user via room events (instead of the to-device API)
      */
     fun requestKeyVerificationInDMs(methods: List<VerificationMethod>,
                                     otherUserId: String,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/identity/IdentityService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/identity/IdentityService.kt
index ae546b6c..1485ec47 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/identity/IdentityService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/identity/IdentityService.kt
@@ -16,6 +16,8 @@
 
 package org.matrix.android.sdk.api.session.identity
 
+import org.matrix.android.sdk.internal.session.identity.model.SignInvitationResult
+
 /**
  * Provides access to the identity server configuration and services identity server can provide
  */
@@ -121,6 +123,18 @@ interface IdentityService {
      */
     suspend fun getShareStatus(threePids: List<ThreePid>): Map<ThreePid, SharedState>
 
+    /**
+     * When one performs a 3pid invite and the third party identifier is unknown, the home server
+     * will store the invitation in the Identity server and store some information in the room state membership event.
+     * The email invite will contains the token and secret that can be used to claim the stored invitation
+     *
+     * To aid clients who may not be able to perform crypto themselves,
+     * the identity server offers some crypto functionality to help in accepting invitations.
+     * This is less secure than the client doing it itself, but may be useful where this isn't possible.
+     */
+    suspend fun sign3pidInvitation(identiyServer: String, token: String, secret: String) : SignInvitationResult
+
     fun addListener(listener: IdentityServiceListener)
+
     fun removeListener(listener: IdentityServiceListener)
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/PermalinkData.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/PermalinkData.kt
index b3fbdcf1..85291cf0 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/PermalinkData.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/PermalinkData.kt
@@ -17,6 +17,8 @@
 package org.matrix.android.sdk.api.session.permalinks
 
 import android.net.Uri
+import android.os.Parcelable
+import kotlinx.parcelize.Parcelize
 
 /**
  * This sealed class represents all the permalink cases.
@@ -31,6 +33,25 @@ sealed class PermalinkData {
             val viaParameters: List<String>
     ) : PermalinkData()
 
+    /**
+     * &room_name=Team2
+        &room_avatar_url=mxc:
+         &inviter_name=bob
+     */
+    @Parcelize
+    data class RoomEmailInviteLink(
+        val roomId: String,
+        val email: String,
+        val signUrl: String,
+        val roomName: String?,
+        val roomAvatarUrl: String?,
+        val inviterName: String?,
+        val identityServer: String,
+        val token: String,
+        val privateKey: String,
+        val roomType: String?
+    ) : PermalinkData(), Parcelable
+
     data class UserLink(val userId: String) : PermalinkData()
 
     data class GroupLink(val groupId: String) : PermalinkData()
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/PermalinkParser.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/PermalinkParser.kt
index 347a3bb5..005a2eda 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/PermalinkParser.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/PermalinkParser.kt
@@ -19,14 +19,18 @@ package org.matrix.android.sdk.api.session.permalinks
 import android.net.Uri
 import android.net.UrlQuerySanitizer
 import org.matrix.android.sdk.api.MatrixPatterns
+import timber.log.Timber
+import java.net.URLDecoder
 
 /**
- * This class turns an uri to a [PermalinkData]
+ * This class turns a uri to a [PermalinkData]
+ * element-based domains (e.g. https://app.element.io/#/user/@chagai95:matrix.org) permalinks
+ * or matrix.to permalinks (e.g. https://matrix.to/#/@chagai95:matrix.org)
  */
 object PermalinkParser {
 
     /**
-     * Turns an uri string to a [PermalinkData]
+     * Turns a uri string to a [PermalinkData]
      */
     fun parse(uriString: String): PermalinkData {
         val uri = Uri.parse(uriString)
@@ -34,13 +38,16 @@ object PermalinkParser {
     }
 
     /**
-     * Turns an uri to a [PermalinkData]
+     * Turns a uri to a [PermalinkData]
+     * https://github.com/matrix-org/matrix-doc/blob/master/proposals/1704-matrix.to-permalinks.md
      */
     fun parse(uri: Uri): PermalinkData {
         if (!uri.toString().startsWith(PermalinkService.MATRIX_TO_URL_BASE)) {
             return PermalinkData.FallbackLink(uri)
         }
-        val fragment = uri.fragment
+        // We can't use uri.fragment as it is decoding to early and it will break the parsing
+        // of parameters that represents url (like signurl)
+        val fragment = uri.toString().substringAfter("#") // uri.fragment
         if (fragment.isNullOrEmpty()) {
             return PermalinkData.FallbackLink(uri)
         }
@@ -51,21 +58,23 @@ object PermalinkParser {
         val params = safeFragment
                 .split(MatrixPatterns.SEP_REGEX)
                 .filter { it.isNotEmpty() }
+                .map { URLDecoder.decode(it, "UTF-8") }
                 .take(2)
 
-        val identifier = params.getOrNull(0)
+        // the element-based domain permalinks (e.g. https://app.element.io/#/user/@chagai95:matrix.org) don't have the
+        // mxid in the first param (like matrix.to does - https://matrix.to/#/@chagai95:matrix.org) but rather in the second after /user/ so /user/mxid
+        var identifier = params.getOrNull(0)
+        if (identifier.equals("user")) {
+            identifier = params.getOrNull(1)
+        }
+
         val extraParameter = params.getOrNull(1)
         return when {
             identifier.isNullOrEmpty()             -> PermalinkData.FallbackLink(uri)
             MatrixPatterns.isUserId(identifier)    -> PermalinkData.UserLink(userId = identifier)
             MatrixPatterns.isGroupId(identifier)   -> PermalinkData.GroupLink(groupId = identifier)
             MatrixPatterns.isRoomId(identifier)    -> {
-                PermalinkData.RoomLink(
-                        roomIdOrAlias = identifier,
-                        isRoomAlias = false,
-                        eventId = extraParameter.takeIf { !it.isNullOrEmpty() && MatrixPatterns.isEventId(it) },
-                        viaParameters = viaQueryParameters
-                )
+                handleRoomIdCase(fragment, identifier, uri, extraParameter, viaQueryParameters)
             }
             MatrixPatterns.isRoomAlias(identifier) -> {
                 PermalinkData.RoomLink(
@@ -79,13 +88,59 @@ object PermalinkParser {
         }
     }
 
+    private fun handleRoomIdCase(fragment: String, identifier: String, uri: Uri, extraParameter: String?, viaQueryParameters: List<String>): PermalinkData {
+        // Can't rely on built in parsing because it's messing around the signurl
+        val paramList = safeExtractParams(fragment)
+        val signUrl = paramList.firstOrNull { it.first == "signurl" }?.second
+        val email = paramList.firstOrNull { it.first == "email" }?.second
+        return if (signUrl.isNullOrEmpty().not() && email.isNullOrEmpty().not()) {
+            try {
+                val signValidUri = Uri.parse(signUrl)
+                val identityServerHost = signValidUri.authority ?: throw IllegalArgumentException()
+                val token = signValidUri.getQueryParameter("token") ?: throw IllegalArgumentException()
+                val privateKey = signValidUri.getQueryParameter("private_key") ?: throw IllegalArgumentException()
+                PermalinkData.RoomEmailInviteLink(
+                        roomId = identifier,
+                        email = email!!,
+                        signUrl = signUrl!!,
+                        roomName = paramList.firstOrNull { it.first == "room_name" }?.second,
+                        inviterName = paramList.firstOrNull { it.first == "inviter_name" }?.second,
+                        roomAvatarUrl = paramList.firstOrNull { it.first == "room_avatar_url" }?.second,
+                        roomType = paramList.firstOrNull { it.first == "room_type" }?.second,
+                        identityServer = identityServerHost,
+                        token = token,
+                        privateKey = privateKey
+                )
+            } catch (failure: Throwable) {
+                Timber.i("## Permalink: Failed to parse permalink $signUrl")
+                PermalinkData.FallbackLink(uri)
+            }
+        } else {
+            PermalinkData.RoomLink(
+                    roomIdOrAlias = identifier,
+                    isRoomAlias = false,
+                    eventId = extraParameter.takeIf { !it.isNullOrEmpty() && MatrixPatterns.isEventId(it) },
+                    viaParameters = viaQueryParameters
+            )
+        }
+    }
+
+    private fun safeExtractParams(fragment: String) = fragment.substringAfter("?").split('&').mapNotNull {
+        val splitNameValue = it.split("=")
+        if (splitNameValue.size == 2) {
+            Pair(splitNameValue[0], URLDecoder.decode(splitNameValue[1], "UTF-8"))
+        } else null
+    }
+
     private fun String.getViaParameters(): List<String> {
         return UrlQuerySanitizer(this)
                 .parameterList
                 .filter {
                     it.mParameter == "via"
                 }.map {
-                    it.mValue
+                    it.mValue.let {
+                        URLDecoder.decode(it, "UTF-8")
+                    }
                 }
     }
 }
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 b7377df1..5d26b213 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
@@ -27,6 +27,7 @@ import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams
 import org.matrix.android.sdk.api.session.room.peeking.PeekResult
 import org.matrix.android.sdk.api.session.room.summary.RoomAggregateNotificationCount
 import org.matrix.android.sdk.api.util.Optional
+import org.matrix.android.sdk.internal.session.identity.model.SignInvitationResult
 import org.matrix.android.sdk.internal.session.room.alias.RoomAliasDescription
 
 /**
@@ -63,6 +64,18 @@ interface RoomService {
                          reason: String? = null,
                          viaServers: List<String> = emptyList())
 
+    /**
+     * @param roomId the roomId of the room to join
+     * @param reason optional reason for joining the room
+     * @param thirdPartySigned A signature of an m.third_party_invite token to prove that this user owns a third party identity
+     * which has been invited to the room.
+     */
+    suspend fun joinRoom(
+            roomId: String,
+            reason: String? = null,
+            thirdPartySigned: SignInvitationResult
+    )
+
     /**
      * Get a room from a roomId
      * @param roomId the roomId to look for.
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomSortOrder.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomSortOrder.kt
index 36da2425..e721abd6 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomSortOrder.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomSortOrder.kt
@@ -19,5 +19,6 @@ package org.matrix.android.sdk.api.session.room
 enum class RoomSortOrder {
     NAME,
     ACTIVITY,
+    PRIORITY_AND_ACTIVITY,
     NONE
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomJoinRulesAllowEntry.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomJoinRulesAllowEntry.kt
index 7b87bc34..16298b8b 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomJoinRulesAllowEntry.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomJoinRulesAllowEntry.kt
@@ -23,11 +23,15 @@ import com.squareup.moshi.JsonClass
 @JsonClass(generateAdapter = true)
 data class RoomJoinRulesAllowEntry(
         /**
-         * space: The room ID of the space to check the membership of.
+         * The room ID to check the membership of.
          */
-        @Json(name = "space") val spaceID: String,
+        @Json(name = "room_id") val roomId: String?,
         /**
-         * via: A list of servers which may be used to peek for membership of the space.
+         * "m.room_membership" to describe that we are allowing access via room membership. Future MSCs may define other types.
          */
-        @Json(name = "via") val via: List<String>
-)
+        @Json(name = "type") val type: String?
+) {
+    companion object {
+        fun restrictedToRoom(roomId: String) = RoomJoinRulesAllowEntry(roomId, "m.room_membership")
+    }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomJoinRulesContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomJoinRulesContent.kt
index 33f402ca..871b299f 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomJoinRulesContent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomJoinRulesContent.kt
@@ -29,7 +29,9 @@ import timber.log.Timber
 data class RoomJoinRulesContent(
         @Json(name = "join_rule") val _joinRules: String? = null,
         /**
-         * If the allow key is an empty list (or not a list at all), then the room reverts to standard public join rules
+         * If the allow key is an empty list (or not a list at all),
+         * then no users are allowed to join without an invite.
+         * Each entry is expected to be an object with the following keys:
          */
         @Json(name = "allow") val allowList: List<RoomJoinRulesAllowEntry>? = null
 ) {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/SpaceChildInfo.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/SpaceChildInfo.kt
index 8cd2a053..7d3109fb 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/SpaceChildInfo.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/SpaceChildInfo.kt
@@ -27,7 +27,7 @@ data class SpaceChildInfo(
         val avatarUrl: String?,
         val order: String?,
         val activeMemberCount: Int?,
-        val autoJoin: Boolean,
+//        val autoJoin: Boolean,
         val viaServers: List<String>,
         val parentRoomId: String?,
         val suggested: Boolean?,
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
index d576f105..aa05eb71 100644
--- 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
@@ -32,5 +32,5 @@ data class AudioWaveformInfo(
          * List of integers between zero and 1024, inclusive.
          */
         @Json(name = "waveform")
-        val waveform: List<Int>? = null
+        val waveform: List<Int?>? = null
 )
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 3bae6126..207050be 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
@@ -36,7 +36,7 @@ interface Space {
     suspend fun addChildren(roomId: String,
                             viaServers: List<String>?,
                             order: String?,
-                            autoJoin: Boolean = false,
+//                            autoJoin: Boolean = false,
                             suggested: Boolean? = false)
 
     fun getChildInfo(roomId: String): SpaceChildContent?
@@ -46,8 +46,8 @@ interface Space {
     @Throws
     suspend fun setChildrenOrder(roomId: String, order: String?)
 
-    @Throws
-    suspend fun setChildrenAutoJoin(roomId: String, autoJoin: Boolean)
+//    @Throws
+//    suspend fun setChildrenAutoJoin(roomId: String, autoJoin: Boolean)
 
     @Throws
     suspend fun setChildrenSuggested(roomId: String, suggested: Boolean)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/SpaceHierarchyData.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/SpaceHierarchyData.kt
new file mode 100644
index 00000000..ecc3eb52
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/SpaceHierarchyData.kt
@@ -0,0 +1,28 @@
+/*
+ * 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.session.events.model.Event
+import org.matrix.android.sdk.api.session.room.model.RoomSummary
+import org.matrix.android.sdk.api.session.room.model.SpaceChildInfo
+
+data class SpaceHierarchyData(
+        val rootSummary: RoomSummary,
+        val children: List<SpaceChildInfo>,
+        val childrenState: List<Event>,
+        val nextToken: String? = null
+)
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 e5288e40..bcc36b57 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
@@ -18,9 +18,9 @@ package org.matrix.android.sdk.api.session.space
 
 import android.net.Uri
 import androidx.lifecycle.LiveData
+import org.matrix.android.sdk.api.session.events.model.Event
 import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams
 import org.matrix.android.sdk.api.session.room.model.RoomSummary
-import org.matrix.android.sdk.api.session.room.model.SpaceChildInfo
 import org.matrix.android.sdk.internal.session.space.peeking.SpacePeekResult
 
 typealias SpaceSummaryQueryParams = RoomSummaryQueryParams
@@ -58,10 +58,17 @@ interface SpaceService {
 
     /**
      * Get's information of a space by querying the server
+     * @param suggestedOnly If true, return only child events and rooms where the m.space.child event has suggested: true.
+     * @param limit a client-defined limit to the maximum number of rooms to return per page. Must be a non-negative integer.
+     * @param from: Optional. Pagination token given to retrieve the next set of rooms. Note that if a pagination token is provided,
+     * then the parameters given for suggested_only and max_depth must be the same.
      */
     suspend fun querySpaceChildren(spaceId: String,
                                    suggestedOnly: Boolean? = null,
-                                   autoJoinedOnly: Boolean? = null): Pair<RoomSummary, List<SpaceChildInfo>>
+                                   limit: Int? = null,
+                                   from: String? = null,
+                                   // when paginating, pass back the m.space.child state events
+                                   knownStateList: List<Event>? = null): SpaceHierarchyData
 
     /**
      * Get a live list of space summaries. This list is refreshed as soon as the data changes.
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/model/SpaceChildContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/model/SpaceChildContent.kt
index 0c33cfa1..b55f90ce 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/model/SpaceChildContent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/model/SpaceChildContent.kt
@@ -40,12 +40,12 @@ data class SpaceChildContent(
          * or consist of more than 50 characters, are forbidden and should be ignored if received.)
          */
         @Json(name = "order") val order: String? = null,
-        /**
-         * The auto_join flag on a child listing allows a space admin to list the sub-spaces and rooms in that space which should
-         * be automatically joined by members of that space.
-         * (This is not a force-join, which are descoped for a future MSC; the user can subsequently part these room if they desire.)
-         */
-        @Json(name = "auto_join") val autoJoin: Boolean? = false,
+//        /**
+//         * The auto_join flag on a child listing allows a space admin to list the sub-spaces and rooms in that space which should
+//         * be automatically joined by members of that space.
+//         * (This is not a force-join, which are descoped for a future MSC; the user can subsequently part these room if they desire.)
+//         */
+//        @Json(name = "auto_join") val autoJoin: Boolean? = false,
 
         /**
          * If `suggested` is set to `true`, that indicates that the child should be advertised to
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CancelGossipRequestWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CancelGossipRequestWorker.kt
index 7e92ff3e..0ec020bc 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CancelGossipRequestWorker.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CancelGossipRequestWorker.kt
@@ -23,12 +23,12 @@ import org.matrix.android.sdk.api.auth.data.Credentials
 import org.matrix.android.sdk.api.failure.shouldBeRetried
 import org.matrix.android.sdk.api.session.events.model.Event
 import org.matrix.android.sdk.api.session.events.model.EventType
-import org.matrix.android.sdk.api.session.events.model.LocalEcho
 import org.matrix.android.sdk.api.session.events.model.toContent
 import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap
 import org.matrix.android.sdk.internal.crypto.model.rest.ShareRequestCancellation
 import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
 import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask
+import org.matrix.android.sdk.internal.crypto.tasks.createUniqueTxnId
 import org.matrix.android.sdk.internal.session.SessionComponent
 import org.matrix.android.sdk.internal.worker.SessionSafeCoroutineWorker
 import org.matrix.android.sdk.internal.worker.SessionWorkerParams
@@ -43,6 +43,9 @@ internal class CancelGossipRequestWorker(context: Context,
             override val sessionId: String,
             val requestId: String,
             val recipients: Map<String, List<String>>,
+            // The txnId for the sendToDevice request. Nullable for compatibility reasons, but MUST always be provided
+            // to use the same value if this worker is retried.
+            val txnId: String? = null,
             override val lastFailureMessage: String? = null
     ) : SessionWorkerParams {
         companion object {
@@ -51,6 +54,7 @@ internal class CancelGossipRequestWorker(context: Context,
                         sessionId = sessionId,
                         requestId = request.requestId,
                         recipients = request.recipients,
+                        txnId = createUniqueTxnId(),
                         lastFailureMessage = null
                 )
             }
@@ -66,7 +70,10 @@ internal class CancelGossipRequestWorker(context: Context,
     }
 
     override suspend fun doSafeWork(params: Params): Result {
-        val localId = LocalEcho.createLocalEchoId()
+        // params.txnId should be provided in all cases now. But Params can be deserialized by
+        // the WorkManager from data serialized in a previous version of the application, so without the txnId field.
+        // So if not present, we create a txnId
+        val txnId = params.txnId ?: createUniqueTxnId()
         val contentMap = MXUsersDevicesMap<Any>()
         val toDeviceContent = ShareRequestCancellation(
                 requestingDeviceId = credentials.deviceId,
@@ -92,7 +99,7 @@ internal class CancelGossipRequestWorker(context: Context,
                     SendToDeviceTask.Params(
                             eventType = EventType.ROOM_KEY_REQUEST,
                             contentMap = contentMap,
-                            transactionId = localId
+                            transactionId = txnId
                     )
             )
             cryptoStore.updateOutgoingGossipingRequestState(params.requestId, OutgoingGossipingRequestState.CANCELLED)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/IncomingGossipingRequestManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/IncomingGossipingRequestManager.kt
index 4a0a274f..e8640d50 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/IncomingGossipingRequestManager.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/IncomingGossipingRequestManager.kt
@@ -35,6 +35,7 @@ import org.matrix.android.sdk.internal.crypto.model.rest.GossipingDefaultContent
 import org.matrix.android.sdk.internal.crypto.model.rest.GossipingToDeviceObject
 import org.matrix.android.sdk.internal.crypto.model.rest.RoomKeyRequestBody
 import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
+import org.matrix.android.sdk.internal.crypto.tasks.createUniqueTxnId
 import org.matrix.android.sdk.internal.di.SessionId
 import org.matrix.android.sdk.internal.session.SessionScope
 import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers
@@ -356,7 +357,8 @@ internal class IncomingGossipingRequestManager @Inject constructor(
                         secretValue = secretValue,
                         requestUserId = request.userId,
                         requestDeviceId = request.deviceId,
-                        requestId = request.requestId
+                        requestId = request.requestId,
+                        txnId = createUniqueTxnId()
                 )
 
                 cryptoStore.updateGossipingRequestState(request, GossipingRequestState.ACCEPTING)
@@ -376,13 +378,13 @@ internal class IncomingGossipingRequestManager @Inject constructor(
         }
 
         request.share = { secretValue ->
-
             val params = SendGossipWorker.Params(
                     sessionId = userId,
                     secretValue = secretValue,
                     requestUserId = request.userId,
                     requestDeviceId = request.deviceId,
-                    requestId = request.requestId
+                    requestId = request.requestId,
+                    txnId = createUniqueTxnId()
             )
 
             cryptoStore.updateGossipingRequestState(request, GossipingRequestState.ACCEPTING)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OutgoingGossipingRequestManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OutgoingGossipingRequestManager.kt
index c86f2be0..ccdb5ab1 100755
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OutgoingGossipingRequestManager.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OutgoingGossipingRequestManager.kt
@@ -16,7 +16,6 @@
 
 package org.matrix.android.sdk.internal.crypto
 
-import org.matrix.android.sdk.api.session.events.model.LocalEcho
 import org.matrix.android.sdk.internal.crypto.model.rest.RoomKeyRequestBody
 import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
 import org.matrix.android.sdk.internal.di.SessionId
@@ -26,6 +25,8 @@ import org.matrix.android.sdk.internal.worker.WorkerParamsFactory
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.launch
+import org.matrix.android.sdk.internal.crypto.tasks.createUniqueTxnId
+import org.matrix.android.sdk.internal.crypto.util.RequestIdHelper
 import timber.log.Timber
 import javax.inject.Inject
 
@@ -131,7 +132,8 @@ internal class OutgoingGossipingRequestManager @Inject constructor(
         val params = SendGossipRequestWorker.Params(
                 sessionId = sessionId,
                 keyShareRequest = request as? OutgoingRoomKeyRequest,
-                secretShareRequest = request as? OutgoingSecretRequest
+                secretShareRequest = request as? OutgoingSecretRequest,
+                txnId = createUniqueTxnId()
         )
         cryptoStore.updateOutgoingGossipingRequestState(request.requestId, OutgoingGossipingRequestState.SENDING)
         val workRequest = gossipingWorkManager.createWork<SendGossipRequestWorker>(WorkerParamsFactory.toData(params), true)
@@ -154,7 +156,8 @@ internal class OutgoingGossipingRequestManager @Inject constructor(
         if (resend) {
             val reSendParams = SendGossipRequestWorker.Params(
                     sessionId = sessionId,
-                    keyShareRequest = request.copy(requestId = LocalEcho.createLocalEchoId())
+                    keyShareRequest = request.copy(requestId = RequestIdHelper.createUniqueRequestId()),
+                    txnId = createUniqueTxnId()
             )
             val reSendWorkRequest = gossipingWorkManager.createWork<SendGossipRequestWorker>(WorkerParamsFactory.toData(reSendParams), true)
             gossipingWorkManager.postWork(reSendWorkRequest)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/SendGossipRequestWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/SendGossipRequestWorker.kt
index e8d567b9..2e26720a 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/SendGossipRequestWorker.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/SendGossipRequestWorker.kt
@@ -23,7 +23,6 @@ import org.matrix.android.sdk.api.auth.data.Credentials
 import org.matrix.android.sdk.api.failure.shouldBeRetried
 import org.matrix.android.sdk.api.session.events.model.Event
 import org.matrix.android.sdk.api.session.events.model.EventType
-import org.matrix.android.sdk.api.session.events.model.LocalEcho
 import org.matrix.android.sdk.api.session.events.model.toContent
 import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap
 import org.matrix.android.sdk.internal.crypto.model.rest.GossipingToDeviceObject
@@ -31,6 +30,7 @@ import org.matrix.android.sdk.internal.crypto.model.rest.RoomKeyShareRequest
 import org.matrix.android.sdk.internal.crypto.model.rest.SecretShareRequest
 import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
 import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask
+import org.matrix.android.sdk.internal.crypto.tasks.createUniqueTxnId
 import org.matrix.android.sdk.internal.session.SessionComponent
 import org.matrix.android.sdk.internal.worker.SessionSafeCoroutineWorker
 import org.matrix.android.sdk.internal.worker.SessionWorkerParams
@@ -46,6 +46,9 @@ internal class SendGossipRequestWorker(context: Context,
             override val sessionId: String,
             val keyShareRequest: OutgoingRoomKeyRequest? = null,
             val secretShareRequest: OutgoingSecretRequest? = null,
+            // The txnId for the sendToDevice request. Nullable for compatibility reasons, but MUST always be provided
+            // to use the same value if this worker is retried.
+            val txnId: String? = null,
             override val lastFailureMessage: String? = null
     ) : SessionWorkerParams
 
@@ -58,7 +61,10 @@ internal class SendGossipRequestWorker(context: Context,
     }
 
     override suspend fun doSafeWork(params: Params): Result {
-        val localId = LocalEcho.createLocalEchoId()
+        // params.txnId should be provided in all cases now. But Params can be deserialized by
+        // the WorkManager from data serialized in a previous version of the application, so without the txnId field.
+        // So if not present, we create a txnId
+        val txnId = params.txnId ?: createUniqueTxnId()
         val contentMap = MXUsersDevicesMap<Any>()
         val eventType: String
         val requestId: String
@@ -122,7 +128,7 @@ internal class SendGossipRequestWorker(context: Context,
                     SendToDeviceTask.Params(
                             eventType = eventType,
                             contentMap = contentMap,
-                            transactionId = localId
+                            transactionId = txnId
                     )
             )
             cryptoStore.updateOutgoingGossipingRequestState(requestId, OutgoingGossipingRequestState.SENT)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/SendGossipWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/SendGossipWorker.kt
index 8c680570..c5c6d26f 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/SendGossipWorker.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/SendGossipWorker.kt
@@ -23,7 +23,6 @@ import org.matrix.android.sdk.api.auth.data.Credentials
 import org.matrix.android.sdk.api.failure.shouldBeRetried
 import org.matrix.android.sdk.api.session.events.model.Event
 import org.matrix.android.sdk.api.session.events.model.EventType
-import org.matrix.android.sdk.api.session.events.model.LocalEcho
 import org.matrix.android.sdk.api.session.events.model.toContent
 import org.matrix.android.sdk.internal.crypto.actions.EnsureOlmSessionsForDevicesAction
 import org.matrix.android.sdk.internal.crypto.actions.MessageEncrypter
@@ -31,6 +30,7 @@ import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap
 import org.matrix.android.sdk.internal.crypto.model.event.SecretSendEventContent
 import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
 import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask
+import org.matrix.android.sdk.internal.crypto.tasks.createUniqueTxnId
 import org.matrix.android.sdk.internal.session.SessionComponent
 import org.matrix.android.sdk.internal.worker.SessionSafeCoroutineWorker
 import org.matrix.android.sdk.internal.worker.SessionWorkerParams
@@ -48,6 +48,9 @@ internal class SendGossipWorker(context: Context,
             val requestUserId: String?,
             val requestDeviceId: String?,
             val requestId: String?,
+            // The txnId for the sendToDevice request. Nullable for compatibility reasons, but MUST always be provided
+            // to use the same value if this worker is retried.
+            val txnId: String? = null,
             override val lastFailureMessage: String? = null
     ) : SessionWorkerParams
 
@@ -62,7 +65,10 @@ internal class SendGossipWorker(context: Context,
     }
 
     override suspend fun doSafeWork(params: Params): Result {
-        val localId = LocalEcho.createLocalEchoId()
+        // params.txnId should be provided in all cases now. But Params can be deserialized by
+        // the WorkManager from data serialized in a previous version of the application, so without the txnId field.
+        // So if not present, we create a txnId
+        val txnId = params.txnId ?: createUniqueTxnId()
         val eventType: String = EventType.SEND_SECRET
 
         val toDeviceContent = SecretSendEventContent(
@@ -127,7 +133,7 @@ internal class SendGossipWorker(context: Context,
                     SendToDeviceTask.Params(
                             eventType = EventType.ENCRYPTED,
                             contentMap = sendToDeviceMap,
-                            transactionId = localId
+                            transactionId = txnId
                     )
             )
             cryptoStore.updateGossipingRequestState(
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 d9979988..3c8353e8 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
@@ -27,7 +27,6 @@ import io.realm.Sort
 import io.realm.kotlin.where
 import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo
 import org.matrix.android.sdk.api.session.events.model.Event
-import org.matrix.android.sdk.api.session.events.model.LocalEcho
 import org.matrix.android.sdk.api.session.room.send.SendState
 import org.matrix.android.sdk.api.util.Optional
 import org.matrix.android.sdk.api.util.toOptional
@@ -88,6 +87,7 @@ import org.matrix.android.sdk.internal.crypto.store.db.query.delete
 import org.matrix.android.sdk.internal.crypto.store.db.query.get
 import org.matrix.android.sdk.internal.crypto.store.db.query.getById
 import org.matrix.android.sdk.internal.crypto.store.db.query.getOrCreate
+import org.matrix.android.sdk.internal.crypto.util.RequestIdHelper
 import org.matrix.android.sdk.internal.database.mapper.ContentMapper
 import org.matrix.android.sdk.internal.database.tools.RealmDebugTools
 import org.matrix.android.sdk.internal.di.CryptoDatabase
@@ -1121,7 +1121,7 @@ internal class RealmCryptoStore @Inject constructor(
 
             if (existing == null) {
                 request = realm.createObject(OutgoingGossipingRequestEntity::class.java).apply {
-                    this.requestId = LocalEcho.createLocalEchoId()
+                    this.requestId = RequestIdHelper.createUniqueRequestId()
                     this.setRecipients(recipients)
                     this.requestState = OutgoingGossipingRequestState.UNSENT
                     this.type = GossipRequestType.KEY
@@ -1151,7 +1151,7 @@ internal class RealmCryptoStore @Inject constructor(
                     this.type = GossipRequestType.SECRET
                     setRecipients(recipients)
                     this.requestState = OutgoingGossipingRequestState.UNSENT
-                    this.requestId = LocalEcho.createLocalEchoId()
+                    this.requestId = RequestIdHelper.createUniqueRequestId()
                     this.requestedInfoStr = secretName
                 }.toOutgoingGossipingRequest() as? OutgoingSecretRequest
             } else {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendToDeviceTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendToDeviceTask.kt
index 41a5118b..c6af9b0c 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendToDeviceTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendToDeviceTask.kt
@@ -22,8 +22,8 @@ import org.matrix.android.sdk.internal.crypto.model.rest.SendToDeviceBody
 import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import org.matrix.android.sdk.internal.network.executeRequest
 import org.matrix.android.sdk.internal.task.Task
+import java.util.UUID
 import javax.inject.Inject
-import kotlin.random.Random
 
 internal interface SendToDeviceTask : Task<SendToDeviceTask.Params, Unit> {
     data class Params(
@@ -31,7 +31,7 @@ internal interface SendToDeviceTask : Task<SendToDeviceTask.Params, Unit> {
             val eventType: String,
             // the content to send. Map from user_id to device_id to content dictionary.
             val contentMap: MXUsersDevicesMap<Any>,
-            // the transactionId
+            // the transactionId. If not provided, a transactionId will be created by the task
             val transactionId: String? = null
     )
 }
@@ -46,16 +46,23 @@ internal class DefaultSendToDeviceTask @Inject constructor(
                 messages = params.contentMap.map
         )
 
+        // If params.transactionId is not provided, we create a unique txnId.
+        // It's important to do that outside the requestBlock parameter of executeRequest()
+        // to use the same value if the request is retried
+        val txnId = params.transactionId ?: createUniqueTxnId()
+
         return executeRequest(
                 globalErrorReceiver,
                 canRetry = true,
                 maxRetriesCount = 3
         ) {
             cryptoApi.sendToDevice(
-                    params.eventType,
-                    params.transactionId ?: Random.nextInt(Integer.MAX_VALUE).toString(),
-                    sendToDeviceBody
+                    eventType = params.eventType,
+                    transactionId = txnId,
+                    body = sendToDeviceBody
             )
         }
     }
 }
+
+internal fun createUniqueTxnId() = UUID.randomUUID().toString()
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/util/RequestIdHelper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/util/RequestIdHelper.kt
new file mode 100644
index 00000000..3106d582
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/util/RequestIdHelper.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) 2021 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.internal.crypto.util
+
+import java.util.UUID
+
+internal object RequestIdHelper {
+    fun createUniqueRequestId() = UUID.randomUUID().toString()
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/EventInsertLiveObserver.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/EventInsertLiveObserver.kt
index 88aa432f..758c7aa5 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/EventInsertLiveObserver.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/EventInsertLiveObserver.kt
@@ -17,6 +17,9 @@
 package org.matrix.android.sdk.internal.database
 
 import com.zhuinden.monarchy.Monarchy
+import io.realm.RealmConfiguration
+import io.realm.RealmResults
+import kotlinx.coroutines.launch
 import org.matrix.android.sdk.internal.database.mapper.asDomain
 import org.matrix.android.sdk.internal.database.model.EventEntity
 import org.matrix.android.sdk.internal.database.model.EventInsertEntity
@@ -24,20 +27,15 @@ import org.matrix.android.sdk.internal.database.model.EventInsertEntityFields
 import org.matrix.android.sdk.internal.database.query.where
 import org.matrix.android.sdk.internal.di.SessionDatabase
 import org.matrix.android.sdk.internal.session.EventInsertLiveProcessor
-import io.realm.RealmConfiguration
-import io.realm.RealmResults
-import kotlinx.coroutines.launch
-import org.matrix.android.sdk.internal.crypto.EventDecryptor
 import timber.log.Timber
 import javax.inject.Inject
 
 internal class EventInsertLiveObserver @Inject constructor(@SessionDatabase realmConfiguration: RealmConfiguration,
-                                                           private val processors: Set<@JvmSuppressWildcards EventInsertLiveProcessor>,
-                                                           private val eventDecryptor: EventDecryptor)
+                                                           private val processors: Set<@JvmSuppressWildcards EventInsertLiveProcessor>)
     : RealmLiveEntityObserver<EventInsertEntity>(realmConfiguration) {
 
-    override val query = Monarchy.Query<EventInsertEntity> {
-        it.where(EventInsertEntity::class.java)
+    override val query = Monarchy.Query {
+        it.where(EventInsertEntity::class.java).equalTo(EventInsertEntityFields.CAN_BE_PROCESSED, true)
     }
 
     override fun onChange(results: RealmResults<EventInsertEntity>) {
@@ -86,23 +84,6 @@ internal class EventInsertLiveObserver @Inject constructor(@SessionDatabase real
         }
     }
 
-//    private fun decryptIfNeeded(event: Event) {
-//        if (event.isEncrypted() && event.mxDecryptionResult == null) {
-//            try {
-//                val result = eventDecryptor.decryptEvent(event, event.roomId ?: "")
-//                event.mxDecryptionResult = OlmDecryptionResult(
-//                        payload = result.clearEvent,
-//                        senderKey = result.senderCurve25519Key,
-//                        keysClaimed = result.claimedEd25519Key?.let { k -> mapOf("ed25519" to k) },
-//                        forwardingCurve25519KeyChain = result.forwardingCurve25519KeyChain
-//                )
-//            } catch (e: MXCryptoError) {
-//                Timber.v("Failed to decrypt event")
-//                // TODO -> we should keep track of this and retry, or some processing will never be handled
-//            }
-//        }
-//    }
-
     private fun shouldProcess(eventInsertEntity: EventInsertEntity): Boolean {
         return processors.any {
             it.shouldProcess(eventInsertEntity.eventId, eventInsertEntity.eventType, eventInsertEntity.insertType)
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 28ae4d8b..aa96ca5e 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
@@ -29,6 +29,7 @@ import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntityFie
 import org.matrix.android.sdk.internal.database.model.EditAggregatedSummaryEntityFields
 import org.matrix.android.sdk.internal.database.model.EditionOfEventFields
 import org.matrix.android.sdk.internal.database.model.EventEntityFields
+import org.matrix.android.sdk.internal.database.model.EventInsertEntityFields
 import org.matrix.android.sdk.internal.database.model.HomeServerCapabilitiesEntityFields
 import org.matrix.android.sdk.internal.database.model.PendingThreePidEntityFields
 import org.matrix.android.sdk.internal.database.model.PreviewUrlCacheEntityFields
@@ -46,7 +47,7 @@ import timber.log.Timber
 
 internal object RealmSessionStoreMigration : RealmMigration {
 
-    const val SESSION_STORE_SCHEMA_VERSION = 16L
+    const val SESSION_STORE_SCHEMA_VERSION = 17L
 
     override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) {
         Timber.v("Migrating Realm Session from $oldVersion to $newVersion")
@@ -67,6 +68,7 @@ internal object RealmSessionStoreMigration : RealmMigration {
         if (oldVersion <= 13) migrateTo14(realm)
         if (oldVersion <= 14) migrateTo15(realm)
         if (oldVersion <= 15) migrateTo16(realm)
+        if (oldVersion <= 16) migrateTo17(realm)
     }
 
     private fun migrateTo1(realm: DynamicRealm) {
@@ -330,4 +332,10 @@ internal object RealmSessionStoreMigration : RealmMigration {
                     obj.setLong(HomeServerCapabilitiesEntityFields.LAST_UPDATED_TIMESTAMP, 0)
                 }
     }
+
+    private fun migrateTo17(realm: DynamicRealm) {
+        Timber.d("Step 16 -> 17")
+        realm.schema.get("EventInsertEntity")
+                ?.addField(EventInsertEntityFields.CAN_BE_PROCESSED, Boolean::class.java)
+    }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/RoomSummaryMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/RoomSummaryMapper.kt
index c32c0196..0cf431c3 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/RoomSummaryMapper.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/RoomSummaryMapper.kt
@@ -88,7 +88,7 @@ internal class RoomSummaryMapper @Inject constructor(private val timelineEventMa
                             avatarUrl = it.childSummaryEntity?.avatarUrl,
                             activeMemberCount = it.childSummaryEntity?.joinedMembersCount,
                             order = it.order,
-                            autoJoin = it.autoJoin ?: false,
+//                            autoJoin = it.autoJoin ?: false,
                             viaServers = it.viaServers.toList(),
                             parentRoomId = roomSummaryEntity.roomId,
                             suggested = it.suggested,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/EventEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/EventEntity.kt
index c9edbcd8..4dc8712a 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/EventEntity.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/EventEntity.kt
@@ -22,6 +22,7 @@ import org.matrix.android.sdk.internal.crypto.algorithms.olm.OlmDecryptionResult
 import org.matrix.android.sdk.internal.di.MoshiProvider
 import io.realm.RealmObject
 import io.realm.annotations.Index
+import org.matrix.android.sdk.internal.extensions.assertIsManaged
 
 internal open class EventEntity(@Index var eventId: String = "",
                                 @Index var roomId: String = "",
@@ -56,15 +57,22 @@ internal open class EventEntity(@Index var eventId: String = "",
     companion object
 
     fun setDecryptionResult(result: MXEventDecryptionResult) {
+        assertIsManaged()
         val decryptionResult = OlmDecryptionResult(
                 payload = result.clearEvent,
                 senderKey = result.senderCurve25519Key,
                 keysClaimed = result.claimedEd25519Key?.let { mapOf("ed25519" to it) },
                 forwardingCurve25519KeyChain = result.forwardingCurve25519KeyChain
         )
-        val adapter = MoshiProvider.providesMoshi().adapter<OlmDecryptionResult>(OlmDecryptionResult::class.java)
+        val adapter = MoshiProvider.providesMoshi().adapter(OlmDecryptionResult::class.java)
         decryptionResultJson = adapter.toJson(decryptionResult)
         decryptionErrorCode = null
         decryptionErrorReason = null
+
+        // If we have an EventInsertEntity for the eventId we make sures it can be processed now.
+        realm.where(EventInsertEntity::class.java)
+                .equalTo(EventInsertEntityFields.EVENT_ID, eventId)
+                .findFirst()
+                ?.canBeProcessed = true
     }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/EventInsertEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/EventInsertEntity.kt
index f4426207..5cfd306d 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/EventInsertEntity.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/EventInsertEntity.kt
@@ -23,7 +23,12 @@ import io.realm.RealmObject
  * in EventEntity table.
  */
 internal open class EventInsertEntity(var eventId: String = "",
-                                      var eventType: String = ""
+                                      var eventType: String = "",
+                                      /**
+                                       * This flag will be used to filter EventInsertEntity in EventInsertLiveObserver.
+                                       * Currently it's set to false when the event content is encrypted.
+                                       */
+                                      var canBeProcessed: Boolean = true
 ) : RealmObject() {
 
     private var insertTypeStr: String = EventInsertType.INCREMENTAL_SYNC.name
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/EventEntityQueries.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/EventEntityQueries.kt
index 0bf62a19..57e24cf8 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/EventEntityQueries.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/EventEntityQueries.kt
@@ -24,6 +24,7 @@ import io.realm.Realm
 import io.realm.RealmList
 import io.realm.RealmQuery
 import io.realm.kotlin.where
+import org.matrix.android.sdk.api.session.events.model.EventType
 
 internal fun EventEntity.copyToRealmOrIgnore(realm: Realm, insertType: EventInsertType): EventEntity {
     val eventEntity = realm.where<EventEntity>()
@@ -31,7 +32,8 @@ internal fun EventEntity.copyToRealmOrIgnore(realm: Realm, insertType: EventInse
             .equalTo(EventEntityFields.ROOM_ID, roomId)
             .findFirst()
     return if (eventEntity == null) {
-        val insertEntity = EventInsertEntity(eventId = eventId, eventType = type).apply {
+        val canBeProcessed = type != EventType.ENCRYPTED || decryptionResultJson != null
+        val insertEntity = EventInsertEntity(eventId = eventId, eventType = type, canBeProcessed = canBeProcessed).apply {
             this.insertType = insertType
         }
         realm.insert(insertEntity)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/NetworkConstants.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/NetworkConstants.kt
index 99c12255..361a306d 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/NetworkConstants.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/NetworkConstants.kt
@@ -30,6 +30,7 @@ internal object NetworkConstants {
     // Identity server
     const val URI_IDENTITY_PREFIX_PATH = "_matrix/identity/v2"
     const val URI_IDENTITY_PATH_V2 = "$URI_IDENTITY_PREFIX_PATH/"
+    const val URI_IDENTITY_PATH_V1 = "_matrix/identity/api/v1/"
 
     // Push Gateway
     const val URI_PUSH_GATEWAY_PREFIX_PATH = "_matrix/push/v1/"
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/RetrofitExtensions.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/RetrofitExtensions.kt
index 21160636..8a031025 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/RetrofitExtensions.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/RetrofitExtensions.kt
@@ -19,13 +19,13 @@
 package org.matrix.android.sdk.internal.network
 
 import com.squareup.moshi.JsonEncodingException
+import kotlinx.coroutines.suspendCancellableCoroutine
+import okhttp3.ResponseBody
+import org.matrix.android.sdk.api.extensions.orFalse
 import org.matrix.android.sdk.api.failure.Failure
 import org.matrix.android.sdk.api.failure.GlobalError
 import org.matrix.android.sdk.api.failure.MatrixError
 import org.matrix.android.sdk.internal.di.MoshiProvider
-import kotlinx.coroutines.suspendCancellableCoroutine
-import okhttp3.ResponseBody
-import org.matrix.android.sdk.api.extensions.orFalse
 import retrofit2.HttpException
 import retrofit2.Response
 import timber.log.Timber
@@ -86,13 +86,18 @@ private fun toFailure(errorBody: ResponseBody?, httpCode: Int, globalErrorReceiv
         val matrixError = matrixErrorAdapter.fromJson(errorBodyStr)
 
         if (matrixError != null) {
-            if (matrixError.code == MatrixError.M_CONSENT_NOT_GIVEN && !matrixError.consentUri.isNullOrBlank()) {
-                // Also send this error to the globalErrorReceiver, for a global management
-                globalErrorReceiver?.handleGlobalError(GlobalError.ConsentNotGivenError(matrixError.consentUri))
-            } else if (httpCode == HttpURLConnection.HTTP_UNAUTHORIZED /* 401 */
-                    && matrixError.code == MatrixError.M_UNKNOWN_TOKEN) {
-                // Also send this error to the globalErrorReceiver, for a global management
-                globalErrorReceiver?.handleGlobalError(GlobalError.InvalidToken(matrixError.isSoftLogout.orFalse()))
+            // Also send following errors to the globalErrorReceiver, for a global management
+            when {
+                matrixError.code == MatrixError.M_CONSENT_NOT_GIVEN && !matrixError.consentUri.isNullOrBlank() -> {
+                    globalErrorReceiver?.handleGlobalError(GlobalError.ConsentNotGivenError(matrixError.consentUri))
+                }
+                httpCode == HttpURLConnection.HTTP_UNAUTHORIZED /* 401 */
+                        && matrixError.code == MatrixError.M_UNKNOWN_TOKEN                                     -> {
+                    globalErrorReceiver?.handleGlobalError(GlobalError.InvalidToken(matrixError.isSoftLogout.orFalse()))
+                }
+                matrixError.code == MatrixError.ORG_MATRIX_EXPIRED_ACCOUNT                                     -> {
+                    globalErrorReceiver?.handleGlobalError(GlobalError.ExpiredAccount)
+                }
             }
 
             return Failure.ServerError(matrixError, httpCode)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/query/QueryRoomOrderProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/query/QueryRoomOrderProcessor.kt
index 7a06c212..7294ce4a 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/query/QueryRoomOrderProcessor.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/query/QueryRoomOrderProcessor.kt
@@ -24,13 +24,21 @@ import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields
 
 internal fun RealmQuery<RoomSummaryEntity>.process(sortOrder: RoomSortOrder): RealmQuery<RoomSummaryEntity> {
     when (sortOrder) {
-        RoomSortOrder.NAME     -> {
+        RoomSortOrder.NAME                  -> {
             sort(RoomSummaryEntityFields.DISPLAY_NAME, Sort.ASCENDING)
         }
-        RoomSortOrder.ACTIVITY -> {
+        RoomSortOrder.ACTIVITY              -> {
             sort(RoomSummaryEntityFields.LAST_ACTIVITY_TIME, Sort.DESCENDING)
         }
-        RoomSortOrder.NONE     -> {
+        RoomSortOrder.PRIORITY_AND_ACTIVITY -> {
+            sort(
+                    arrayOf(
+                            RoomSummaryEntityFields.IS_FAVOURITE,
+                            RoomSummaryEntityFields.IS_LOW_PRIORITY,
+                            RoomSummaryEntityFields.LAST_ACTIVITY_TIME),
+                    arrayOf(Sort.DESCENDING, Sort.ASCENDING, Sort.DESCENDING))
+        }
+        RoomSortOrder.NONE                  -> {
         }
     }
     return this
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 1fe4f9d9..b36d05b6 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
@@ -84,7 +84,7 @@ internal data class RoomVersions(
          *      }
          * }
          */
-        @Json(name = "room_capabilities")
+        @Json(name = "org.matrix.msc3244.room_capabilities")
         val roomCapabilities: JsonDict? = null
 )
 
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 48870b86..fdb6caf5 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
@@ -52,6 +52,7 @@ import kotlinx.coroutines.withContext
 import okhttp3.OkHttpClient
 import org.matrix.android.sdk.api.extensions.orFalse
 import org.matrix.android.sdk.api.session.Session
+import org.matrix.android.sdk.internal.session.identity.model.SignInvitationResult
 import timber.log.Timber
 import javax.inject.Inject
 import javax.net.ssl.HttpsURLConnection
@@ -79,6 +80,7 @@ internal class DefaultIdentityService @Inject constructor(
         private val identityApiProvider: IdentityApiProvider,
         private val accountDataDataSource: UserAccountDataDataSource,
         private val homeServerCapabilitiesService: HomeServerCapabilitiesService,
+        private val sign3pidInvitationTask: DefaultSign3pidInvitationTask,
         private val sessionParams: SessionParams
 ) : IdentityService, SessionLifecycleObserver {
 
@@ -290,6 +292,14 @@ internal class DefaultIdentityService @Inject constructor(
         return token.token
     }
 
+    override suspend fun sign3pidInvitation(identiyServer: String, token: String, secret: String): SignInvitationResult {
+        return sign3pidInvitationTask.execute(Sign3pidInvitationTask.Params(
+                url = identiyServer,
+                token = token,
+                privateKey = secret
+        ))
+    }
+
     override fun addListener(listener: IdentityServiceListener) {
         listeners.add(listener)
     }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityAPI.kt
index e9e4d17e..99bd7404 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityAPI.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityAPI.kt
@@ -26,10 +26,12 @@ import org.matrix.android.sdk.internal.session.identity.model.IdentityRequestOwn
 import org.matrix.android.sdk.internal.session.identity.model.IdentityRequestTokenForEmailBody
 import org.matrix.android.sdk.internal.session.identity.model.IdentityRequestTokenForMsisdnBody
 import org.matrix.android.sdk.internal.session.identity.model.IdentityRequestTokenResponse
+import org.matrix.android.sdk.internal.session.identity.model.SignInvitationResult
 import retrofit2.http.Body
 import retrofit2.http.GET
 import retrofit2.http.POST
 import retrofit2.http.Path
+import retrofit2.http.Query
 
 /**
  * Ref: https://matrix.org/docs/spec/identity_service/latest
@@ -95,4 +97,16 @@ internal interface IdentityAPI {
     @POST(NetworkConstants.URI_IDENTITY_PATH_V2 + "validate/{medium}/submitToken")
     suspend fun submitToken(@Path("medium") medium: String,
                             @Body body: IdentityRequestOwnershipParams): SuccessResult
+
+    /**
+     * https://matrix.org/docs/spec/identity_service/r0.3.0#post-matrix-identity-v2-sign-ed25519
+     *
+     * Have to rely on V1 for now
+     */
+    @POST(NetworkConstants.URI_IDENTITY_PATH_V1 + "sign-ed25519")
+    suspend fun signInvitationDetails(
+            @Query("token") token: String,
+            @Query("private_key") privateKey: String,
+            @Query("mxid") mxid: String
+    ): SignInvitationResult
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/Sign3pidInvitationTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/Sign3pidInvitationTask.kt
new file mode 100644
index 00000000..d491af33
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/Sign3pidInvitationTask.kt
@@ -0,0 +1,49 @@
+/*
+ * 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.identity
+
+import dagger.Lazy
+import okhttp3.OkHttpClient
+import org.matrix.android.sdk.internal.di.Unauthenticated
+import org.matrix.android.sdk.internal.di.UserId
+import org.matrix.android.sdk.internal.network.RetrofitFactory
+import org.matrix.android.sdk.internal.session.identity.model.SignInvitationResult
+import org.matrix.android.sdk.internal.task.Task
+import javax.inject.Inject
+
+internal interface Sign3pidInvitationTask : Task<Sign3pidInvitationTask.Params, SignInvitationResult> {
+    data class Params(
+            val token: String,
+            val url: String,
+            val privateKey: String
+    )
+}
+
+internal class DefaultSign3pidInvitationTask @Inject constructor(
+        @Unauthenticated
+        private val okHttpClient: Lazy<OkHttpClient>,
+        private val retrofitFactory: RetrofitFactory,
+        @UserId private val userId: String
+) : Sign3pidInvitationTask {
+
+    override suspend fun execute(params: Sign3pidInvitationTask.Params): SignInvitationResult {
+        val identityAPI = retrofitFactory
+                .create(okHttpClient, "https://${params.url}")
+                .create(IdentityAPI::class.java)
+        return identityAPI.signInvitationDetails(params.token, params.privateKey, userId)
+    }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/model/SignInvitationBody.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/model/SignInvitationBody.kt
new file mode 100644
index 00000000..69982572
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/model/SignInvitationBody.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2021 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.internal.session.identity.model
+
+import com.squareup.moshi.JsonClass
+
+@JsonClass(generateAdapter = true)
+internal data class SignInvitationBody(
+        /**The Matrix user ID of the user accepting the invitation.*/
+        val mxid: String,
+        /**The token from the call to store- invite..*/
+        val token: String,
+        /** The private key, encoded as Unpadded base64. */
+        val private_key: String
+)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/model/SignInvitationResult.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/model/SignInvitationResult.kt
new file mode 100644
index 00000000..27a3f320
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/model/SignInvitationResult.kt
@@ -0,0 +1,31 @@
+/*
+ * 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.identity.model
+
+import com.squareup.moshi.JsonClass
+
+@JsonClass(generateAdapter = true)
+data class SignInvitationResult(
+        /** The Matrix user ID of the user accepting the invitation.*/
+        val mxid: String,
+        /** The Matrix user ID of the user who sent the invitation.*/
+        val sender: String,
+        /**The token from the call to store- invite..*/
+        val signatures: Map<String, *>,
+        /** The token for the invitation */
+        val token: String
+)
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 4e8abcf7..65974151 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
@@ -15,6 +15,8 @@
  */
 package org.matrix.android.sdk.internal.session.notification
 
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.Transformations
 import com.zhuinden.monarchy.Monarchy
 import org.matrix.android.sdk.api.pushrules.Action
 import org.matrix.android.sdk.api.pushrules.PushRuleService
@@ -26,6 +28,7 @@ import org.matrix.android.sdk.api.pushrules.rest.PushRule
 import org.matrix.android.sdk.api.pushrules.rest.RuleSet
 import org.matrix.android.sdk.api.session.events.model.Event
 import org.matrix.android.sdk.internal.database.mapper.PushRulesMapper
+import org.matrix.android.sdk.internal.database.model.PushRuleEntity
 import org.matrix.android.sdk.internal.database.model.PushRulesEntity
 import org.matrix.android.sdk.internal.database.query.where
 import org.matrix.android.sdk.internal.di.SessionDatabase
@@ -117,8 +120,8 @@ internal class DefaultPushRuleService @Inject constructor(
         updatePushRuleActionsTask.execute(UpdatePushRuleActionsTask.Params(kind, ruleId, enable, actions))
     }
 
-    override suspend fun removePushRule(kind: RuleKind, pushRule: PushRule) {
-        removePushRuleTask.execute(RemovePushRuleTask.Params(kind, pushRule))
+    override suspend fun removePushRule(kind: RuleKind, ruleId: String) {
+        removePushRuleTask.execute(RemovePushRuleTask.Params(kind, ruleId))
     }
 
     override fun removePushRuleListener(listener: PushRuleService.PushRuleListener) {
@@ -211,4 +214,19 @@ internal class DefaultPushRuleService @Inject constructor(
             }
         }
     }
+
+    override fun getKeywords(): LiveData<Set<String>> {
+        // Keywords are all content rules that don't start with '.'
+        val liveData = monarchy.findAllMappedWithChanges(
+                { realm ->
+                    PushRulesEntity.where(realm, RuleScope.GLOBAL, RuleSetKey.CONTENT)
+                },
+                { result ->
+                    result.pushRules.map(PushRuleEntity::ruleId).filter { !it.startsWith(".") }
+                }
+        )
+        return Transformations.map(liveData) { results ->
+            results.firstOrNull().orEmpty().toSet()
+        }
+    }
 }
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 21f55bbc..dff82cb4 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
@@ -78,6 +78,10 @@ internal class ViaParameterFinder @Inject constructor(
                 .toSet()
     }
 
+    // not used much for now but as per MSC1772
+    // the via parameter of m.space.child must contain a via key which gives a list of candidate servers that can be used to join the room.
+    // It is possible for the list of candidate servers and the list of authorised servers to diverge.
+    // It may not be possible for a user to join a room if there's no overlap between these
     fun computeViaParamsForRestricted(roomId: String, max: Int): List<String> {
         val userThatCanInvite = roomGetterProvider.get().getRoom(roomId)
                 ?.getRoomMembers(roomMemberQueryParams { memberships = listOf(Membership.JOIN) })
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/EnabledBody.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/EnabledBody.kt
new file mode 100644
index 00000000..5d620ee7
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/EnabledBody.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2021 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.matrix.android.sdk.internal.session.pushers
+
+import com.squareup.moshi.Json
+import com.squareup.moshi.JsonClass
+
+@JsonClass(generateAdapter = true)
+internal data class EnabledBody(
+        @Json(name = "enabled")
+        val enabled: Boolean
+)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/GetPushersResponse.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/GetPushersResponse.kt
index d8f0ce12..4fed4302 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/GetPushersResponse.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/GetPushersResponse.kt
@@ -19,7 +19,7 @@ import com.squareup.moshi.Json
 import com.squareup.moshi.JsonClass
 
 @JsonClass(generateAdapter = true)
-internal class GetPushersResponse(
+internal data class GetPushersResponse(
         @Json(name = "pushers")
         val pushers: List<JsonPusher>? = null
 )
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/PushRulesApi.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/PushRulesApi.kt
index daf9397c..994b4860 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/PushRulesApi.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/PushRulesApi.kt
@@ -41,7 +41,7 @@ internal interface PushRulesApi {
     @PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "pushrules/global/{kind}/{ruleId}/enabled")
     suspend fun updateEnableRuleStatus(@Path("kind") kind: String,
                                        @Path("ruleId") ruleId: String,
-                                       @Body enable: Boolean?)
+                                       @Body enabledBody: EnabledBody)
 
     /**
      * Update the ruleID action
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/RemovePushRuleTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/RemovePushRuleTask.kt
index 23d0515f..bae89360 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/RemovePushRuleTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/RemovePushRuleTask.kt
@@ -16,7 +16,6 @@
 package org.matrix.android.sdk.internal.session.pushers
 
 import org.matrix.android.sdk.api.pushrules.RuleKind
-import org.matrix.android.sdk.api.pushrules.rest.PushRule
 import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import org.matrix.android.sdk.internal.network.executeRequest
 import org.matrix.android.sdk.internal.task.Task
@@ -25,7 +24,7 @@ import javax.inject.Inject
 internal interface RemovePushRuleTask : Task<RemovePushRuleTask.Params, Unit> {
     data class Params(
             val kind: RuleKind,
-            val pushRule: PushRule
+            val ruleId: String
     )
 }
 
@@ -36,7 +35,7 @@ internal class DefaultRemovePushRuleTask @Inject constructor(
 
     override suspend fun execute(params: RemovePushRuleTask.Params) {
         return executeRequest(globalErrorReceiver) {
-            pushRulesApi.deleteRule(params.kind.value, params.pushRule.ruleId)
+            pushRulesApi.deleteRule(params.kind.value, params.ruleId)
         }
     }
 }
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 b8dbabd0..33589dc5 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
@@ -39,7 +39,11 @@ internal class DefaultUpdatePushRuleActionsTask @Inject constructor(
 
     override suspend fun execute(params: UpdatePushRuleActionsTask.Params) {
             executeRequest(globalErrorReceiver) {
-                pushRulesApi.updateEnableRuleStatus(params.kind.value, params.ruleId, enable = params.enable)
+                pushRulesApi.updateEnableRuleStatus(
+                        params.kind.value,
+                        params.ruleId,
+                        EnabledBody(params.enable)
+                )
             }
             if (params.actions != null) {
                 val body = mapOf("actions" to params.actions.toJson())
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/UpdatePushRuleEnableStatusTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/UpdatePushRuleEnableStatusTask.kt
index 9d7a46be..3fe16146 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/UpdatePushRuleEnableStatusTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/UpdatePushRuleEnableStatusTask.kt
@@ -35,7 +35,11 @@ internal class DefaultUpdatePushRuleEnableStatusTask @Inject constructor(
 
     override suspend fun execute(params: UpdatePushRuleEnableStatusTask.Params) {
         return executeRequest(globalErrorReceiver) {
-            pushRulesApi.updateEnableRuleStatus(params.kind.value, params.pushRule.ruleId, params.enabled)
+            pushRulesApi.updateEnableRuleStatus(
+                    params.kind.value,
+                    params.pushRule.ruleId,
+                    EnabledBody(params.enabled)
+            )
         }
     }
 }
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 632ea4c4..f69949cb 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
@@ -38,6 +38,7 @@ import org.matrix.android.sdk.api.util.toOptional
 import org.matrix.android.sdk.internal.database.mapper.asDomain
 import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntityFields
 import org.matrix.android.sdk.internal.di.SessionDatabase
+import org.matrix.android.sdk.internal.session.identity.model.SignInvitationResult
 import org.matrix.android.sdk.internal.session.room.alias.DeleteRoomAliasTask
 import org.matrix.android.sdk.internal.session.room.alias.GetRoomIdByAliasTask
 import org.matrix.android.sdk.internal.session.room.alias.RoomAliasDescription
@@ -122,6 +123,12 @@ internal class DefaultRoomService @Inject constructor(
         joinRoomTask.execute(JoinRoomTask.Params(roomIdOrAlias, reason, viaServers))
     }
 
+    override suspend fun joinRoom(roomId: String,
+                                  reason: String?,
+                                  thirdPartySigned: SignInvitationResult) {
+        joinRoomTask.execute(JoinRoomTask.Params(roomId, reason, thirdPartySigned = thirdPartySigned))
+    }
+
     override suspend fun markAllAsRead(roomIds: List<String>) {
         markAllRoomsReadTask.execute(MarkAllRoomsReadTask.Params(roomIds))
     }
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 18ece606..535fa9df 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
@@ -159,7 +159,8 @@ internal interface RoomAPI {
     @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/receipt/{receiptType}/{eventId}")
     suspend fun sendReceipt(@Path("roomId") roomId: String,
                             @Path("receiptType") receiptType: String,
-                            @Path("eventId") eventId: String)
+                            @Path("eventId") eventId: String,
+                            @Body body: JsonDict = emptyMap())
 
     /**
      * Invite a user to the given room.
@@ -253,7 +254,7 @@ internal interface RoomAPI {
     @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "join/{roomIdOrAlias}")
     suspend fun join(@Path("roomIdOrAlias") roomIdOrAlias: String,
                      @Query("server_name") viaServers: List<String>,
-                     @Body params: Map<String, String?>): JoinRoomResponse
+                     @Body params:  JsonDict): JoinRoomResponse
 
     /**
      * Leave the given room.
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 c04c899e..79497070 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
@@ -28,6 +28,8 @@ import org.matrix.android.sdk.api.session.space.SpaceService
 import org.matrix.android.sdk.internal.session.DefaultFileService
 import org.matrix.android.sdk.internal.session.SessionScope
 import org.matrix.android.sdk.internal.session.directory.DirectoryAPI
+import org.matrix.android.sdk.internal.session.identity.DefaultSign3pidInvitationTask
+import org.matrix.android.sdk.internal.session.identity.Sign3pidInvitationTask
 import org.matrix.android.sdk.internal.session.room.accountdata.DefaultUpdateRoomAccountDataTask
 import org.matrix.android.sdk.internal.session.room.accountdata.UpdateRoomAccountDataTask
 import org.matrix.android.sdk.internal.session.room.alias.AddRoomAliasTask
@@ -248,4 +250,7 @@ internal abstract class RoomModule {
 
     @Binds
     abstract fun bindRoomVersionUpgradeTask(task: DefaultRoomVersionUpgradeTask): RoomVersionUpgradeTask
+
+    @Binds
+    abstract fun bindSign3pidInvitationTask(task: DefaultSign3pidInvitationTask): Sign3pidInvitationTask
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomTask.kt
index de6a71e5..518f0a0a 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomTask.kt
@@ -123,7 +123,7 @@ internal class DefaultCreateRoomTask @Inject constructor(
                 this.isDirect = true
             }
         }
-        val directChats = directChatsHelper.getLocalUserAccount()
+        val directChats = directChatsHelper.getLocalDirectMessages()
         updateUserAccountDataTask.execute(UpdateUserAccountDataTask.DirectChatParams(directMessages = directChats))
     }
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomMemberEventHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomMemberEventHandler.kt
index 2ecacf33..7528f80c 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomMemberEventHandler.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomMemberEventHandler.kt
@@ -20,22 +20,30 @@ import io.realm.Realm
 import org.matrix.android.sdk.api.session.events.model.Event
 import org.matrix.android.sdk.api.session.events.model.EventType
 import org.matrix.android.sdk.api.session.room.model.RoomMemberContent
+import org.matrix.android.sdk.internal.di.UserId
 import org.matrix.android.sdk.internal.session.events.getFixedRoomMemberContent
+import org.matrix.android.sdk.internal.session.sync.SyncResponsePostTreatmentAggregator
 import org.matrix.android.sdk.internal.session.user.UserEntityFactory
 import javax.inject.Inject
 
-internal class RoomMemberEventHandler @Inject constructor() {
+internal class RoomMemberEventHandler @Inject constructor(
+        @UserId private val myUserId: String
+) {
 
-    fun handle(realm: Realm, roomId: String, event: Event): Boolean {
+    fun handle(realm: Realm, roomId: String, event: Event, aggregator: SyncResponsePostTreatmentAggregator? = null): Boolean {
         if (event.type != EventType.STATE_ROOM_MEMBER) {
             return false
         }
         val userId = event.stateKey ?: return false
         val roomMember = event.getFixedRoomMemberContent()
-        return handle(realm, roomId, userId, roomMember)
+        return handle(realm, roomId, userId, roomMember, aggregator)
     }
 
-    fun handle(realm: Realm, roomId: String, userId: String, roomMember: RoomMemberContent?): Boolean {
+    fun handle(realm: Realm,
+               roomId: String,
+               userId: String,
+               roomMember: RoomMemberContent?,
+               aggregator: SyncResponsePostTreatmentAggregator? = null): Boolean {
         if (roomMember == null) {
             return false
         }
@@ -45,6 +53,14 @@ internal class RoomMemberEventHandler @Inject constructor() {
             val userEntity = UserEntityFactory.create(userId, roomMember)
             realm.insertOrUpdate(userEntity)
         }
+
+        // check whether this new room member event may be used to update the directs dictionary in account data
+        // this is required to handle correctly invite by email in DM
+        val mxId = roomMember.thirdPartyInvite?.signed?.mxid
+        if (mxId != null && mxId != myUserId) {
+            aggregator?.directChatsToCheck?.put(roomId, mxId)
+        }
+
         return true
     }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/joining/JoinRoomTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/joining/JoinRoomTask.kt
index 562b2568..209a904f 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
@@ -19,6 +19,7 @@ package org.matrix.android.sdk.internal.session.room.membership.joining
 import io.realm.Realm
 import io.realm.RealmConfiguration
 import kotlinx.coroutines.TimeoutCancellationException
+import org.matrix.android.sdk.api.session.events.model.toContent
 import org.matrix.android.sdk.api.session.room.failure.JoinRoomFailure
 import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState
 import org.matrix.android.sdk.api.session.room.model.Membership
@@ -29,6 +30,7 @@ import org.matrix.android.sdk.internal.database.query.where
 import org.matrix.android.sdk.internal.di.SessionDatabase
 import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import org.matrix.android.sdk.internal.network.executeRequest
+import org.matrix.android.sdk.internal.session.identity.model.SignInvitationResult
 import org.matrix.android.sdk.internal.session.room.RoomAPI
 import org.matrix.android.sdk.internal.session.room.membership.RoomChangeMembershipStateDataSource
 import org.matrix.android.sdk.internal.session.room.read.SetReadMarkersTask
@@ -40,7 +42,8 @@ internal interface JoinRoomTask : Task<JoinRoomTask.Params, Unit> {
     data class Params(
             val roomIdOrAlias: String,
             val reason: String?,
-            val viaServers: List<String> = emptyList()
+            val viaServers: List<String> = emptyList(),
+            val thirdPartySigned : SignInvitationResult? = null
     )
 }
 
@@ -59,12 +62,16 @@ internal class DefaultJoinRoomTask @Inject constructor(
             return
         }
         roomChangeMembershipStateDataSource.updateState(params.roomIdOrAlias, ChangeMembershipState.Joining)
+        val extraParams = mutableMapOf<String, Any>().apply {
+            params.reason?.let { this["reason"] = it }
+            params.thirdPartySigned?.let { this["third_party_signed"] = it.toContent() }
+        }
         val joinRoomResponse = try {
             executeRequest(globalErrorReceiver) {
                 roomAPI.join(
                         roomIdOrAlias = params.roomIdOrAlias,
                         viaServers = params.viaServers.take(3),
-                        params = mapOf("reason" to params.reason)
+                        params = extraParams
                 )
             }
         } catch (failure: Throwable) {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/notification/SetRoomNotificationStateTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/notification/SetRoomNotificationStateTask.kt
index de049d75..9cea1fe4 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/notification/SetRoomNotificationStateTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/notification/SetRoomNotificationStateTask.kt
@@ -45,7 +45,7 @@ internal class DefaultSetRoomNotificationStateTask @Inject constructor(@SessionD
             PushRuleEntity.where(it, scope = RuleScope.GLOBAL, ruleId = params.roomId).findFirst()?.toRoomPushRule()
         }
         if (currentRoomPushRule != null) {
-            removePushRuleTask.execute(RemovePushRuleTask.Params(currentRoomPushRule.kind, currentRoomPushRule.rule))
+            removePushRuleTask.execute(RemovePushRuleTask.Params(currentRoomPushRule.kind, currentRoomPushRule.rule.ruleId))
         }
         val newRoomPushRule = params.roomNotificationState.toRoomPushRule(params.roomId)
         if (newRoomPushRule != null) {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relationship/RoomChildRelationInfo.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relationship/RoomChildRelationInfo.kt
index 2efea7f1..5bad334a 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relationship/RoomChildRelationInfo.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relationship/RoomChildRelationInfo.kt
@@ -43,7 +43,7 @@ internal class RoomChildRelationInfo(
     data class SpaceChildInfo(
             val roomId: String,
             val order: String?,
-            val autoJoin: Boolean,
+//            val autoJoin: Boolean,
             val viaServers: List<String>
     )
 
@@ -71,7 +71,7 @@ internal class RoomChildRelationInfo(
                             SpaceChildInfo(
                                     roomId = it.stateKey,
                                     order = scc.validOrder(),
-                                    autoJoin = scc.autoJoin ?: false,
+//                                    autoJoin = scc.autoJoin ?: false,
                                     viaServers = via
                             )
                         }
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 a64b9039..6dbb71e0 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
@@ -185,7 +185,7 @@ internal class DefaultSendService @AssistedInject constructor(
                             name = messageContent.body,
                             queryUri = Uri.parse(messageContent.url),
                             type = ContentAttachmentData.Type.AUDIO,
-                            waveform = messageContent.audioWaveformInfo?.waveform
+                            waveform = messageContent.audioWaveformInfo?.waveform?.filterNotNull()
                     )
                     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/LocalEchoRepository.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoRepository.kt
index e98e5646..13095fbd 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoRepository.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoRepository.kt
@@ -77,7 +77,7 @@ internal class LocalEchoRepository @Inject constructor(@SessionDatabase private
         val timelineEvent = timelineEventMapper.map(timelineEventEntity)
         timelineInput.onLocalEchoCreated(roomId = roomId, timelineEvent = timelineEvent)
         taskExecutor.executorScope.asyncTransaction(monarchy) { realm ->
-            val eventInsertEntity = EventInsertEntity(event.eventId, event.type).apply {
+            val eventInsertEntity = EventInsertEntity(event.eventId, event.type, canBeProcessed = true).apply {
                 this.insertType = EventInsertType.LOCAL_ECHO
             }
             realm.insert(eventInsertEntity)
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 7eed22f6..4ec27976 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
@@ -182,7 +182,7 @@ internal class DefaultStateService @AssistedInject constructor(@Assisted private
     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))
+            RoomJoinRulesAllowEntry.restrictedToRoom(spaceId)
         }
         updateJoinRule(RoomJoinRules.RESTRICTED, null, allowEntries)
     }
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 842c9d3a..89a35339 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
@@ -220,7 +220,7 @@ internal class RoomSummaryUpdater @Inject constructor(
                                     this.childRoomId = child.roomId
                                     this.childSummaryEntity = RoomSummaryEntity.where(realm, child.roomId).findFirst()
                                     this.order = child.order
-                                    this.autoJoin = child.autoJoin
+//                                    this.autoJoin = child.autoJoin
                                     this.viaServers.addAll(child.viaServers)
                                 }
                         )
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineEventDecryptor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineEventDecryptor.kt
index 3517f26c..721dae0b 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineEventDecryptor.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineEventDecryptor.kt
@@ -106,7 +106,8 @@ internal class TimelineEventDecryptor @Inject constructor(
             val result = cryptoService.decryptEvent(request.event, timelineId)
             Timber.v("Successfully decrypted event ${event.eventId}")
             realm.executeTransaction {
-                EventEntity.where(it, eventId = event.eventId ?: "")
+                val eventId = event.eventId ?: ""
+                EventEntity.where(it, eventId = eventId)
                         .findFirst()
                         ?.setDecryptionResult(result)
             }
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 233eef45..8a6bbc18 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
@@ -21,6 +21,7 @@ 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.events.model.toModel
 import org.matrix.android.sdk.api.session.room.Room
+import org.matrix.android.sdk.api.session.room.model.RoomJoinRules
 import org.matrix.android.sdk.api.session.room.model.RoomSummary
 import org.matrix.android.sdk.api.session.space.Space
 import org.matrix.android.sdk.api.session.space.model.SpaceChildContent
@@ -50,16 +51,24 @@ internal class DefaultSpace(
     override suspend fun addChildren(roomId: String,
                                      viaServers: List<String>?,
                                      order: String?,
-                                     autoJoin: Boolean,
+//                                     autoJoin: Boolean,
                                      suggested: Boolean?) {
         // Find best via
+        val bestVia = viaServers
+                ?: (spaceSummaryDataSource.getRoomSummary(roomId)
+                        ?.takeIf { it.joinRules == RoomJoinRules.RESTRICTED }
+                        ?.let {
+                            // for restricted room, best to take via from users that can invite in the
+                            // child room
+                            viaParameterFinder.computeViaParamsForRestricted(roomId, 3)
+                        }
+                        ?: viaParameterFinder.computeViaParams(roomId, 3))
 
         room.sendStateEvent(
                 eventType = EventType.STATE_SPACE_CHILD,
                 stateKey = roomId,
                 body = SpaceChildContent(
-                        via = viaServers ?: viaParameterFinder.computeViaParams(roomId, 3),
-                        autoJoin = autoJoin,
+                        via = bestVia,
                         order = order,
                         suggested = suggested
                 ).toContent()
@@ -80,7 +89,7 @@ internal class DefaultSpace(
                 body = SpaceChildContent(
                         order = null,
                         via = null,
-                        autoJoin = null,
+//                        autoJoin = null,
                         suggested = null
                 ).toContent()
         )
@@ -105,35 +114,35 @@ internal class DefaultSpace(
                 body = SpaceChildContent(
                         order = order,
                         via = existing.via,
-                        autoJoin = existing.autoJoin,
+//                        autoJoin = existing.autoJoin,
                         suggested = existing.suggested
                 ).toContent()
         )
     }
 
-    override suspend fun setChildrenAutoJoin(roomId: String, autoJoin: Boolean) {
-        val existing = room.getStateEvents(setOf(EventType.STATE_SPACE_CHILD), QueryStringValue.Equals(roomId))
-                .firstOrNull()
-                ?.content.toModel<SpaceChildContent>()
-                ?: throw IllegalArgumentException("$roomId is not a child of this space")
-
-        if (existing.autoJoin == autoJoin) {
-            // nothing to do?
-            return
-        }
-
-        // edit state event and set via to null
-        room.sendStateEvent(
-                eventType = EventType.STATE_SPACE_CHILD,
-                stateKey = roomId,
-                body = SpaceChildContent(
-                        order = existing.order,
-                        via = existing.via,
-                        autoJoin = autoJoin,
-                        suggested = existing.suggested
-                ).toContent()
-        )
-    }
+//    override suspend fun setChildrenAutoJoin(roomId: String, autoJoin: Boolean) {
+//        val existing = room.getStateEvents(setOf(EventType.STATE_SPACE_CHILD), QueryStringValue.Equals(roomId))
+//                .firstOrNull()
+//                ?.content.toModel<SpaceChildContent>()
+//                ?: throw IllegalArgumentException("$roomId is not a child of this space")
+//
+//        if (existing.autoJoin == autoJoin) {
+//            // nothing to do?
+//            return
+//        }
+//
+//        // edit state event and set via to null
+//        room.sendStateEvent(
+//                eventType = EventType.STATE_SPACE_CHILD,
+//                stateKey = roomId,
+//                body = SpaceChildContent(
+//                        order = existing.order,
+//                        via = existing.via,
+//                        autoJoin = autoJoin,
+//                        suggested = existing.suggested
+//                ).toContent()
+//        )
+//    }
 
     override suspend fun setChildrenSuggested(roomId: String, suggested: Boolean) {
         val existing = room.getStateEvents(setOf(EventType.STATE_SPACE_CHILD), QueryStringValue.Equals(roomId))
@@ -152,7 +161,7 @@ internal class DefaultSpace(
                 body = SpaceChildContent(
                         order = existing.order,
                         via = existing.via,
-                        autoJoin = existing.autoJoin,
+//                        autoJoin = existing.autoJoin,
                         suggested = suggested
                 ).toContent()
         )
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 0c5c0416..7be4cdcd 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
@@ -19,6 +19,7 @@ package org.matrix.android.sdk.internal.session.space
 import android.net.Uri
 import androidx.lifecycle.LiveData
 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
 import org.matrix.android.sdk.api.session.events.model.toContent
 import org.matrix.android.sdk.api.session.events.model.toModel
@@ -27,6 +28,7 @@ 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.model.RoomDirectoryVisibility
 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.RoomSummary
 import org.matrix.android.sdk.api.session.room.model.SpaceChildInfo
 import org.matrix.android.sdk.api.session.room.model.create.CreateRoomPreset
@@ -34,6 +36,7 @@ import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper
 import org.matrix.android.sdk.api.session.space.CreateSpaceParams
 import org.matrix.android.sdk.api.session.space.JoinSpaceResult
 import org.matrix.android.sdk.api.session.space.Space
+import org.matrix.android.sdk.api.session.space.SpaceHierarchyData
 import org.matrix.android.sdk.api.session.space.SpaceService
 import org.matrix.android.sdk.api.session.space.SpaceSummaryQueryParams
 import org.matrix.android.sdk.api.session.space.model.SpaceChildContent
@@ -108,53 +111,65 @@ internal class DefaultSpaceService @Inject constructor(
 
     override suspend fun querySpaceChildren(spaceId: String,
                                             suggestedOnly: Boolean?,
-                                            autoJoinedOnly: Boolean?): Pair<RoomSummary, List<SpaceChildInfo>> {
-        return resolveSpaceInfoTask.execute(ResolveSpaceInfoTask.Params.withId(spaceId, suggestedOnly, autoJoinedOnly)).let { response ->
+                                            limit: Int?,
+                                            from: String?,
+                                            knownStateList: List<Event>?): SpaceHierarchyData {
+        return resolveSpaceInfoTask.execute(
+                ResolveSpaceInfoTask.Params(
+                        spaceId = spaceId, limit = limit, maxDepth = 1, from = from, suggestedOnly = suggestedOnly
+                )
+        ).let { response ->
             val spaceDesc = response.rooms?.firstOrNull { it.roomId == spaceId }
-            Pair(
-                    first = RoomSummary(
-                            roomId = spaceDesc?.roomId ?: spaceId,
-                            roomType = spaceDesc?.roomType,
-                            name = spaceDesc?.name ?: "",
-                            displayName = spaceDesc?.name ?: "",
-                            topic = spaceDesc?.topic ?: "",
-                            joinedMembersCount = spaceDesc?.numJoinedMembers,
-                            avatarUrl = spaceDesc?.avatarUrl ?: "",
-                            encryptionEventTs = null,
-                            typingUsers = emptyList(),
-                            isEncrypted = false,
-                            flattenParentIds = emptyList()
-                    ),
-                    second = response.rooms
-                            ?.filter { it.roomId != spaceId }
-                            ?.flatMap { childSummary ->
-                                response.events
-                                        ?.filter { it.stateKey == childSummary.roomId && it.type == EventType.STATE_SPACE_CHILD }
-                                        ?.mapNotNull { childStateEv ->
-                                            // create a child entry for everytime this room is the child of a space
-                                            // beware that a room could appear then twice in this list
-                                            childStateEv.content.toModel<SpaceChildContent>()?.let { childStateEvContent ->
-                                                SpaceChildInfo(
-                                                        childRoomId = childSummary.roomId,
-                                                        isKnown = true,
-                                                        roomType = childSummary.roomType,
-                                                        name = childSummary.name,
-                                                        topic = childSummary.topic,
-                                                        avatarUrl = childSummary.avatarUrl,
-                                                        order = childStateEvContent.order,
-                                                        autoJoin = childStateEvContent.autoJoin ?: false,
-                                                        viaServers = childStateEvContent.via.orEmpty(),
-                                                        activeMemberCount = childSummary.numJoinedMembers,
-                                                        parentRoomId = childStateEv.roomId,
-                                                        suggested = childStateEvContent.suggested,
-                                                        canonicalAlias = childSummary.canonicalAlias,
-                                                        aliases = childSummary.aliases,
-                                                        worldReadable = childSummary.worldReadable
-                                                )
-                                            }
-                                        }.orEmpty()
-                            }
-                            .orEmpty()
+            val root = RoomSummary(
+                    roomId = spaceDesc?.roomId ?: spaceId,
+                    roomType = spaceDesc?.roomType,
+                    name = spaceDesc?.name ?: "",
+                    displayName = spaceDesc?.name ?: "",
+                    topic = spaceDesc?.topic ?: "",
+                    joinedMembersCount = spaceDesc?.numJoinedMembers,
+                    avatarUrl = spaceDesc?.avatarUrl ?: "",
+                    encryptionEventTs = null,
+                    typingUsers = emptyList(),
+                    isEncrypted = false,
+                    flattenParentIds = emptyList(),
+                    canonicalAlias = spaceDesc?.canonicalAlias,
+                    joinRules = RoomJoinRules.PUBLIC.takeIf { spaceDesc?.worldReadable == true }
+            )
+            val children = response.rooms
+                    ?.filter { it.roomId != spaceId }
+                    ?.flatMap { childSummary ->
+                        (spaceDesc?.childrenState ?: knownStateList)
+                                ?.filter { it.stateKey == childSummary.roomId && it.type == EventType.STATE_SPACE_CHILD }
+                                ?.mapNotNull { childStateEv ->
+                                    // create a child entry for everytime this room is the child of a space
+                                    // beware that a room could appear then twice in this list
+                                    childStateEv.content.toModel<SpaceChildContent>()?.let { childStateEvContent ->
+                                        SpaceChildInfo(
+                                                childRoomId = childSummary.roomId,
+                                                isKnown = true,
+                                                roomType = childSummary.roomType,
+                                                name = childSummary.name,
+                                                topic = childSummary.topic,
+                                                avatarUrl = childSummary.avatarUrl,
+                                                order = childStateEvContent.order,
+//                                                        autoJoin = childStateEvContent.autoJoin ?: false,
+                                                viaServers = childStateEvContent.via.orEmpty(),
+                                                activeMemberCount = childSummary.numJoinedMembers,
+                                                parentRoomId = childStateEv.roomId,
+                                                suggested = childStateEvContent.suggested,
+                                                canonicalAlias = childSummary.canonicalAlias,
+                                                aliases = childSummary.aliases,
+                                                worldReadable = childSummary.worldReadable
+                                        )
+                                    }
+                                }.orEmpty()
+                    }
+                    .orEmpty()
+            SpaceHierarchyData(
+                    rootSummary = root,
+                    children = children,
+                    childrenState = spaceDesc?.childrenState.orEmpty(),
+                    nextToken = response.nextBatch
             )
         }
     }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/JoinSpaceTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/JoinSpaceTask.kt
index e9d5ba51..7eeaed4f 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/JoinSpaceTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/JoinSpaceTask.kt
@@ -19,7 +19,6 @@ package org.matrix.android.sdk.internal.session.space
 import io.realm.RealmConfiguration
 import kotlinx.coroutines.TimeoutCancellationException
 import org.matrix.android.sdk.api.session.room.model.Membership
-import org.matrix.android.sdk.api.session.room.model.RoomType
 import org.matrix.android.sdk.api.session.space.JoinSpaceResult
 import org.matrix.android.sdk.internal.database.awaitNotEmptyResult
 import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity
@@ -84,39 +83,39 @@ internal class DefaultJoinSpaceTask @Inject constructor(
         // after that i should have the children (? do I need to paginate to get state)
         val summary = roomSummaryDataSource.getSpaceSummary(params.roomIdOrAlias)
         Timber.v("## Space: Found space summary Name:[${summary?.name}] children: ${summary?.spaceChildren?.size}")
-        summary?.spaceChildren?.forEach {
+//        summary?.spaceChildren?.forEach {
 //            val childRoomSummary = it.roomSummary ?: return@forEach
-            Timber.v("## Space: Processing child :[${it.childRoomId}] autoJoin:${it.autoJoin}")
-            if (it.autoJoin) {
-                // I should try to join as well
-                if (it.roomType == RoomType.SPACE) {
-                    // recursively join auto-joined child of this space?
-                    when (val subspaceJoinResult = execute(JoinSpaceTask.Params(it.childRoomId, null, it.viaServers))) {
-                        JoinSpaceResult.Success           -> {
-                            // nop
-                        }
-                        is JoinSpaceResult.Fail           -> {
-                            errors[it.childRoomId] = subspaceJoinResult.error
-                        }
-                        is JoinSpaceResult.PartialSuccess -> {
-                            errors.putAll(subspaceJoinResult.failedRooms)
-                        }
-                    }
-                } else {
-                    try {
-                        Timber.v("## Space: Joining room child ${it.childRoomId}")
-                        joinRoomTask.execute(JoinRoomTask.Params(
-                                roomIdOrAlias = it.childRoomId,
-                                reason = "Auto-join parent space",
-                                viaServers = it.viaServers
-                        ))
-                    } catch (failure: Throwable) {
-                        errors[it.childRoomId] = failure
-                        Timber.e("## Space: Failed to join room child ${it.childRoomId}")
-                    }
-                }
-            }
-        }
+//            Timber.v("## Space: Processing child :[${it.childRoomId}] suggested:${it.suggested}")
+//            if (it.autoJoin) {
+//                // I should try to join as well
+//                if (it.roomType == RoomType.SPACE) {
+//                    // recursively join auto-joined child of this space?
+//                    when (val subspaceJoinResult = execute(JoinSpaceTask.Params(it.childRoomId, null, it.viaServers))) {
+//                        JoinSpaceResult.Success           -> {
+//                            // nop
+//                        }
+//                        is JoinSpaceResult.Fail           -> {
+//                            errors[it.childRoomId] = subspaceJoinResult.error
+//                        }
+//                        is JoinSpaceResult.PartialSuccess -> {
+//                            errors.putAll(subspaceJoinResult.failedRooms)
+//                        }
+//                    }
+//                } else {
+//                    try {
+//                        Timber.v("## Space: Joining room child ${it.childRoomId}")
+//                        joinRoomTask.execute(JoinRoomTask.Params(
+//                                roomIdOrAlias = it.childRoomId,
+//                                reason = "Auto-join parent space",
+//                                viaServers = it.viaServers
+//                        ))
+//                    } catch (failure: Throwable) {
+//                        errors[it.childRoomId] = failure
+//                        Timber.e("## Space: Failed to join room child ${it.childRoomId}")
+//                    }
+//                }
+//            }
+//        }
 
         return if (errors.isEmpty()) {
             JoinSpaceResult.Success
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/ResolveSpaceInfoTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/ResolveSpaceInfoTask.kt
index d2be49b7..2a396d6e 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/ResolveSpaceInfoTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/ResolveSpaceInfoTask.kt
@@ -24,24 +24,12 @@ import javax.inject.Inject
 internal interface ResolveSpaceInfoTask : Task<ResolveSpaceInfoTask.Params, SpacesResponse> {
     data class Params(
             val spaceId: String,
-            val maxRoomPerSpace: Int?,
-            val limit: Int,
-            val batchToken: String?,
-            val suggestedOnly: Boolean?,
-            val autoJoinOnly: Boolean?
-    ) {
-        companion object {
-            fun withId(spaceId: String, suggestedOnly: Boolean?, autoJoinOnly: Boolean?) =
-                    Params(
-                            spaceId = spaceId,
-                            maxRoomPerSpace = 10,
-                            limit = 20,
-                            batchToken = null,
-                            suggestedOnly = suggestedOnly,
-                            autoJoinOnly = autoJoinOnly
-                    )
-        }
-    }
+            val limit: Int?,
+            val maxDepth: Int?,
+            val from: String?,
+            val suggestedOnly: Boolean?
+//            val autoJoinOnly: Boolean?
+    )
 }
 
 internal class DefaultResolveSpaceInfoTask @Inject constructor(
@@ -49,15 +37,13 @@ internal class DefaultResolveSpaceInfoTask @Inject constructor(
         private val globalErrorReceiver: GlobalErrorReceiver
 ) : ResolveSpaceInfoTask {
     override suspend fun execute(params: ResolveSpaceInfoTask.Params): SpacesResponse {
-        val body = SpaceSummaryParams(
-                maxRoomPerSpace = params.maxRoomPerSpace,
-                limit = params.limit,
-                batch = params.batchToken ?: "",
-                autoJoinedOnly = params.autoJoinOnly,
-                suggestedOnly = params.suggestedOnly
-        )
         return executeRequest(globalErrorReceiver) {
-            spaceApi.getSpaces(params.spaceId, body)
+            spaceApi.getSpaceHierarchy(
+                    spaceId = params.spaceId,
+                    suggestedOnly = params.suggestedOnly,
+                    limit = params.limit,
+                    maxDepth = params.maxDepth,
+                    from = params.from)
         }
     }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpaceApi.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpaceApi.kt
index 0fcc95fd..edd10bc2 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpaceApi.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpaceApi.kt
@@ -17,27 +17,25 @@
 package org.matrix.android.sdk.internal.session.space
 
 import org.matrix.android.sdk.internal.network.NetworkConstants
-import retrofit2.http.Body
-import retrofit2.http.POST
+import retrofit2.http.GET
 import retrofit2.http.Path
+import retrofit2.http.Query
 
 internal interface SpaceApi {
 
     /**
-     *
-     * POST /_matrix/client/r0/rooms/{roomID}/spaces
-     *  {
-     *    "max_rooms_per_space": 5,      // The maximum number of rooms/subspaces to return for a given space, if negative unbounded. default: -1.
-     *    "auto_join_only": true,        // If true, only return m.space.child events with auto_join:true, default: false, which returns all events.
-     *    "limit": 100,                  // The maximum number of rooms/subspaces to return, server can override this, default: 100.
-     *    "batch": "opaque_string"       // A token to use if this is a subsequent HTTP hit, default: "".
-     *  }
-     *
-     * Ref:
-     * - MSC 2946 https://github.com/matrix-org/matrix-doc/blob/kegan/spaces-summary/proposals/2946-spaces-summary.md
-     * - https://hackmd.io/fNYh4tjUT5mQfR1uuRzWDA
+     * @param suggestedOnly Optional. If true, return only child events and rooms where the m.space.child event has suggested: true.
+     * @param limit: Optional: a client-defined limit to the maximum number of rooms to return per page. Must be a non-negative integer.
+     * @param maxDepth: Optional: The maximum depth in the tree (from the root room) to return.
+     * The deepest depth returned will not include children events. Defaults to no-limit. Must be a non-negative integer.
+     * @param from: Optional. Pagination token given to retrieve the next set of rooms.
+     * Note that if a pagination token is provided, then the parameters given for suggested_only and max_depth must be the same.
      */
-    @POST(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "org.matrix.msc2946/rooms/{roomId}/spaces")
-    suspend fun getSpaces(@Path("roomId") spaceId: String,
-                  @Body params: SpaceSummaryParams): SpacesResponse
+    @GET(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "org.matrix.msc2946/rooms/{roomId}/hierarchy")
+    suspend fun getSpaceHierarchy(
+            @Path("roomId") spaceId: String,
+            @Query("suggested_only") suggestedOnly: Boolean?,
+            @Query("limit") limit: Int?,
+            @Query("max_depth") maxDepth: Int?,
+            @Query("from") from: String?): SpacesResponse
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpaceChildSummaryResponse.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpaceChildSummaryResponse.kt
index 5021ff63..e0f273d0 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpaceChildSummaryResponse.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpaceChildSummaryResponse.kt
@@ -18,14 +18,17 @@ package org.matrix.android.sdk.internal.session.space
 
 import com.squareup.moshi.Json
 import com.squareup.moshi.JsonClass
+import org.matrix.android.sdk.api.session.events.model.Event
 
+/**
+ * The fields are the same as those returned by /publicRooms (see spec), with the addition of:
+ *       room_type: the value of the m.type field from the room's m.room.create event, if any.
+ *       children_state: The m.space.child events of the room.
+ *       For each event, only the following fields are included: type, state_key, content, room_id, sender,
+ *       with the addition of: origin_server_ts: This is required for sorting of rooms as specified below.
+ */
 @JsonClass(generateAdapter = true)
 internal data class SpaceChildSummaryResponse(
-        /**
-         * The total number of state events which point to or from this room (inbound/outbound edges).
-         * This includes all m.space.child events in the room, in addition to m.room.parent events which point to this room as a parent.
-         */
-        @Json(name = "num_refs") val numRefs: Int? = null,
 
         /**
          * The room type, which is m.space for subspaces.
@@ -33,6 +36,11 @@ internal data class SpaceChildSummaryResponse(
          */
         @Json(name = "room_type") val roomType: String? = null,
 
+        /**  The m.space.child events of the room. For each event, only the following fields are included:
+         *  type, state_key, content, room_id, sender, with the addition of origin_server_ts.
+         */
+        @Json(name = "children_state") val childrenState: List<Event>? = null,
+
         /**
          * Aliases of the room. May be empty.
          */
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpaceSummaryParams.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpaceSummaryParams.kt
deleted file mode 100644
index 013db1c2..00000000
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpaceSummaryParams.kt
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright 2020 The Matrix.org Foundation C.I.C.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.matrix.android.sdk.internal.session.space
-
-import com.squareup.moshi.Json
-import com.squareup.moshi.JsonClass
-
-@JsonClass(generateAdapter = true)
-internal data class SpaceSummaryParams(
-        /**  The maximum number of rooms/subspaces to return for a given space, if negative unbounded. default: -1 */
-        @Json(name = "max_rooms_per_space") val maxRoomPerSpace: Int?,
-        /** The maximum number of rooms/subspaces to return, server can override this, default: 100 */
-        @Json(name = "limit") val limit: Int?,
-        /** A token to use if this is a subsequent HTTP hit, default: "". */
-        @Json(name = "batch") val batch: String = "",
-        /** whether we should only return children with the "suggested" flag set. */
-        @Json(name = "suggested_only") val suggestedOnly: Boolean?,
-        /** whether we should only return children with the "suggested" flag set. */
-        @Json(name = "auto_join_only") val autoJoinedOnly: Boolean?
-)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpacesResponse.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpacesResponse.kt
index 20d63c88..7d22dce9 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpacesResponse.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpacesResponse.kt
@@ -18,14 +18,11 @@ package org.matrix.android.sdk.internal.session.space
 
 import com.squareup.moshi.Json
 import com.squareup.moshi.JsonClass
-import org.matrix.android.sdk.api.session.events.model.Event
 
 @JsonClass(generateAdapter = true)
 internal data class SpacesResponse(
         /** Its presence indicates that there are more results to return. */
         @Json(name = "next_batch") val nextBatch: String? = null,
         /** Rooms information like name/avatar/type ... */
-        @Json(name = "rooms") val rooms: List<SpaceChildSummaryResponse>? = null,
-        /** These are the edges of the graph. The objects in the array are complete (or stripped?) m.room.parent or m.space.child events. */
-        @Json(name = "events") val events: List<Event>? = null
+        @Json(name = "rooms") val rooms: List<SpaceChildSummaryResponse>? = null
 )
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/peeking/PeekSpaceTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/peeking/PeekSpaceTask.kt
index f6b156a6..5cbaaa45 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/peeking/PeekSpaceTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/peeking/PeekSpaceTask.kt
@@ -103,7 +103,7 @@ internal class DefaultPeekSpaceTask @Inject constructor(
                     // can't peek :/
                     spaceChildResults.add(
                             SpaceChildPeekResult(
-                                    childId, childPeek, entry.second?.autoJoin, entry.second?.order
+                                    childId, childPeek, entry.second?.order
                             )
                     )
                     // continue to next child
@@ -116,7 +116,7 @@ internal class DefaultPeekSpaceTask @Inject constructor(
                             SpaceSubChildPeekResult(
                                     childId,
                                     childPeek,
-                                    entry.second?.autoJoin,
+//                                    entry.second?.autoJoin,
                                     entry.second?.order,
                                     peekChildren(childStateEvents, depth + 1, maxDepth)
                             )
@@ -127,7 +127,7 @@ internal class DefaultPeekSpaceTask @Inject constructor(
                     Timber.v("## SPACE_PEEK: room child $entry")
                     spaceChildResults.add(
                             SpaceChildPeekResult(
-                                    childId, childPeek, entry.second?.autoJoin, entry.second?.order
+                                    childId, childPeek, entry.second?.order
                             )
                     )
                 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/peeking/SpacePeekResult.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/peeking/SpacePeekResult.kt
index 1df62e94..44d879f0 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/peeking/SpacePeekResult.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/peeking/SpacePeekResult.kt
@@ -28,21 +28,21 @@ data class SpacePeekSummary(
 interface ISpaceChild {
     val id: String
     val roomPeekResult: PeekResult
-    val default: Boolean?
+//    val default: Boolean?
     val order: String?
 }
 
 data class SpaceChildPeekResult(
         override val id: String,
         override val roomPeekResult: PeekResult,
-        override val default: Boolean? = null,
+//        override val default: Boolean? = null,
         override val order: String? = null
 ) : ISpaceChild
 
 data class SpaceSubChildPeekResult(
         override val id: String,
         override val roomPeekResult: PeekResult,
-        override val default: Boolean?,
+//        override val default: Boolean?,
         override val order: String?,
         val children: List<ISpaceChild>
 ) : ISpaceChild
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomSyncHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomSyncHandler.kt
index c3586bce..830e666c 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomSyncHandler.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomSyncHandler.kt
@@ -221,7 +221,7 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
                 }
                 // Give info to crypto module
                 cryptoService.onStateEvent(roomId, event)
-                roomMemberEventHandler.handle(realm, roomId, event)
+                roomMemberEventHandler.handle(realm, roomId, event, aggregator)
             }
         }
         if (roomSync.timeline?.events?.isNotEmpty() == true) {
@@ -233,7 +233,8 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
                     roomSync.timeline.prevToken,
                     roomSync.timeline.limited,
                     insertType,
-                    syncLocalTimestampMillis
+                    syncLocalTimestampMillis,
+                    aggregator
             )
             roomEntity.addIfNecessary(chunkEntity)
         }
@@ -337,7 +338,8 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
                                      prevToken: String? = null,
                                      isLimited: Boolean = true,
                                      insertType: EventInsertType,
-                                     syncLocalTimestampMillis: Long): ChunkEntity {
+                                     syncLocalTimestampMillis: Long,
+                                     aggregator: SyncResponsePostTreatmentAggregator): ChunkEntity {
         val lastChunk = ChunkEntity.findLastForwardChunkOfRoom(realm, roomEntity.roomId)
         val chunkEntity = if (!isLimited && lastChunk != null) {
             lastChunk
@@ -371,7 +373,7 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
                 if (event.type == EventType.STATE_ROOM_MEMBER) {
                     val fixedContent = event.getFixedRoomMemberContent()
                     roomMemberContentsByUser[event.stateKey] = fixedContent
-                    roomMemberEventHandler.handle(realm, roomEntity.roomId, event.stateKey, fixedContent)
+                    roomMemberEventHandler.handle(realm, roomEntity.roomId, event.stateKey, fixedContent, aggregator)
                 }
             }
             roomMemberContentsByUser.getOrPut(event.senderId) {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponsePostTreatmentAggregator.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponsePostTreatmentAggregator.kt
index ea10a32f..9bb2bfc9 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponsePostTreatmentAggregator.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponsePostTreatmentAggregator.kt
@@ -19,4 +19,6 @@ package org.matrix.android.sdk.internal.session.sync
 internal class SyncResponsePostTreatmentAggregator {
     // List of RoomId
     val ephemeralFilesToDelete = mutableListOf<String>()
+    // Map of roomId to directUserId
+    val directChatsToCheck = mutableMapOf<String, String>()
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponsePostTreatmentAggregatorHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponsePostTreatmentAggregatorHandler.kt
index 12b77c70..db1100d7 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponsePostTreatmentAggregatorHandler.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponsePostTreatmentAggregatorHandler.kt
@@ -16,13 +16,20 @@
 
 package org.matrix.android.sdk.internal.session.sync
 
+import org.matrix.android.sdk.api.MatrixPatterns
+import org.matrix.android.sdk.internal.session.sync.model.accountdata.toMutable
+import org.matrix.android.sdk.internal.session.user.accountdata.DirectChatsHelper
+import org.matrix.android.sdk.internal.session.user.accountdata.UpdateUserAccountDataTask
 import javax.inject.Inject
 
 internal class SyncResponsePostTreatmentAggregatorHandler @Inject constructor(
-        private val ephemeralTemporaryStore: RoomSyncEphemeralTemporaryStore
+        private val directChatsHelper: DirectChatsHelper,
+        private val ephemeralTemporaryStore: RoomSyncEphemeralTemporaryStore,
+        private val updateUserAccountDataTask: UpdateUserAccountDataTask
 ) {
-    fun handle(synResHaResponsePostTreatmentAggregator: SyncResponsePostTreatmentAggregator) {
+    suspend fun handle(synResHaResponsePostTreatmentAggregator: SyncResponsePostTreatmentAggregator) {
         cleanupEphemeralFiles(synResHaResponsePostTreatmentAggregator.ephemeralFilesToDelete)
+        updateDirectUserIds(synResHaResponsePostTreatmentAggregator.directChatsToCheck)
     }
 
     private fun cleanupEphemeralFiles(ephemeralFilesToDelete: List<String>) {
@@ -30,4 +37,33 @@ internal class SyncResponsePostTreatmentAggregatorHandler @Inject constructor(
             ephemeralTemporaryStore.delete(it)
         }
     }
+
+    private suspend fun updateDirectUserIds(directUserIdsToUpdate: Map<String, String>) {
+        val directChats = directChatsHelper.getLocalDirectMessages().toMutable()
+        var hasUpdate = false
+        directUserIdsToUpdate.forEach { (roomId, candidateUserId) ->
+            // consider room is a DM if referenced in the DM dictionary
+            val currentDirectUserId = directChats.firstNotNullOfOrNull { (userId, roomIds) -> userId.takeIf { roomId in roomIds } }
+            // update directUserId with the given candidateUserId if it mismatches the current one
+            if (currentDirectUserId != null && !MatrixPatterns.isUserId(currentDirectUserId)) {
+                // link roomId with the matrix id
+                directChats
+                        .getOrPut(candidateUserId) { arrayListOf() }
+                        .apply {
+                            if (!contains(roomId)) {
+                                hasUpdate = true
+                                add(roomId)
+                            }
+                        }
+
+                // remove roomId from currentDirectUserId entry
+                hasUpdate = hasUpdate or(directChats[currentDirectUserId]?.remove(roomId) == true)
+                // remove currentDirectUserId entry if there is no attached room anymore
+                hasUpdate = hasUpdate or(directChats.takeIf { it[currentDirectUserId].isNullOrEmpty() }?.remove(currentDirectUserId) != null)
+            }
+        }
+        if (hasUpdate) {
+            updateUserAccountDataTask.execute(UpdateUserAccountDataTask.DirectChatParams(directMessages = directChats))
+        }
+    }
 }
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 b8d987d5..110e7781 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
@@ -53,6 +53,7 @@ import org.matrix.android.sdk.internal.session.sync.model.accountdata.Breadcrumb
 import org.matrix.android.sdk.internal.session.sync.model.accountdata.DirectMessagesContent
 import org.matrix.android.sdk.internal.session.sync.model.accountdata.IgnoredUsersContent
 import org.matrix.android.sdk.internal.session.sync.model.accountdata.UserAccountDataSync
+import org.matrix.android.sdk.internal.session.sync.model.accountdata.toMutable
 import org.matrix.android.sdk.internal.session.user.accountdata.DirectChatsHelper
 import org.matrix.android.sdk.internal.session.user.accountdata.UpdateUserAccountDataTask
 import timber.log.Timber
@@ -83,7 +84,7 @@ internal class UserAccountDataSyncHandler @Inject constructor(
     // If we get some direct chat invites, we synchronize the user account data including those.
     suspend fun synchronizeWithServerIfNeeded(invites: Map<String, InvitedRoomSync>) {
         if (invites.isNullOrEmpty()) return
-        val directChats = directChatsHelper.getLocalUserAccount()
+        val directChats = directChatsHelper.getLocalDirectMessages().toMutable()
         var hasUpdate = false
         monarchy.doWithRealm { realm ->
             invites.forEach { (roomId, _) ->
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/model/accountdata/DirectMessagesContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/model/accountdata/DirectMessagesContent.kt
index 41173dea..7c73f1fe 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/model/accountdata/DirectMessagesContent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/model/accountdata/DirectMessagesContent.kt
@@ -20,3 +20,12 @@ package org.matrix.android.sdk.internal.session.sync.model.accountdata
  * Keys are userIds, values are list of roomIds
  */
 internal typealias DirectMessagesContent = Map<String, List<String>>
+
+/**
+ * Returns a new [MutableMap] with all elements of this collection.
+ */
+internal fun DirectMessagesContent.toMutable(): MutableMap<String, MutableList<String>> {
+    return map { it.key to it.value.toMutableList() }
+            .toMap()
+            .toMutableMap()
+}
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 a9e50897..37030576 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
@@ -21,6 +21,7 @@ import org.matrix.android.sdk.internal.database.query.getDirectRooms
 import org.matrix.android.sdk.internal.di.SessionDatabase
 import io.realm.Realm
 import io.realm.RealmConfiguration
+import org.matrix.android.sdk.internal.session.sync.model.accountdata.DirectMessagesContent
 import javax.inject.Inject
 
 internal class DirectChatsHelper @Inject constructor(@SessionDatabase
@@ -29,7 +30,7 @@ internal class DirectChatsHelper @Inject constructor(@SessionDatabase
     /**
      * @return a map of userId <-> list of roomId
      */
-    fun getLocalUserAccount(filterRoomId: String? = null): MutableMap<String, MutableList<String>> {
+    fun getLocalDirectMessages(filterRoomId: String? = null): DirectMessagesContent {
         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()
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/DefaultWidgetService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/DefaultWidgetService.kt
index 5912dc7b..dfe4b6b8 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/DefaultWidgetService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/DefaultWidgetService.kt
@@ -52,7 +52,7 @@ internal class DefaultWidgetService @Inject constructor(private val widgetManage
         return widgetManager.getWidgetComputedUrl(widget, isLightTheme)
     }
 
-    override fun getRoomWidgetsLive(
+override fun getRoomWidgetsLive(
             roomId: String,
             widgetId: QueryStringValue,
             widgetTypes: Set<String>?,
diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/task/CoroutineSequencersTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/task/CoroutineSequencersTest.kt
index 74b6c03d..3572a1a5 100644
--- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/task/CoroutineSequencersTest.kt
+++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/task/CoroutineSequencersTest.kt
@@ -32,6 +32,7 @@ class CoroutineSequencersTest: MatrixTest {
     private val dispatcher = Executors.newSingleThreadExecutor().asCoroutineDispatcher()
 
     @Test
+    @Suppress("EXPERIMENTAL_API_USAGE")
     fun sequencer_should_run_sequential() {
         val sequencer = SemaphoreCoroutineSequencer()
         val results = ArrayList<String>()
@@ -60,6 +61,7 @@ class CoroutineSequencersTest: MatrixTest {
     }
 
     @Test
+    @Suppress("EXPERIMENTAL_API_USAGE")
     fun sequencer_should_run_parallel() {
         val sequencer1 = SemaphoreCoroutineSequencer()
         val sequencer2 = SemaphoreCoroutineSequencer()
@@ -86,6 +88,7 @@ class CoroutineSequencersTest: MatrixTest {
     }
 
     @Test
+    @Suppress("EXPERIMENTAL_API_USAGE")
     fun sequencer_should_jump_to_next_when_current_job_canceled() {
         val sequencer = SemaphoreCoroutineSequencer()
         val results = ArrayList<String>()
-- 
GitLab