diff --git a/CHANGES.md b/CHANGES.md
index 4cecceda29a3507359b5329c7d9d6f349b08e7a7..45e0895624b4b3afc2e50f1472937ce703ea7d70 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,5 +1,16 @@
 Please also refer to the Changelog of Element Android: https://github.com/vector-im/element-android/blob/main/CHANGES.md
 
+Changes in Matrix-SDK v1.5.1 (2022-09-28)
+=======================================
+
+Imported from Element 1.5.1. (https://github.com/vector-im/element-android/releases/tag/v1.5.1)
+
+Security ⚠️
+----------
+
+This update provides important security fixes, update now.
+Ref: CVE-2022-39246 CVE-2022-39248
+
 Changes in Matrix-SDK v1.4.36 (2022-09-13)
 =======================================
 
diff --git a/dependencies.gradle b/dependencies.gradle
index 3759763fb76e2d11894c0054ffec42ce5262beb7..9641a63f263b3ac5cb21a96cd3cef9625299beb3 100644
--- a/dependencies.gradle
+++ b/dependencies.gradle
@@ -15,14 +15,14 @@ def gradle = "7.1.3"
 def kotlin = "1.6.21"
 def kotlinCoroutines = "1.6.4"
 def dagger = "2.42"
-def appDistribution = "16.0.0-beta03"
+def appDistribution = "16.0.0-beta04"
 def retrofit = "2.9.0"
 def arrow = "0.8.2"
 def markwon = "4.6.2"
 def moshi = "1.13.0"
 def lifecycle = "2.5.1"
 def flowBinding = "1.2.0"
-def flipper = "0.163.0"
+def flipper = "0.164.0"
 def epoxy = "4.6.2"
 def mavericks = "2.7.0"
 def glide = "4.13.2"
@@ -86,7 +86,7 @@ ext.libs = [
                 'appdistributionApi'      : "com.google.firebase:firebase-appdistribution-api-ktx:$appDistribution",
                 'appdistribution'         : "com.google.firebase:firebase-appdistribution:$appDistribution",
                 // Phone number https://github.com/google/libphonenumber
-                'phonenumber'             : "com.googlecode.libphonenumber:libphonenumber:8.12.54"
+                'phonenumber'             : "com.googlecode.libphonenumber:libphonenumber:8.12.55"
         ],
         dagger      : [
                 'dagger'                  : "com.google.dagger:dagger:$dagger",
diff --git a/dependencies_groups.gradle b/dependencies_groups.gradle
index fcd127905e553f87ec160ac3e18fbc2a053580cc..f905612a22130da3bd07984def35d99cb48f31b6 100644
--- a/dependencies_groups.gradle
+++ b/dependencies_groups.gradle
@@ -70,8 +70,6 @@ ext.groups = [
                         'com.gabrielittner.threetenbp',
                         'com.getkeepsafe.relinker',
                         'com.github.bumptech.glide',
-                        'com.github.filippudak',
-                        'com.github.filippudak.progresspieview',
                         'com.github.javaparser',
                         'com.github.piasy',
                         'com.github.shyiko.klob',
diff --git a/gradle.properties b/gradle.properties
index 14a2c0b06ec9c08941e4f74e78201c0b3610265f..ca3a954156ed20f296576893d625b05e656f687f 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -26,7 +26,7 @@ vector.httpLogLevel=NONE
 # Ref: https://github.com/vanniktech/gradle-maven-publish-plugin
 GROUP=org.matrix.android
 POM_ARTIFACT_ID=matrix-android-sdk2
-VERSION_NAME=1.4.36
+VERSION_NAME=1.5.1
 
 POM_PACKAGING=aar
 
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 a78953caacc87a7cfa27534f64b9d104876ff597..43f42a3ed460c08d009a1d5d67ea846aee584bf8 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
@@ -38,6 +38,7 @@ import org.matrix.android.sdk.api.MatrixCallback
 import org.matrix.android.sdk.api.MatrixConfiguration
 import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
 import org.matrix.android.sdk.api.auth.registration.RegistrationResult
+import org.matrix.android.sdk.api.crypto.MXCryptoConfig
 import org.matrix.android.sdk.api.session.Session
 import org.matrix.android.sdk.api.session.events.model.EventType
 import org.matrix.android.sdk.api.session.events.model.toModel
@@ -61,7 +62,7 @@ import java.util.concurrent.TimeUnit
  * This class exposes methods to be used in common cases
  * Registration, login, Sync, Sending messages...
  */
-class CommonTestHelper internal constructor(context: Context) {
+class CommonTestHelper internal constructor(context: Context, val cryptoConfig: MXCryptoConfig? = null) {
 
     companion object {
         internal fun runSessionTest(context: Context, autoSignoutOnClose: Boolean = true, block: (CommonTestHelper) -> Unit) {
@@ -75,8 +76,10 @@ class CommonTestHelper internal constructor(context: Context) {
             }
         }
 
-        internal fun runCryptoTest(context: Context, autoSignoutOnClose: Boolean = true, block: (CryptoTestHelper, CommonTestHelper) -> Unit) {
-            val testHelper = CommonTestHelper(context)
+        internal fun runCryptoTest(context: Context, autoSignoutOnClose: Boolean = true,
+                                   cryptoConfig: MXCryptoConfig? = null,
+                                   block: (CryptoTestHelper, CommonTestHelper) -> Unit) {
+            val testHelper = CommonTestHelper(context, cryptoConfig)
             val cryptoTestHelper = CryptoTestHelper(testHelper)
             return try {
                 block(cryptoTestHelper, testHelper)
@@ -103,7 +106,8 @@ class CommonTestHelper internal constructor(context: Context) {
                     context,
                     MatrixConfiguration(
                             applicationFlavor = "TestFlavor",
-                            roomDisplayNameFallbackProvider = TestRoomDisplayNameFallbackProvider()
+                            roomDisplayNameFallbackProvider = TestRoomDisplayNameFallbackProvider(),
+                            cryptoConfig = cryptoConfig ?: MXCryptoConfig()
                     )
             )
         }
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 f36bfb6210e164e8e4c82316bbd4b70601ff3776..210ce906928ac8c161777018ed0a20da835ccc51 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
@@ -529,7 +529,8 @@ class CryptoTestHelper(val testHelper: CommonTestHelper) {
                                         payload = result.clearEvent,
                                         senderKey = result.senderCurve25519Key,
                                         keysClaimed = result.claimedEd25519Key?.let { mapOf("ed25519" to it) },
-                                        forwardingCurve25519KeyChain = result.forwardingCurve25519KeyChain
+                                        forwardingCurve25519KeyChain = result.forwardingCurve25519KeyChain,
+                                        isSafe = result.isSafe
                                 )
                             }
                         } catch (error: MXCryptoError) {
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeSanityTests.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeSanityTests.kt
index f8832954950e7ff5570023e56ae0bced2ca8c4fd..410fb4f5d4778be9e415eeca3b933eec41605805 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeSanityTests.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeSanityTests.kt
@@ -29,9 +29,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.crypto.MXCryptoConfig
 import org.matrix.android.sdk.api.session.Session
 import org.matrix.android.sdk.api.session.crypto.MXCryptoError
-import org.matrix.android.sdk.api.session.crypto.RequestResult
 import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysVersion
 import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysVersionResult
 import org.matrix.android.sdk.api.session.crypto.keysbackup.MegolmBackupCreationInfo
@@ -45,7 +45,6 @@ import org.matrix.android.sdk.api.session.crypto.verification.VerificationServic
 import org.matrix.android.sdk.api.session.crypto.verification.VerificationTransaction
 import org.matrix.android.sdk.api.session.events.model.EventType
 import org.matrix.android.sdk.api.session.events.model.content.EncryptedEventContent
-import org.matrix.android.sdk.api.session.events.model.content.WithHeldCode
 import org.matrix.android.sdk.api.session.events.model.toModel
 import org.matrix.android.sdk.api.session.getRoom
 import org.matrix.android.sdk.api.session.room.Room
@@ -134,7 +133,8 @@ class E2eeSanityTests : InstrumentedTest {
                     val timeLineEvent = otherSession.getRoom(e2eRoomID)?.getTimelineEvent(sentEventId!!)
                     timeLineEvent != null &&
                             timeLineEvent.isEncrypted() &&
-                            timeLineEvent.root.getClearType() == EventType.MESSAGE
+                            timeLineEvent.root.getClearType() == EventType.MESSAGE &&
+                            timeLineEvent.root.mxDecryptionResult?.isSafe == true
                 }
             }
         }
@@ -331,6 +331,15 @@ class E2eeSanityTests : InstrumentedTest {
 
         // ensure bob can now decrypt
         cryptoTestHelper.ensureCanDecrypt(sentEventIds, newBobSession, e2eRoomID, messagesText)
+
+        // Check key trust
+        sentEventIds.forEach { sentEventId ->
+            val timelineEvent = newBobSession.getRoom(e2eRoomID)?.getTimelineEvent(sentEventId)!!
+            val result = testHelper.runBlockingTest {
+                newBobSession.cryptoService().decryptEvent(timelineEvent.root, "")
+            }
+            assertEquals("Keys from history should be deniable", false, result.isSafe)
+        }
     }
 
     /**
@@ -379,10 +388,6 @@ class E2eeSanityTests : InstrumentedTest {
         Log.v("#E2E TEST", "check that new bob can't currently decrypt")
 
         cryptoTestHelper.ensureCannotDecrypt(sentEventIds, newBobSession, e2eRoomID, null)
-//        newBobSession.cryptoService().getOutgoingRoomKeyRequests()
-//                .firstOrNull {
-//                    it.sessionId ==
-//                }
 
         // Try to request
         sentEventIds.forEach { sentEventId ->
@@ -390,33 +395,30 @@ class E2eeSanityTests : InstrumentedTest {
             newBobSession.cryptoService().requestRoomKeyForEvent(event)
         }
 
-        // wait a bit
-        // we need to wait a couple of syncs to let sharing occurs
-//        testHelper.waitFewSyncs(newBobSession, 6)
-
         // Ensure that new bob still can't decrypt (keys must have been withheld)
-        sentEventIds.forEach { sentEventId ->
-            val megolmSessionId = newBobSession.getRoom(e2eRoomID)!!
-                    .getTimelineEvent(sentEventId)!!
-                    .root.content.toModel<EncryptedEventContent>()!!.sessionId
-            testHelper.waitWithLatch { latch ->
-                testHelper.retryPeriodicallyWithLatch(latch) {
-                    val aliceReply = newBobSession.cryptoService().getOutgoingRoomKeyRequests()
-                            .first {
-                                it.sessionId == megolmSessionId &&
-                                        it.roomId == e2eRoomID
-                            }
-                            .results.also {
-                                Log.w("##TEST", "result list is $it")
-                            }
-                            .firstOrNull { it.userId == aliceSession.myUserId }
-                            ?.result
-                    aliceReply != null &&
-                            aliceReply is RequestResult.Failure &&
-                            WithHeldCode.UNAUTHORISED == aliceReply.code
-                }
-            }
-        }
+        // as per new config we won't request to alice, so ignore following test
+//        sentEventIds.forEach { sentEventId ->
+//            val megolmSessionId = newBobSession.getRoom(e2eRoomID)!!
+//                    .getTimelineEvent(sentEventId)!!
+//                    .root.content.toModel<EncryptedEventContent>()!!.sessionId
+//            testHelper.waitWithLatch { latch ->
+//                testHelper.retryPeriodicallyWithLatch(latch) {
+//                    val aliceReply = newBobSession.cryptoService().getOutgoingRoomKeyRequests()
+//                            .first {
+//                                it.sessionId == megolmSessionId &&
+//                                        it.roomId == e2eRoomID
+//                            }
+//                            .results.also {
+//                                Log.w("##TEST", "result list is $it")
+//                            }
+//                            .firstOrNull { it.userId == aliceSession.myUserId }
+//                            ?.result
+//                    aliceReply != null &&
+//                            aliceReply is RequestResult.Failure &&
+//                            WithHeldCode.UNAUTHORISED == aliceReply.code
+//                }
+//            }
+//        }
 
         cryptoTestHelper.ensureCannotDecrypt(sentEventIds, newBobSession, e2eRoomID, null)
 
@@ -438,7 +440,10 @@ class E2eeSanityTests : InstrumentedTest {
      * Test that if a better key is forwarded (lower index, it is then used)
      */
     @Test
-    fun testForwardBetterKey() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
+    fun testForwardBetterKey() = runCryptoTest(
+            context(),
+            cryptoConfig = MXCryptoConfig(limitRoomKeyRequestsToMyDevices = false)
+    ) { cryptoTestHelper, testHelper ->
 
         val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(true)
         val aliceSession = cryptoTestData.firstSession
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeShareKeysHistoryTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeShareKeysHistoryTest.kt
index 32a95008b1ac5017ff1790dc6442f09041795e66..4b44aab18b8b0030a48e279d7b3539282b80eaa5 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeShareKeysHistoryTest.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeShareKeysHistoryTest.kt
@@ -77,6 +77,7 @@ class E2eeShareKeysHistoryTest : InstrumentedTest {
      */
     private fun testShareHistoryWithRoomVisibility(roomHistoryVisibility: RoomHistoryVisibility? = null) =
             runCryptoTest(context()) { cryptoTestHelper, testHelper ->
+                val aliceMessageText = "Hello Bob, I am Alice!"
                 val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(true, roomHistoryVisibility)
 
                 val e2eRoomID = cryptoTestData.roomId
@@ -96,7 +97,7 @@ class E2eeShareKeysHistoryTest : InstrumentedTest {
                 assertEquals(bobRoomPOV.roomSummary()?.joinedMembersCount, 2)
                 Log.v("#E2E TEST", "Alice and Bob are in roomId: $e2eRoomID")
 
-                val aliceMessageId: String? = sendMessageInRoom(aliceRoomPOV, "Hello Bob, I am Alice!", testHelper)
+                val aliceMessageId: String? = sendMessageInRoom(aliceRoomPOV, aliceMessageText, testHelper)
                 Assert.assertTrue("Message should be sent", aliceMessageId != null)
                 Log.v("#E2E TEST", "Alice sent message to roomId: $e2eRoomID")
 
@@ -106,7 +107,8 @@ class E2eeShareKeysHistoryTest : InstrumentedTest {
                         val timelineEvent = bobSession.roomService().getRoom(e2eRoomID)?.timelineService()?.getTimelineEvent(aliceMessageId!!)
                         (timelineEvent != null &&
                                 timelineEvent.isEncrypted() &&
-                                timelineEvent.root.getClearType() == EventType.MESSAGE).also {
+                                timelineEvent.root.getClearType() == EventType.MESSAGE &&
+                                timelineEvent.root.mxDecryptionResult?.isSafe == true).also {
                             if (it) {
                                 Log.v("#E2E TEST", "Bob can decrypt the message: ${timelineEvent?.root?.getDecryptedTextSummary()}")
                             }
@@ -142,7 +144,8 @@ class E2eeShareKeysHistoryTest : InstrumentedTest {
                                 val timelineEvent = arisSession.roomService().getRoom(e2eRoomID)?.timelineService()?.getTimelineEvent(aliceMessageId!!)
                                 (timelineEvent != null &&
                                         timelineEvent.isEncrypted() &&
-                                        timelineEvent.root.getClearType() == EventType.MESSAGE
+                                        timelineEvent.root.getClearType() == EventType.MESSAGE &&
+                                        timelineEvent.root.mxDecryptionResult?.isSafe == false
                                         ).also {
                                             if (it) {
                                                 Log.v("#E2E TEST", "Aris can decrypt the message: ${timelineEvent?.root?.getDecryptedTextSummary()}")
@@ -377,7 +380,10 @@ class E2eeShareKeysHistoryTest : InstrumentedTest {
     }
 
     private fun sendMessageInRoom(aliceRoomPOV: Room, text: String, testHelper: CommonTestHelper): String? {
-        return testHelper.sendTextMessage(aliceRoomPOV, text, 1).firstOrNull()?.eventId
+        return testHelper.sendTextMessage(aliceRoomPOV, text, 1).firstOrNull()?.let {
+            Log.v("#E2E TEST", "Message sent with session ${it.root.content?.get("session_id")}")
+            return it.eventId
+        }
     }
 
     private fun ensureMembersHaveJoined(aliceSession: Session, otherAccounts: List<Session>, e2eRoomID: String, testHelper: CommonTestHelper) {
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/UnwedgingTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/UnwedgingTest.kt
index 5fe737618411831e27d93b4baa6c465b4b11a8d6..130c8d13f9909a242ee33e12118f8b0ee437125f 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/UnwedgingTest.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/UnwedgingTest.kt
@@ -29,6 +29,7 @@ import org.matrix.android.sdk.api.auth.UIABaseAuth
 import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
 import org.matrix.android.sdk.api.auth.UserPasswordAuth
 import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse
+import org.matrix.android.sdk.api.crypto.MXCryptoConfig
 import org.matrix.android.sdk.api.extensions.tryOrNull
 import org.matrix.android.sdk.api.session.crypto.MXCryptoError
 import org.matrix.android.sdk.api.session.events.model.EventType
@@ -82,7 +83,10 @@ class UnwedgingTest : InstrumentedTest {
      * -> This is automatically fixed after SDKs restarted the olm session
      */
     @Test
-    fun testUnwedging() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
+    fun testUnwedging() = runCryptoTest(
+            context(),
+            cryptoConfig = MXCryptoConfig(limitRoomKeyRequestsToMyDevices = false)
+    ) { cryptoTestHelper, testHelper ->
         val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
 
         val aliceSession = cryptoTestData.firstSession
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/KeyShareTests.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/KeyShareTests.kt
index 7bb53e139c3bd5d63206c844f0fa57aeb51755a2..df0b10ea6d9143e49b856c5d268fe19413ab6715 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/KeyShareTests.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/KeyShareTests.kt
@@ -22,15 +22,16 @@ import androidx.test.filters.LargeTest
 import junit.framework.TestCase.assertNotNull
 import junit.framework.TestCase.assertTrue
 import org.amshove.kluent.internal.assertEquals
+import org.amshove.kluent.shouldBeEqualTo
 import org.junit.Assert
 import org.junit.Assert.assertNull
 import org.junit.FixMethodOrder
-import org.junit.Ignore
-import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
 import org.matrix.android.sdk.InstrumentedTest
+import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_OLM
+import org.matrix.android.sdk.api.crypto.MXCryptoConfig
 import org.matrix.android.sdk.api.session.crypto.OutgoingRoomKeyRequestState
 import org.matrix.android.sdk.api.session.crypto.RequestResult
 import org.matrix.android.sdk.api.session.crypto.crosssigning.DeviceTrustLevel
@@ -43,7 +44,6 @@ import org.matrix.android.sdk.api.session.room.model.RoomDirectoryVisibility
 import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams
 import org.matrix.android.sdk.api.session.room.timeline.getLastMessageContent
 import org.matrix.android.sdk.common.CommonTestHelper.Companion.runCryptoTest
-import org.matrix.android.sdk.common.RetryTestRule
 import org.matrix.android.sdk.common.SessionTestParams
 import org.matrix.android.sdk.common.TestConstants
 import org.matrix.android.sdk.mustFail
@@ -51,16 +51,15 @@ import org.matrix.android.sdk.mustFail
 @RunWith(AndroidJUnit4::class)
 @FixMethodOrder(MethodSorters.JVM)
 @LargeTest
-@Ignore
 class KeyShareTests : InstrumentedTest {
 
-    @get:Rule val rule = RetryTestRule(3)
+    // @get:Rule val rule = RetryTestRule(3)
 
     @Test
     fun test_DoNotSelfShareIfNotTrusted() = runCryptoTest(context()) { cryptoTestHelper, commonTestHelper ->
 
         val aliceSession = commonTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true))
-        Log.v("TEST", "=======> AliceSession 1 is ${aliceSession.sessionParams.deviceId}")
+        Log.v("#TEST", "=======> AliceSession 1 is ${aliceSession.sessionParams.deviceId}")
 
         // Create an encrypted room and add a message
         val roomId = commonTestHelper.runBlockingTest {
@@ -86,7 +85,7 @@ class KeyShareTests : InstrumentedTest {
         aliceSession2.cryptoService().enableKeyGossiping(false)
         commonTestHelper.syncSession(aliceSession2)
 
-        Log.v("TEST", "=======> AliceSession 2 is ${aliceSession2.sessionParams.deviceId}")
+        Log.v("#TEST", "=======> AliceSession 2 is ${aliceSession2.sessionParams.deviceId}")
 
         val roomSecondSessionPOV = aliceSession2.getRoom(roomId)
 
@@ -121,7 +120,7 @@ class KeyShareTests : InstrumentedTest {
                         }
             }
         }
-        Log.v("TEST", "=======> Outgoing requet Id is $outGoingRequestId")
+        Log.v("#TEST", "=======> Outgoing requet Id is $outGoingRequestId")
 
         val outgoingRequestAfter = aliceSession2.cryptoService().getOutgoingRoomKeyRequests()
 
@@ -134,14 +133,17 @@ class KeyShareTests : InstrumentedTest {
         commonTestHelper.waitWithLatch { latch ->
             commonTestHelper.retryPeriodicallyWithLatch(latch) {
                 // DEBUG LOGS
-//                aliceSession.cryptoService().getIncomingRoomKeyRequests().let {
-//                    Log.v("TEST", "Incoming request Session 1 (looking for $outGoingRequestId)")
-//                    Log.v("TEST", "=========================")
-//                    it.forEach { keyRequest ->
-//                        Log.v("TEST", "[ts${keyRequest.localCreationTimestamp}] requestId ${keyRequest.requestId}, for sessionId ${keyRequest.requestBody?.sessionId}")
-//                    }
-//                    Log.v("TEST", "=========================")
-//                }
+                aliceSession.cryptoService().getIncomingRoomKeyRequests().let {
+                    Log.v("#TEST", "Incoming request Session 1 (looking for $outGoingRequestId)")
+                    Log.v("#TEST", "=========================")
+                    it.forEach { keyRequest ->
+                        Log.v(
+                                "#TEST",
+                                "[ts${keyRequest.localCreationTimestamp}] requestId ${keyRequest.requestId}, for sessionId ${keyRequest.requestBody?.sessionId}"
+                        )
+                    }
+                    Log.v("#TEST", "=========================")
+                }
 
                 val incoming = aliceSession.cryptoService().getIncomingRoomKeyRequests().firstOrNull { it.requestId == outGoingRequestId }
                 incoming != null
@@ -152,10 +154,10 @@ class KeyShareTests : InstrumentedTest {
             commonTestHelper.retryPeriodicallyWithLatch(latch) {
                 // DEBUG LOGS
                 aliceSession2.cryptoService().getOutgoingRoomKeyRequests().forEach { keyRequest ->
-                    Log.v("TEST", "=========================")
-                    Log.v("TEST", "requestId ${keyRequest.requestId}, for sessionId ${keyRequest.requestBody?.sessionId}")
-                    Log.v("TEST", "replies -> ${keyRequest.results.joinToString { it.toString() }}")
-                    Log.v("TEST", "=========================")
+                    Log.v("#TEST", "=========================")
+                    Log.v("#TEST", "requestId ${keyRequest.requestId}, for sessionId ${keyRequest.requestBody?.sessionId}")
+                    Log.v("#TEST", "replies -> ${keyRequest.results.joinToString { it.toString() }}")
+                    Log.v("#TEST", "=========================")
                 }
 
                 val outgoing = aliceSession2.cryptoService().getOutgoingRoomKeyRequests().firstOrNull { it.requestId == outGoingRequestId }
@@ -172,11 +174,24 @@ class KeyShareTests : InstrumentedTest {
         }
 
         // Mark the device as trusted
+
+        Log.v("#TEST", "=======> Alice device 1 is ${aliceSession.sessionParams.deviceId}|${aliceSession.cryptoService().getMyDevice().identityKey()}")
+        val aliceSecondSession = aliceSession2.cryptoService().getMyDevice()
+        Log.v("#TEST", "=======> Alice device 2 is ${aliceSession2.sessionParams.deviceId}|${aliceSecondSession.identityKey()}")
+
         aliceSession.cryptoService().setDeviceVerification(
                 DeviceTrustLevel(crossSigningVerified = false, locallyVerified = true), aliceSession.myUserId,
                 aliceSession2.sessionParams.deviceId ?: ""
         )
 
+        // We only accept forwards from trusted session, so we need to trust on other side to
+        aliceSession2.cryptoService().setDeviceVerification(
+                DeviceTrustLevel(crossSigningVerified = false, locallyVerified = true), aliceSession.myUserId,
+                aliceSession.sessionParams.deviceId ?: ""
+        )
+
+        aliceSession.cryptoService().deviceWithIdentityKey(aliceSecondSession.identityKey()!!, MXCRYPTO_ALGORITHM_OLM)!!.isVerified shouldBeEqualTo true
+
         // Re request
         aliceSession2.cryptoService().reRequestRoomKeyForEvent(receivedEvent.root)
 
@@ -193,7 +208,10 @@ class KeyShareTests : InstrumentedTest {
      * if the key was originally shared with him
      */
     @Test
-    fun test_reShareIfWasIntendedToBeShared() = runCryptoTest(context()) { cryptoTestHelper, commonTestHelper ->
+    fun test_reShareIfWasIntendedToBeShared() = runCryptoTest(
+            context(),
+            cryptoConfig = MXCryptoConfig(limitRoomKeyRequestsToMyDevices = false)
+    ) { cryptoTestHelper, commonTestHelper ->
 
         val testData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(true)
         val aliceSession = testData.firstSession
@@ -224,7 +242,10 @@ class KeyShareTests : InstrumentedTest {
      * if the key was originally shared with him
      */
     @Test
-    fun test_reShareToUnverifiedIfWasIntendedToBeShared() = runCryptoTest(context()) { cryptoTestHelper, commonTestHelper ->
+    fun test_reShareToUnverifiedIfWasIntendedToBeShared() = runCryptoTest(
+            context(),
+            cryptoConfig = MXCryptoConfig(limitRoomKeyRequestsToMyDevices = false)
+    ) { cryptoTestHelper, commonTestHelper ->
 
         val testData = cryptoTestHelper.doE2ETestWithAliceInARoom(true)
         val aliceSession = testData.firstSession
@@ -242,7 +263,6 @@ class KeyShareTests : InstrumentedTest {
         }
         val sentEvent = commonTestHelper.sendTextMessage(roomFromAlice, "Hello", 1).first()
         val sentEventMegolmSession = sentEvent.root.content.toModel<EncryptedEventContent>()!!.sessionId!!
-
         // Let's try to request any how.
         // As it was share previously alice should accept to reshare
         aliceNewSession.cryptoService().reRequestRoomKeyForEvent(sentEvent.root)
@@ -261,7 +281,10 @@ class KeyShareTests : InstrumentedTest {
      * Tests that keys reshared with own verified session are done from the earliest known index
      */
     @Test
-    fun test_reShareFromTheEarliestKnownIndexWithOwnVerifiedSession() = runCryptoTest(context()) { cryptoTestHelper, commonTestHelper ->
+    fun test_reShareFromTheEarliestKnownIndexWithOwnVerifiedSession() = runCryptoTest(
+            context(),
+            cryptoConfig = MXCryptoConfig(limitRoomKeyRequestsToMyDevices = false)
+    ) { cryptoTestHelper, commonTestHelper ->
 
         val testData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(true)
         val aliceSession = testData.firstSession
@@ -333,6 +356,9 @@ class KeyShareTests : InstrumentedTest {
         aliceSession.cryptoService()
                 .verificationService()
                 .markedLocallyAsManuallyVerified(aliceNewSession.myUserId, aliceNewSession.sessionParams.deviceId!!)
+        aliceNewSession.cryptoService()
+                .verificationService()
+                .markedLocallyAsManuallyVerified(aliceSession.myUserId, aliceSession.sessionParams.deviceId!!)
 
         // Let's now try to request
         aliceNewSession.cryptoService().reRequestRoomKeyForEvent(sentEvents.first().root)
@@ -381,7 +407,10 @@ class KeyShareTests : InstrumentedTest {
      * Tests that we don't cancel a request to early on first forward if the index is not good enough
      */
     @Test
-    fun test_dontCancelToEarly() = runCryptoTest(context()) { cryptoTestHelper, commonTestHelper ->
+    fun test_dontCancelToEarly() = runCryptoTest(
+            context(),
+            cryptoConfig = MXCryptoConfig(limitRoomKeyRequestsToMyDevices = false)
+    ) { cryptoTestHelper, commonTestHelper ->
         val testData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(true)
         val aliceSession = testData.firstSession
         val bobSession = testData.secondSession!!
@@ -421,6 +450,9 @@ class KeyShareTests : InstrumentedTest {
         aliceSession.cryptoService()
                 .verificationService()
                 .markedLocallyAsManuallyVerified(aliceNewSession.myUserId, aliceNewSession.sessionParams.deviceId!!)
+        aliceNewSession.cryptoService()
+                .verificationService()
+                .markedLocallyAsManuallyVerified(aliceSession.myUserId, aliceSession.sessionParams.deviceId!!)
 
         // /!\ Stop initial alice session syncing so that it can't reply
         aliceSession.cryptoService().enableKeyGossiping(false)
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/WithHeldTests.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/WithHeldTests.kt
index 0aac4297e4265114ac8b405218e7fcf424b444f1..910a349b403ac108b7f7809dfde68600a6bb690c 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/WithHeldTests.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/WithHeldTests.kt
@@ -27,6 +27,7 @@ import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
 import org.matrix.android.sdk.InstrumentedTest
 import org.matrix.android.sdk.api.NoOpMatrixCallback
+import org.matrix.android.sdk.api.crypto.MXCryptoConfig
 import org.matrix.android.sdk.api.extensions.tryOrNull
 import org.matrix.android.sdk.api.session.crypto.MXCryptoError
 import org.matrix.android.sdk.api.session.crypto.RequestResult
@@ -153,7 +154,10 @@ class WithHeldTests : InstrumentedTest {
     }
 
     @Test
-    fun test_WithHeldNoOlm() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
+    fun test_WithHeldNoOlm() = runCryptoTest(
+            context(),
+            cryptoConfig = MXCryptoConfig(limitRoomKeyRequestsToMyDevices = false)
+    ) { cryptoTestHelper, testHelper ->
 
         val testData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
         val aliceSession = testData.firstSession
@@ -233,7 +237,10 @@ class WithHeldTests : InstrumentedTest {
     }
 
     @Test
-    fun test_WithHeldKeyRequest() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
+    fun test_WithHeldKeyRequest() = runCryptoTest(
+            context(),
+            cryptoConfig = MXCryptoConfig(limitRoomKeyRequestsToMyDevices = false)
+    ) { cryptoTestHelper, testHelper ->
 
         val testData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
         val aliceSession = testData.firstSession
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/crypto/MXCryptoConfig.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/crypto/MXCryptoConfig.kt
index 015cb6a1a28bfc409aa7ee625aefd71447d70de0..38f522586f069c4596a82bb92accbfd8e82f26a2 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/crypto/MXCryptoConfig.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/crypto/MXCryptoConfig.kt
@@ -35,8 +35,9 @@ data class MXCryptoConfig constructor(
 
         /**
          * Currently megolm keys are requested to the sender device and to all of our devices.
-         * You can limit request only to your sessions by turning this setting to `true`
+         * You can limit request only to your sessions by turning this setting to `true`.
+         * Forwarded keys coming from other users will also be ignored if set to true.
          */
-        val limitRoomKeyRequestsToMyDevices: Boolean = false,
+        val limitRoomKeyRequestsToMyDevices: Boolean = true,
 
         )
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/query/QueryStringValue.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/query/QueryStringValue.kt
index d3f6ec2287f8e375f43788c2e1cab9bf45489de9..1d6e79c8f63041803aec822fbc941e162d041839 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/query/QueryStringValue.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/query/QueryStringValue.kt
@@ -68,6 +68,11 @@ sealed interface QueryStringValue {
      */
     data class Contains(override val string: String, override val case: Case = Case.SENSITIVE) : ContentQueryStringValue
 
+    /**
+     * The tested field must not contain the [string].
+     */
+    data class NotContains(override val string: String, override val case: Case = Case.SENSITIVE) : ContentQueryStringValue
+
     /**
      * Case enum for [ContentQueryStringValue].
      */
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/SessionExtensions.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/SessionExtensions.kt
index a15e73eb88f007dd917797478d1c6c9896641f6c..96dac2761869116842d9b90ef95b2e4414f85b9d 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/SessionExtensions.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/SessionExtensions.kt
@@ -32,5 +32,13 @@ fun Session.getRoomSummary(roomIdOrAlias: String): RoomSummary? = roomService().
 
 /**
  * Get a user using the UserService of a Session.
+ * @param userId the userId to look for.
+ * @return a user with userId or null if the User is not known yet by the SDK.
+ * See [org.matrix.android.sdk.api.session.user.UserService.resolveUser] to ensure that a User is retrieved.
  */
 fun Session.getUser(userId: String): User? = userService().getUser(userId)
+
+/**
+ * Similar to [getUser], but fallback to a User without details if the User is not known by the SDK, or if Session is null.
+ */
+fun Session?.getUserOrDefault(userId: String): User = this?.userService()?.getUser(userId) ?: User(userId)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/MXEventDecryptionResult.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/MXEventDecryptionResult.kt
index 0a0ccc2db3e26be08aa2496f591f503090c3b48d..66d7558fe205c53bef67bef27675aae65538ad5e 100755
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/MXEventDecryptionResult.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/MXEventDecryptionResult.kt
@@ -43,5 +43,7 @@ data class MXEventDecryptionResult(
          * List of curve25519 keys involved in telling us about the senderCurve25519Key and
          * claimedEd25519Key. See MXEvent.forwardingCurve25519KeyChain.
          */
-        val forwardingCurve25519KeyChain: List<String> = emptyList()
+        val forwardingCurve25519KeyChain: List<String> = emptyList(),
+
+        val isSafe: Boolean = false
 )
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/OlmDecryptionResult.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/OlmDecryptionResult.kt
index a26f6606edd1a8f7fea8108520ad9a277619b516..6d57318f87a51eb628753ebf4d4aa0dfddc5d8c2 100755
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/OlmDecryptionResult.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/OlmDecryptionResult.kt
@@ -44,5 +44,10 @@ data class OlmDecryptionResult(
         /**
          * Devices which forwarded this session to us (normally empty).
          */
-        @Json(name = "forwardingCurve25519KeyChain") val forwardingCurve25519KeyChain: List<String>? = null
+        @Json(name = "forwardingCurve25519KeyChain") val forwardingCurve25519KeyChain: List<String>? = null,
+
+        /**
+         * True if the key used to decrypt is considered safe (trusted).
+         */
+        @Json(name = "key_safety") val isSafe: Boolean? = null,
 )
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt
index 59dc6c434d00bfd083f2ac905da2051fd4ebf359..f5d2c0d9a0ef6592e932fd34c5f6ba2dd321d76b 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt
@@ -174,15 +174,29 @@ data class Event(
      * @return the event type
      */
     fun getClearType(): String {
-        return mxDecryptionResult?.payload?.get("type")?.toString() ?: type ?: EventType.MISSING_TYPE
+        return getDecryptedType() ?: type ?: EventType.MISSING_TYPE
+    }
+
+    /**
+     * @return The decrypted type, or null. Won't fallback to the wired type
+     */
+    fun getDecryptedType(): String? {
+        return mxDecryptionResult?.payload?.get("type")?.toString()
     }
 
     /**
      * @return the event content
      */
     fun getClearContent(): Content? {
+        return getDecryptedContent() ?: content
+    }
+
+    /**
+     * @return the decrypted event content or null, Won't fallback to the wired content
+     */
+    fun getDecryptedContent(): Content? {
         @Suppress("UNCHECKED_CAST")
-        return mxDecryptionResult?.payload?.get("content") as? Content ?: content
+        return mxDecryptionResult?.payload?.get("content") as? Content
     }
 
     fun toContentStringWithIndent(): String {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/Room.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/Room.kt
index 5d2769ac3cbd774c5c78051101ee9b48316673b0..8031fcaeea893dae8cb5e4dbdbb2e3895ebf6d49 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/Room.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/Room.kt
@@ -24,6 +24,7 @@ import org.matrix.android.sdk.api.session.room.call.RoomCallService
 import org.matrix.android.sdk.api.session.room.crypto.RoomCryptoService
 import org.matrix.android.sdk.api.session.room.location.LocationSharingService
 import org.matrix.android.sdk.api.session.room.members.MembershipService
+import org.matrix.android.sdk.api.session.room.model.LocalRoomSummary
 import org.matrix.android.sdk.api.session.room.model.RoomSummary
 import org.matrix.android.sdk.api.session.room.model.relation.RelationService
 import org.matrix.android.sdk.api.session.room.notification.RoomPushRuleService
@@ -60,11 +61,22 @@ interface Room {
      */
     fun getRoomSummaryLive(): LiveData<Optional<RoomSummary>>
 
+    /**
+     * A live [LocalRoomSummary] associated with the room.
+     * You can observe this summary to get dynamic data from this room.
+     */
+    fun getLocalRoomSummaryLive(): LiveData<Optional<LocalRoomSummary>>
+
     /**
      * A current snapshot of [RoomSummary] associated with the room.
      */
     fun roomSummary(): RoomSummary?
 
+    /**
+     * A current snapshot of [LocalRoomSummary] associated with the room.
+     */
+    fun localRoomSummary(): LocalRoomSummary?
+
     /**
      * Use this room as a Space, if the type is correct.
      */
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 ad8106c9c10dd8a179d1e9d2aa86f02def0417a0..65383f1007b1874da82cc0c99e2d4eff3077834f 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
@@ -22,6 +22,7 @@ import org.matrix.android.sdk.api.session.events.model.Event
 import org.matrix.android.sdk.api.session.identity.model.SignInvitationResult
 import org.matrix.android.sdk.api.session.room.alias.RoomAliasDescription
 import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState
+import org.matrix.android.sdk.api.session.room.model.LocalRoomSummary
 import org.matrix.android.sdk.api.session.room.model.Membership
 import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary
 import org.matrix.android.sdk.api.session.room.model.RoomSummary
@@ -117,6 +118,12 @@ interface RoomService {
      */
     fun getRoomSummaryLive(roomId: String): LiveData<Optional<RoomSummary>>
 
+    /**
+     * A live [LocalRoomSummary] associated with the room with id [roomId].
+     * You can observe this summary to get dynamic data from this room, even if the room is not joined yet
+     */
+    fun getLocalRoomSummaryLive(roomId: String): LiveData<Optional<LocalRoomSummary>>
+
     /**
      * Get a snapshot list of room summaries.
      * @return the immutable list of [RoomSummary]
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomSummaryQueryParams.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomSummaryQueryParams.kt
index 60963ef25a4da2e1a530cbb16bd24c34699c3456..d651f06e2351b0206be9b5d362b1ac122236deb9 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomSummaryQueryParams.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomSummaryQueryParams.kt
@@ -20,8 +20,10 @@ import org.matrix.android.sdk.api.query.QueryStringValue
 import org.matrix.android.sdk.api.query.RoomCategoryFilter
 import org.matrix.android.sdk.api.query.RoomTagQueryFilter
 import org.matrix.android.sdk.api.query.SpaceFilter
+import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams.Builder
 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.room.model.localecho.RoomLocalEcho
 import org.matrix.android.sdk.api.session.space.SpaceSummaryQueryParams
 
 /**
@@ -52,6 +54,10 @@ fun spaceSummaryQueryParams(init: (RoomSummaryQueryParams.Builder.() -> Unit) =
  * [roomSummaryQueryParams] and [spaceSummaryQueryParams] can also be used to build an instance of this class.
  */
 data class RoomSummaryQueryParams(
+        /**
+         * Query for the roomId.
+         */
+        val roomId: QueryStringValue,
         /**
          * Query for the displayName of the room. The display name can be the value of the state event,
          * or a value returned by [org.matrix.android.sdk.api.RoomDisplayNameFallbackProvider].
@@ -94,6 +100,7 @@ data class RoomSummaryQueryParams(
      * [roomSummaryQueryParams] and [spaceSummaryQueryParams] can also be used to build an instance of [RoomSummaryQueryParams].
      */
     class Builder {
+        var roomId: QueryStringValue = QueryStringValue.NotContains(RoomLocalEcho.PREFIX)
         var displayName: QueryStringValue = QueryStringValue.NoCondition
         var canonicalAlias: QueryStringValue = QueryStringValue.NoCondition
         var memberships: List<Membership> = Membership.all()
@@ -104,6 +111,7 @@ data class RoomSummaryQueryParams(
         var spaceFilter: SpaceFilter = SpaceFilter.NoFilter
 
         fun build() = RoomSummaryQueryParams(
+                roomId = roomId,
                 displayName = displayName,
                 canonicalAlias = canonicalAlias,
                 memberships = memberships,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/LocalRoomCreationState.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/LocalRoomCreationState.kt
new file mode 100644
index 0000000000000000000000000000000000000000..4fc99225c496944ceab1e508d5c3e47da01bd2f8
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/LocalRoomCreationState.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2022 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.api.session.room.model
+
+enum class LocalRoomCreationState {
+    NOT_CREATED,
+    CREATING,
+    FAILURE,
+    CREATED
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/LocalRoomSummary.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/LocalRoomSummary.kt
new file mode 100644
index 0000000000000000000000000000000000000000..eced1dd5815c363498611767f8b6afda6ca15067
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/LocalRoomSummary.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2022 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.api.session.room.model
+
+import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams
+
+/**
+ * This class holds some data of a local room.
+ * It can be retrieved by [org.matrix.android.sdk.api.session.room.Room] and [org.matrix.android.sdk.api.session.room.RoomService]
+ */
+data class LocalRoomSummary(
+        /**
+         * The roomId of the room.
+         */
+        val roomId: String,
+        /**
+         * The room summary of the room.
+         */
+        val roomSummary: RoomSummary?,
+        /**
+         * The creation params attached to the room.
+         */
+        val createRoomParams: CreateRoomParams?,
+        /**
+         * The roomId of the created room (ie. created on the server), if any.
+         */
+        val replacementRoomId: String?,
+        /**
+         * The creation state of the room.
+         */
+        val creationState: LocalRoomCreationState,
+)
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 7d3109fb6e60018daac58a830c8898ca201d1819..2388bee0ee7a3b45f1f9173efc6cd552fb7cccf3 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
@@ -34,5 +34,4 @@ data class SpaceChildInfo(
         val canonicalAlias: String?,
         val aliases: List<String>?,
         val worldReadable: Boolean
-
 )
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/localecho/RoomLocalEcho.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/localecho/RoomLocalEcho.kt
index 7ef0d63924ddba419eeb183a28871108c5f2fc28..ec0e642ad348f5bbfadc243671004d042508ddb6 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/localecho/RoomLocalEcho.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/localecho/RoomLocalEcho.kt
@@ -20,7 +20,7 @@ import java.util.UUID
 
 object RoomLocalEcho {
 
-    private const val PREFIX = "!local."
+    const val PREFIX = "!local."
 
     /**
      * Tell whether the provider room id is a local id.
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
index ecc3eb5224890c1345e16b73cd2d7b529b9a28ac..d03f4c42cfb4e89cf21e404ebcdcb54e9d693b0c 100644
--- 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
@@ -16,13 +16,13 @@
 
 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
+import org.matrix.android.sdk.api.session.space.model.SpaceChildSummaryEvent
 
 data class SpaceHierarchyData(
         val rootSummary: RoomSummary,
         val children: List<SpaceChildInfo>,
-        val childrenState: List<Event>,
+        val childrenState: List<SpaceChildSummaryEvent>,
         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 c7a64050146be35ae42b3cb5c663c4a625948337..5d2a9412d1109d2be2dfee45ba32b99fb1a39c4d 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,10 +18,10 @@ 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.RoomSortOrder
 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.space.model.SpaceChildSummaryEvent
 import org.matrix.android.sdk.api.session.space.peeking.SpacePeekResult
 
 typealias SpaceSummaryQueryParams = RoomSummaryQueryParams
@@ -75,12 +75,12 @@ interface SpaceService {
             suggestedOnly: Boolean? = null,
             limit: Int? = null,
             from: String? = null,
-            knownStateList: List<Event>? = null
+            knownStateList: List<SpaceChildSummaryEvent>? = null
     ): SpaceHierarchyData
 
     /**
      * Get a live list of space summaries. This list is refreshed as soon as the data changes.
-     * @return the [LiveData] of List[SpaceSummary]
+     * @return the [LiveData] of List[RoomSummary]
      */
     fun getSpaceSummariesLive(
             queryParams: SpaceSummaryQueryParams,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/model/SpaceChildSummaryEvent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/model/SpaceChildSummaryEvent.kt
new file mode 100644
index 0000000000000000000000000000000000000000..13aa0336e581fd28978f0c2b69c6d83ce9de8e46
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/model/SpaceChildSummaryEvent.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2022 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.api.session.space.model
+
+import com.squareup.moshi.Json
+import com.squareup.moshi.JsonClass
+import org.matrix.android.sdk.api.session.events.model.Content
+
+@JsonClass(generateAdapter = true)
+data class SpaceChildSummaryEvent(
+        @Json(name = "type") val type: String? = null,
+        @Json(name = "state_key") val stateKey: String? = null,
+        @Json(name = "content") val content: Content? = null,
+        @Json(name = "sender") val senderId: String? = null,
+        @Json(name = "origin_server_ts") val originServerTs: Long? = null,
+)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/user/UserService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/user/UserService.kt
index 0c5465e12a8ae51a8a018c89121ab8e4dbf68232..7075023798e6089c59f986ef8b4018d86b8af810 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/user/UserService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/user/UserService.kt
@@ -29,7 +29,7 @@ interface UserService {
     /**
      * Get a user from a userId.
      * @param userId the userId to look for.
-     * @return a user with userId or null
+     * @return a user with userId or null if the User is not known yet by the SDK. See [resolveUser] to ensure that a User is retrieved.
      */
     fun getUser(userId: String): User?
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt
index 8dd7c309c6b432b37daf1838638a9f2f3a94fb40..322f297ac30ae61625f2df17ededcf13e89ac431 100755
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt
@@ -79,6 +79,7 @@ import org.matrix.android.sdk.internal.crypto.actions.SetDeviceVerificationActio
 import org.matrix.android.sdk.internal.crypto.algorithms.IMXEncrypting
 import org.matrix.android.sdk.internal.crypto.algorithms.IMXGroupEncryption
 import org.matrix.android.sdk.internal.crypto.algorithms.megolm.MXMegolmEncryptionFactory
+import org.matrix.android.sdk.internal.crypto.algorithms.megolm.UnRequestedForwardManager
 import org.matrix.android.sdk.internal.crypto.algorithms.olm.MXOlmEncryptionFactory
 import org.matrix.android.sdk.internal.crypto.crosssigning.DefaultCrossSigningService
 import org.matrix.android.sdk.internal.crypto.keysbackup.DefaultKeysBackupService
@@ -183,7 +184,8 @@ internal class DefaultCryptoService @Inject constructor(
         private val cryptoCoroutineScope: CoroutineScope,
         private val eventDecryptor: EventDecryptor,
         private val verificationMessageProcessor: VerificationMessageProcessor,
-        private val liveEventManager: Lazy<StreamEventsManager>
+        private val liveEventManager: Lazy<StreamEventsManager>,
+        private val unrequestedForwardManager: UnRequestedForwardManager,
 ) : CryptoService {
 
     private val isStarting = AtomicBoolean(false)
@@ -399,6 +401,7 @@ internal class DefaultCryptoService @Inject constructor(
         cryptoCoroutineScope.coroutineContext.cancelChildren(CancellationException("Closing crypto module"))
         incomingKeyRequestManager.close()
         outgoingKeyRequestManager.close()
+        unrequestedForwardManager.close()
         olmDevice.release()
         cryptoStore.close()
     }
@@ -485,6 +488,14 @@ internal class DefaultCryptoService @Inject constructor(
                     // just for safety but should not throw
                     Timber.tag(loggerTag.value).w("failed to process incoming room key requests")
                 }
+
+                unrequestedForwardManager.postSyncProcessParkedKeysIfNeeded(clock.epochMillis()) { events ->
+                    cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
+                        events.forEach {
+                            onRoomKeyEvent(it, true)
+                        }
+                    }
+                }
             }
         }
     }
@@ -845,9 +856,9 @@ internal class DefaultCryptoService @Inject constructor(
      *
      * @param event the key event.
      */
-    private fun onRoomKeyEvent(event: Event) {
-        val roomKeyContent = event.getClearContent().toModel<RoomKeyContent>() ?: return
-        Timber.tag(loggerTag.value).i("onRoomKeyEvent() from: ${event.senderId} type<${event.getClearType()}> , sessionId<${roomKeyContent.sessionId}>")
+    private fun onRoomKeyEvent(event: Event, acceptUnrequested: Boolean = false) {
+        val roomKeyContent = event.getDecryptedContent().toModel<RoomKeyContent>() ?: return
+        Timber.tag(loggerTag.value).i("onRoomKeyEvent(forceAccept:$acceptUnrequested) from: ${event.senderId} type<${event.getClearType()}> , sessionId<${roomKeyContent.sessionId}>")
         if (roomKeyContent.roomId.isNullOrEmpty() || roomKeyContent.algorithm.isNullOrEmpty()) {
             Timber.tag(loggerTag.value).e("onRoomKeyEvent() : missing fields")
             return
@@ -857,7 +868,7 @@ internal class DefaultCryptoService @Inject constructor(
             Timber.tag(loggerTag.value).e("GOSSIP onRoomKeyEvent() : Unable to handle keys for ${roomKeyContent.algorithm}")
             return
         }
-        alg.onRoomKeyEvent(event, keysBackupService)
+        alg.onRoomKeyEvent(event, keysBackupService, acceptUnrequested)
     }
 
     private fun onKeyWithHeldReceived(event: Event) {
@@ -950,6 +961,15 @@ internal class DefaultCryptoService @Inject constructor(
      * @param event the membership event causing the change
      */
     private fun onRoomMembershipEvent(roomId: String, event: Event) {
+        // because the encryption event can be after the join/invite in the same batch
+        event.stateKey?.let { _ ->
+            val roomMember: RoomMemberContent? = event.content.toModel()
+            val membership = roomMember?.membership
+            if (membership == Membership.INVITE) {
+                unrequestedForwardManager.onInviteReceived(roomId, event.senderId.orEmpty(), clock.epochMillis())
+            }
+        }
+
         roomEncryptorsStore.get(roomId) ?: /* No encrypting in this room */ return
 
         event.stateKey?.let { userId ->
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/InboundGroupSessionStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/InboundGroupSessionStore.kt
index 39dfb72149047bd09ca80ca7182d3512f6064612..6d197a09edd461d5c72c4972235ad42a2c84ae6c 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/InboundGroupSessionStore.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/InboundGroupSessionStore.kt
@@ -91,6 +91,21 @@ internal class InboundGroupSessionStore @Inject constructor(
         internalStoreGroupSession(new, sessionId, senderKey)
     }
 
+    @Synchronized
+    fun updateToSafe(old: InboundGroupSessionHolder, sessionId: String, senderKey: String) {
+        Timber.tag(loggerTag.value).v("## updateToSafe for session ${old.wrapper.roomId}-${old.wrapper.senderKey}")
+
+        store.storeInboundGroupSessions(
+                listOf(
+                        old.wrapper.copy(
+                                sessionData = old.wrapper.sessionData.copy(trusted = true)
+                        )
+                )
+        )
+        // will release it :/
+        sessionCache.remove(CacheKey(sessionId, senderKey))
+    }
+
     @Synchronized
     fun storeInBoundGroupSession(holder: InboundGroupSessionHolder, sessionId: String, senderKey: String) {
         internalStoreGroupSession(holder, sessionId, senderKey)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MXOlmDevice.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MXOlmDevice.kt
index 96ccba51dc10094967157ff4267884a9d35975dc..48b46523048c2d24a00c6c8f67f8767108c4534b 100755
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MXOlmDevice.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MXOlmDevice.kt
@@ -19,6 +19,7 @@ package org.matrix.android.sdk.internal.crypto
 import androidx.annotation.VisibleForTesting
 import kotlinx.coroutines.sync.Mutex
 import kotlinx.coroutines.sync.withLock
+import org.matrix.android.sdk.api.extensions.orFalse
 import org.matrix.android.sdk.api.extensions.tryOrNull
 import org.matrix.android.sdk.api.logger.LoggerTag
 import org.matrix.android.sdk.api.session.crypto.MXCryptoError
@@ -612,7 +613,8 @@ internal class MXOlmDevice @Inject constructor(
             forwardingCurve25519KeyChain: List<String>,
             keysClaimed: Map<String, String>,
             exportFormat: Boolean,
-            sharedHistory: Boolean
+            sharedHistory: Boolean,
+            trusted: Boolean
     ): AddSessionResult {
         val candidateSession = tryOrNull("Failed to create inbound session in room $roomId") {
             if (exportFormat) {
@@ -620,6 +622,8 @@ internal class MXOlmDevice @Inject constructor(
             } else {
                 OlmInboundGroupSession(sessionKey)
             }
+        } ?: return AddSessionResult.NotImported.also {
+            Timber.tag(loggerTag.value).d("## addInboundGroupSession() : failed to import key candidate $senderKey/$sessionId")
         }
 
         val existingSessionHolder = tryOrNull { getInboundGroupSession(sessionId, senderKey, roomId) }
@@ -631,31 +635,49 @@ internal class MXOlmDevice @Inject constructor(
                 val existingFirstKnown = tryOrNull { existingSession.session.firstKnownIndex } ?: return AddSessionResult.NotImported.also {
                     // This is quite unexpected, could throw if native was released?
                     Timber.tag(loggerTag.value).e("## addInboundGroupSession() null firstKnownIndex on existing session")
-                    candidateSession?.releaseSession()
+                    candidateSession.releaseSession()
                     // Probably should discard it?
                 }
-                val newKnownFirstIndex = tryOrNull("Failed to get candidate first known index") { candidateSession?.firstKnownIndex }
-                // If our existing session is better we keep it
-                if (newKnownFirstIndex != null && existingFirstKnown <= newKnownFirstIndex) {
-                    Timber.tag(loggerTag.value).d("## addInboundGroupSession() : ignore session our is better $senderKey/$sessionId")
-                    candidateSession?.releaseSession()
-                    return AddSessionResult.NotImportedHigherIndex(newKnownFirstIndex.toInt())
+                val newKnownFirstIndex = tryOrNull("Failed to get candidate first known index") { candidateSession.firstKnownIndex }
+                        ?: return AddSessionResult.NotImported.also {
+                            candidateSession.releaseSession()
+                            Timber.tag(loggerTag.value).d("## addInboundGroupSession() : Failed to get new session index")
+                        }
+
+                val keyConnects = existingSession.session.connects(candidateSession)
+                if (!keyConnects) {
+                    Timber.tag(loggerTag.value)
+                            .e("## addInboundGroupSession() Unconnected key")
+                    if (!trusted) {
+                        // Ignore the not connecting unsafe, keep existing
+                        Timber.tag(loggerTag.value)
+                                .e("## addInboundGroupSession() Received unsafe unconnected key")
+                        return AddSessionResult.NotImported
+                    }
+                    // else if the new one is safe and does not connect with existing, import the new one
+                } else {
+                    // If our existing session is better we keep it
+                    if (existingFirstKnown <= newKnownFirstIndex) {
+                        val shouldUpdateTrust = trusted && (existingSession.sessionData.trusted != true)
+                        Timber.tag(loggerTag.value).d("## addInboundGroupSession() : updateTrust for $sessionId")
+                        if (shouldUpdateTrust) {
+                            // the existing as a better index but the new one is trusted so update trust
+                            inboundGroupSessionStore.updateToSafe(existingSessionHolder, sessionId, senderKey)
+                        }
+                        Timber.tag(loggerTag.value).d("## addInboundGroupSession() : ignore session our is better $senderKey/$sessionId")
+                        candidateSession.releaseSession()
+                        return AddSessionResult.NotImportedHigherIndex(newKnownFirstIndex.toInt())
+                    }
                 }
             } catch (failure: Throwable) {
                 Timber.tag(loggerTag.value).e("## addInboundGroupSession() Failed to add inbound: ${failure.localizedMessage}")
-                candidateSession?.releaseSession()
+                candidateSession.releaseSession()
                 return AddSessionResult.NotImported
             }
         }
 
         Timber.tag(loggerTag.value).d("## addInboundGroupSession() : Candidate session should be added $senderKey/$sessionId")
 
-        // sanity check on the new session
-        if (null == candidateSession) {
-            Timber.tag(loggerTag.value).e("## addInboundGroupSession : invalid session <null>")
-            return AddSessionResult.NotImported
-        }
-
         try {
             if (candidateSession.sessionIdentifier() != sessionId) {
                 Timber.tag(loggerTag.value).e("## addInboundGroupSession : ERROR: Mismatched group session ID from senderKey: $senderKey")
@@ -674,6 +696,7 @@ internal class MXOlmDevice @Inject constructor(
                 keysClaimed = keysClaimed,
                 forwardingCurve25519KeyChain = forwardingCurve25519KeyChain,
                 sharedHistory = sharedHistory,
+                trusted = trusted
         )
 
         val wrapper = MXInboundMegolmSessionWrapper(
@@ -689,6 +712,16 @@ internal class MXOlmDevice @Inject constructor(
         return AddSessionResult.Imported(candidateSession.firstKnownIndex.toInt())
     }
 
+    fun OlmInboundGroupSession.connects(other: OlmInboundGroupSession): Boolean {
+        return try {
+            val lowestCommonIndex = this.firstKnownIndex.coerceAtLeast(other.firstKnownIndex)
+            this.export(lowestCommonIndex) == other.export(lowestCommonIndex)
+        } catch (failure: Throwable) {
+            // native error? key disposed?
+            false
+        }
+    }
+
     /**
      * Import an inbound group sessions to the session store.
      *
@@ -821,7 +854,8 @@ internal class MXOlmDevice @Inject constructor(
                 payload,
                 wrapper.sessionData.keysClaimed,
                 senderKey,
-                wrapper.sessionData.forwardingCurve25519KeyChain
+                wrapper.sessionData.forwardingCurve25519KeyChain,
+                isSafe = sessionHolder.wrapper.sessionData.trusted.orFalse()
         )
     }
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/SecretShareManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/SecretShareManager.kt
index a79e1a89012a7e3d4b02aa18825111cfb6047f27..5691f24d17de077990a14bb9d4e8cb88f7edf964 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/SecretShareManager.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/SecretShareManager.kt
@@ -267,13 +267,24 @@ internal class SecretShareManager @Inject constructor(
             Timber.tag(loggerTag.value).e("onSecretSend() :Received unencrypted secret send event")
             return
         }
+        // no need to download keys, after a verification we already forced download
+        val sendingDevice = toDevice.getSenderKey()?.let { cryptoStore.deviceWithIdentityKey(it) }
+        if (sendingDevice == null) {
+            Timber.tag(loggerTag.value).e("onSecretSend() : Ignore secret from unknown  device ${toDevice.getSenderKey()}")
+            return
+        }
 
         // Was that sent by us?
-        if (toDevice.senderId != credentials.userId) {
+        if (sendingDevice.userId != credentials.userId) {
             Timber.tag(loggerTag.value).e("onSecretSend() : Ignore secret from other user ${toDevice.senderId}")
             return
         }
 
+        if (!sendingDevice.isVerified) {
+            Timber.tag(loggerTag.value).e("onSecretSend() : Ignore secret from untrusted device ${toDevice.getSenderKey()}")
+            return
+        }
+
         val secretContent = toDevice.getClearContent().toModel<SecretSendEventContent>() ?: return
 
         val existingRequest = verifMutex.withLock {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/IMXDecrypting.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/IMXDecrypting.kt
index 6847a463690c369774fd09d6efb7bf0c52bdabc4..e2ddd5d19f9598e6d9b31c1f1ec32c9144e41473 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/IMXDecrypting.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/IMXDecrypting.kt
@@ -42,5 +42,5 @@ internal interface IMXDecrypting {
      * @param event the key event.
      * @param defaultKeysBackupService the keys backup service
      */
-    fun onRoomKeyEvent(event: Event, defaultKeysBackupService: DefaultKeysBackupService) {}
+    fun onRoomKeyEvent(event: Event, defaultKeysBackupService: DefaultKeysBackupService, forceAccept: Boolean = false) {}
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt
index 410b74e19f1d6b155875241bff96d50e79e584c7..5354cbff3bc52753981d3ed25d4fe22ba2987bc2 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt
@@ -17,7 +17,8 @@
 package org.matrix.android.sdk.internal.crypto.algorithms.megolm
 
 import dagger.Lazy
-import org.matrix.android.sdk.api.MatrixConfiguration
+import org.matrix.android.sdk.api.crypto.MXCryptoConfig
+import org.matrix.android.sdk.api.extensions.orFalse
 import org.matrix.android.sdk.api.logger.LoggerTag
 import org.matrix.android.sdk.api.session.crypto.MXCryptoError
 import org.matrix.android.sdk.api.session.crypto.NewSessionListener
@@ -34,16 +35,20 @@ import org.matrix.android.sdk.internal.crypto.algorithms.IMXDecrypting
 import org.matrix.android.sdk.internal.crypto.keysbackup.DefaultKeysBackupService
 import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
 import org.matrix.android.sdk.internal.session.StreamEventsManager
+import org.matrix.android.sdk.internal.util.time.Clock
 import timber.log.Timber
 
 private val loggerTag = LoggerTag("MXMegolmDecryption", LoggerTag.CRYPTO)
 
 internal class MXMegolmDecryption(
         private val olmDevice: MXOlmDevice,
+        private val myUserId: String,
         private val outgoingKeyRequestManager: OutgoingKeyRequestManager,
         private val cryptoStore: IMXCryptoStore,
-        private val matrixConfiguration: MatrixConfiguration,
-        private val liveEventManager: Lazy<StreamEventsManager>
+        private val liveEventManager: Lazy<StreamEventsManager>,
+        private val unrequestedForwardManager: UnRequestedForwardManager,
+        private val cryptoConfig: MXCryptoConfig,
+        private val clock: Clock,
 ) : IMXDecrypting {
 
     var newSessionListener: NewSessionListener? = null
@@ -94,7 +99,8 @@ internal class MXMegolmDecryption(
                                         senderCurve25519Key = olmDecryptionResult.senderKey,
                                         claimedEd25519Key = olmDecryptionResult.keysClaimed?.get("ed25519"),
                                         forwardingCurve25519KeyChain = olmDecryptionResult.forwardingCurve25519KeyChain
-                                                .orEmpty()
+                                                .orEmpty(),
+                                        isSafe = olmDecryptionResult.isSafe.orFalse()
                                 ).also {
                                     liveEventManager.get().dispatchLiveEventDecrypted(event, it)
                                 }
@@ -182,12 +188,21 @@ internal class MXMegolmDecryption(
      * @param event the key event.
      * @param defaultKeysBackupService the keys backup service
      */
-    override fun onRoomKeyEvent(event: Event, defaultKeysBackupService: DefaultKeysBackupService) {
-        Timber.tag(loggerTag.value).v("onRoomKeyEvent()")
+    override fun onRoomKeyEvent(event: Event, defaultKeysBackupService: DefaultKeysBackupService, forceAccept: Boolean) {
+        Timber.tag(loggerTag.value).v("onRoomKeyEvent(${event.getSenderKey()})")
         var exportFormat = false
-        val roomKeyContent = event.getClearContent().toModel<RoomKeyContent>() ?: return
+        val roomKeyContent = event.getDecryptedContent()?.toModel<RoomKeyContent>() ?: return
+
+        val eventSenderKey: String = event.getSenderKey() ?: return Unit.also {
+            Timber.tag(loggerTag.value).e("onRoom Key/Forward Event() : event is missing sender_key field")
+        }
+
+        // this device might not been downloaded now?
+        val fromDevice = cryptoStore.deviceWithIdentityKey(eventSenderKey)
+
+        lateinit var sessionInitiatorSenderKey: String
+        val trusted: Boolean
 
-        var senderKey: String? = event.getSenderKey()
         var keysClaimed: MutableMap<String, String> = HashMap()
         val forwardingCurve25519KeyChain: MutableList<String> = ArrayList()
 
@@ -195,32 +210,25 @@ internal class MXMegolmDecryption(
             Timber.tag(loggerTag.value).e("onRoomKeyEvent() :  Key event is missing fields")
             return
         }
-        if (event.getClearType() == EventType.FORWARDED_ROOM_KEY) {
+        if (event.getDecryptedType() == EventType.FORWARDED_ROOM_KEY) {
             if (!cryptoStore.isKeyGossipingEnabled()) {
                 Timber.tag(loggerTag.value)
                         .i("onRoomKeyEvent(), ignore forward adding as per crypto config : ${roomKeyContent.roomId}|${roomKeyContent.sessionId}")
                 return
             }
             Timber.tag(loggerTag.value).i("onRoomKeyEvent(), forward adding key : ${roomKeyContent.roomId}|${roomKeyContent.sessionId}")
-            val forwardedRoomKeyContent = event.getClearContent().toModel<ForwardedRoomKeyContent>()
+            val forwardedRoomKeyContent = event.getDecryptedContent()?.toModel<ForwardedRoomKeyContent>()
                     ?: return
 
             forwardedRoomKeyContent.forwardingCurve25519KeyChain?.let {
                 forwardingCurve25519KeyChain.addAll(it)
             }
 
-            if (senderKey == null) {
-                Timber.tag(loggerTag.value).e("onRoomKeyEvent() : event is missing sender_key field")
-                return
-            }
-
-            forwardingCurve25519KeyChain.add(senderKey)
+            forwardingCurve25519KeyChain.add(eventSenderKey)
 
             exportFormat = true
-            senderKey = forwardedRoomKeyContent.senderKey
-            if (null == senderKey) {
+            sessionInitiatorSenderKey = forwardedRoomKeyContent.senderKey ?: return Unit.also {
                 Timber.tag(loggerTag.value).e("onRoomKeyEvent() : forwarded_room_key event is missing sender_key field")
-                return
             }
 
             if (null == forwardedRoomKeyContent.senderClaimedEd25519Key) {
@@ -229,13 +237,51 @@ internal class MXMegolmDecryption(
             }
 
             keysClaimed["ed25519"] = forwardedRoomKeyContent.senderClaimedEd25519Key
-        } else {
-            Timber.tag(loggerTag.value).i("onRoomKeyEvent(), Adding key : ${roomKeyContent.roomId}|${roomKeyContent.sessionId}")
-            if (null == senderKey) {
-                Timber.tag(loggerTag.value).e("## onRoomKeyEvent() : key event has no sender key (not encrypted?)")
+
+            // checking if was requested once.
+            // should we check if the request is sort of active?
+            val wasNotRequested = cryptoStore.getOutgoingRoomKeyRequest(
+                    roomId = forwardedRoomKeyContent.roomId.orEmpty(),
+                    sessionId = forwardedRoomKeyContent.sessionId.orEmpty(),
+                    algorithm = forwardedRoomKeyContent.algorithm.orEmpty(),
+                    senderKey = forwardedRoomKeyContent.senderKey.orEmpty(),
+            ).isEmpty()
+
+            trusted = false
+
+            if (!forceAccept && wasNotRequested) {
+//                val senderId = cryptoStore.deviceWithIdentityKey(event.getSenderKey().orEmpty())?.userId.orEmpty()
+                unrequestedForwardManager.onUnRequestedKeyForward(roomKeyContent.roomId, event, clock.epochMillis())
+                // Ignore unsolicited
+                Timber.tag(loggerTag.value).w("Ignoring forwarded_room_key_event for ${roomKeyContent.sessionId} that was not requested")
+                return
+            }
+
+            // Check who sent the request, as we requested we have the device keys (no need to download)
+            val sessionThatIsSharing = cryptoStore.deviceWithIdentityKey(eventSenderKey)
+            if (sessionThatIsSharing == null) {
+                Timber.tag(loggerTag.value).w("Ignoring forwarded_room_key from unknown device with identity $eventSenderKey")
                 return
             }
+            val isOwnDevice = myUserId == sessionThatIsSharing.userId
+            val isDeviceVerified = sessionThatIsSharing.isVerified
+            val isFromSessionInitiator = sessionThatIsSharing.identityKey() == sessionInitiatorSenderKey
+
+            val isLegitForward = (isOwnDevice && isDeviceVerified) ||
+                    (!cryptoConfig.limitRoomKeyRequestsToMyDevices && isFromSessionInitiator)
 
+            val shouldAcceptForward = forceAccept || isLegitForward
+
+            if (!shouldAcceptForward) {
+                Timber.tag(loggerTag.value)
+                        .w("Ignoring forwarded_room_key device:$eventSenderKey, ownVerified:{$isOwnDevice&&$isDeviceVerified}, fromInitiator:$isFromSessionInitiator")
+                return
+            }
+        } else {
+            // It's a m.room_key so safe
+            trusted = true
+            sessionInitiatorSenderKey = eventSenderKey
+            Timber.tag(loggerTag.value).i("onRoomKeyEvent(), Adding key : ${roomKeyContent.roomId}|${roomKeyContent.sessionId}")
             // inherit the claimed ed25519 key from the setup message
             keysClaimed = event.getKeysClaimed().toMutableMap()
         }
@@ -245,12 +291,15 @@ internal class MXMegolmDecryption(
                 sessionId = roomKeyContent.sessionId,
                 sessionKey = roomKeyContent.sessionKey,
                 roomId = roomKeyContent.roomId,
-                senderKey = senderKey,
+                senderKey = sessionInitiatorSenderKey,
                 forwardingCurve25519KeyChain = forwardingCurve25519KeyChain,
                 keysClaimed = keysClaimed,
                 exportFormat = exportFormat,
-                sharedHistory = roomKeyContent.getSharedKey()
-        )
+                sharedHistory = roomKeyContent.getSharedKey(),
+                trusted = trusted
+        ).also {
+            Timber.tag(loggerTag.value).v(".. onRoomKeyEvent addInboundGroupSession ${roomKeyContent.sessionId} result: $it")
+        }
 
         when (addSessionResult) {
             is MXOlmDevice.AddSessionResult.Imported -> addSessionResult.ratchetIndex
@@ -258,35 +307,28 @@ internal class MXMegolmDecryption(
             else -> null
         }?.let { index ->
             if (event.getClearType() == EventType.FORWARDED_ROOM_KEY) {
-                val fromDevice = (event.content?.get("sender_key") as? String)?.let { senderDeviceIdentityKey ->
-                    cryptoStore.getUserDeviceList(event.senderId ?: "")
-                            ?.firstOrNull {
-                                it.identityKey() == senderDeviceIdentityKey
-                            }
-                }?.deviceId
-
                 outgoingKeyRequestManager.onRoomKeyForwarded(
                         sessionId = roomKeyContent.sessionId,
                         algorithm = roomKeyContent.algorithm ?: "",
                         roomId = roomKeyContent.roomId,
-                        senderKey = senderKey,
+                        senderKey = sessionInitiatorSenderKey,
                         fromIndex = index,
-                        fromDevice = fromDevice,
+                        fromDevice = fromDevice?.deviceId,
                         event = event
                 )
 
                 cryptoStore.saveIncomingForwardKeyAuditTrail(
                         roomId = roomKeyContent.roomId,
                         sessionId = roomKeyContent.sessionId,
-                        senderKey = senderKey,
+                        senderKey = sessionInitiatorSenderKey,
                         algorithm = roomKeyContent.algorithm ?: "",
-                        userId = event.senderId ?: "",
-                        deviceId = fromDevice ?: "",
+                        userId = event.senderId.orEmpty(),
+                        deviceId = fromDevice?.deviceId.orEmpty(),
                         chainIndex = index.toLong()
                 )
 
                 // The index is used to decide if we cancel sent request or if we wait for a better key
-                outgoingKeyRequestManager.postCancelRequestForSessionIfNeeded(roomKeyContent.sessionId, roomKeyContent.roomId, senderKey, index)
+                outgoingKeyRequestManager.postCancelRequestForSessionIfNeeded(roomKeyContent.sessionId, roomKeyContent.roomId, sessionInitiatorSenderKey, index)
             }
         }
 
@@ -295,7 +337,7 @@ internal class MXMegolmDecryption(
                     .d("onRoomKeyEvent(${event.getClearType()}) : Added megolm session ${roomKeyContent.sessionId} in ${roomKeyContent.roomId}")
             defaultKeysBackupService.maybeBackupKeys()
 
-            onNewSession(roomKeyContent.roomId, senderKey, roomKeyContent.sessionId)
+            onNewSession(roomKeyContent.roomId, sessionInitiatorSenderKey, roomKeyContent.sessionId)
         }
     }
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryptionFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryptionFactory.kt
index 38edbb7430a2bd932dd86c855a237cc4482ad6d1..99f8bc69e05edafd4a5b3338e709eb6c367f7352 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryptionFactory.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryptionFactory.kt
@@ -17,28 +17,36 @@
 package org.matrix.android.sdk.internal.crypto.algorithms.megolm
 
 import dagger.Lazy
-import org.matrix.android.sdk.api.MatrixConfiguration
+import org.matrix.android.sdk.api.crypto.MXCryptoConfig
 import org.matrix.android.sdk.internal.crypto.MXOlmDevice
 import org.matrix.android.sdk.internal.crypto.OutgoingKeyRequestManager
 import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
+import org.matrix.android.sdk.internal.di.UserId
 import org.matrix.android.sdk.internal.session.StreamEventsManager
+import org.matrix.android.sdk.internal.util.time.Clock
 import javax.inject.Inject
 
 internal class MXMegolmDecryptionFactory @Inject constructor(
         private val olmDevice: MXOlmDevice,
+        @UserId private val myUserId: String,
         private val outgoingKeyRequestManager: OutgoingKeyRequestManager,
         private val cryptoStore: IMXCryptoStore,
-        private val matrixConfiguration: MatrixConfiguration,
-        private val eventsManager: Lazy<StreamEventsManager>
+        private val eventsManager: Lazy<StreamEventsManager>,
+        private val unrequestedForwardManager: UnRequestedForwardManager,
+        private val mxCryptoConfig: MXCryptoConfig,
+        private val clock: Clock,
 ) {
 
     fun create(): MXMegolmDecryption {
         return MXMegolmDecryption(
-                olmDevice,
-                outgoingKeyRequestManager,
-                cryptoStore,
-                matrixConfiguration,
-                eventsManager
+                olmDevice = olmDevice,
+                myUserId = myUserId,
+                outgoingKeyRequestManager = outgoingKeyRequestManager,
+                cryptoStore = cryptoStore,
+                liveEventManager = eventsManager,
+                unrequestedForwardManager = unrequestedForwardManager,
+                cryptoConfig = mxCryptoConfig,
+                clock = clock,
         )
     }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt
index 771b5f9a626e6eaf17322a2cb87638f9ac34f850..fca6fab66c27bbdc0d402dabf5561b4e62d3514c 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt
@@ -162,7 +162,8 @@ internal class MXMegolmEncryption(
                 forwardingCurve25519KeyChain = emptyList(),
                 keysClaimed = keysClaimedMap,
                 exportFormat = false,
-                sharedHistory = sharedHistory
+                sharedHistory = sharedHistory,
+                trusted = true
         )
 
         defaultKeysBackupService.maybeBackupKeys()
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/UnRequestedForwardManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/UnRequestedForwardManager.kt
new file mode 100644
index 0000000000000000000000000000000000000000..42629b617e6dd8e1aeb59f239e1e7a03d374c4a4
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/UnRequestedForwardManager.kt
@@ -0,0 +1,150 @@
+/*
+ * Copyright 2022 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.algorithms.megolm
+
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.SupervisorJob
+import kotlinx.coroutines.asCoroutineDispatcher
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.launch
+import org.matrix.android.sdk.api.extensions.tryOrNull
+import org.matrix.android.sdk.api.session.events.model.Event
+import org.matrix.android.sdk.internal.crypto.DeviceListManager
+import org.matrix.android.sdk.internal.session.SessionScope
+import org.matrix.android.sdk.internal.task.SemaphoreCoroutineSequencer
+import timber.log.Timber
+import java.util.concurrent.Executors
+import javax.inject.Inject
+import kotlin.math.abs
+
+private val INVITE_VALIDITY_TIME_WINDOW_MILLIS = 10 * 60_000
+
+@SessionScope
+internal class UnRequestedForwardManager @Inject constructor(
+        private val deviceListManager: DeviceListManager,
+) {
+
+    private val dispatcher = Executors.newSingleThreadExecutor().asCoroutineDispatcher()
+    private val scope = CoroutineScope(SupervisorJob() + dispatcher)
+    private val sequencer = SemaphoreCoroutineSequencer()
+
+    // For now only in memory storage. Maybe we should persist? in case of gappy sync and long catchups?
+    private val forwardedKeysPerRoom = mutableMapOf<String, MutableMap<String, MutableList<ForwardInfo>>>()
+
+    data class InviteInfo(
+            val roomId: String,
+            val fromMxId: String,
+            val timestamp: Long
+    )
+
+    data class ForwardInfo(
+            val event: Event,
+            val timestamp: Long
+    )
+
+    // roomId, local timestamp of invite
+    private val recentInvites = mutableListOf<InviteInfo>()
+
+    fun close() {
+        try {
+            scope.cancel("User Terminate")
+        } catch (failure: Throwable) {
+            Timber.w(failure, "Failed to shutDown UnrequestedForwardManager")
+        }
+    }
+
+    fun onInviteReceived(roomId: String, fromUserId: String, localTimeStamp: Long) {
+        Timber.w("Invite received in room:$roomId from:$fromUserId at $localTimeStamp")
+        scope.launch {
+            sequencer.post {
+                if (!recentInvites.any { it.roomId == roomId && it.fromMxId == fromUserId }) {
+                    recentInvites.add(
+                            InviteInfo(
+                                    roomId,
+                                    fromUserId,
+                                    localTimeStamp
+                            )
+                    )
+                }
+            }
+        }
+    }
+
+    fun onUnRequestedKeyForward(roomId: String, event: Event, localTimeStamp: Long) {
+        Timber.w("Received unrequested forward in room:$roomId from:${event.senderId} at $localTimeStamp")
+        scope.launch {
+            sequencer.post {
+                val claimSenderId = event.senderId.orEmpty()
+                val senderKey = event.getSenderKey()
+                // we might want to download keys, as this user might not be known yet, cache is ok
+                val ownerMxId =
+                        tryOrNull {
+                            deviceListManager.downloadKeys(listOf(claimSenderId), false)
+                                    .map[claimSenderId]
+                                    ?.values
+                                    ?.firstOrNull { it.identityKey() == senderKey }
+                                    ?.userId
+                        }
+                // Not sure what to do if the device has been deleted? I can't proove the mxid
+                if (ownerMxId == null || claimSenderId != ownerMxId) {
+                    Timber.w("Mismatch senderId between event and olm owner")
+                    return@post
+                }
+
+                forwardedKeysPerRoom
+                        .getOrPut(roomId) { mutableMapOf() }
+                        .getOrPut(ownerMxId) { mutableListOf() }
+                        .add(ForwardInfo(event, localTimeStamp))
+            }
+        }
+    }
+
+    fun postSyncProcessParkedKeysIfNeeded(currentTimestamp: Long, handleForwards: suspend (List<Event>) -> Unit) {
+        scope.launch {
+            sequencer.post {
+                // Prune outdated invites
+                recentInvites.removeAll { currentTimestamp - it.timestamp > INVITE_VALIDITY_TIME_WINDOW_MILLIS }
+                val cleanUpEvents = mutableListOf<Pair<String, String>>()
+                forwardedKeysPerRoom.forEach { (roomId, senderIdToForwardMap) ->
+                    senderIdToForwardMap.forEach { (senderId, eventList) ->
+                        // is there a matching invite in a valid timewindow?
+                        val matchingInvite = recentInvites.firstOrNull { it.fromMxId == senderId && it.roomId == roomId }
+                        if (matchingInvite != null) {
+                            Timber.v("match  for room:$roomId from sender:$senderId -> count =${eventList.size}")
+
+                            eventList.filter {
+                                abs(matchingInvite.timestamp - it.timestamp) <= INVITE_VALIDITY_TIME_WINDOW_MILLIS
+                            }.map {
+                                it.event
+                            }.takeIf { it.isNotEmpty() }?.let {
+                                Timber.w("Re-processing forwarded_room_key_event that was not requested after invite")
+                                scope.launch {
+                                    handleForwards.invoke(it)
+                                }
+                            }
+                            cleanUpEvents.add(roomId to senderId)
+                        }
+                    }
+                }
+
+                cleanUpEvents.forEach { roomIdToSenderPair ->
+                    forwardedKeysPerRoom[roomIdToSenderPair.first]?.get(roomIdToSenderPair.second)?.clear()
+                }
+            }
+        }
+    }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/DefaultKeysBackupService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/DefaultKeysBackupService.kt
index 23ddf8315fc4fad4334e78cde57df96213d957da..21d57f5be43f9e0e423c65093f229b0779552013 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/DefaultKeysBackupService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/DefaultKeysBackupService.kt
@@ -703,14 +703,7 @@ internal class DefaultKeysBackupService @Inject constructor(
                         }
                 val recoveryKey = computeRecoveryKey(secret.fromBase64())
                 if (isValidRecoveryKeyForKeysBackupVersion(recoveryKey, keysBackupVersion)) {
-                    awaitCallback<Unit> {
-                        trustKeysBackupVersion(keysBackupVersion, true, it)
-                    }
                     // we don't want to start immediately downloading all as it can take very long
-
-//                    val importResult = awaitCallback<ImportRoomKeysResult> {
-//                        restoreKeysWithRecoveryKey(keysBackupVersion, recoveryKey, null, null, null, it)
-//                    }
                     withContext(coroutineDispatchers.crypto) {
                         cryptoStore.saveBackupRecoveryKey(recoveryKey, keysBackupVersion.version)
                     }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/InboundGroupSessionData.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/InboundGroupSessionData.kt
index 2ce36aa209ba7f90d922652b431f7890187f429d..15e8ba835b5cabdfd93a239585cee17b6eb2087f 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/InboundGroupSessionData.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/InboundGroupSessionData.kt
@@ -38,9 +38,6 @@ data class InboundGroupSessionData(
         @Json(name = "forwarding_curve25519_key_chain")
         var forwardingCurve25519KeyChain: List<String>? = emptyList(),
 
-        /** Not yet used, will be in backup v2
-        val untrusted?: Boolean = false */
-
         /**
          * Flag that indicates whether or not the current inboundSession will be shared to
          * invited users to decrypt past messages.
@@ -48,4 +45,10 @@ data class InboundGroupSessionData(
         @Json(name = "shared_history")
         val sharedHistory: Boolean = false,
 
+        /**
+         * Flag indicating that this key is trusted.
+         */
+        @Json(name = "trusted")
+        val trusted: Boolean? = null,
+
         )
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/MXInboundMegolmSessionWrapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/MXInboundMegolmSessionWrapper.kt
index 2772b3483594e1e58205386a6e6526ac27d34bbd..2c6a0a967af20de122a85fb97e6d08fecf9dae47 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/MXInboundMegolmSessionWrapper.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/MXInboundMegolmSessionWrapper.kt
@@ -86,6 +86,7 @@ data class MXInboundMegolmSessionWrapper(
                     keysClaimed = megolmSessionData.senderClaimedKeys,
                     forwardingCurve25519KeyChain = megolmSessionData.forwardingCurve25519KeyChain,
                     sharedHistory = megolmSessionData.sharedHistory,
+                    trusted = false
             )
 
             return MXInboundMegolmSessionWrapper(
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStoreMigration.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStoreMigration.kt
index c36d572da63024fe55622e1a283ca9c029d76b0b..426d50a54fc997c1398e1d25874974b23d2c8d3d 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStoreMigration.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStoreMigration.kt
@@ -34,6 +34,7 @@ import org.matrix.android.sdk.internal.crypto.store.db.migration.MigrateCryptoTo
 import org.matrix.android.sdk.internal.crypto.store.db.migration.MigrateCryptoTo015
 import org.matrix.android.sdk.internal.crypto.store.db.migration.MigrateCryptoTo016
 import org.matrix.android.sdk.internal.crypto.store.db.migration.MigrateCryptoTo017
+import org.matrix.android.sdk.internal.crypto.store.db.migration.MigrateCryptoTo018
 import org.matrix.android.sdk.internal.util.database.MatrixRealmMigration
 import org.matrix.android.sdk.internal.util.time.Clock
 import javax.inject.Inject
@@ -48,7 +49,7 @@ internal class RealmCryptoStoreMigration @Inject constructor(
         private val clock: Clock,
 ) : MatrixRealmMigration(
         dbName = "Crypto",
-        schemaVersion = 17L,
+        schemaVersion = 18L,
 ) {
     /**
      * Forces all RealmCryptoStoreMigration instances to be equal.
@@ -75,5 +76,6 @@ internal class RealmCryptoStoreMigration @Inject constructor(
         if (oldVersion < 15) MigrateCryptoTo015(realm).perform()
         if (oldVersion < 16) MigrateCryptoTo016(realm).perform()
         if (oldVersion < 17) MigrateCryptoTo017(realm).perform()
+        if (oldVersion < 18) MigrateCryptoTo018(realm).perform()
     }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo018.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo018.kt
new file mode 100644
index 0000000000000000000000000000000000000000..3bedf58ca2796c416e8388c9e1da459077cb4ad4
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo018.kt
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2022 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.internal.crypto.store.db.migration
+
+import io.realm.DynamicRealm
+import org.matrix.android.sdk.internal.crypto.model.InboundGroupSessionData
+import org.matrix.android.sdk.internal.crypto.store.db.model.OlmInboundGroupSessionEntityFields
+import org.matrix.android.sdk.internal.di.MoshiProvider
+import org.matrix.android.sdk.internal.util.database.RealmMigrator
+import timber.log.Timber
+
+/**
+ * This migration is adding support for trusted flags on megolm sessions.
+ * We can't really assert the trust of existing keys, so for the sake of simplicity we are going to
+ * mark existing keys as safe.
+ * This migration can take long depending on the account
+ */
+internal class MigrateCryptoTo018(realm: DynamicRealm) : RealmMigrator(realm, 18) {
+
+    private val moshiAdapter = MoshiProvider.providesMoshi().adapter(InboundGroupSessionData::class.java)
+
+    override fun doMigrate(realm: DynamicRealm) {
+        realm.schema.get("OlmInboundGroupSessionEntity")
+                ?.transform { dynamicObject ->
+                    try {
+                        dynamicObject.getString(OlmInboundGroupSessionEntityFields.INBOUND_GROUP_SESSION_DATA_JSON)?.let { oldData ->
+                            moshiAdapter.fromJson(oldData)?.let { dataToMigrate ->
+                                dataToMigrate.copy(trusted = true).let {
+                                    dynamicObject.setString(OlmInboundGroupSessionEntityFields.INBOUND_GROUP_SESSION_DATA_JSON, moshiAdapter.toJson(it))
+                                }
+                            }
+                        }
+                    } catch (failure: Throwable) {
+                        Timber.e(failure, "Failed to migrate megolm session")
+                    }
+                }
+    }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/EncryptEventTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/EncryptEventTask.kt
index a4b4cd0761bca993a558e8c857644f64e4be6a8e..f93da745073ded51382476c3c1eaa04b89c41732 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/EncryptEventTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/EncryptEventTask.kt
@@ -82,7 +82,8 @@ internal class DefaultEncryptEventTask @Inject constructor(
                         ).toContent(),
                         forwardingCurve25519KeyChain = emptyList(),
                         senderCurve25519Key = result.eventContent["sender_key"] as? String,
-                        claimedEd25519Key = cryptoService.get().getMyDevice().fingerprint()
+                        claimedEd25519Key = cryptoService.get().getMyDevice().fingerprint(),
+                        isSafe = true
                 )
             } else {
                 null
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 0b1186386439e19dc6c72eb7db82014ab4a90ad5..2693ca474c21d8cf434b9c94f9ee876916a81a2b 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
@@ -53,6 +53,7 @@ import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo033
 import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo034
 import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo035
 import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo036
+import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo037
 import org.matrix.android.sdk.internal.util.Normalizer
 import org.matrix.android.sdk.internal.util.database.MatrixRealmMigration
 import javax.inject.Inject
@@ -61,7 +62,7 @@ internal class RealmSessionStoreMigration @Inject constructor(
         private val normalizer: Normalizer
 ) : MatrixRealmMigration(
         dbName = "Session",
-        schemaVersion = 36L,
+        schemaVersion = 37L,
 ) {
     /**
      * Forces all RealmSessionStoreMigration instances to be equal.
@@ -107,5 +108,6 @@ internal class RealmSessionStoreMigration @Inject constructor(
         if (oldVersion < 34) MigrateSessionTo034(realm).perform()
         if (oldVersion < 35) MigrateSessionTo035(realm).perform()
         if (oldVersion < 36) MigrateSessionTo036(realm).perform()
+        if (oldVersion < 37) MigrateSessionTo037(realm).perform()
     }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadSummaryHelper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadSummaryHelper.kt
index 0a6d4bf833285e5312630fdb373faa39e448b6a0..193710f9621fc0b6e562561054f4f8be5d6d2550 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadSummaryHelper.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadSummaryHelper.kt
@@ -228,7 +228,8 @@ private fun decryptIfNeeded(cryptoService: CryptoService?, eventEntity: EventEnt
                     payload = result.clearEvent,
                     senderKey = result.senderCurve25519Key,
                     keysClaimed = result.claimedEd25519Key?.let { k -> mapOf("ed25519" to k) },
-                    forwardingCurve25519KeyChain = result.forwardingCurve25519KeyChain
+                    forwardingCurve25519KeyChain = result.forwardingCurve25519KeyChain,
+                    isSafe = result.isSafe
             )
             // Save decryption result, to not decrypt every time we enter the thread list
             eventEntity.setDecryptionResult(result)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/LocalRoomSummaryMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/LocalRoomSummaryMapper.kt
new file mode 100644
index 0000000000000000000000000000000000000000..09cb5985f3ccb1d0c9cd183683ea64d7c34eb06c
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/LocalRoomSummaryMapper.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2022 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.database.mapper
+
+import org.matrix.android.sdk.api.session.room.model.LocalRoomSummary
+import org.matrix.android.sdk.internal.database.model.LocalRoomSummaryEntity
+import javax.inject.Inject
+
+internal class LocalRoomSummaryMapper @Inject constructor(
+        private val roomSummaryMapper: RoomSummaryMapper,
+) {
+
+    fun map(localRoomSummaryEntity: LocalRoomSummaryEntity): LocalRoomSummary {
+        return LocalRoomSummary(
+                roomId = localRoomSummaryEntity.roomId,
+                roomSummary = localRoomSummaryEntity.roomSummaryEntity?.let { roomSummaryMapper.map(it) },
+                createRoomParams = localRoomSummaryEntity.createRoomParams,
+                replacementRoomId = localRoomSummaryEntity.replacementRoomId,
+                creationState = localRoomSummaryEntity.creationState
+        )
+    }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo037.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo037.kt
new file mode 100644
index 0000000000000000000000000000000000000000..cdb0b6c6827014b075bcc608d48af8e0f99fc9ca
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo037.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2022 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.database.migration
+
+import io.realm.DynamicRealm
+import org.matrix.android.sdk.api.session.room.model.LocalRoomCreationState
+import org.matrix.android.sdk.internal.database.model.LocalRoomSummaryEntityFields
+import org.matrix.android.sdk.internal.util.database.RealmMigrator
+
+internal class MigrateSessionTo037(realm: DynamicRealm) : RealmMigrator(realm, 37) {
+
+    override fun doMigrate(realm: DynamicRealm) {
+        realm.schema.get("LocalRoomSummaryEntity")
+                ?.addField(LocalRoomSummaryEntityFields.REPLACEMENT_ROOM_ID, String::class.java)
+                ?.addField(LocalRoomSummaryEntityFields.STATE_STR, String::class.java)
+                ?.transform { obj ->
+                    obj.set(LocalRoomSummaryEntityFields.STATE_STR, LocalRoomCreationState.NOT_CREATED.name)
+                }
+    }
+}
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 8b5a211fbaba4d5a477baff5aec881a48f047765..ee5c3d90c1282929e52867f697012bceb99dd076 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
@@ -87,7 +87,8 @@ internal open class EventEntity(
                 payload = result.clearEvent,
                 senderKey = result.senderCurve25519Key,
                 keysClaimed = result.claimedEd25519Key?.let { mapOf("ed25519" to it) },
-                forwardingCurve25519KeyChain = result.forwardingCurve25519KeyChain
+                forwardingCurve25519KeyChain = result.forwardingCurve25519KeyChain,
+                isSafe = result.isSafe
         )
         val adapter = MoshiProvider.providesMoshi().adapter(OlmDecryptionResult::class.java)
         decryptionResultJson = adapter.toJson(decryptionResult)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/LocalRoomSummaryEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/LocalRoomSummaryEntity.kt
index fd8331e986505293dbcc1ab5c8e63d84e1731914..a978e3719d351a9827d4f09d32449f0b0bae8d99 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/LocalRoomSummaryEntity.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/LocalRoomSummaryEntity.kt
@@ -18,15 +18,24 @@ package org.matrix.android.sdk.internal.database.model
 
 import io.realm.RealmObject
 import io.realm.annotations.PrimaryKey
+import org.matrix.android.sdk.api.session.room.model.LocalRoomCreationState
 import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams
 import org.matrix.android.sdk.api.session.room.model.create.toJSONString
 
 internal open class LocalRoomSummaryEntity(
         @PrimaryKey var roomId: String = "",
         var roomSummaryEntity: RoomSummaryEntity? = null,
-        private var createRoomParamsStr: String? = null
+        var replacementRoomId: String? = null,
 ) : RealmObject() {
 
+    private var stateStr: String = LocalRoomCreationState.NOT_CREATED.name
+    var creationState: LocalRoomCreationState
+        get() = LocalRoomCreationState.valueOf(stateStr)
+        set(value) {
+            stateStr = value.name
+        }
+
+    private var createRoomParamsStr: String? = null
     var createRoomParams: CreateRoomParams?
         get() {
             return CreateRoomParams.fromJson(createRoomParamsStr)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/LocalRoomSummaryEntityQueries.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/LocalRoomSummaryEntityQueries.kt
index 527350bedc261cdc4b352c5c0f97c8803a24584b..44730eb75d74ac77b71c07767b51c313e0284a3f 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/LocalRoomSummaryEntityQueries.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/LocalRoomSummaryEntityQueries.kt
@@ -22,10 +22,6 @@ import io.realm.kotlin.where
 import org.matrix.android.sdk.internal.database.model.LocalRoomSummaryEntity
 import org.matrix.android.sdk.internal.database.model.LocalRoomSummaryEntityFields
 
-internal fun LocalRoomSummaryEntity.Companion.where(realm: Realm, roomId: String? = null): RealmQuery<LocalRoomSummaryEntity> {
-    val query = realm.where<LocalRoomSummaryEntity>()
-    if (roomId != null) {
-        query.equalTo(LocalRoomSummaryEntityFields.ROOM_ID, roomId)
-    }
-    return query
+internal fun LocalRoomSummaryEntity.Companion.where(realm: Realm, roomId: String): RealmQuery<LocalRoomSummaryEntity> {
+    return realm.where<LocalRoomSummaryEntity>().equalTo(LocalRoomSummaryEntityFields.ROOM_ID, roomId)
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/ReadReceiptEntityQueries.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/ReadReceiptEntityQueries.kt
index b180c06e2c2283739e9345e29be6566ecf6e43bf..170814d3f280ede63662e52710a8d9371bec7a70 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/ReadReceiptEntityQueries.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/ReadReceiptEntityQueries.kt
@@ -33,6 +33,11 @@ internal fun ReadReceiptEntity.Companion.whereUserId(realm: Realm, userId: Strin
             .equalTo(ReadReceiptEntityFields.USER_ID, userId)
 }
 
+internal fun ReadReceiptEntity.Companion.whereRoomId(realm: Realm, roomId: String): RealmQuery<ReadReceiptEntity> {
+    return realm.where<ReadReceiptEntity>()
+            .equalTo(ReadReceiptEntityFields.ROOM_ID, roomId)
+}
+
 internal fun ReadReceiptEntity.Companion.createUnmanaged(roomId: String, eventId: String, userId: String, originServerTs: Double): ReadReceiptEntity {
     return ReadReceiptEntity().apply {
         this.primaryKey = "${roomId}_$userId"
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/query/QueryStringValueProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/query/QueryStringValueProcessor.kt
index b2ab9879df934ed0047d684b9241d925d23a8331..a93ff42c9e290b1f54d840b5b7a6bc8bb5276248 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/query/QueryStringValueProcessor.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/query/QueryStringValueProcessor.kt
@@ -38,6 +38,7 @@ internal class QueryStringValueProcessor @Inject constructor(
             is ContentQueryStringValue -> when (queryStringValue) {
                 is QueryStringValue.Equals -> equalTo(field, queryStringValue.toRealmValue(), queryStringValue.case.toRealmCase())
                 is QueryStringValue.Contains -> contains(field, queryStringValue.toRealmValue(), queryStringValue.case.toRealmCase())
+                is QueryStringValue.NotContains -> not().process(field, QueryStringValue.Contains(queryStringValue.string, queryStringValue.case))
             }
         }
     }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoom.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoom.kt
index abea2d34cd210834c680d81dc819b5346ecaf17a..262c111b736a48b446dfbb9a95d55005e2abe1ad 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoom.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoom.kt
@@ -25,6 +25,7 @@ import org.matrix.android.sdk.api.session.room.call.RoomCallService
 import org.matrix.android.sdk.api.session.room.crypto.RoomCryptoService
 import org.matrix.android.sdk.api.session.room.location.LocationSharingService
 import org.matrix.android.sdk.api.session.room.members.MembershipService
+import org.matrix.android.sdk.api.session.room.model.LocalRoomSummary
 import org.matrix.android.sdk.api.session.room.model.RoomSummary
 import org.matrix.android.sdk.api.session.room.model.RoomType
 import org.matrix.android.sdk.api.session.room.model.relation.RelationService
@@ -82,6 +83,14 @@ internal class DefaultRoom(
         return roomSummaryDataSource.getRoomSummary(roomId)
     }
 
+    override fun getLocalRoomSummaryLive(): LiveData<Optional<LocalRoomSummary>> {
+        return roomSummaryDataSource.getLocalRoomSummaryLive(roomId)
+    }
+
+    override fun localRoomSummary(): LocalRoomSummary? {
+        return roomSummaryDataSource.getLocalRoomSummary(roomId)
+    }
+
     override fun asSpace(): Space? {
         if (roomSummary()?.roomType != RoomType.SPACE) return null
         return DefaultSpace(this, roomSummaryDataSource, viaParameterFinder)
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 989bcaee4474ad2b8bf33f944ed95d33e0c4f2f8..6d72b8ef2062aeaca73787a3eedebe28019616cf 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
@@ -29,10 +29,12 @@ import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams
 import org.matrix.android.sdk.api.session.room.UpdatableLivePageResult
 import org.matrix.android.sdk.api.session.room.alias.RoomAliasDescription
 import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState
+import org.matrix.android.sdk.api.session.room.model.LocalRoomSummary
 import org.matrix.android.sdk.api.session.room.model.Membership
 import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary
 import org.matrix.android.sdk.api.session.room.model.RoomSummary
 import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams
+import org.matrix.android.sdk.api.session.room.model.localecho.RoomLocalEcho
 import org.matrix.android.sdk.api.session.room.peeking.PeekResult
 import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams
 import org.matrix.android.sdk.api.session.room.summary.RoomAggregateNotificationCount
@@ -106,6 +108,10 @@ internal class DefaultRoomService @Inject constructor(
         return roomSummaryDataSource.getRoomSummaryLive(roomId)
     }
 
+    override fun getLocalRoomSummaryLive(roomId: String): LiveData<Optional<LocalRoomSummary>> {
+        return roomSummaryDataSource.getLocalRoomSummaryLive(roomId)
+    }
+
     override fun getRoomSummaries(
             queryParams: RoomSummaryQueryParams,
             sortOrder: RoomSortOrder
@@ -173,7 +179,10 @@ internal class DefaultRoomService @Inject constructor(
     }
 
     override suspend fun onRoomDisplayed(roomId: String) {
-        updateBreadcrumbsTask.execute(UpdateBreadcrumbsTask.Params(roomId))
+        // Do not add local rooms to the recent rooms list as they should not be known by the server
+        if (!RoomLocalEcho.isLocalEchoId(roomId)) {
+            updateBreadcrumbsTask.execute(UpdateBreadcrumbsTask.Params(roomId))
+        }
     }
 
     override suspend fun joinRoom(roomIdOrAlias: String, reason: String?, viaServers: List<String>) {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomFromLocalRoomTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomFromLocalRoomTask.kt
index 02538a5cc33153e8f1f3940811e03329d370a3c2..2245eb851380e26e9cd3c79a5e61d2a1d8869567 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomFromLocalRoomTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomFromLocalRoomTask.kt
@@ -17,38 +17,23 @@
 package org.matrix.android.sdk.internal.session.room.create
 
 import com.zhuinden.monarchy.Monarchy
-import io.realm.kotlin.where
 import kotlinx.coroutines.TimeoutCancellationException
-import org.matrix.android.sdk.api.extensions.orFalse
-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
 import org.matrix.android.sdk.api.session.room.failure.CreateRoomFailure
+import org.matrix.android.sdk.api.session.room.model.LocalRoomCreationState
+import org.matrix.android.sdk.api.session.room.model.RoomSummary
 import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams
-import org.matrix.android.sdk.api.session.room.model.tombstone.RoomTombstoneContent
-import org.matrix.android.sdk.api.session.room.send.SendState
 import org.matrix.android.sdk.internal.database.awaitNotEmptyResult
-import org.matrix.android.sdk.internal.database.mapper.toEntity
-import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntity
 import org.matrix.android.sdk.internal.database.model.EventEntity
 import org.matrix.android.sdk.internal.database.model.EventEntityFields
-import org.matrix.android.sdk.internal.database.model.EventInsertType
 import org.matrix.android.sdk.internal.database.model.LocalRoomSummaryEntity
-import org.matrix.android.sdk.internal.database.model.LocalRoomSummaryEntityFields
 import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity
 import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields
-import org.matrix.android.sdk.internal.database.query.copyToRealmOrIgnore
-import org.matrix.android.sdk.internal.database.query.getOrCreate
+import org.matrix.android.sdk.internal.database.query.where
 import org.matrix.android.sdk.internal.database.query.whereRoomId
 import org.matrix.android.sdk.internal.di.SessionDatabase
-import org.matrix.android.sdk.internal.di.UserId
-import org.matrix.android.sdk.internal.session.room.state.StateEventDataSource
+import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryDataSource
 import org.matrix.android.sdk.internal.task.Task
-import org.matrix.android.sdk.internal.util.awaitTransaction
-import org.matrix.android.sdk.internal.util.time.Clock
-import java.util.UUID
 import java.util.concurrent.TimeUnit
 import javax.inject.Inject
 
@@ -56,94 +41,100 @@ import javax.inject.Inject
  * Create a room on the server from a local room.
  * The configuration of the local room will be use to configure the new room.
  * The potential local room members will also be invited to this new room.
- *
- * A local tombstone event will be created to indicate that the local room has been replacing by the new one.
  */
 internal interface CreateRoomFromLocalRoomTask : Task<CreateRoomFromLocalRoomTask.Params, String> {
     data class Params(val localRoomId: String)
 }
 
 internal class DefaultCreateRoomFromLocalRoomTask @Inject constructor(
-        @UserId private val userId: String,
         @SessionDatabase private val monarchy: Monarchy,
         private val createRoomTask: CreateRoomTask,
-        private val stateEventDataSource: StateEventDataSource,
-        private val clock: Clock,
+        private val roomSummaryDataSource: RoomSummaryDataSource,
 ) : CreateRoomFromLocalRoomTask {
 
     private val realmConfiguration
         get() = monarchy.realmConfiguration
 
     override suspend fun execute(params: CreateRoomFromLocalRoomTask.Params): String {
-        val replacementRoomId = stateEventDataSource.getStateEvent(params.localRoomId, EventType.STATE_ROOM_TOMBSTONE, QueryStringValue.IsEmpty)
-                ?.content.toModel<RoomTombstoneContent>()
-                ?.replacementRoomId
+        val localRoomSummary = roomSummaryDataSource.getLocalRoomSummary(params.localRoomId)
+                ?: error("## CreateRoomFromLocalRoomTask - Cannot retrieve LocalRoomSummary with roomId ${params.localRoomId}")
 
-        if (replacementRoomId != null) {
-            return replacementRoomId
+        // If a room has already been created for the given local room, return the existing roomId
+        if (localRoomSummary.replacementRoomId != null) {
+            return localRoomSummary.replacementRoomId
         }
 
-        var createRoomParams: CreateRoomParams? = null
-        var isEncrypted = false
-        monarchy.doWithRealm { realm ->
-            realm.where<LocalRoomSummaryEntity>()
-                    .equalTo(LocalRoomSummaryEntityFields.ROOM_ID, params.localRoomId)
-                    .findFirst()
-                    ?.let {
-                        createRoomParams = it.createRoomParams
-                        isEncrypted = it.roomSummaryEntity?.isEncrypted.orFalse()
-                    }
+        if (localRoomSummary.createRoomParams != null && localRoomSummary.roomSummary != null) {
+            return createRoom(params.localRoomId, localRoomSummary.roomSummary, localRoomSummary.createRoomParams)
+        } else {
+            error("## CreateRoomFromLocalRoomTask - Invalid LocalRoomSummary: $localRoomSummary")
         }
-        val roomId = createRoomTask.execute(createRoomParams!!)
+    }
 
+    /**
+     * Create a room on the server for the given local room.
+     *
+     * @param localRoomId the local room identifier.
+     * @param localRoomSummary the RoomSummary of the local room.
+     * @param createRoomParams the CreateRoomParams object which was used to configure the local room.
+     *
+     * @return the identifier of the created room.
+     */
+    private suspend fun createRoom(localRoomId: String, localRoomSummary: RoomSummary, createRoomParams: CreateRoomParams): String {
+        updateCreationState(localRoomId, LocalRoomCreationState.CREATING)
+        val replacementRoomId = runCatching {
+            createRoomTask.execute(createRoomParams)
+        }.fold(
+                { it },
+                {
+                    updateCreationState(localRoomId, LocalRoomCreationState.FAILURE)
+                    throw it
+                }
+        )
+        updateReplacementRoomId(localRoomId, replacementRoomId)
+        waitForRoomEvents(replacementRoomId, localRoomSummary)
+        updateCreationState(localRoomId, LocalRoomCreationState.CREATED)
+        return replacementRoomId
+    }
+
+    /**
+     * Wait for all the room events before triggering the created state.
+     *
+     * @param replacementRoomId the identifier of the created room
+     * @param localRoomSummary the RoomSummary of the local room.
+     */
+    private suspend fun waitForRoomEvents(replacementRoomId: String, localRoomSummary: RoomSummary) {
         try {
-            // Wait for all the room events before triggering the replacement room
             awaitNotEmptyResult(realmConfiguration, TimeUnit.MINUTES.toMillis(1L)) { realm ->
                 realm.where(RoomSummaryEntity::class.java)
-                        .equalTo(RoomSummaryEntityFields.ROOM_ID, roomId)
-                        .equalTo(RoomSummaryEntityFields.INVITED_MEMBERS_COUNT, createRoomParams?.invitedUserIds?.size ?: 0)
+                        .equalTo(RoomSummaryEntityFields.ROOM_ID, replacementRoomId)
+                        .equalTo(RoomSummaryEntityFields.INVITED_MEMBERS_COUNT, localRoomSummary.invitedMembersCount)
             }
             awaitNotEmptyResult(realmConfiguration, TimeUnit.MINUTES.toMillis(1L)) { realm ->
-                EventEntity.whereRoomId(realm, roomId)
+                EventEntity.whereRoomId(realm, replacementRoomId)
                         .equalTo(EventEntityFields.TYPE, EventType.STATE_ROOM_HISTORY_VISIBILITY)
             }
-            if (isEncrypted) {
+            if (localRoomSummary.isEncrypted) {
                 awaitNotEmptyResult(realmConfiguration, TimeUnit.MINUTES.toMillis(1L)) { realm ->
-                    EventEntity.whereRoomId(realm, roomId)
+                    EventEntity.whereRoomId(realm, replacementRoomId)
                             .equalTo(EventEntityFields.TYPE, EventType.STATE_ROOM_ENCRYPTION)
                 }
             }
         } catch (exception: TimeoutCancellationException) {
-            throw CreateRoomFailure.CreatedWithTimeout(roomId)
+            updateCreationState(localRoomSummary.roomId, LocalRoomCreationState.FAILURE)
+            throw CreateRoomFailure.CreatedWithTimeout(replacementRoomId)
         }
+    }
 
-        createTombstoneEvent(params, roomId)
-        return roomId
+    private fun updateCreationState(roomId: String, creationState: LocalRoomCreationState) {
+        monarchy.runTransactionSync { realm ->
+            LocalRoomSummaryEntity.where(realm, roomId).findFirst()?.creationState = creationState
+        }
     }
 
-    /**
-     * Create a Tombstone event to indicate that the local room has been replaced by a new one.
-     */
-    private suspend fun createTombstoneEvent(params: CreateRoomFromLocalRoomTask.Params, roomId: String) {
-        val now = clock.epochMillis()
-        val event = Event(
-                type = EventType.STATE_ROOM_TOMBSTONE,
-                senderId = userId,
-                originServerTs = now,
-                stateKey = "",
-                eventId = UUID.randomUUID().toString(),
-                content = RoomTombstoneContent(
-                        replacementRoomId = roomId
-                ).toContent()
-        )
-        monarchy.awaitTransaction { realm ->
-            val eventEntity = event.toEntity(params.localRoomId, SendState.SYNCED, now).copyToRealmOrIgnore(realm, EventInsertType.INCREMENTAL_SYNC)
-            if (event.stateKey != null && event.type != null && event.eventId != null) {
-                CurrentStateEventEntity.getOrCreate(realm, params.localRoomId, event.stateKey, event.type).apply {
-                    eventId = event.eventId
-                    root = eventEntity
-                }
-            }
+    private fun updateReplacementRoomId(localRoomId: String, replacementRoomId: String) {
+        monarchy.runTransactionSync { realm ->
+            LocalRoomSummaryEntity.where(realm, localRoomId).findFirst()?.replacementRoomId = replacementRoomId
         }
     }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/delete/DeleteLocalRoomTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/delete/DeleteLocalRoomTask.kt
index 49951d2da05ec11e6a89ebc3ed93dcfa026d3131..a60c7e6a27c3ce1bc335e8b8da84e47648bc7a7a 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/delete/DeleteLocalRoomTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/delete/DeleteLocalRoomTask.kt
@@ -22,12 +22,15 @@ import org.matrix.android.sdk.internal.database.model.ChunkEntity
 import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntity
 import org.matrix.android.sdk.internal.database.model.EventEntity
 import org.matrix.android.sdk.internal.database.model.LocalRoomSummaryEntity
+import org.matrix.android.sdk.internal.database.model.ReadReceiptEntity
+import org.matrix.android.sdk.internal.database.model.ReadReceiptsSummaryEntity
 import org.matrix.android.sdk.internal.database.model.RoomEntity
 import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntity
 import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity
 import org.matrix.android.sdk.internal.database.model.TimelineEventEntity
 import org.matrix.android.sdk.internal.database.model.deleteOnCascade
 import org.matrix.android.sdk.internal.database.query.where
+import org.matrix.android.sdk.internal.database.query.whereInRoom
 import org.matrix.android.sdk.internal.database.query.whereRoomId
 import org.matrix.android.sdk.internal.di.SessionDatabase
 import org.matrix.android.sdk.internal.session.room.delete.DeleteLocalRoomTask.Params
@@ -50,6 +53,12 @@ internal class DefaultDeleteLocalRoomTask @Inject constructor(
         if (RoomLocalEcho.isLocalEchoId(roomId)) {
             monarchy.awaitTransaction { realm ->
                 Timber.i("## DeleteLocalRoomTask - delete local room id $roomId")
+                ReadReceiptsSummaryEntity.whereInRoom(realm, roomId = roomId).findAll()
+                        ?.also { Timber.i("## DeleteLocalRoomTask - ReadReceiptsSummaryEntity - delete ${it.size} entries") }
+                        ?.deleteAllFromRealm()
+                ReadReceiptEntity.whereRoomId(realm, roomId = roomId).findAll()
+                        ?.also { Timber.i("## DeleteLocalRoomTask - ReadReceiptEntity - delete ${it.size} entries") }
+                        ?.deleteAllFromRealm()
                 RoomMemberSummaryEntity.where(realm, roomId = roomId).findAll()
                         ?.also { Timber.i("## DeleteLocalRoomTask - RoomMemberSummaryEntity - delete ${it.size} entries") }
                         ?.deleteAllFromRealm()
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/threads/FetchThreadTimelineTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/threads/FetchThreadTimelineTask.kt
index bac810f424704bb3604d25d07dd044410c4198bc..edd74c2ce04a2013c0ec4b2685dc378cabdb7f79 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/threads/FetchThreadTimelineTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/threads/FetchThreadTimelineTask.kt
@@ -225,7 +225,8 @@ internal class DefaultFetchThreadTimelineTask @Inject constructor(
                     payload = result.clearEvent,
                     senderKey = result.senderCurve25519Key,
                     keysClaimed = result.claimedEd25519Key?.let { k -> mapOf("ed25519" to k) },
-                    forwardingCurve25519KeyChain = result.forwardingCurve25519KeyChain
+                    forwardingCurve25519KeyChain = result.forwardingCurve25519KeyChain,
+                    isSafe = result.isSafe
             )
         } catch (e: MXCryptoError) {
             if (e is MXCryptoError.Base) {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt
index 82fc94df7c8fcdb80714d5e684a70e47b54ebd63..5c4ed8012bf754e41ca4a3cb93499e8a1b3b6c5b 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt
@@ -34,6 +34,7 @@ import org.matrix.android.sdk.api.session.room.ResultBoundaries
 import org.matrix.android.sdk.api.session.room.RoomSortOrder
 import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams
 import org.matrix.android.sdk.api.session.room.UpdatableLivePageResult
+import org.matrix.android.sdk.api.session.room.model.LocalRoomSummary
 import org.matrix.android.sdk.api.session.room.model.Membership
 import org.matrix.android.sdk.api.session.room.model.RoomSummary
 import org.matrix.android.sdk.api.session.room.model.RoomType
@@ -43,7 +44,9 @@ import org.matrix.android.sdk.api.session.room.summary.RoomAggregateNotification
 import org.matrix.android.sdk.api.session.space.SpaceSummaryQueryParams
 import org.matrix.android.sdk.api.util.Optional
 import org.matrix.android.sdk.api.util.toOptional
+import org.matrix.android.sdk.internal.database.mapper.LocalRoomSummaryMapper
 import org.matrix.android.sdk.internal.database.mapper.RoomSummaryMapper
+import org.matrix.android.sdk.internal.database.model.LocalRoomSummaryEntity
 import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity
 import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields
 import org.matrix.android.sdk.internal.database.query.findByAlias
@@ -57,6 +60,7 @@ import javax.inject.Inject
 internal class RoomSummaryDataSource @Inject constructor(
         @SessionDatabase private val monarchy: Monarchy,
         private val roomSummaryMapper: RoomSummaryMapper,
+        private val localRoomSummaryMapper: LocalRoomSummaryMapper,
         private val queryStringValueProcessor: QueryStringValueProcessor,
 ) {
 
@@ -95,6 +99,25 @@ internal class RoomSummaryDataSource @Inject constructor(
         )
     }
 
+    fun getLocalRoomSummary(roomId: String): LocalRoomSummary? {
+        return monarchy
+                .fetchCopyMap({
+                    LocalRoomSummaryEntity.where(it, roomId).findFirst()
+                }, { entity, _ ->
+                    localRoomSummaryMapper.map(entity)
+                })
+    }
+
+    fun getLocalRoomSummaryLive(roomId: String): LiveData<Optional<LocalRoomSummary>> {
+        val liveData = monarchy.findAllMappedWithChanges(
+                { realm -> LocalRoomSummaryEntity.where(realm, roomId) },
+                { localRoomSummaryMapper.map(it) }
+        )
+        return Transformations.map(liveData) { results ->
+            results.firstOrNull().toOptional()
+        }
+    }
+
     fun getRoomSummariesLive(
             queryParams: RoomSummaryQueryParams,
             sortOrder: RoomSortOrder = RoomSortOrder.NONE
@@ -272,6 +295,7 @@ internal class RoomSummaryDataSource @Inject constructor(
         val query = with(queryStringValueProcessor) {
             RoomSummaryEntity.where(realm)
                     .process(RoomSummaryEntityFields.ROOM_ID, QueryStringValue.IsNotEmpty)
+                    .process(RoomSummaryEntityFields.ROOM_ID, queryParams.roomId)
                     .process(queryParams.displayName.toDisplayNameField(), queryParams.displayName)
                     .process(RoomSummaryEntityFields.CANONICAL_ALIAS, queryParams.canonicalAlias)
                     .process(RoomSummaryEntityFields.MEMBERSHIP_STR, queryParams.memberships)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/GetEventTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/GetEventTask.kt
index 7c662444e4bb10cbf28db56acfda64a8f524518b..e0751865ad5f60625ba3c687b2daaefb20c25158 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/GetEventTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/GetEventTask.kt
@@ -56,7 +56,8 @@ internal class DefaultGetEventTask @Inject constructor(
                                 payload = result.clearEvent,
                                 senderKey = result.senderCurve25519Key,
                                 keysClaimed = result.claimedEd25519Key?.let { mapOf("ed25519" to it) },
-                                forwardingCurve25519KeyChain = result.forwardingCurve25519KeyChain
+                                forwardingCurve25519KeyChain = result.forwardingCurve25519KeyChain,
+                                isSafe = result.isSafe
                         )
                     }
         }
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 d2f1b3202b049da22a5d84c6d8516c2418b26c52..cd13b03017f60feee229715fb5043b2319664ed3 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
@@ -21,7 +21,6 @@ import androidx.lifecycle.LiveData
 import kotlinx.coroutines.withContext
 import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
 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
@@ -45,6 +44,7 @@ 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
+import org.matrix.android.sdk.api.session.space.model.SpaceChildSummaryEvent
 import org.matrix.android.sdk.api.session.space.model.SpaceParentContent
 import org.matrix.android.sdk.api.session.space.peeking.SpacePeekResult
 import org.matrix.android.sdk.internal.di.UserId
@@ -128,7 +128,7 @@ internal class DefaultSpaceService @Inject constructor(
             suggestedOnly: Boolean?,
             limit: Int?,
             from: String?,
-            knownStateList: List<Event>?
+            knownStateList: List<SpaceChildSummaryEvent>?
     ): SpaceHierarchyData {
         val spacesResponse = getSpacesResponse(spaceId, suggestedOnly, limit, from)
         val spaceRootResponse = spacesResponse.getRoot(spaceId)
@@ -180,7 +180,7 @@ internal class DefaultSpaceService @Inject constructor(
     private fun List<SpaceChildSummaryResponse>?.mapSpaceChildren(
             spaceId: String,
             spaceRootResponse: SpaceChildSummaryResponse?,
-            knownStateList: List<Event>?,
+            knownStateList: List<SpaceChildSummaryEvent>?,
     ) = this?.filterIdIsNot(spaceId)
             ?.toSpaceChildInfoList(spaceId, spaceRootResponse, knownStateList)
             .orEmpty()
@@ -190,7 +190,7 @@ internal class DefaultSpaceService @Inject constructor(
     private fun List<SpaceChildSummaryResponse>.toSpaceChildInfoList(
             spaceId: String,
             rootRoomResponse: SpaceChildSummaryResponse?,
-            knownStateList: List<Event>?,
+            knownStateList: List<SpaceChildSummaryEvent>?,
     ) = flatMap { spaceChildSummary ->
         (rootRoomResponse?.childrenState ?: knownStateList)
                 ?.filter { it.isChildOf(spaceChildSummary) }
@@ -198,10 +198,14 @@ internal class DefaultSpaceService @Inject constructor(
                 .orEmpty()
     }
 
-    private fun Event.isChildOf(space: SpaceChildSummaryResponse) = stateKey == space.roomId && type == EventType.STATE_SPACE_CHILD
+    private fun SpaceChildSummaryEvent.isChildOf(space: SpaceChildSummaryResponse): Boolean {
+        return stateKey == space.roomId && type == EventType.STATE_SPACE_CHILD
+    }
 
-    private fun Event.toSpaceChildInfo(spaceId: String, summary: SpaceChildSummaryResponse) = content.toModel<SpaceChildContent>()?.let { content ->
-        createSpaceChildInfo(spaceId, summary, content)
+    private fun SpaceChildSummaryEvent.toSpaceChildInfo(spaceId: String, summary: SpaceChildSummaryResponse): SpaceChildInfo? {
+        return content.toModel<SpaceChildContent>()?.let { content ->
+            createSpaceChildInfo(spaceId, summary, content)
+        }
     }
 
     private fun createSpaceChildInfo(
@@ -255,7 +259,7 @@ internal class DefaultSpaceService @Inject constructor(
                     stateKey = QueryStringValue.IsEmpty
             )
             val powerLevelsContent = powerLevelsEvent?.content?.toModel<PowerLevelsContent>()
-                    ?: throw UnsupportedOperationException("Cannot add canonical child, missing powerlevel")
+                    ?: throw UnsupportedOperationException("Cannot add canonical child, missing power level")
             val powerLevelsHelper = PowerLevelsHelper(powerLevelsContent)
             if (!powerLevelsHelper.isUserAllowedToSend(userId, true, EventType.STATE_SPACE_CHILD)) {
                 throw UnsupportedOperationException("Cannot add canonical child, not enough power level")
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 e3f8977ac59bd5dd6ac6547310ee13215fee7b44..0419c5acf168d2b03b768e3a61965faf0ead0119 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,7 +18,7 @@ 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
+import org.matrix.android.sdk.api.session.space.model.SpaceChildSummaryEvent
 
 /**
  * The fields are the same as those returned by /publicRooms (see spec), with the addition of:
@@ -36,10 +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.
+        /**
+         * The m.space.child events of the room. For each event, only the following fields are included:
+         * type, state_key, content, sender, and of origin_server_ts.
          */
-        @Json(name = "children_state") val childrenState: List<Event>? = null,
+        @Json(name = "children_state") val childrenState: List<SpaceChildSummaryEvent>? = null,
 
         /**
          * Aliases of the room. May be empty.
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/CryptoSyncHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/CryptoSyncHandler.kt
index b6142b3a7aef7192deb59982d3f1f67fa90d5e88..b2fe12ebc3fd8dceecd198f497dd49f4c9e093ee 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/CryptoSyncHandler.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/CryptoSyncHandler.kt
@@ -16,6 +16,7 @@
 
 package org.matrix.android.sdk.internal.session.sync.handler
 
+import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_OLM
 import org.matrix.android.sdk.api.logger.LoggerTag
 import org.matrix.android.sdk.api.session.crypto.MXCryptoError
 import org.matrix.android.sdk.api.session.crypto.model.MXEventDecryptionResult
@@ -42,17 +43,41 @@ internal class CryptoSyncHandler @Inject constructor(
 
     suspend fun handleToDevice(toDevice: ToDeviceSyncResponse, progressReporter: ProgressReporter? = null) {
         val total = toDevice.events?.size ?: 0
-        toDevice.events?.forEachIndexed { index, event ->
-            progressReporter?.reportProgress(index * 100F / total)
-            // Decrypt event if necessary
-            Timber.tag(loggerTag.value).i("To device event from ${event.senderId} of type:${event.type}")
-            decryptToDeviceEvent(event, null)
-            if (event.getClearType() == EventType.MESSAGE &&
-                    event.getClearContent()?.toModel<MessageContent>()?.msgType == "m.bad.encrypted") {
-                Timber.tag(loggerTag.value).e("handleToDeviceEvent() : Warning: Unable to decrypt to-device event : ${event.content}")
-            } else {
-                verificationService.onToDeviceEvent(event)
-                cryptoService.onToDeviceEvent(event)
+        toDevice.events
+                ?.filter { isSupportedToDevice(it) }
+                ?.forEachIndexed { index, event ->
+                    progressReporter?.reportProgress(index * 100F / total)
+                    // Decrypt event if necessary
+                    Timber.tag(loggerTag.value).i("To device event from ${event.senderId} of type:${event.type}")
+                    decryptToDeviceEvent(event, null)
+                    if (event.getClearType() == EventType.MESSAGE &&
+                            event.getClearContent()?.toModel<MessageContent>()?.msgType == "m.bad.encrypted") {
+                        Timber.tag(loggerTag.value).e("handleToDeviceEvent() : Warning: Unable to decrypt to-device event : ${event.content}")
+                    } else {
+                        verificationService.onToDeviceEvent(event)
+                        cryptoService.onToDeviceEvent(event)
+                    }
+                }
+    }
+
+    private val unsupportedPlainToDeviceEventTypes = listOf(
+            EventType.ROOM_KEY,
+            EventType.FORWARDED_ROOM_KEY,
+            EventType.SEND_SECRET
+    )
+
+    private fun isSupportedToDevice(event: Event): Boolean {
+        val algorithm = event.content?.get("algorithm") as? String
+        val type = event.type.orEmpty()
+        return if (event.isEncrypted()) {
+            algorithm == MXCRYPTO_ALGORITHM_OLM
+        } else {
+            // some clear events are not allowed
+            type !in unsupportedPlainToDeviceEventTypes
+        }.also {
+            if (!it) {
+                Timber.tag(loggerTag.value)
+                        .w("Ignoring unsupported to device event ${event.type} alg:${algorithm}")
             }
         }
     }
@@ -91,7 +116,8 @@ internal class CryptoSyncHandler @Inject constructor(
                         payload = result.clearEvent,
                         senderKey = result.senderCurve25519Key,
                         keysClaimed = result.claimedEd25519Key?.let { mapOf("ed25519" to it) },
-                        forwardingCurve25519KeyChain = result.forwardingCurve25519KeyChain
+                        forwardingCurve25519KeyChain = result.forwardingCurve25519KeyChain,
+                        isSafe = result.isSafe
                 )
                 return true
             } else {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt
index bc91ca205d8aa18c541fd89ab2055246068b4f11..a2f2251b70e032c6f768cced51d275335a5932d1 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt
@@ -40,6 +40,7 @@ import org.matrix.android.sdk.api.session.sync.model.RoomSync
 import org.matrix.android.sdk.api.session.sync.model.RoomsSyncResponse
 import org.matrix.android.sdk.api.settings.LightweightSettingsStorage
 import org.matrix.android.sdk.internal.crypto.DefaultCryptoService
+import org.matrix.android.sdk.internal.crypto.algorithms.megolm.UnRequestedForwardManager
 import org.matrix.android.sdk.internal.database.helper.addIfNecessary
 import org.matrix.android.sdk.internal.database.helper.addTimelineEvent
 import org.matrix.android.sdk.internal.database.helper.createOrUpdate
@@ -99,6 +100,7 @@ internal class RoomSyncHandler @Inject constructor(
         private val timelineInput: TimelineInput,
         private val liveEventService: Lazy<StreamEventsManager>,
         private val clock: Clock,
+        private val unRequestedForwardManager: UnRequestedForwardManager,
 ) {
 
     sealed class HandlingStrategy {
@@ -322,6 +324,7 @@ internal class RoomSyncHandler @Inject constructor(
         }
         roomChangeMembershipStateDataSource.setMembershipFromSync(roomId, Membership.INVITE)
         roomSummaryUpdater.update(realm, roomId, Membership.INVITE, updateMembers = true, inviterId = inviterEvent?.senderId, aggregator = aggregator)
+        unRequestedForwardManager.onInviteReceived(roomId, inviterEvent?.senderId.orEmpty(), clock.epochMillis())
         return roomEntity
     }
 
@@ -551,7 +554,8 @@ internal class RoomSyncHandler @Inject constructor(
                     payload = result.clearEvent,
                     senderKey = result.senderCurve25519Key,
                     keysClaimed = result.claimedEd25519Key?.let { k -> mapOf("ed25519" to k) },
-                    forwardingCurve25519KeyChain = result.forwardingCurve25519KeyChain
+                    forwardingCurve25519KeyChain = result.forwardingCurve25519KeyChain,
+                    isSafe = result.isSafe
             )
         } catch (e: MXCryptoError) {
             if (e is MXCryptoError.Base) {
diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/crypto/UnRequestedKeysManagerTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/crypto/UnRequestedKeysManagerTest.kt
new file mode 100644
index 0000000000000000000000000000000000000000..950093760a11b874950f06357d320a590a454653
--- /dev/null
+++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/crypto/UnRequestedKeysManagerTest.kt
@@ -0,0 +1,248 @@
+/*
+ * Copyright 2022 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
+
+import io.mockk.coEvery
+import io.mockk.mockk
+import kotlinx.coroutines.runBlocking
+import org.amshove.kluent.fail
+import org.amshove.kluent.shouldBe
+import org.junit.Test
+import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM
+import org.matrix.android.sdk.api.session.crypto.crosssigning.DeviceTrustLevel
+import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
+import org.matrix.android.sdk.api.session.crypto.model.ForwardedRoomKeyContent
+import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap
+import org.matrix.android.sdk.api.session.crypto.model.OlmDecryptionResult
+import org.matrix.android.sdk.api.session.crypto.model.UnsignedDeviceInfo
+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.content.OlmEventContent
+import org.matrix.android.sdk.api.session.events.model.toContent
+import org.matrix.android.sdk.internal.crypto.algorithms.megolm.UnRequestedForwardManager
+
+class UnRequestedKeysManagerTest {
+
+    private val aliceMxId = "alice@example.com"
+    private val bobMxId = "bob@example.com"
+    private val bobDeviceId = "MKRJDSLYGA"
+
+    private val device1Id = "MGDAADVDMG"
+
+    private val aliceFirstDevice = CryptoDeviceInfo(
+            deviceId = device1Id,
+            userId = aliceMxId,
+            algorithms = MXCryptoAlgorithms.supportedAlgorithms(),
+            keys = mapOf(
+                    "curve25519:$device1Id" to "yDa6cWOZ/WGBqm/JMUfTUCdEbAIzKHhuIcdDbnPEhDU",
+                    "ed25519:$device1Id" to "XTge+TDwfm+WW10IGnaqEyLTSukPPzg3R1J1YvO1SBI",
+            ),
+            signatures = mapOf(
+                    aliceMxId to mapOf(
+                            "ed25519:$device1Id" to "bPOAqM40+QSMgeEzUbYbPSZZccDDMUG00lCNdSXCoaS1gKKBGkSEaHO1OcibISIabjLYzmhp9mgtivz32fbABQ",
+                            "ed25519:Ru4ni66dbQ6FZgUoHyyBtmjKecOHMvMSsSBZ2SABtt0" to "owzUsQ4Pvn35uEIc5FdVnXVRPzsVYBV8uJRUSqr4y8r5tp0DvrMArtJukKETgYEAivcZMT1lwNihHIN9xh06DA"
+                    )
+            ),
+            unsigned = UnsignedDeviceInfo(deviceDisplayName = "Element Web"),
+            trustLevel = DeviceTrustLevel(crossSigningVerified = true, locallyVerified = true)
+    )
+
+    private val aBobDevice = CryptoDeviceInfo(
+            deviceId = bobDeviceId,
+            userId = bobMxId,
+            algorithms = MXCryptoAlgorithms.supportedAlgorithms(),
+            keys = mapOf(
+                    "curve25519:$bobDeviceId" to "tWwg63Yfn//61Ylhir6z4QGejvo193E6MVHmURtYVn0",
+                    "ed25519:$bobDeviceId" to "pS5NJ1LiVksQFX+p58NlphqMxE705laRVtUtZpYIAfs",
+            ),
+            signatures = mapOf(
+                    bobMxId to mapOf(
+                            "ed25519:$bobDeviceId" to "zAJqsmOSzkx8EWXcrynCsWtbgWZifN7A6DLyEBs+ZPPLnNuPN5Jwzc1Rg+oZWZaRPvOPcSL0cgcxRegSBU0NBA",
+                    )
+            ),
+            unsigned = UnsignedDeviceInfo(deviceDisplayName = "Element Ios")
+    )
+
+    @Test
+    fun `test process key request if invite received`() {
+        val fakeDeviceListManager = mockk<DeviceListManager> {
+            coEvery { downloadKeys(any(), any()) } returns MXUsersDevicesMap<CryptoDeviceInfo>().apply {
+                setObject(bobMxId, bobDeviceId, aBobDevice)
+            }
+        }
+        val unrequestedForwardManager = UnRequestedForwardManager(fakeDeviceListManager)
+
+        val roomId = "someRoomId"
+
+        unrequestedForwardManager.onUnRequestedKeyForward(
+                roomId,
+                createFakeSuccessfullyDecryptedForwardToDevice(
+                        aBobDevice,
+                        aliceFirstDevice,
+                        aBobDevice,
+                        megolmSessionId = "megolmId1"
+                ),
+                1_000
+        )
+
+        unrequestedForwardManager.onUnRequestedKeyForward(
+                roomId,
+                createFakeSuccessfullyDecryptedForwardToDevice(
+                        aBobDevice,
+                        aliceFirstDevice,
+                        aBobDevice,
+                        megolmSessionId = "megolmId2"
+                ),
+                1_000
+        )
+        // for now no reason to accept
+        runBlocking {
+            unrequestedForwardManager.postSyncProcessParkedKeysIfNeeded(1000) {
+                fail("There should be no key to process")
+            }
+        }
+
+        // ACT
+        // suppose an invite is received but from another user
+        val inviteTime = 1_000L
+        unrequestedForwardManager.onInviteReceived(roomId, "@jhon:example.com", inviteTime)
+
+        // we shouldn't process the requests!
+//        runBlocking {
+            unrequestedForwardManager.postSyncProcessParkedKeysIfNeeded(inviteTime) {
+                fail("There should be no key to process")
+            }
+//        }
+
+        // ACT
+        // suppose an invite is received from correct user
+
+        unrequestedForwardManager.onInviteReceived(roomId, aBobDevice.userId, inviteTime)
+        runBlocking {
+            unrequestedForwardManager.postSyncProcessParkedKeysIfNeeded(inviteTime) {
+                it.size shouldBe 2
+            }
+        }
+    }
+
+    @Test
+    fun `test invite before keys`() {
+        val fakeDeviceListManager = mockk<DeviceListManager> {
+            coEvery { downloadKeys(any(), any()) } returns MXUsersDevicesMap<CryptoDeviceInfo>().apply {
+                setObject(bobMxId, bobDeviceId, aBobDevice)
+            }
+        }
+        val unrequestedForwardManager = UnRequestedForwardManager(fakeDeviceListManager)
+
+        val roomId = "someRoomId"
+
+        unrequestedForwardManager.onInviteReceived(roomId, aBobDevice.userId, 1_000)
+
+        unrequestedForwardManager.onUnRequestedKeyForward(
+                roomId,
+                createFakeSuccessfullyDecryptedForwardToDevice(
+                        aBobDevice,
+                        aliceFirstDevice,
+                        aBobDevice,
+                        megolmSessionId = "megolmId1"
+                ),
+                1_000
+        )
+
+        runBlocking {
+            unrequestedForwardManager.postSyncProcessParkedKeysIfNeeded(1000) {
+                it.size shouldBe 1
+            }
+        }
+    }
+
+    @Test
+    fun `test validity window`() {
+        val fakeDeviceListManager = mockk<DeviceListManager> {
+            coEvery { downloadKeys(any(), any()) } returns MXUsersDevicesMap<CryptoDeviceInfo>().apply {
+                setObject(bobMxId, bobDeviceId, aBobDevice)
+            }
+        }
+        val unrequestedForwardManager = UnRequestedForwardManager(fakeDeviceListManager)
+
+        val roomId = "someRoomId"
+
+        val timeOfKeyReception = 1_000L
+
+        unrequestedForwardManager.onUnRequestedKeyForward(
+                roomId,
+                createFakeSuccessfullyDecryptedForwardToDevice(
+                        aBobDevice,
+                        aliceFirstDevice,
+                        aBobDevice,
+                        megolmSessionId = "megolmId1"
+                ),
+                timeOfKeyReception
+        )
+
+        val currentTimeWindow = 10 * 60_000
+
+        // simulate very late invite
+        val inviteTime = timeOfKeyReception + currentTimeWindow + 1_000
+        unrequestedForwardManager.onInviteReceived(roomId, aBobDevice.userId, inviteTime)
+
+        runBlocking {
+            unrequestedForwardManager.postSyncProcessParkedKeysIfNeeded(inviteTime) {
+                fail("There should be no key to process")
+            }
+        }
+    }
+
+    private fun createFakeSuccessfullyDecryptedForwardToDevice(
+            sentBy: CryptoDeviceInfo,
+            dest: CryptoDeviceInfo,
+            sessionInitiator: CryptoDeviceInfo,
+            algorithm: String = MXCRYPTO_ALGORITHM_MEGOLM,
+            roomId: String = "!zzgDlIhbWOevcdFBXr:example.com",
+            megolmSessionId: String = "Z/FSE8wDYheouGjGP9pezC4S1i39RtAXM3q9VXrBVZw"
+    ): Event {
+        return Event(
+                type = EventType.ENCRYPTED,
+                eventId = "!fake",
+                senderId = sentBy.userId,
+                content = OlmEventContent(
+                        ciphertext = mapOf(
+                                dest.identityKey()!! to mapOf(
+                                        "type" to 0,
+                                        "body" to "AwogcziNF/tv60X0elsBmnKPN3+LTXr4K3vXw+1ZJ6jpTxESIJCmMMDvOA+"
+                                )
+                        ),
+                        senderKey = sentBy.identityKey()
+                ).toContent(),
+
+                ).apply {
+            mxDecryptionResult = OlmDecryptionResult(
+                    payload = mapOf(
+                            "type" to EventType.FORWARDED_ROOM_KEY,
+                            "content" to ForwardedRoomKeyContent(
+                                    algorithm = algorithm,
+                                    roomId = roomId,
+                                    senderKey = sessionInitiator.identityKey(),
+                                    sessionId = megolmSessionId,
+                                    sessionKey = "AQAAAAAc4dK+lXxXyaFbckSxwjIEoIGDLKYovONJ7viWpwevhfvoBh+Q..."
+                            ).toContent()
+                    ),
+                    senderKey = sentBy.identityKey()
+            )
+        }
+    }
+}
diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/create/DefaultCreateRoomFromLocalRoomTaskTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/create/DefaultCreateRoomFromLocalRoomTaskTest.kt
index d3732363b5a77a9dd808706710e32dc3997c0619..9e3428043786ef390f3f0bce31167c277a9abb89 100644
--- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/create/DefaultCreateRoomFromLocalRoomTaskTest.kt
+++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/create/DefaultCreateRoomFromLocalRoomTaskTest.kt
@@ -22,21 +22,22 @@ import io.mockk.coVerify
 import io.mockk.every
 import io.mockk.mockk
 import io.mockk.mockkStatic
+import io.mockk.spyk
 import io.mockk.unmockkAll
+import io.mockk.verify
+import io.mockk.verifyOrder
 import io.realm.kotlin.where
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.runTest
 import org.amshove.kluent.shouldBeEqualTo
+import org.amshove.kluent.shouldBeNull
 import org.junit.After
 import org.junit.Before
 import org.junit.Test
-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
+import org.matrix.android.sdk.api.extensions.tryOrNull
+import org.matrix.android.sdk.api.session.room.model.LocalRoomCreationState
+import org.matrix.android.sdk.api.session.room.model.LocalRoomSummary
 import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams
-import org.matrix.android.sdk.api.session.room.model.tombstone.RoomTombstoneContent
 import org.matrix.android.sdk.internal.database.awaitNotEmptyResult
 import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntity
 import org.matrix.android.sdk.internal.database.model.EventEntity
@@ -44,29 +45,24 @@ import org.matrix.android.sdk.internal.database.model.LocalRoomSummaryEntity
 import org.matrix.android.sdk.internal.database.model.LocalRoomSummaryEntityFields
 import org.matrix.android.sdk.internal.database.query.copyToRealmOrIgnore
 import org.matrix.android.sdk.internal.database.query.getOrCreate
-import org.matrix.android.sdk.internal.util.time.DefaultClock
 import org.matrix.android.sdk.test.fakes.FakeMonarchy
-import org.matrix.android.sdk.test.fakes.FakeStateEventDataSource
+import org.matrix.android.sdk.test.fakes.FakeRoomSummaryDataSource
 
 private const val A_LOCAL_ROOM_ID = "local.a-local-room-id"
 private const val AN_EXISTING_ROOM_ID = "an-existing-room-id"
 private const val A_ROOM_ID = "a-room-id"
-private const val MY_USER_ID = "my-user-id"
 
 @ExperimentalCoroutinesApi
 internal class DefaultCreateRoomFromLocalRoomTaskTest {
 
     private val fakeMonarchy = FakeMonarchy()
-    private val clock = DefaultClock()
     private val createRoomTask = mockk<CreateRoomTask>()
-    private val fakeStateEventDataSource = FakeStateEventDataSource()
+    private val fakeRoomSummaryDataSource = FakeRoomSummaryDataSource()
 
     private val defaultCreateRoomFromLocalRoomTask = DefaultCreateRoomFromLocalRoomTask(
-            userId = MY_USER_ID,
             monarchy = fakeMonarchy.instance,
             createRoomTask = createRoomTask,
-            stateEventDataSource = fakeStateEventDataSource.instance,
-            clock = clock
+            roomSummaryDataSource = fakeRoomSummaryDataSource.instance,
     )
 
     @Before
@@ -91,13 +87,12 @@ internal class DefaultCreateRoomFromLocalRoomTaskTest {
     @Test
     fun `given a local room id when execute then the existing room id is kept`() = runTest {
         // Given
-        givenATombstoneEvent(
-                Event(
-                        roomId = A_LOCAL_ROOM_ID,
-                        type = EventType.STATE_ROOM_TOMBSTONE,
-                        stateKey = "",
-                        content = RoomTombstoneContent(replacementRoomId = AN_EXISTING_ROOM_ID).toContent()
-                )
+        val aCreateRoomParams = mockk<CreateRoomParams>(relaxed = true)
+        givenALocalRoomSummary(aCreateRoomParams = aCreateRoomParams, aCreationState = LocalRoomCreationState.CREATED, aReplacementRoomId = AN_EXISTING_ROOM_ID)
+        val aLocalRoomSummaryEntity = givenALocalRoomSummaryEntity(
+                aCreateRoomParams = aCreateRoomParams,
+                aCreationState = LocalRoomCreationState.CREATED,
+                aReplacementRoomId = AN_EXISTING_ROOM_ID
         )
 
         // When
@@ -105,20 +100,18 @@ internal class DefaultCreateRoomFromLocalRoomTaskTest {
         val result = defaultCreateRoomFromLocalRoomTask.execute(params)
 
         // Then
-        verifyTombstoneEvent(AN_EXISTING_ROOM_ID)
+        fakeRoomSummaryDataSource.verifyGetLocalRoomSummary(A_LOCAL_ROOM_ID)
         result shouldBeEqualTo AN_EXISTING_ROOM_ID
+        aLocalRoomSummaryEntity.replacementRoomId shouldBeEqualTo AN_EXISTING_ROOM_ID
+        aLocalRoomSummaryEntity.creationState shouldBeEqualTo LocalRoomCreationState.CREATED
     }
 
     @Test
     fun `given a local room id when execute then it is correctly executed`() = runTest {
         // Given
-        val aCreateRoomParams = mockk<CreateRoomParams>()
-        val aLocalRoomSummaryEntity = mockk<LocalRoomSummaryEntity> {
-            every { roomSummaryEntity } returns mockk(relaxed = true)
-            every { createRoomParams } returns aCreateRoomParams
-        }
-        givenATombstoneEvent(null)
-        givenALocalRoomSummaryEntity(aLocalRoomSummaryEntity)
+        val aCreateRoomParams = mockk<CreateRoomParams>(relaxed = true)
+        givenALocalRoomSummary(aCreateRoomParams = aCreateRoomParams, aReplacementRoomId = null)
+        val aLocalRoomSummaryEntity = givenALocalRoomSummaryEntity(aCreateRoomParams = aCreateRoomParams, aReplacementRoomId = null)
 
         coEvery { createRoomTask.execute(any()) } returns A_ROOM_ID
 
@@ -127,32 +120,84 @@ internal class DefaultCreateRoomFromLocalRoomTaskTest {
         val result = defaultCreateRoomFromLocalRoomTask.execute(params)
 
         // Then
-        verifyTombstoneEvent(null)
+        fakeRoomSummaryDataSource.verifyGetLocalRoomSummary(A_LOCAL_ROOM_ID)
         // CreateRoomTask has been called with the initial CreateRoomParams
         coVerify { createRoomTask.execute(aCreateRoomParams) }
         // The resulting roomId matches the roomId returned by the createRoomTask
         result shouldBeEqualTo A_ROOM_ID
-        // A tombstone state event has been created
-        coVerify { CurrentStateEventEntity.getOrCreate(realm = any(), roomId = A_LOCAL_ROOM_ID, stateKey = any(), type = EventType.STATE_ROOM_TOMBSTONE) }
+        // The room creation state has correctly been updated
+        verifyOrder {
+            aLocalRoomSummaryEntity.creationState = LocalRoomCreationState.CREATING
+            aLocalRoomSummaryEntity.creationState = LocalRoomCreationState.CREATED
+        }
+        // The local room summary has been updated with the created room id
+        verify { aLocalRoomSummaryEntity.replacementRoomId = A_ROOM_ID }
+        aLocalRoomSummaryEntity.replacementRoomId shouldBeEqualTo A_ROOM_ID
+        aLocalRoomSummaryEntity.creationState shouldBeEqualTo LocalRoomCreationState.CREATED
+    }
+
+    @Test
+    fun `given a local room id when execute with an exception then the creation state is correctly updated`() = runTest {
+        // Given
+        val aCreateRoomParams = mockk<CreateRoomParams>(relaxed = true)
+        givenALocalRoomSummary(aCreateRoomParams = aCreateRoomParams, aReplacementRoomId = null)
+        val aLocalRoomSummaryEntity = givenALocalRoomSummaryEntity(aCreateRoomParams = aCreateRoomParams, aReplacementRoomId = null)
+
+        coEvery { createRoomTask.execute(any()) }.throws(mockk())
+
+        // When
+        val params = CreateRoomFromLocalRoomTask.Params(A_LOCAL_ROOM_ID)
+        tryOrNull { defaultCreateRoomFromLocalRoomTask.execute(params) }
+
+        // Then
+        fakeRoomSummaryDataSource.verifyGetLocalRoomSummary(A_LOCAL_ROOM_ID)
+        // CreateRoomTask has been called with the initial CreateRoomParams
+        coVerify { createRoomTask.execute(aCreateRoomParams) }
+        // The room creation state has correctly been updated
+        verifyOrder {
+            aLocalRoomSummaryEntity.creationState = LocalRoomCreationState.CREATING
+            aLocalRoomSummaryEntity.creationState = LocalRoomCreationState.FAILURE
+        }
+        // The local room summary has been updated with the created room id
+        aLocalRoomSummaryEntity.replacementRoomId.shouldBeNull()
+        aLocalRoomSummaryEntity.creationState shouldBeEqualTo LocalRoomCreationState.FAILURE
     }
 
-    private fun givenATombstoneEvent(event: Event?) {
-        fakeStateEventDataSource.givenGetStateEventReturns(event)
+    private fun givenALocalRoomSummary(
+            aCreateRoomParams: CreateRoomParams,
+            aCreationState: LocalRoomCreationState = LocalRoomCreationState.NOT_CREATED,
+            aReplacementRoomId: String? = null
+    ): LocalRoomSummary {
+        val aLocalRoomSummary = LocalRoomSummary(
+                roomId = A_LOCAL_ROOM_ID,
+                roomSummary = mockk(relaxed = true),
+                createRoomParams = aCreateRoomParams,
+                creationState = aCreationState,
+                replacementRoomId = aReplacementRoomId,
+        )
+        fakeRoomSummaryDataSource.givenGetLocalRoomSummaryReturns(A_LOCAL_ROOM_ID, aLocalRoomSummary)
+        return aLocalRoomSummary
     }
 
-    private fun givenALocalRoomSummaryEntity(localRoomSummaryEntity: LocalRoomSummaryEntity) {
+    private fun givenALocalRoomSummaryEntity(
+            aCreateRoomParams: CreateRoomParams,
+            aCreationState: LocalRoomCreationState = LocalRoomCreationState.NOT_CREATED,
+            aReplacementRoomId: String? = null
+    ): LocalRoomSummaryEntity {
+        val aLocalRoomSummaryEntity = spyk(LocalRoomSummaryEntity(
+                roomId = A_LOCAL_ROOM_ID,
+                roomSummaryEntity = mockk(relaxed = true),
+                replacementRoomId = aReplacementRoomId,
+        ).apply {
+            createRoomParams = aCreateRoomParams
+            creationState = aCreationState
+        })
         every {
             fakeMonarchy.fakeRealm.instance
                     .where<LocalRoomSummaryEntity>()
                     .equalTo(LocalRoomSummaryEntityFields.ROOM_ID, A_LOCAL_ROOM_ID)
                     .findFirst()
-        } returns localRoomSummaryEntity
-    }
-
-    private fun verifyTombstoneEvent(expectedRoomId: String?) {
-        fakeStateEventDataSource.verifyGetStateEvent(A_LOCAL_ROOM_ID, EventType.STATE_ROOM_TOMBSTONE, QueryStringValue.IsEmpty)
-        fakeStateEventDataSource.instance.getStateEvent(A_LOCAL_ROOM_ID, EventType.STATE_ROOM_TOMBSTONE, QueryStringValue.IsEmpty)
-                ?.content.toModel<RoomTombstoneContent>()
-                ?.replacementRoomId shouldBeEqualTo expectedRoomId
+        } returns aLocalRoomSummaryEntity
+        return aLocalRoomSummaryEntity
     }
 }
diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeMonarchy.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeMonarchy.kt
index 2d501f12af2595e97643e1ca3e0ac2c29ba31e0c..93999458c68caa0d72d09fb591324cc67792de8c 100644
--- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeMonarchy.kt
+++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeMonarchy.kt
@@ -47,6 +47,11 @@ internal class FakeMonarchy {
         } coAnswers {
             firstArg<Monarchy.RealmBlock>().doWithRealm(fakeRealm.instance)
         }
+        coEvery {
+            instance.runTransactionSync(any())
+        } coAnswers {
+            firstArg<Realm.Transaction>().execute(fakeRealm.instance)
+        }
         every { instance.realmConfiguration } returns mockk()
     }
 
diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRoomSummaryDataSource.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRoomSummaryDataSource.kt
new file mode 100644
index 0000000000000000000000000000000000000000..c7b70a3ad5c8450a4ac064db49cbfa4e219a7ff2
--- /dev/null
+++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRoomSummaryDataSource.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2022 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.test.fakes
+
+import io.mockk.every
+import io.mockk.mockk
+import io.mockk.verify
+import org.matrix.android.sdk.api.session.room.model.LocalRoomSummary
+import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryDataSource
+
+internal class FakeRoomSummaryDataSource {
+
+    val instance: RoomSummaryDataSource = mockk()
+
+    fun givenGetLocalRoomSummaryReturns(roomId: String?, localRoomSummary: LocalRoomSummary?) {
+        every { instance.getLocalRoomSummary(roomId = roomId ?: any()) } returns localRoomSummary
+    }
+
+    fun verifyGetLocalRoomSummary(roomId: String) {
+        verify { instance.getLocalRoomSummary(roomId) }
+    }
+}