diff --git a/changelog.d/5853.feature b/changelog.d/5853.feature
new file mode 100644
index 0000000000..2a399e76aa
--- /dev/null
+++ b/changelog.d/5853.feature
@@ -0,0 +1 @@
+Improve user experience when he is first invited to a room. Users will be able to decrypt and view previous messages
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 7dafe33935..a78953caac 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
@@ -18,12 +18,15 @@ package org.matrix.android.sdk.common
 
 import android.content.Context
 import android.net.Uri
+import android.util.Log
 import androidx.lifecycle.Observer
 import androidx.test.internal.runner.junit4.statement.UiThreadStatement
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.Job
 import kotlinx.coroutines.SupervisorJob
+import kotlinx.coroutines.cancel
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.runBlocking
@@ -38,7 +41,10 @@ import org.matrix.android.sdk.api.auth.registration.RegistrationResult
 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
+import org.matrix.android.sdk.api.session.getRoomSummary
 import org.matrix.android.sdk.api.session.room.Room
+import org.matrix.android.sdk.api.session.room.failure.JoinRoomFailure
+import org.matrix.android.sdk.api.session.room.model.Membership
 import org.matrix.android.sdk.api.session.room.model.message.MessageContent
 import org.matrix.android.sdk.api.session.room.send.SendState
 import org.matrix.android.sdk.api.session.room.timeline.Timeline
@@ -47,6 +53,7 @@ import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings
 import org.matrix.android.sdk.api.session.sync.SyncState
 import timber.log.Timber
 import java.util.UUID
+import java.util.concurrent.CancellationException
 import java.util.concurrent.CountDownLatch
 import java.util.concurrent.TimeUnit
 
@@ -54,7 +61,7 @@ import java.util.concurrent.TimeUnit
  * This class exposes methods to be used in common cases
  * Registration, login, Sync, Sending messages...
  */
-class CommonTestHelper private constructor(context: Context) {
+class CommonTestHelper internal constructor(context: Context) {
 
     companion object {
         internal fun runSessionTest(context: Context, autoSignoutOnClose: Boolean = true, block: (CommonTestHelper) -> Unit) {
@@ -241,6 +248,37 @@ class CommonTestHelper private constructor(context: Context) {
         return sentEvents
     }
 
+    fun waitForAndAcceptInviteInRoom(otherSession: Session, roomID: String) {
+        waitWithLatch { latch ->
+            retryPeriodicallyWithLatch(latch) {
+                val roomSummary = otherSession.getRoomSummary(roomID)
+                (roomSummary != null && roomSummary.membership == Membership.INVITE).also {
+                    if (it) {
+                        Log.v("# TEST", "${otherSession.myUserId} can see the invite")
+                    }
+                }
+            }
+        }
+
+        // not sure why it's taking so long :/
+        runBlockingTest(90_000) {
+            Log.v("#E2E TEST", "${otherSession.myUserId} tries to join room $roomID")
+            try {
+                otherSession.roomService().joinRoom(roomID)
+            } catch (ex: JoinRoomFailure.JoinedWithTimeout) {
+                // it's ok we will wait after
+            }
+        }
+
+        Log.v("#E2E TEST", "${otherSession.myUserId} waiting for join echo ...")
+        waitWithLatch {
+            retryPeriodicallyWithLatch(it) {
+                val roomSummary = otherSession.getRoomSummary(roomID)
+                roomSummary != null && roomSummary.membership == Membership.JOIN
+            }
+        }
+    }
+
     /**
      * Reply in a thread
      * @param room the room where to send the messages
@@ -285,6 +323,8 @@ class CommonTestHelper private constructor(context: Context) {
         )
         assertNotNull(session)
         return session.also {
+            // most of the test was created pre-MSC3061 so ensure compatibility
+            it.cryptoService().enableShareKeyOnInvite(false)
             trackedSessions.add(session)
         }
     }
@@ -428,16 +468,26 @@ class CommonTestHelper private constructor(context: Context) {
      * @param latch
      * @throws InterruptedException
      */
-    fun await(latch: CountDownLatch, timeout: Long? = TestConstants.timeOutMillis) {
+    fun await(latch: CountDownLatch, timeout: Long? = TestConstants.timeOutMillis, job: Job? = null) {
         assertTrue(
                 "Timed out after " + timeout + "ms waiting for something to happen. See stacktrace for cause.",
-                latch.await(timeout ?: TestConstants.timeOutMillis, TimeUnit.MILLISECONDS)
+                latch.await(timeout ?: TestConstants.timeOutMillis, TimeUnit.MILLISECONDS).also {
+                    if (!it) {
+                        // cancel job on timeout
+                        job?.cancel("Await timeout")
+                    }
+                }
         )
     }
 
     suspend fun retryPeriodicallyWithLatch(latch: CountDownLatch, condition: (() -> Boolean)) {
         while (true) {
-            delay(1000)
+            try {
+                delay(1000)
+            } catch (ex: CancellationException) {
+                // the job was canceled, just stop
+                return
+            }
             if (condition()) {
                 latch.countDown()
                 return
@@ -447,10 +497,10 @@ class CommonTestHelper private constructor(context: Context) {
 
     fun waitWithLatch(timeout: Long? = TestConstants.timeOutMillis, dispatcher: CoroutineDispatcher = Dispatchers.Main, block: suspend (CountDownLatch) -> Unit) {
         val latch = CountDownLatch(1)
-        coroutineScope.launch(dispatcher) {
+        val job = coroutineScope.launch(dispatcher) {
             block(latch)
         }
-        await(latch, timeout)
+        await(latch, timeout, job)
     }
 
     fun <T> runBlockingTest(timeout: Long = TestConstants.timeOutMillis, block: suspend () -> T): T {
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 5fd86d4fdb..f36bfb6210 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
@@ -53,6 +53,7 @@ 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
 import org.matrix.android.sdk.api.session.room.model.Membership
+import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility
 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.message.MessageContent
@@ -76,11 +77,14 @@ class CryptoTestHelper(val testHelper: CommonTestHelper) {
     /**
      * @return alice session
      */
-    fun doE2ETestWithAliceInARoom(encryptedRoom: Boolean = true): CryptoTestData {
+    fun doE2ETestWithAliceInARoom(encryptedRoom: Boolean = true, roomHistoryVisibility: RoomHistoryVisibility? = null): CryptoTestData {
         val aliceSession = testHelper.createAccount(TestConstants.USER_ALICE, defaultSessionParams)
 
         val roomId = testHelper.runBlockingTest {
-            aliceSession.roomService().createRoom(CreateRoomParams().apply { name = "MyRoom" })
+            aliceSession.roomService().createRoom(CreateRoomParams().apply {
+                historyVisibility = roomHistoryVisibility
+                name = "MyRoom"
+            })
         }
         if (encryptedRoom) {
             testHelper.waitWithLatch { latch ->
@@ -104,8 +108,8 @@ class CryptoTestHelper(val testHelper: CommonTestHelper) {
     /**
      * @return alice and bob sessions
      */
-    fun doE2ETestWithAliceAndBobInARoom(encryptedRoom: Boolean = true): CryptoTestData {
-        val cryptoTestData = doE2ETestWithAliceInARoom(encryptedRoom)
+    fun doE2ETestWithAliceAndBobInARoom(encryptedRoom: Boolean = true, roomHistoryVisibility: RoomHistoryVisibility? = null): CryptoTestData {
+        val cryptoTestData = doE2ETestWithAliceInARoom(encryptedRoom, roomHistoryVisibility)
         val aliceSession = cryptoTestData.firstSession
         val aliceRoomId = cryptoTestData.roomId
 
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2EShareKeysConfigTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2EShareKeysConfigTest.kt
new file mode 100644
index 0000000000..32d63a1934
--- /dev/null
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2EShareKeysConfigTest.kt
@@ -0,0 +1,298 @@
+/*
+ * 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 android.util.Log
+import androidx.test.filters.LargeTest
+import org.amshove.kluent.internal.assertEquals
+import org.junit.Assert
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.junit.runners.MethodSorters
+import org.matrix.android.sdk.InstrumentedTest
+import org.matrix.android.sdk.api.session.Session
+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
+import org.matrix.android.sdk.api.session.crypto.model.ImportRoomKeysResult
+import org.matrix.android.sdk.api.session.getRoom
+import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility
+import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams
+import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
+import org.matrix.android.sdk.common.CommonTestHelper
+import org.matrix.android.sdk.common.CommonTestHelper.Companion.runCryptoTest
+import org.matrix.android.sdk.common.CryptoTestData
+import org.matrix.android.sdk.common.SessionTestParams
+import org.matrix.android.sdk.common.TestConstants
+import org.matrix.android.sdk.common.TestMatrixCallback
+
+@RunWith(JUnit4::class)
+@FixMethodOrder(MethodSorters.JVM)
+@LargeTest
+class E2EShareKeysConfigTest : InstrumentedTest {
+
+    @Test
+    fun msc3061ShouldBeDisabledByDefault() = runCryptoTest(context()) { _, commonTestHelper ->
+        val aliceSession = commonTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(withInitialSync = false))
+        Assert.assertFalse("MSC3061 is lab and should be disabled by default", aliceSession.cryptoService().isShareKeysOnInviteEnabled())
+    }
+
+    @Test
+    fun ensureKeysAreNotSharedIfOptionDisabled() = runCryptoTest(context()) { cryptoTestHelper, commonTestHelper ->
+        val aliceSession = commonTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(withInitialSync = true))
+        aliceSession.cryptoService().enableShareKeyOnInvite(false)
+        val roomId = commonTestHelper.runBlockingTest {
+            aliceSession.roomService().createRoom(CreateRoomParams().apply {
+                historyVisibility = RoomHistoryVisibility.SHARED
+                name = "MyRoom"
+                enableEncryption()
+            })
+        }
+
+        commonTestHelper.waitWithLatch { latch ->
+            commonTestHelper.retryPeriodicallyWithLatch(latch) {
+                aliceSession.roomService().getRoomSummary(roomId)?.isEncrypted == true
+            }
+        }
+        val roomAlice = aliceSession.roomService().getRoom(roomId)!!
+
+        // send some messages
+        val withSession1 = commonTestHelper.sendTextMessage(roomAlice, "Hello", 1)
+        aliceSession.cryptoService().discardOutboundSession(roomId)
+        val withSession2 = commonTestHelper.sendTextMessage(roomAlice, "World", 1)
+
+        // Create bob account
+        val bobSession = commonTestHelper.createAccount(TestConstants.USER_BOB, SessionTestParams(withInitialSync = true))
+
+        // Let alice invite bob
+        commonTestHelper.runBlockingTest {
+            roomAlice.membershipService().invite(bobSession.myUserId)
+        }
+
+        commonTestHelper.waitForAndAcceptInviteInRoom(bobSession, roomId)
+
+        // Bob has join but should not be able to decrypt history
+        cryptoTestHelper.ensureCannotDecrypt(
+                withSession1.map { it.eventId } + withSession2.map { it.eventId },
+                bobSession,
+                roomId
+        )
+
+        // We don't need bob anymore
+        commonTestHelper.signOutAndClose(bobSession)
+
+        // Now let's enable history key sharing on alice side
+        aliceSession.cryptoService().enableShareKeyOnInvite(true)
+
+        // let's add a new message first
+        val afterFlagOn = commonTestHelper.sendTextMessage(roomAlice, "After", 1)
+
+        // Worth nothing to check that the session was rotated
+        Assert.assertNotEquals(
+                "Session should have been rotated",
+                withSession2.first().root.content?.get("session_id")!!,
+                afterFlagOn.first().root.content?.get("session_id")!!
+        )
+
+        // Invite a new user
+        val samSession = commonTestHelper.createAccount(TestConstants.USER_SAM, SessionTestParams(withInitialSync = true))
+
+        // Let alice invite sam
+        commonTestHelper.runBlockingTest {
+            roomAlice.membershipService().invite(samSession.myUserId)
+        }
+
+        commonTestHelper.waitForAndAcceptInviteInRoom(samSession, roomId)
+
+        // Sam shouldn't be able to decrypt messages with the first session, but should decrypt the one with 3rd session
+        cryptoTestHelper.ensureCannotDecrypt(
+                withSession1.map { it.eventId } + withSession2.map { it.eventId },
+                samSession,
+                roomId
+        )
+
+        cryptoTestHelper.ensureCanDecrypt(
+                afterFlagOn.map { it.eventId },
+                samSession,
+                roomId,
+                afterFlagOn.map { it.root.getClearContent()?.get("body") as String })
+    }
+
+    @Test
+    fun ifSharingDisabledOnAliceSideBobShouldNotShareAliceHistoty() = runCryptoTest(context()) { cryptoTestHelper, commonTestHelper ->
+        val testData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(roomHistoryVisibility = RoomHistoryVisibility.SHARED)
+        val aliceSession = testData.firstSession.also {
+            it.cryptoService().enableShareKeyOnInvite(false)
+        }
+        val bobSession = testData.secondSession!!.also {
+            it.cryptoService().enableShareKeyOnInvite(true)
+        }
+
+        val (fromAliceNotSharable, fromBobSharable, samSession) = commonAliceAndBobSendMessages(commonTestHelper, aliceSession, testData, bobSession)
+
+        // Bob should have shared history keys to sam.
+        // But has alice hasn't enabled sharing, bob shouldn't send her sessions
+        cryptoTestHelper.ensureCannotDecrypt(
+                fromAliceNotSharable.map { it.eventId },
+                samSession,
+                testData.roomId
+        )
+
+        cryptoTestHelper.ensureCanDecrypt(
+                fromBobSharable.map { it.eventId },
+                samSession,
+                testData.roomId,
+                fromBobSharable.map { it.root.getClearContent()?.get("body") as String })
+    }
+
+    @Test
+    fun ifSharingEnabledOnAliceSideBobShouldShareAliceHistoty() = runCryptoTest(context()) { cryptoTestHelper, commonTestHelper ->
+        val testData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(roomHistoryVisibility = RoomHistoryVisibility.SHARED)
+        val aliceSession = testData.firstSession.also {
+            it.cryptoService().enableShareKeyOnInvite(true)
+        }
+        val bobSession = testData.secondSession!!.also {
+            it.cryptoService().enableShareKeyOnInvite(true)
+        }
+
+        val (fromAliceNotSharable, fromBobSharable, samSession) = commonAliceAndBobSendMessages(commonTestHelper, aliceSession, testData, bobSession)
+
+        cryptoTestHelper.ensureCanDecrypt(
+                fromAliceNotSharable.map { it.eventId },
+                samSession,
+                testData.roomId,
+                fromAliceNotSharable.map { it.root.getClearContent()?.get("body") as String })
+
+        cryptoTestHelper.ensureCanDecrypt(
+                fromBobSharable.map { it.eventId },
+                samSession,
+                testData.roomId,
+                fromBobSharable.map { it.root.getClearContent()?.get("body") as String })
+    }
+
+    private fun commonAliceAndBobSendMessages(commonTestHelper: CommonTestHelper, aliceSession: Session, testData: CryptoTestData, bobSession: Session): Triple<List<TimelineEvent>, List<TimelineEvent>, Session> {
+        val fromAliceNotSharable = commonTestHelper.sendTextMessage(aliceSession.getRoom(testData.roomId)!!, "Hello from alice", 1)
+        val fromBobSharable = commonTestHelper.sendTextMessage(bobSession.getRoom(testData.roomId)!!, "Hello from bob", 1)
+
+        // Now let bob invite Sam
+        // Invite a new user
+        val samSession = commonTestHelper.createAccount(TestConstants.USER_SAM, SessionTestParams(withInitialSync = true))
+
+        // Let bob invite sam
+        commonTestHelper.runBlockingTest {
+            bobSession.getRoom(testData.roomId)!!.membershipService().invite(samSession.myUserId)
+        }
+
+        commonTestHelper.waitForAndAcceptInviteInRoom(samSession, testData.roomId)
+        return Triple(fromAliceNotSharable, fromBobSharable, samSession)
+    }
+
+    // test flag on backup is correct
+
+    @Test
+    fun testBackupFlagIsCorrect() = runCryptoTest(context()) { cryptoTestHelper, commonTestHelper ->
+        val aliceSession = commonTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(withInitialSync = true))
+        aliceSession.cryptoService().enableShareKeyOnInvite(false)
+        val roomId = commonTestHelper.runBlockingTest {
+            aliceSession.roomService().createRoom(CreateRoomParams().apply {
+                historyVisibility = RoomHistoryVisibility.SHARED
+                name = "MyRoom"
+                enableEncryption()
+            })
+        }
+
+        commonTestHelper.waitWithLatch { latch ->
+            commonTestHelper.retryPeriodicallyWithLatch(latch) {
+                aliceSession.roomService().getRoomSummary(roomId)?.isEncrypted == true
+            }
+        }
+        val roomAlice = aliceSession.roomService().getRoom(roomId)!!
+
+        // send some messages
+        val notSharableMessage = commonTestHelper.sendTextMessage(roomAlice, "Hello", 1)
+        aliceSession.cryptoService().enableShareKeyOnInvite(true)
+        val sharableMessage = commonTestHelper.sendTextMessage(roomAlice, "World", 1)
+
+        Log.v("#E2E TEST", "Create and start key backup for bob ...")
+        val keysBackupService = aliceSession.cryptoService().keysBackupService()
+        val keyBackupPassword = "FooBarBaz"
+        val megolmBackupCreationInfo = commonTestHelper.doSync<MegolmBackupCreationInfo> {
+            keysBackupService.prepareKeysBackupVersion(keyBackupPassword, null, it)
+        }
+        val version = commonTestHelper.doSync<KeysVersion> {
+            keysBackupService.createKeysBackupVersion(megolmBackupCreationInfo, it)
+        }
+
+        commonTestHelper.waitWithLatch { latch ->
+            keysBackupService.backupAllGroupSessions(
+                    null,
+                    TestMatrixCallback(latch, true)
+            )
+        }
+
+        // signout
+        commonTestHelper.signOutAndClose(aliceSession)
+
+        val newAliceSession = commonTestHelper.logIntoAccount(aliceSession.myUserId, SessionTestParams(true))
+        newAliceSession.cryptoService().enableShareKeyOnInvite(true)
+
+        newAliceSession.cryptoService().keysBackupService().let { kbs ->
+            val keyVersionResult = commonTestHelper.doSync<KeysVersionResult?> {
+                kbs.getVersion(version.version, it)
+            }
+
+            val importedResult = commonTestHelper.doSync<ImportRoomKeysResult> {
+                kbs.restoreKeyBackupWithPassword(
+                        keyVersionResult!!,
+                        keyBackupPassword,
+                        null,
+                        null,
+                        null,
+                        it
+                )
+            }
+
+            assertEquals(2, importedResult.totalNumberOfKeys)
+        }
+
+        // Now let's invite sam
+        // Invite a new user
+        val samSession = commonTestHelper.createAccount(TestConstants.USER_SAM, SessionTestParams(withInitialSync = true))
+
+        // Let alice invite sam
+        commonTestHelper.runBlockingTest {
+            newAliceSession.getRoom(roomId)!!.membershipService().invite(samSession.myUserId)
+        }
+
+        commonTestHelper.waitForAndAcceptInviteInRoom(samSession, roomId)
+
+        // Sam shouldn't be able to decrypt messages with the first session, but should decrypt the one with 3rd session
+        cryptoTestHelper.ensureCannotDecrypt(
+                notSharableMessage.map { it.eventId },
+                samSession,
+                roomId
+        )
+
+        cryptoTestHelper.ensureCanDecrypt(
+                sharableMessage.map { it.eventId },
+                samSession,
+                roomId,
+                sharableMessage.map { it.root.getClearContent()?.get("body") as String })
+    }
+}
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 5a61eee7fe..251c13ccbf 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
@@ -23,7 +23,6 @@ import org.amshove.kluent.fail
 import org.amshove.kluent.internal.assertEquals
 import org.junit.Assert
 import org.junit.FixMethodOrder
-import org.junit.Ignore
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -49,9 +48,7 @@ import org.matrix.android.sdk.api.session.events.model.content.EncryptedEventCon
 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.getRoomSummary
 import org.matrix.android.sdk.api.session.room.Room
-import org.matrix.android.sdk.api.session.room.failure.JoinRoomFailure
 import org.matrix.android.sdk.api.session.room.getTimelineEvent
 import org.matrix.android.sdk.api.session.room.model.Membership
 import org.matrix.android.sdk.api.session.room.model.message.MessageContent
@@ -67,10 +64,10 @@ import org.matrix.android.sdk.common.TestMatrixCallback
 import org.matrix.android.sdk.mustFail
 import java.util.concurrent.CountDownLatch
 
+// @Ignore("This test fails with an unhandled exception thrown from a coroutine which terminates the entire test run.")
 @RunWith(JUnit4::class)
 @FixMethodOrder(MethodSorters.JVM)
 @LargeTest
-@Ignore("This test fails with an unhandled exception thrown from a coroutine which terminates the entire test run.")
 class E2eeSanityTests : InstrumentedTest {
 
     @get:Rule val rule = RetryTestRule(3)
@@ -115,7 +112,7 @@ class E2eeSanityTests : InstrumentedTest {
 
         // All user should accept invite
         otherAccounts.forEach { otherSession ->
-            waitForAndAcceptInviteInRoom(testHelper, otherSession, e2eRoomID)
+            testHelper.waitForAndAcceptInviteInRoom(otherSession, e2eRoomID)
             Log.v("#E2E TEST", "${otherSession.myUserId} joined room $e2eRoomID")
         }
 
@@ -156,7 +153,7 @@ class E2eeSanityTests : InstrumentedTest {
         }
 
         newAccount.forEach {
-            waitForAndAcceptInviteInRoom(testHelper, it, e2eRoomID)
+            testHelper.waitForAndAcceptInviteInRoom(it, e2eRoomID)
         }
 
         ensureMembersHaveJoined(testHelper, aliceSession, newAccount, e2eRoomID)
@@ -740,37 +737,6 @@ class E2eeSanityTests : InstrumentedTest {
         }
     }
 
-    private fun waitForAndAcceptInviteInRoom(testHelper: CommonTestHelper, otherSession: Session, e2eRoomID: String) {
-        testHelper.waitWithLatch { latch ->
-            testHelper.retryPeriodicallyWithLatch(latch) {
-                val roomSummary = otherSession.getRoomSummary(e2eRoomID)
-                (roomSummary != null && roomSummary.membership == Membership.INVITE).also {
-                    if (it) {
-                        Log.v("#E2E TEST", "${otherSession.myUserId} can see the invite from alice")
-                    }
-                }
-            }
-        }
-
-        // not sure why it's taking so long :/
-        testHelper.runBlockingTest(90_000) {
-            Log.v("#E2E TEST", "${otherSession.myUserId} tries to join room $e2eRoomID")
-            try {
-                otherSession.roomService().joinRoom(e2eRoomID)
-            } catch (ex: JoinRoomFailure.JoinedWithTimeout) {
-                // it's ok we will wait after
-            }
-        }
-
-        Log.v("#E2E TEST", "${otherSession.myUserId} waiting for join echo ...")
-        testHelper.waitWithLatch {
-            testHelper.retryPeriodicallyWithLatch(it) {
-                val roomSummary = otherSession.getRoomSummary(e2eRoomID)
-                roomSummary != null && roomSummary.membership == Membership.JOIN
-            }
-        }
-    }
-
     private fun ensureIsDecrypted(testHelper: CommonTestHelper, sentEventIds: List<String>, session: Session, e2eRoomID: String) {
         testHelper.waitWithLatch { latch ->
             sentEventIds.forEach { sentEventId ->
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
new file mode 100644
index 0000000000..32a95008b1
--- /dev/null
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeShareKeysHistoryTest.kt
@@ -0,0 +1,424 @@
+/*
+ * 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 android.util.Log
+import androidx.test.filters.LargeTest
+import org.amshove.kluent.internal.assertEquals
+import org.amshove.kluent.internal.assertNotEquals
+import org.junit.Assert
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.junit.runners.MethodSorters
+import org.matrix.android.sdk.InstrumentedTest
+import org.matrix.android.sdk.api.query.QueryStringValue
+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.toContent
+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
+import org.matrix.android.sdk.api.session.room.failure.JoinRoomFailure
+import org.matrix.android.sdk.api.session.room.model.Membership
+import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility
+import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibilityContent
+import org.matrix.android.sdk.api.session.room.model.shouldShareHistory
+import org.matrix.android.sdk.common.CommonTestHelper
+import org.matrix.android.sdk.common.CommonTestHelper.Companion.runCryptoTest
+import org.matrix.android.sdk.common.CryptoTestHelper
+import org.matrix.android.sdk.common.SessionTestParams
+
+@RunWith(JUnit4::class)
+@FixMethodOrder(MethodSorters.JVM)
+@LargeTest
+class E2eeShareKeysHistoryTest : InstrumentedTest {
+
+    @Test
+    fun testShareMessagesHistoryWithRoomWorldReadable() {
+        testShareHistoryWithRoomVisibility(RoomHistoryVisibility.WORLD_READABLE)
+    }
+
+    @Test
+    fun testShareMessagesHistoryWithRoomShared() {
+        testShareHistoryWithRoomVisibility(RoomHistoryVisibility.SHARED)
+    }
+
+    @Test
+    fun testShareMessagesHistoryWithRoomJoined() {
+        testShareHistoryWithRoomVisibility(RoomHistoryVisibility.JOINED)
+    }
+
+    @Test
+    fun testShareMessagesHistoryWithRoomInvited() {
+        testShareHistoryWithRoomVisibility(RoomHistoryVisibility.INVITED)
+    }
+
+    /**
+     * In this test we create a room and test that new members
+     * can decrypt history when the room visibility is
+     * RoomHistoryVisibility.SHARED or RoomHistoryVisibility.WORLD_READABLE.
+     * We should not be able to view messages/decrypt otherwise
+     */
+    private fun testShareHistoryWithRoomVisibility(roomHistoryVisibility: RoomHistoryVisibility? = null) =
+            runCryptoTest(context()) { cryptoTestHelper, testHelper ->
+                val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(true, roomHistoryVisibility)
+
+                val e2eRoomID = cryptoTestData.roomId
+
+                // Alice
+                val aliceSession = cryptoTestData.firstSession.also {
+                    it.cryptoService().enableShareKeyOnInvite(true)
+                }
+                val aliceRoomPOV = aliceSession.roomService().getRoom(e2eRoomID)!!
+
+                // Bob
+                val bobSession = cryptoTestData.secondSession!!.also {
+                    it.cryptoService().enableShareKeyOnInvite(true)
+                }
+                val bobRoomPOV = bobSession.roomService().getRoom(e2eRoomID)!!
+
+                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)
+                Assert.assertTrue("Message should be sent", aliceMessageId != null)
+                Log.v("#E2E TEST", "Alice sent message to roomId: $e2eRoomID")
+
+                // Bob should be able to decrypt the message
+                testHelper.waitWithLatch { latch ->
+                    testHelper.retryPeriodicallyWithLatch(latch) {
+                        val timelineEvent = bobSession.roomService().getRoom(e2eRoomID)?.timelineService()?.getTimelineEvent(aliceMessageId!!)
+                        (timelineEvent != null &&
+                                timelineEvent.isEncrypted() &&
+                                timelineEvent.root.getClearType() == EventType.MESSAGE).also {
+                            if (it) {
+                                Log.v("#E2E TEST", "Bob can decrypt the message: ${timelineEvent?.root?.getDecryptedTextSummary()}")
+                            }
+                        }
+                    }
+                }
+
+                // Create a new user
+                val arisSession = testHelper.createAccount("aris", SessionTestParams(true)).also {
+                    it.cryptoService().enableShareKeyOnInvite(true)
+                }
+                Log.v("#E2E TEST", "Aris user created")
+
+                // Alice invites new user to the room
+                testHelper.runBlockingTest {
+                    Log.v("#E2E TEST", "Alice invites ${arisSession.myUserId}")
+                    aliceRoomPOV.membershipService().invite(arisSession.myUserId)
+                }
+
+                waitForAndAcceptInviteInRoom(arisSession, e2eRoomID, testHelper)
+
+                ensureMembersHaveJoined(aliceSession, arrayListOf(arisSession), e2eRoomID, testHelper)
+                Log.v("#E2E TEST", "Aris has joined roomId: $e2eRoomID")
+
+                when (roomHistoryVisibility) {
+                    RoomHistoryVisibility.WORLD_READABLE,
+                    RoomHistoryVisibility.SHARED,
+                    null
+                    -> {
+                        // Aris should be able to decrypt the message
+                        testHelper.waitWithLatch { latch ->
+                            testHelper.retryPeriodicallyWithLatch(latch) {
+                                val timelineEvent = arisSession.roomService().getRoom(e2eRoomID)?.timelineService()?.getTimelineEvent(aliceMessageId!!)
+                                (timelineEvent != null &&
+                                        timelineEvent.isEncrypted() &&
+                                        timelineEvent.root.getClearType() == EventType.MESSAGE
+                                        ).also {
+                                            if (it) {
+                                                Log.v("#E2E TEST", "Aris can decrypt the message: ${timelineEvent?.root?.getDecryptedTextSummary()}")
+                                            }
+                                        }
+                            }
+                        }
+                    }
+                    RoomHistoryVisibility.INVITED,
+                    RoomHistoryVisibility.JOINED -> {
+                        // Aris should not even be able to get the message
+                        testHelper.waitWithLatch { latch ->
+                            testHelper.retryPeriodicallyWithLatch(latch) {
+                                val timelineEvent = arisSession.roomService().getRoom(e2eRoomID)
+                                        ?.timelineService()
+                                        ?.getTimelineEvent(aliceMessageId!!)
+                                timelineEvent == null
+                            }
+                        }
+                    }
+                }
+
+                testHelper.signOutAndClose(arisSession)
+                cryptoTestData.cleanUp(testHelper)
+            }
+
+    @Test
+    fun testNeedsRotationFromWorldReadableToShared() {
+        testRotationDueToVisibilityChange(RoomHistoryVisibility.WORLD_READABLE, RoomHistoryVisibilityContent("shared"))
+    }
+
+    @Test
+    fun testNeedsRotationFromWorldReadableToInvited() {
+        testRotationDueToVisibilityChange(RoomHistoryVisibility.WORLD_READABLE, RoomHistoryVisibilityContent("invited"))
+    }
+
+    @Test
+    fun testNeedsRotationFromWorldReadableToJoined() {
+        testRotationDueToVisibilityChange(RoomHistoryVisibility.WORLD_READABLE, RoomHistoryVisibilityContent("joined"))
+    }
+
+    @Test
+    fun testNeedsRotationFromSharedToWorldReadable() {
+        testRotationDueToVisibilityChange(RoomHistoryVisibility.SHARED, RoomHistoryVisibilityContent("world_readable"))
+    }
+
+    @Test
+    fun testNeedsRotationFromSharedToInvited() {
+        testRotationDueToVisibilityChange(RoomHistoryVisibility.SHARED, RoomHistoryVisibilityContent("invited"))
+    }
+
+    @Test
+    fun testNeedsRotationFromSharedToJoined() {
+        testRotationDueToVisibilityChange(RoomHistoryVisibility.SHARED, RoomHistoryVisibilityContent("joined"))
+    }
+
+    @Test
+    fun testNeedsRotationFromInvitedToShared() {
+        testRotationDueToVisibilityChange(RoomHistoryVisibility.WORLD_READABLE, RoomHistoryVisibilityContent("shared"))
+    }
+
+    @Test
+    fun testNeedsRotationFromInvitedToWorldReadable() {
+        testRotationDueToVisibilityChange(RoomHistoryVisibility.WORLD_READABLE, RoomHistoryVisibilityContent("world_readable"))
+    }
+
+    @Test
+    fun testNeedsRotationFromInvitedToJoined() {
+        testRotationDueToVisibilityChange(RoomHistoryVisibility.WORLD_READABLE, RoomHistoryVisibilityContent("joined"))
+    }
+
+    @Test
+    fun testNeedsRotationFromJoinedToShared() {
+        testRotationDueToVisibilityChange(RoomHistoryVisibility.WORLD_READABLE, RoomHistoryVisibilityContent("shared"))
+    }
+
+    @Test
+    fun testNeedsRotationFromJoinedToInvited() {
+        testRotationDueToVisibilityChange(RoomHistoryVisibility.WORLD_READABLE, RoomHistoryVisibilityContent("invited"))
+    }
+
+    @Test
+    fun testNeedsRotationFromJoinedToWorldReadable() {
+        testRotationDueToVisibilityChange(RoomHistoryVisibility.WORLD_READABLE, RoomHistoryVisibilityContent("world_readable"))
+    }
+
+    /**
+     * In this test we will test that a rotation is needed when
+     * When the room's history visibility setting changes to world_readable or shared
+     * from invited or joined, or changes to invited or joined from world_readable or shared,
+     * senders that support this flag must rotate their megolm sessions.
+     */
+    private fun testRotationDueToVisibilityChange(
+            initRoomHistoryVisibility: RoomHistoryVisibility,
+            nextRoomHistoryVisibility: RoomHistoryVisibilityContent
+    ) {
+        val testHelper = CommonTestHelper(context())
+        val cryptoTestHelper = CryptoTestHelper(testHelper)
+
+        val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(true, initRoomHistoryVisibility)
+        val e2eRoomID = cryptoTestData.roomId
+
+        // Alice
+        val aliceSession = cryptoTestData.firstSession.also {
+            it.cryptoService().enableShareKeyOnInvite(true)
+        }
+        val aliceRoomPOV = aliceSession.roomService().getRoom(e2eRoomID)!!
+//        val aliceCryptoStore = (aliceSession.cryptoService() as DefaultCryptoService).cryptoStoreForTesting
+
+        // Bob
+        val bobSession = cryptoTestData.secondSession!!
+
+        val bobRoomPOV = bobSession.roomService().getRoom(e2eRoomID)!!
+
+        assertEquals(bobRoomPOV.roomSummary()?.joinedMembersCount, 2)
+        Log.v("#E2E TEST ROTATION", "Alice and Bob are in roomId: $e2eRoomID")
+
+        val aliceMessageId: String? = sendMessageInRoom(aliceRoomPOV, "Hello Bob, I am Alice!", testHelper)
+        Assert.assertTrue("Message should be sent", aliceMessageId != null)
+        Log.v("#E2E TEST ROTATION", "Alice sent message to roomId: $e2eRoomID")
+
+        // Bob should be able to decrypt the message
+        var firstAliceMessageMegolmSessionId: String? = null
+        val bobRoomPov = bobSession.roomService().getRoom(e2eRoomID)
+        testHelper.waitWithLatch { latch ->
+            testHelper.retryPeriodicallyWithLatch(latch) {
+                val timelineEvent = bobRoomPov
+                        ?.timelineService()
+                        ?.getTimelineEvent(aliceMessageId!!)
+                (timelineEvent != null &&
+                        timelineEvent.isEncrypted() &&
+                        timelineEvent.root.getClearType() == EventType.MESSAGE).also {
+                    if (it) {
+                        firstAliceMessageMegolmSessionId = timelineEvent?.root?.content?.get("session_id") as? String
+                        Log.v(
+                                "#E2E TEST",
+                                "Bob can decrypt the message (sid:$firstAliceMessageMegolmSessionId): ${timelineEvent?.root?.getDecryptedTextSummary()}"
+                        )
+                    }
+                }
+            }
+        }
+
+        Assert.assertNotNull("megolm session id can't be null", firstAliceMessageMegolmSessionId)
+
+        var secondAliceMessageSessionId: String? = null
+        sendMessageInRoom(aliceRoomPOV, "Other msg", testHelper)?.let { secondMessage ->
+            testHelper.waitWithLatch { latch ->
+                testHelper.retryPeriodicallyWithLatch(latch) {
+                    val timelineEvent = bobRoomPov
+                            ?.timelineService()
+                            ?.getTimelineEvent(secondMessage)
+                    (timelineEvent != null &&
+                            timelineEvent.isEncrypted() &&
+                            timelineEvent.root.getClearType() == EventType.MESSAGE).also {
+                        if (it) {
+                            secondAliceMessageSessionId = timelineEvent?.root?.content?.get("session_id") as? String
+                            Log.v(
+                                    "#E2E TEST",
+                                    "Bob can decrypt the message (sid:$secondAliceMessageSessionId): ${timelineEvent?.root?.getDecryptedTextSummary()}"
+                            )
+                        }
+                    }
+                }
+            }
+        }
+        assertEquals("No rotation needed session should be the same", firstAliceMessageMegolmSessionId, secondAliceMessageSessionId)
+        Log.v("#E2E TEST ROTATION", "No rotation needed yet")
+
+        // Let's change the room history visibility
+        testHelper.runBlockingTest {
+            aliceRoomPOV.stateService()
+                    .sendStateEvent(
+                            eventType = EventType.STATE_ROOM_HISTORY_VISIBILITY,
+                            stateKey = "",
+                            body = RoomHistoryVisibilityContent(
+                                    historyVisibilityStr = nextRoomHistoryVisibility.historyVisibilityStr
+                            ).toContent()
+                    )
+        }
+
+        // ensure that the state did synced down
+        testHelper.waitWithLatch { latch ->
+            testHelper.retryPeriodicallyWithLatch(latch) {
+                aliceRoomPOV.stateService().getStateEvent(EventType.STATE_ROOM_HISTORY_VISIBILITY, QueryStringValue.IsEmpty)?.content
+                        ?.toModel<RoomHistoryVisibilityContent>()?.historyVisibility == nextRoomHistoryVisibility.historyVisibility
+            }
+        }
+
+        testHelper.waitWithLatch { latch ->
+            testHelper.retryPeriodicallyWithLatch(latch) {
+                val roomVisibility = aliceSession.getRoom(e2eRoomID)!!
+                        .stateService()
+                        .getStateEvent(EventType.STATE_ROOM_HISTORY_VISIBILITY, QueryStringValue.IsEmpty)
+                        ?.content
+                        ?.toModel<RoomHistoryVisibilityContent>()
+                Log.v("#E2E TEST ROTATION", "Room visibility changed from: ${initRoomHistoryVisibility.name} to: ${roomVisibility?.historyVisibility?.name}")
+                roomVisibility?.historyVisibility == nextRoomHistoryVisibility.historyVisibility
+            }
+        }
+
+        var aliceThirdMessageSessionId: String? = null
+        sendMessageInRoom(aliceRoomPOV, "Message after visibility change", testHelper)?.let { thirdMessage ->
+            testHelper.waitWithLatch { latch ->
+                testHelper.retryPeriodicallyWithLatch(latch) {
+                    val timelineEvent = bobRoomPov
+                            ?.timelineService()
+                            ?.getTimelineEvent(thirdMessage)
+                    (timelineEvent != null &&
+                            timelineEvent.isEncrypted() &&
+                            timelineEvent.root.getClearType() == EventType.MESSAGE).also {
+                        if (it) {
+                            aliceThirdMessageSessionId = timelineEvent?.root?.content?.get("session_id") as? String
+                        }
+                    }
+                }
+            }
+        }
+
+        when {
+            initRoomHistoryVisibility.shouldShareHistory() == nextRoomHistoryVisibility.historyVisibility?.shouldShareHistory() -> {
+                assertEquals("Session shouldn't have been rotated", secondAliceMessageSessionId, aliceThirdMessageSessionId)
+                Log.v("#E2E TEST ROTATION", "Rotation is not needed")
+            }
+            initRoomHistoryVisibility.shouldShareHistory() != nextRoomHistoryVisibility.historyVisibility!!.shouldShareHistory() -> {
+                assertNotEquals("Session should have been rotated", secondAliceMessageSessionId, aliceThirdMessageSessionId)
+                Log.v("#E2E TEST ROTATION", "Rotation is needed!")
+            }
+        }
+
+        cryptoTestData.cleanUp(testHelper)
+    }
+
+    private fun sendMessageInRoom(aliceRoomPOV: Room, text: String, testHelper: CommonTestHelper): String? {
+        return testHelper.sendTextMessage(aliceRoomPOV, text, 1).firstOrNull()?.eventId
+    }
+
+    private fun ensureMembersHaveJoined(aliceSession: Session, otherAccounts: List<Session>, e2eRoomID: String, testHelper: CommonTestHelper) {
+        testHelper.waitWithLatch { latch ->
+            testHelper.retryPeriodicallyWithLatch(latch) {
+                otherAccounts.map {
+                    aliceSession.roomService().getRoomMember(it.myUserId, e2eRoomID)?.membership
+                }.all {
+                    it == Membership.JOIN
+                }
+            }
+        }
+    }
+
+    private fun waitForAndAcceptInviteInRoom(otherSession: Session, e2eRoomID: String, testHelper: CommonTestHelper) {
+        testHelper.waitWithLatch { latch ->
+            testHelper.retryPeriodicallyWithLatch(latch) {
+                val roomSummary = otherSession.roomService().getRoomSummary(e2eRoomID)
+                (roomSummary != null && roomSummary.membership == Membership.INVITE).also {
+                    if (it) {
+                        Log.v("#E2E TEST", "${otherSession.myUserId} can see the invite from alice")
+                    }
+                }
+            }
+        }
+
+        testHelper.runBlockingTest(60_000) {
+            Log.v("#E2E TEST", "${otherSession.myUserId} tries to join room $e2eRoomID")
+            try {
+                otherSession.roomService().joinRoom(e2eRoomID)
+            } catch (ex: JoinRoomFailure.JoinedWithTimeout) {
+                // it's ok we will wait after
+            }
+        }
+
+        Log.v("#E2E TEST", "${otherSession.myUserId} waiting for join echo ...")
+        testHelper.waitWithLatch {
+            testHelper.retryPeriodicallyWithLatch(it) {
+                val roomSummary = otherSession.roomService().getRoomSummary(e2eRoomID)
+                roomSummary != null && roomSummary.membership == Membership.JOIN
+            }
+        }
+    }
+}
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/PreShareKeysTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/PreShareKeysTest.kt
index e37ae5be86..e8e7b1d708 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/PreShareKeysTest.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/PreShareKeysTest.kt
@@ -72,7 +72,7 @@ class PreShareKeysTest : InstrumentedTest {
         assertNotNull("Bob should have received and decrypted a room key event from alice", bobInboundForAlice)
         assertEquals("Wrong room", e2eRoomID, bobInboundForAlice!!.roomId)
 
-        val megolmSessionId = bobInboundForAlice.olmInboundGroupSession!!.sessionIdentifier()
+        val megolmSessionId = bobInboundForAlice.session.sessionIdentifier()
 
         assertEquals("Wrong session", aliceOutboundSessionInRoom, megolmSessionId)
 
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupScenarioData.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupScenarioData.kt
index 45fdb9e1e3..cf201611a0 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupScenarioData.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupScenarioData.kt
@@ -19,14 +19,14 @@ package org.matrix.android.sdk.internal.crypto.keysbackup
 import org.matrix.android.sdk.api.session.Session
 import org.matrix.android.sdk.common.CommonTestHelper
 import org.matrix.android.sdk.common.CryptoTestData
-import org.matrix.android.sdk.internal.crypto.model.OlmInboundGroupSessionWrapper2
+import org.matrix.android.sdk.internal.crypto.model.MXInboundMegolmSessionWrapper
 
 /**
  * Data class to store result of [KeysBackupTestHelper.createKeysBackupScenarioWithPassword]
  */
 internal data class KeysBackupScenarioData(
         val cryptoTestData: CryptoTestData,
-        val aliceKeys: List<OlmInboundGroupSessionWrapper2>,
+        val aliceKeys: List<MXInboundMegolmSessionWrapper>,
         val prepareKeysBackupDataResult: PrepareKeysBackupDataResult,
         val aliceSession2: Session
 ) {
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupTest.kt
index fb498e0de5..e160938721 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupTest.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupTest.kt
@@ -301,7 +301,7 @@ class KeysBackupTest : InstrumentedTest {
         val keyBackupCreationInfo = keysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup).megolmBackupCreationInfo
 
         // - Check encryptGroupSession() returns stg
-        val keyBackupData = keysBackup.encryptGroupSession(session)
+        val keyBackupData = testHelper.runBlockingTest { keysBackup.encryptGroupSession(session) }
         assertNotNull(keyBackupData)
         assertNotNull(keyBackupData!!.sessionData)
 
@@ -312,7 +312,7 @@ class KeysBackupTest : InstrumentedTest {
         val sessionData = keysBackup
                 .decryptKeyBackupData(
                         keyBackupData,
-                        session.olmInboundGroupSession!!.sessionIdentifier(),
+                        session.safeSessionId!!,
                         cryptoTestData.roomId,
                         decryption!!
                 )
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupTestHelper.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupTestHelper.kt
index 38f94c5103..2cc2b506b9 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupTestHelper.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupTestHelper.kt
@@ -187,7 +187,7 @@ internal class KeysBackupTestHelper(
         // - Alice must have the same keys on both devices
         for (aliceKey1 in testData.aliceKeys) {
             val aliceKey2 = (testData.aliceSession2.cryptoService().keysBackupService() as DefaultKeysBackupService).store
-                    .getInboundGroupSession(aliceKey1.olmInboundGroupSession!!.sessionIdentifier(), aliceKey1.senderKey!!)
+                    .getInboundGroupSession(aliceKey1.safeSessionId!!, aliceKey1.senderKey!!)
             Assert.assertNotNull(aliceKey2)
             assertKeysEquals(aliceKey1.exportKeys(), aliceKey2!!.exportKeys())
         }
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceCreationTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceCreationTest.kt
index 38136ff5ce..2cd579df24 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceCreationTest.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceCreationTest.kt
@@ -56,19 +56,17 @@ class SpaceCreationTest : InstrumentedTest {
         val roomName = "My Space"
         val topic = "A public space for test"
         var spaceId: String = ""
-        commonTestHelper.waitWithLatch {
+        commonTestHelper.runBlockingTest {
             spaceId = session.spaceService().createSpace(roomName, topic, null, true)
-            // wait a bit to let the summary update it self :/
-            it.countDown()
         }
-        Thread.sleep(4_000)
 
-        val syncedSpace = session.spaceService().getSpace(spaceId)
         commonTestHelper.waitWithLatch {
             commonTestHelper.retryPeriodicallyWithLatch(it) {
-                syncedSpace?.asRoom()?.roomSummary()?.name != null
+                session.spaceService().getSpace(spaceId)?.asRoom()?.roomSummary()?.name != null
             }
         }
+
+        val syncedSpace = session.spaceService().getSpace(spaceId)
         assertEquals("Room name should be set", roomName, syncedSpace?.asRoom()?.roomSummary()?.name)
         assertEquals("Room topic should be set", topic, syncedSpace?.asRoom()?.roomSummary()?.topic)
         // assertEquals(topic, syncedSpace.asRoom().roomSummary()?., "Room topic should be set")
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceHierarchyTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceHierarchyTest.kt
index 63ca963479..80020665f8 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceHierarchyTest.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceHierarchyTest.kt
@@ -20,7 +20,6 @@ import android.util.Log
 import androidx.lifecycle.Observer
 import kotlinx.coroutines.runBlocking
 import org.junit.Assert.assertEquals
-import org.junit.Assert.assertNotNull
 import org.junit.Assert.assertTrue
 import org.junit.FixMethodOrder
 import org.junit.Ignore
@@ -62,47 +61,40 @@ class SpaceHierarchyTest : InstrumentedTest {
         val spaceName = "My Space"
         val topic = "A public space for test"
         var spaceId = ""
-        commonTestHelper.waitWithLatch {
+        commonTestHelper.runBlockingTest {
             spaceId = session.spaceService().createSpace(spaceName, topic, null, true)
-            it.countDown()
         }
 
         val syncedSpace = session.spaceService().getSpace(spaceId)
 
         var roomId = ""
-        commonTestHelper.waitWithLatch {
+        commonTestHelper.runBlockingTest {
             roomId = session.roomService().createRoom(CreateRoomParams().apply { name = "General" })
-            it.countDown()
         }
 
         val viaServers = listOf(session.sessionParams.homeServerHost ?: "")
 
-        commonTestHelper.waitWithLatch {
+        commonTestHelper.runBlockingTest {
             syncedSpace!!.addChildren(roomId, viaServers, null, true)
-            it.countDown()
         }
 
-        commonTestHelper.waitWithLatch {
+        commonTestHelper.runBlockingTest {
             session.spaceService().setSpaceParent(roomId, spaceId, true, viaServers)
-            it.countDown()
         }
 
-        Thread.sleep(9000)
-
-        val parents = session.getRoom(roomId)?.roomSummary()?.spaceParents
-        val canonicalParents = session.getRoom(roomId)?.roomSummary()?.spaceParents?.filter { it.canonical == true }
-
-        parents?.forEach {
-            Log.d("## TEST", "parent : $it")
+        commonTestHelper.waitWithLatch { latch ->
+            commonTestHelper.retryPeriodicallyWithLatch(latch) {
+                val parents = session.getRoom(roomId)?.roomSummary()?.spaceParents
+                val canonicalParents = session.getRoom(roomId)?.roomSummary()?.spaceParents?.filter { it.canonical == true }
+                parents?.forEach {
+                    Log.d("## TEST", "parent : $it")
+                }
+                parents?.size == 1 &&
+                        parents.first().roomSummary?.name == spaceName &&
+                        canonicalParents?.size == 1 &&
+                        canonicalParents.first().roomSummary?.name == spaceName
+            }
         }
-
-        assertNotNull(parents)
-        assertEquals(1, parents!!.size)
-        assertEquals(spaceName, parents.first().roomSummary?.name)
-
-        assertNotNull(canonicalParents)
-        assertEquals(1, canonicalParents!!.size)
-        assertEquals(spaceName, canonicalParents.first().roomSummary?.name)
     }
 
 //    @Test
@@ -173,52 +165,55 @@ class SpaceHierarchyTest : InstrumentedTest {
 //    }
 
     @Test
-    fun testFilteringBySpace() = CommonTestHelper.runSessionTest(context()) { commonTestHelper ->
+    fun testFilteringBySpace() = runSessionTest(context()) { commonTestHelper ->
         val session = commonTestHelper.createAccount("John", SessionTestParams(true))
 
         val spaceAInfo = createPublicSpace(
-                session, "SpaceA", listOf(
-                Triple("A1", true /*auto-join*/, true/*canonical*/),
-                Triple("A2", true, true)
-        )
+                commonTestHelper,
+                session, "SpaceA",
+                listOf(
+                        Triple("A1", true /*auto-join*/, true/*canonical*/),
+                        Triple("A2", true, true)
+                )
         )
 
         /* val spaceBInfo = */ createPublicSpace(
-            session, "SpaceB", listOf(
-            Triple("B1", true /*auto-join*/, true/*canonical*/),
-            Triple("B2", true, true),
-            Triple("B3", true, true)
-    )
+            commonTestHelper,
+            session, "SpaceB",
+            listOf(
+                    Triple("B1", true /*auto-join*/, true/*canonical*/),
+                    Triple("B2", true, true),
+                    Triple("B3", true, true)
+            )
     )
 
         val spaceCInfo = createPublicSpace(
-                session, "SpaceC", listOf(
-                Triple("C1", true /*auto-join*/, true/*canonical*/),
-                Triple("C2", true, true)
-        )
+                commonTestHelper,
+                session, "SpaceC",
+                listOf(
+                        Triple("C1", true /*auto-join*/, true/*canonical*/),
+                        Triple("C2", true, true)
+                )
         )
 
         // add C as a subspace of A
         val spaceA = session.spaceService().getSpace(spaceAInfo.spaceId)
         val viaServers = listOf(session.sessionParams.homeServerHost ?: "")
-        commonTestHelper.waitWithLatch {
+        commonTestHelper.runBlockingTest {
             spaceA!!.addChildren(spaceCInfo.spaceId, viaServers, null, true)
             session.spaceService().setSpaceParent(spaceCInfo.spaceId, spaceAInfo.spaceId, true, viaServers)
-            it.countDown()
         }
 
         // Create orphan rooms
 
         var orphan1 = ""
-        commonTestHelper.waitWithLatch {
+        commonTestHelper.runBlockingTest {
             orphan1 = session.roomService().createRoom(CreateRoomParams().apply { name = "O1" })
-            it.countDown()
         }
 
         var orphan2 = ""
-        commonTestHelper.waitWithLatch {
+        commonTestHelper.runBlockingTest {
             orphan2 = session.roomService().createRoom(CreateRoomParams().apply { name = "O2" })
-            it.countDown()
         }
 
         val allRooms = session.roomService().getRoomSummaries(roomSummaryQueryParams { excludeType = listOf(RoomType.SPACE) })
@@ -240,10 +235,9 @@ class SpaceHierarchyTest : InstrumentedTest {
         assertTrue("A1 should be a grand child of A", aChildren.any { it.name == "C2" })
 
         // Add a non canonical child and check that it does not appear as orphan
-        commonTestHelper.waitWithLatch {
+        commonTestHelper.runBlockingTest {
             val a3 = session.roomService().createRoom(CreateRoomParams().apply { name = "A3" })
             spaceA!!.addChildren(a3, viaServers, null, false)
-            it.countDown()
         }
 
         Thread.sleep(6_000)
@@ -255,37 +249,39 @@ class SpaceHierarchyTest : InstrumentedTest {
 
     @Test
     @Ignore("This test will be ignored until it is fixed")
-    fun testBreakCycle() = CommonTestHelper.runSessionTest(context()) { commonTestHelper ->
+    fun testBreakCycle() = runSessionTest(context()) { commonTestHelper ->
         val session = commonTestHelper.createAccount("John", SessionTestParams(true))
 
         val spaceAInfo = createPublicSpace(
-                session, "SpaceA", listOf(
-                Triple("A1", true /*auto-join*/, true/*canonical*/),
-                Triple("A2", true, true)
-        )
+                commonTestHelper,
+                session, "SpaceA",
+                listOf(
+                        Triple("A1", true /*auto-join*/, true/*canonical*/),
+                        Triple("A2", true, true)
+                )
         )
 
         val spaceCInfo = createPublicSpace(
-                session, "SpaceC", listOf(
-                Triple("C1", true /*auto-join*/, true/*canonical*/),
-                Triple("C2", true, true)
-        )
+                commonTestHelper,
+                session, "SpaceC",
+                listOf(
+                        Triple("C1", true /*auto-join*/, true/*canonical*/),
+                        Triple("C2", true, true)
+                )
         )
 
         // add C as a subspace of A
         val spaceA = session.spaceService().getSpace(spaceAInfo.spaceId)
         val viaServers = listOf(session.sessionParams.homeServerHost ?: "")
-        commonTestHelper.waitWithLatch {
+        commonTestHelper.runBlockingTest {
             spaceA!!.addChildren(spaceCInfo.spaceId, viaServers, null, true)
             session.spaceService().setSpaceParent(spaceCInfo.spaceId, spaceAInfo.spaceId, true, viaServers)
-            it.countDown()
         }
 
         // add back A as subspace of C
-        commonTestHelper.waitWithLatch {
+        commonTestHelper.runBlockingTest {
             val spaceC = session.spaceService().getSpace(spaceCInfo.spaceId)
             spaceC!!.addChildren(spaceAInfo.spaceId, viaServers, null, true)
-            it.countDown()
         }
 
         // A -> C -> A
@@ -300,37 +296,46 @@ class SpaceHierarchyTest : InstrumentedTest {
     }
 
     @Test
-    fun testLiveFlatChildren() = CommonTestHelper.runSessionTest(context()) { commonTestHelper ->
+    fun testLiveFlatChildren() = runSessionTest(context()) { commonTestHelper ->
         val session = commonTestHelper.createAccount("John", SessionTestParams(true))
 
         val spaceAInfo = createPublicSpace(
-                session, "SpaceA", listOf(
-                Triple("A1", true /*auto-join*/, true/*canonical*/),
-                Triple("A2", true, true)
-        )
+                commonTestHelper,
+                session,
+                "SpaceA",
+                listOf(
+                        Triple("A1", true /*auto-join*/, true/*canonical*/),
+                        Triple("A2", true, true)
+                )
         )
 
         val spaceBInfo = createPublicSpace(
-                session, "SpaceB", listOf(
-                Triple("B1", true /*auto-join*/, true/*canonical*/),
-                Triple("B2", true, true),
-                Triple("B3", true, true)
-        )
+                commonTestHelper,
+                session,
+                "SpaceB",
+                listOf(
+                        Triple("B1", true /*auto-join*/, true/*canonical*/),
+                        Triple("B2", true, true),
+                        Triple("B3", true, true)
+                )
         )
 
         // add B as a subspace of A
         val spaceA = session.spaceService().getSpace(spaceAInfo.spaceId)
         val viaServers = listOf(session.sessionParams.homeServerHost ?: "")
-        runBlocking {
+        commonTestHelper.runBlockingTest {
             spaceA!!.addChildren(spaceBInfo.spaceId, viaServers, null, true)
             session.spaceService().setSpaceParent(spaceBInfo.spaceId, spaceAInfo.spaceId, true, viaServers)
         }
 
         val spaceCInfo = createPublicSpace(
-                session, "SpaceC", listOf(
-                Triple("C1", true /*auto-join*/, true/*canonical*/),
-                Triple("C2", true, true)
-        )
+                commonTestHelper,
+                session,
+                "SpaceC",
+                listOf(
+                        Triple("C1", true /*auto-join*/, true/*canonical*/),
+                        Triple("C2", true, true)
+                )
         )
 
         commonTestHelper.waitWithLatch { latch ->
@@ -348,13 +353,13 @@ class SpaceHierarchyTest : InstrumentedTest {
                 }
             }
 
+            flatAChildren.observeForever(childObserver)
+
             // add C as subspace of B
             val spaceB = session.spaceService().getSpace(spaceBInfo.spaceId)
             spaceB!!.addChildren(spaceCInfo.spaceId, viaServers, null, true)
 
             // C1 and C2 should be in flatten child of A now
-
-            flatAChildren.observeForever(childObserver)
         }
 
         // Test part one of the rooms
@@ -374,10 +379,10 @@ class SpaceHierarchyTest : InstrumentedTest {
                 }
             }
 
-            // part from b room
-            session.roomService().leaveRoom(bRoomId)
             // The room should have disapear from flat children
             flatAChildren.observeForever(childObserver)
+            // part from b room
+            session.roomService().leaveRoom(bRoomId)
         }
         commonTestHelper.signOutAndClose(session)
     }
@@ -388,6 +393,7 @@ class SpaceHierarchyTest : InstrumentedTest {
     )
 
     private fun createPublicSpace(
+            commonTestHelper: CommonTestHelper,
             session: Session,
             spaceName: String,
             childInfo: List<Triple<String, Boolean, Boolean?>>
@@ -395,29 +401,27 @@ class SpaceHierarchyTest : InstrumentedTest {
     ): TestSpaceCreationResult {
         var spaceId = ""
         var roomIds: List<String> = emptyList()
-        runSessionTest(context()) { commonTestHelper ->
-            commonTestHelper.waitWithLatch { latch ->
-                spaceId = session.spaceService().createSpace(spaceName, "Test Topic", null, true)
-                val syncedSpace = session.spaceService().getSpace(spaceId)
-                val viaServers = listOf(session.sessionParams.homeServerHost ?: "")
+        commonTestHelper.runBlockingTest {
+            spaceId = session.spaceService().createSpace(spaceName, "Test Topic", null, true)
+            val syncedSpace = session.spaceService().getSpace(spaceId)
+            val viaServers = listOf(session.sessionParams.homeServerHost ?: "")
 
-                roomIds = childInfo.map { entry ->
-                    session.roomService().createRoom(CreateRoomParams().apply { name = entry.first })
+            roomIds = childInfo.map { entry ->
+                session.roomService().createRoom(CreateRoomParams().apply { name = entry.first })
+            }
+            roomIds.forEachIndexed { index, roomId ->
+                syncedSpace!!.addChildren(roomId, viaServers, null, childInfo[index].second)
+                val canonical = childInfo[index].third
+                if (canonical != null) {
+                    session.spaceService().setSpaceParent(roomId, spaceId, canonical, viaServers)
                 }
-                roomIds.forEachIndexed { index, roomId ->
-                    syncedSpace!!.addChildren(roomId, viaServers, null, childInfo[index].second)
-                    val canonical = childInfo[index].third
-                    if (canonical != null) {
-                        session.spaceService().setSpaceParent(roomId, spaceId, canonical, viaServers)
-                    }
-                }
-                latch.countDown()
             }
         }
         return TestSpaceCreationResult(spaceId, roomIds)
     }
 
     private fun createPrivateSpace(
+            commonTestHelper: CommonTestHelper,
             session: Session,
             spaceName: String,
             childInfo: List<Triple<String, Boolean, Boolean?>>
@@ -425,34 +429,31 @@ class SpaceHierarchyTest : InstrumentedTest {
     ): TestSpaceCreationResult {
         var spaceId = ""
         var roomIds: List<String> = emptyList()
-        runSessionTest(context()) { commonTestHelper ->
-            commonTestHelper.waitWithLatch { latch ->
-                spaceId = session.spaceService().createSpace(spaceName, "My Private Space", null, false)
-                val syncedSpace = session.spaceService().getSpace(spaceId)
-                val viaServers = listOf(session.sessionParams.homeServerHost ?: "")
-                roomIds =
-                        childInfo.map { entry ->
-                            val homeServerCapabilities = session
-                                    .homeServerCapabilitiesService()
-                                    .getHomeServerCapabilities()
-                            session.roomService().createRoom(CreateRoomParams().apply {
-                                name = entry.first
-                                this.featurePreset = RestrictedRoomPreset(
-                                        homeServerCapabilities,
-                                        listOf(
-                                                RoomJoinRulesAllowEntry.restrictedToRoom(spaceId)
-                                        )
-                                )
-                            })
-                        }
-                roomIds.forEachIndexed { index, roomId ->
-                    syncedSpace!!.addChildren(roomId, viaServers, null, childInfo[index].second)
-                    val canonical = childInfo[index].third
-                    if (canonical != null) {
-                        session.spaceService().setSpaceParent(roomId, spaceId, canonical, viaServers)
+        commonTestHelper.runBlockingTest {
+            spaceId = session.spaceService().createSpace(spaceName, "My Private Space", null, false)
+            val syncedSpace = session.spaceService().getSpace(spaceId)
+            val viaServers = listOf(session.sessionParams.homeServerHost ?: "")
+            roomIds =
+                    childInfo.map { entry ->
+                        val homeServerCapabilities = session
+                                .homeServerCapabilitiesService()
+                                .getHomeServerCapabilities()
+                        session.roomService().createRoom(CreateRoomParams().apply {
+                            name = entry.first
+                            this.featurePreset = RestrictedRoomPreset(
+                                    homeServerCapabilities,
+                                    listOf(
+                                            RoomJoinRulesAllowEntry.restrictedToRoom(spaceId)
+                                    )
+                            )
+                        })
                     }
+            roomIds.forEachIndexed { index, roomId ->
+                syncedSpace!!.addChildren(roomId, viaServers, null, childInfo[index].second)
+                val canonical = childInfo[index].third
+                if (canonical != null) {
+                    session.spaceService().setSpaceParent(roomId, spaceId, canonical, viaServers)
                 }
-                latch.countDown()
             }
         }
         return TestSpaceCreationResult(spaceId, roomIds)
@@ -463,25 +464,31 @@ class SpaceHierarchyTest : InstrumentedTest {
         val session = commonTestHelper.createAccount("John", SessionTestParams(true))
 
         /* val spaceAInfo = */ createPublicSpace(
-            session, "SpaceA", listOf(
-            Triple("A1", true /*auto-join*/, true/*canonical*/),
-            Triple("A2", true, true)
-    )
+            commonTestHelper,
+            session, "SpaceA",
+            listOf(
+                    Triple("A1", true /*auto-join*/, true/*canonical*/),
+                    Triple("A2", true, true)
+            )
     )
 
         val spaceBInfo = createPublicSpace(
-                session, "SpaceB", listOf(
-                Triple("B1", true /*auto-join*/, true/*canonical*/),
-                Triple("B2", true, true),
-                Triple("B3", true, true)
-        )
+                commonTestHelper,
+                session, "SpaceB",
+                listOf(
+                        Triple("B1", true /*auto-join*/, true/*canonical*/),
+                        Triple("B2", true, true),
+                        Triple("B3", true, true)
+                )
         )
 
         val spaceCInfo = createPublicSpace(
-                session, "SpaceC", listOf(
-                Triple("C1", true /*auto-join*/, true/*canonical*/),
-                Triple("C2", true, true)
-        )
+                commonTestHelper,
+                session, "SpaceC",
+                listOf(
+                        Triple("C1", true /*auto-join*/, true/*canonical*/),
+                        Triple("C2", true, true)
+                )
         )
 
         val viaServers = listOf(session.sessionParams.homeServerHost ?: "")
@@ -490,7 +497,6 @@ class SpaceHierarchyTest : InstrumentedTest {
         runBlocking {
             val spaceB = session.spaceService().getSpace(spaceBInfo.spaceId)
             spaceB!!.addChildren(spaceCInfo.spaceId, viaServers, null, true)
-            Thread.sleep(6_000)
         }
 
 //        Thread.sleep(4_000)
@@ -501,11 +507,12 @@ class SpaceHierarchyTest : InstrumentedTest {
         //   + C
         //     + c1, c2
 
-        val rootSpaces = commonTestHelper.runBlockingTest {
-            session.spaceService().getRootSpaceSummaries()
+        commonTestHelper.waitWithLatch { latch ->
+            commonTestHelper.retryPeriodicallyWithLatch(latch) {
+                val rootSpaces = commonTestHelper.runBlockingTest { session.spaceService().getRootSpaceSummaries() }
+                rootSpaces.size == 2
+            }
         }
-
-        assertEquals("Unexpected number of root spaces ${rootSpaces.map { it.name }}", 2, rootSpaces.size)
     }
 
     @Test
@@ -514,10 +521,12 @@ class SpaceHierarchyTest : InstrumentedTest {
         val bobSession = commonTestHelper.createAccount("Bib", SessionTestParams(true))
 
         val spaceAInfo = createPrivateSpace(
-                aliceSession, "Private Space A", listOf(
-                Triple("General", true /*suggested*/, true/*canonical*/),
-                Triple("Random", true, true)
-        )
+                commonTestHelper,
+                aliceSession, "Private Space A",
+                listOf(
+                        Triple("General", true /*suggested*/, true/*canonical*/),
+                        Triple("Random", true, true)
+                )
         )
 
         commonTestHelper.runBlockingTest {
@@ -529,10 +538,9 @@ class SpaceHierarchyTest : InstrumentedTest {
         }
 
         var bobRoomId = ""
-        commonTestHelper.waitWithLatch {
+        commonTestHelper.runBlockingTest {
             bobRoomId = bobSession.roomService().createRoom(CreateRoomParams().apply { name = "A Bob Room" })
             bobSession.getRoom(bobRoomId)!!.membershipService().invite(aliceSession.myUserId)
-            it.countDown()
         }
 
         commonTestHelper.runBlockingTest {
@@ -545,9 +553,8 @@ class SpaceHierarchyTest : InstrumentedTest {
             }
         }
 
-        commonTestHelper.waitWithLatch {
+        commonTestHelper.runBlockingTest {
             bobSession.spaceService().setSpaceParent(bobRoomId, spaceAInfo.spaceId, false, listOf(bobSession.sessionParams.homeServerHost ?: ""))
-            it.countDown()
         }
 
         commonTestHelper.waitWithLatch { latch ->
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 9507ddda65..015cb6a1a2 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
@@ -38,4 +38,5 @@ data class MXCryptoConfig constructor(
          * You can limit request only to your sessions by turning this setting to `true`
          */
         val limitRoomKeyRequestsToMyDevices: Boolean = false,
-)
+
+        )
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt
index 638da11804..a5e05f69e0 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt
@@ -40,6 +40,7 @@ import org.matrix.android.sdk.api.session.crypto.verification.VerificationServic
 import org.matrix.android.sdk.api.session.events.model.Content
 import org.matrix.android.sdk.api.session.events.model.Event
 import org.matrix.android.sdk.api.session.events.model.content.RoomKeyWithHeldContent
+import org.matrix.android.sdk.internal.crypto.model.SessionInfo
 
 interface CryptoService {
 
@@ -84,6 +85,20 @@ interface CryptoService {
 
     fun isKeyGossipingEnabled(): Boolean
 
+    /**
+     * As per MSC3061.
+     * If true will make it possible to share part of e2ee room history
+     * on invite depending on the room visibility setting.
+     */
+    fun enableShareKeyOnInvite(enable: Boolean)
+
+    /**
+     * As per MSC3061.
+     * If true will make it possible to share part of e2ee room history
+     * on invite depending on the room visibility setting.
+     */
+    fun isShareKeysOnInviteEnabled(): Boolean
+
     fun setRoomUnBlacklistUnverifiedDevices(roomId: String)
 
     fun getDeviceTrackingStatus(userId: String): Int
@@ -176,4 +191,9 @@ interface CryptoService {
      * send, in order to speed up sending of the message.
      */
     fun prepareToEncrypt(roomId: String, callback: MatrixCallback<Unit>)
+
+    /**
+     * Share all inbound sessions of the last chunk messages to the provided userId devices.
+     */
+    suspend fun sendSharedHistoryKeys(roomId: String, userId: String, sessionInfoSet: Set<SessionInfo>?)
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/ForwardedRoomKeyContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/ForwardedRoomKeyContent.kt
index 3df4ef7c9a..664cd00e94 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/ForwardedRoomKeyContent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/ForwardedRoomKeyContent.kt
@@ -69,5 +69,11 @@ data class ForwardedRoomKeyContent(
          * private part of this key unless they have done device verification.
          */
         @Json(name = "sender_claimed_ed25519_key")
-        val senderClaimedEd25519Key: String? = null
+        val senderClaimedEd25519Key: String? = null,
+
+        /**
+         * MSC3061 Identifies keys that were sent when the room's visibility setting was set to world_readable or shared.
+         */
+        @Json(name = "org.matrix.msc3061.shared_history")
+        val sharedHistory: Boolean? = false,
 )
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/content/RoomKeyContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/content/RoomKeyContent.kt
index 0830a566ab..5b18d29ea0 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/content/RoomKeyContent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/content/RoomKeyContent.kt
@@ -38,5 +38,12 @@ data class RoomKeyContent(
 
         // should be a Long but it is sometimes a double
         @Json(name = "chain_index")
-        val chainIndex: Any? = null
+        val chainIndex: Any? = null,
+
+        /**
+         * MSC3061 Identifies keys that were sent when the room's visibility setting was set to world_readable or shared.
+         */
+        @Json(name = "org.matrix.msc3061.shared_history")
+        val sharedHistory: Boolean? = false
+
 )
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomHistoryVisibility.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomHistoryVisibility.kt
index 06069f2646..2b0ea1d8fb 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomHistoryVisibility.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomHistoryVisibility.kt
@@ -48,3 +48,9 @@ enum class RoomHistoryVisibility {
      */
     @Json(name = "joined") JOINED
 }
+
+/**
+ * Room history should be shared only if room visibility is world_readable or shared.
+ */
+internal fun RoomHistoryVisibility.shouldShareHistory() =
+        this == RoomHistoryVisibility.WORLD_READABLE || this == RoomHistoryVisibility.SHARED
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 e0bcde2296..850a4379ca 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
@@ -71,6 +71,7 @@ import org.matrix.android.sdk.api.session.room.model.Membership
 import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility
 import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibilityContent
 import org.matrix.android.sdk.api.session.room.model.RoomMemberContent
+import org.matrix.android.sdk.api.session.room.model.shouldShareHistory
 import org.matrix.android.sdk.api.session.sync.model.SyncResponse
 import org.matrix.android.sdk.internal.crypto.actions.MegolmSessionDataImporter
 import org.matrix.android.sdk.internal.crypto.actions.SetDeviceVerificationAction
@@ -81,6 +82,7 @@ import org.matrix.android.sdk.internal.crypto.algorithms.olm.MXOlmEncryptionFact
 import org.matrix.android.sdk.internal.crypto.crosssigning.DefaultCrossSigningService
 import org.matrix.android.sdk.internal.crypto.keysbackup.DefaultKeysBackupService
 import org.matrix.android.sdk.internal.crypto.model.MXKey.Companion.KEY_SIGNED_CURVE_25519_TYPE
+import org.matrix.android.sdk.internal.crypto.model.SessionInfo
 import org.matrix.android.sdk.internal.crypto.model.toRest
 import org.matrix.android.sdk.internal.crypto.repository.WarnOnUnknownDeviceRepository
 import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
@@ -963,8 +965,12 @@ internal class DefaultCryptoService @Inject constructor(
     private fun onRoomHistoryVisibilityEvent(roomId: String, event: Event) {
         if (!event.isStateEvent()) return
         val eventContent = event.content.toModel<RoomHistoryVisibilityContent>()
-        eventContent?.historyVisibility?.let {
-            cryptoStore.setShouldEncryptForInvitedMembers(roomId, it != RoomHistoryVisibility.JOINED)
+        val historyVisibility = eventContent?.historyVisibility
+        if (historyVisibility == null) {
+            cryptoStore.setShouldShareHistory(roomId, false)
+        } else {
+            cryptoStore.setShouldEncryptForInvitedMembers(roomId, historyVisibility != RoomHistoryVisibility.JOINED)
+            cryptoStore.setShouldShareHistory(roomId, historyVisibility.shouldShareHistory())
         }
     }
 
@@ -1111,6 +1117,10 @@ internal class DefaultCryptoService @Inject constructor(
 
     override fun isKeyGossipingEnabled() = cryptoStore.isKeyGossipingEnabled()
 
+    override fun isShareKeysOnInviteEnabled() = cryptoStore.isShareKeysOnInviteEnabled()
+
+    override fun enableShareKeyOnInvite(enable: Boolean) = cryptoStore.enableShareKeyOnInvite(enable)
+
     /**
      * Tells whether the client should ever send encrypted messages to unverified devices.
      * The default value is false.
@@ -1335,6 +1345,30 @@ internal class DefaultCryptoService @Inject constructor(
         }
     }
 
+    override suspend fun sendSharedHistoryKeys(roomId: String, userId: String, sessionInfoSet: Set<SessionInfo>?) {
+        deviceListManager.downloadKeys(listOf(userId), false)
+        val userDevices = cryptoStore.getUserDeviceList(userId)
+        val sessionToShare = sessionInfoSet.orEmpty().mapNotNull { sessionInfo ->
+            // Get inbound session from sessionId and sessionKey
+            withContext(coroutineDispatchers.crypto) {
+                olmDevice.getInboundGroupSession(
+                        sessionId = sessionInfo.sessionId,
+                        senderKey = sessionInfo.senderKey,
+                        roomId = roomId
+                ).takeIf { it.wrapper.sessionData.sharedHistory }
+            }
+        }
+
+        userDevices?.forEach { deviceInfo ->
+            // Lets share the provided inbound sessions for every user device
+            sessionToShare.forEach { inboundGroupSession ->
+                val encryptor = roomEncryptorsStore.get(roomId)
+                encryptor?.shareHistoryKeysWithDevice(inboundGroupSession, deviceInfo)
+                Timber.i("## CRYPTO | Sharing inbound session")
+            }
+        }
+    }
+
     /* ==========================================================================================
      * For test only
      * ========================================================================================== */
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 e4d322cadd..ab7cbb74b1 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
@@ -23,7 +23,7 @@ import kotlinx.coroutines.sync.Mutex
 import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
 import org.matrix.android.sdk.api.extensions.tryOrNull
 import org.matrix.android.sdk.api.logger.LoggerTag
-import org.matrix.android.sdk.internal.crypto.model.OlmInboundGroupSessionWrapper2
+import org.matrix.android.sdk.internal.crypto.model.MXInboundMegolmSessionWrapper
 import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
 import timber.log.Timber
 import java.util.Timer
@@ -31,7 +31,7 @@ import java.util.TimerTask
 import javax.inject.Inject
 
 internal data class InboundGroupSessionHolder(
-        val wrapper: OlmInboundGroupSessionWrapper2,
+        val wrapper: MXInboundMegolmSessionWrapper,
         val mutex: Mutex = Mutex()
 )
 
@@ -58,7 +58,7 @@ internal class InboundGroupSessionStore @Inject constructor(
                 cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
                     Timber.tag(loggerTag.value).v("## Inbound: entryRemoved ${oldValue.wrapper.roomId}-${oldValue.wrapper.senderKey}")
                     store.storeInboundGroupSessions(listOf(oldValue).map { it.wrapper })
-                    oldValue.wrapper.olmInboundGroupSession?.releaseSession()
+                    oldValue.wrapper.session.releaseSession()
                 }
             }
         }
@@ -67,7 +67,7 @@ internal class InboundGroupSessionStore @Inject constructor(
     private val timer = Timer()
     private var timerTask: TimerTask? = null
 
-    private val dirtySession = mutableListOf<OlmInboundGroupSessionWrapper2>()
+    private val dirtySession = mutableListOf<InboundGroupSessionHolder>()
 
     @Synchronized
     fun clear() {
@@ -90,12 +90,12 @@ internal class InboundGroupSessionStore @Inject constructor(
     @Synchronized
     fun replaceGroupSession(old: InboundGroupSessionHolder, new: InboundGroupSessionHolder, sessionId: String, senderKey: String) {
         Timber.tag(loggerTag.value).v("## Replacing outdated session ${old.wrapper.roomId}-${old.wrapper.senderKey}")
-        dirtySession.remove(old.wrapper)
+        dirtySession.remove(old)
         store.removeInboundGroupSession(sessionId, senderKey)
         sessionCache.remove(CacheKey(sessionId, senderKey))
 
         // release removed session
-        old.wrapper.olmInboundGroupSession?.releaseSession()
+        old.wrapper.session.releaseSession()
 
         internalStoreGroupSession(new, sessionId, senderKey)
     }
@@ -108,7 +108,7 @@ internal class InboundGroupSessionStore @Inject constructor(
     private fun internalStoreGroupSession(holder: InboundGroupSessionHolder, sessionId: String, senderKey: String) {
         Timber.tag(loggerTag.value).v("## Inbound: getInboundGroupSession mark as dirty ${holder.wrapper.roomId}-${holder.wrapper.senderKey}")
         // We want to batch this a bit for performances
-        dirtySession.add(holder.wrapper)
+        dirtySession.add(holder)
 
         if (sessionCache[CacheKey(sessionId, senderKey)] == null) {
             // first time seen, put it in memory cache while waiting for batch insert
@@ -127,12 +127,12 @@ internal class InboundGroupSessionStore @Inject constructor(
 
     @Synchronized
     private fun batchSave() {
-        val toSave = mutableListOf<OlmInboundGroupSessionWrapper2>().apply { addAll(dirtySession) }
+        val toSave = mutableListOf<InboundGroupSessionHolder>().apply { addAll(dirtySession) }
         dirtySession.clear()
         cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
             Timber.tag(loggerTag.value).v("## Inbound: getInboundGroupSession batching save of ${toSave.size}")
             tryOrNull {
-                store.storeInboundGroupSessions(toSave)
+                store.storeInboundGroupSessions(toSave.map { it.wrapper })
             }
         }
     }
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 24b6fd166f..c4a6488258 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
@@ -27,7 +27,8 @@ import org.matrix.android.sdk.api.util.JSON_DICT_PARAMETERIZED_TYPE
 import org.matrix.android.sdk.api.util.JsonDict
 import org.matrix.android.sdk.internal.crypto.algorithms.megolm.MXOutboundSessionInfo
 import org.matrix.android.sdk.internal.crypto.algorithms.megolm.SharedWithHelper
-import org.matrix.android.sdk.internal.crypto.model.OlmInboundGroupSessionWrapper2
+import org.matrix.android.sdk.internal.crypto.model.InboundGroupSessionData
+import org.matrix.android.sdk.internal.crypto.model.MXInboundMegolmSessionWrapper
 import org.matrix.android.sdk.internal.crypto.model.OlmSessionWrapper
 import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
 import org.matrix.android.sdk.internal.di.MoshiProvider
@@ -38,6 +39,7 @@ import org.matrix.android.sdk.internal.util.convertToUTF8
 import org.matrix.android.sdk.internal.util.time.Clock
 import org.matrix.olm.OlmAccount
 import org.matrix.olm.OlmException
+import org.matrix.olm.OlmInboundGroupSession
 import org.matrix.olm.OlmMessage
 import org.matrix.olm.OlmOutboundGroupSession
 import org.matrix.olm.OlmSession
@@ -514,8 +516,9 @@ internal class MXOlmDevice @Inject constructor(
             return MXOutboundSessionInfo(
                     sessionId = sessionId,
                     sharedWithHelper = SharedWithHelper(roomId, sessionId, store),
-                    clock,
-                    restoredOutboundGroupSession.creationTime
+                    clock = clock,
+                    creationTime = restoredOutboundGroupSession.creationTime,
+                    sharedHistory = restoredOutboundGroupSession.sharedHistory
             )
         }
         return null
@@ -598,40 +601,47 @@ internal class MXOlmDevice @Inject constructor(
      * @param forwardingCurve25519KeyChain Devices involved in forwarding this session to us.
      * @param keysClaimed Other keys the sender claims.
      * @param exportFormat true if the megolm keys are in export format
+     * @param sharedHistory MSC3061, this key is sharable on invite
      * @return true if the operation succeeds.
      */
-    fun addInboundGroupSession(
-            sessionId: String,
-            sessionKey: String,
-            roomId: String,
-            senderKey: String,
-            forwardingCurve25519KeyChain: List<String>,
-            keysClaimed: Map<String, String>,
-            exportFormat: Boolean
-    ): AddSessionResult {
-        val candidateSession = OlmInboundGroupSessionWrapper2(sessionKey, exportFormat)
+    fun addInboundGroupSession(sessionId: String,
+                               sessionKey: String,
+                               roomId: String,
+                               senderKey: String,
+                               forwardingCurve25519KeyChain: List<String>,
+                               keysClaimed: Map<String, String>,
+                               exportFormat: Boolean,
+                               sharedHistory: Boolean): AddSessionResult {
+        val candidateSession = tryOrNull("Failed to create inbound session in room $roomId") {
+            if (exportFormat) {
+                OlmInboundGroupSession.importSession(sessionKey)
+            } else {
+                OlmInboundGroupSession(sessionKey)
+            }
+        }
+
         val existingSessionHolder = tryOrNull { getInboundGroupSession(sessionId, senderKey, roomId) }
         val existingSession = existingSessionHolder?.wrapper
         // If we have an existing one we should check if the new one is not better
         if (existingSession != null) {
             Timber.tag(loggerTag.value).d("## addInboundGroupSession() check if known session is better than candidate session")
             try {
-                val existingFirstKnown = existingSession.firstKnownIndex ?: return AddSessionResult.NotImported.also {
+                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.olmInboundGroupSession?.releaseSession()
+                    candidateSession?.releaseSession()
                     // Probably should discard it?
                 }
-                val newKnownFirstIndex = candidateSession.firstKnownIndex
+                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.olmInboundGroupSession?.releaseSession()
+                    candidateSession?.releaseSession()
                     return AddSessionResult.NotImportedHigherIndex(newKnownFirstIndex.toInt())
                 }
             } catch (failure: Throwable) {
                 Timber.tag(loggerTag.value).e("## addInboundGroupSession() Failed to add inbound: ${failure.localizedMessage}")
-                candidateSession.olmInboundGroupSession?.releaseSession()
+                candidateSession?.releaseSession()
                 return AddSessionResult.NotImported
             }
         }
@@ -639,36 +649,42 @@ internal class MXOlmDevice @Inject constructor(
         Timber.tag(loggerTag.value).d("## addInboundGroupSession() : Candidate session should be added $senderKey/$sessionId")
 
         // sanity check on the new session
-        val candidateOlmInboundSession = candidateSession.olmInboundGroupSession
-        if (null == candidateOlmInboundSession) {
+        if (null == candidateSession) {
             Timber.tag(loggerTag.value).e("## addInboundGroupSession : invalid session <null>")
             return AddSessionResult.NotImported
         }
 
         try {
-            if (candidateOlmInboundSession.sessionIdentifier() != sessionId) {
+            if (candidateSession.sessionIdentifier() != sessionId) {
                 Timber.tag(loggerTag.value).e("## addInboundGroupSession : ERROR: Mismatched group session ID from senderKey: $senderKey")
-                candidateOlmInboundSession.releaseSession()
+                candidateSession.releaseSession()
                 return AddSessionResult.NotImported
             }
         } catch (e: Throwable) {
-            candidateOlmInboundSession.releaseSession()
+            candidateSession.releaseSession()
             Timber.tag(loggerTag.value).e(e, "## addInboundGroupSession : sessionIdentifier() failed")
             return AddSessionResult.NotImported
         }
 
-        candidateSession.senderKey = senderKey
-        candidateSession.roomId = roomId
-        candidateSession.keysClaimed = keysClaimed
-        candidateSession.forwardingCurve25519KeyChain = forwardingCurve25519KeyChain
+        val candidateSessionData = InboundGroupSessionData(
+                senderKey = senderKey,
+                roomId = roomId,
+                keysClaimed = keysClaimed,
+                forwardingCurve25519KeyChain = forwardingCurve25519KeyChain,
+                sharedHistory = sharedHistory,
+        )
 
+        val wrapper = MXInboundMegolmSessionWrapper(
+                candidateSession,
+                candidateSessionData
+        )
         if (existingSession != null) {
-            inboundGroupSessionStore.replaceGroupSession(existingSessionHolder, InboundGroupSessionHolder(candidateSession), sessionId, senderKey)
+            inboundGroupSessionStore.replaceGroupSession(existingSessionHolder, InboundGroupSessionHolder(wrapper), sessionId, senderKey)
         } else {
-            inboundGroupSessionStore.storeInBoundGroupSession(InboundGroupSessionHolder(candidateSession), sessionId, senderKey)
+            inboundGroupSessionStore.storeInBoundGroupSession(InboundGroupSessionHolder(wrapper), sessionId, senderKey)
         }
 
-        return AddSessionResult.Imported(candidateSession.firstKnownIndex?.toInt() ?: 0)
+        return AddSessionResult.Imported(candidateSession.firstKnownIndex.toInt())
     }
 
     /**
@@ -677,41 +693,22 @@ internal class MXOlmDevice @Inject constructor(
      * @param megolmSessionsData the megolm sessions data
      * @return the successfully imported sessions.
      */
-    fun importInboundGroupSessions(megolmSessionsData: List<MegolmSessionData>): List<OlmInboundGroupSessionWrapper2> {
-        val sessions = ArrayList<OlmInboundGroupSessionWrapper2>(megolmSessionsData.size)
+    fun importInboundGroupSessions(megolmSessionsData: List<MegolmSessionData>): List<MXInboundMegolmSessionWrapper> {
+        val sessions = ArrayList<MXInboundMegolmSessionWrapper>(megolmSessionsData.size)
 
         for (megolmSessionData in megolmSessionsData) {
             val sessionId = megolmSessionData.sessionId ?: continue
             val senderKey = megolmSessionData.senderKey ?: continue
             val roomId = megolmSessionData.roomId
 
-            var candidateSessionToImport: OlmInboundGroupSessionWrapper2? = null
-
-            try {
-                candidateSessionToImport = OlmInboundGroupSessionWrapper2(megolmSessionData)
-            } catch (e: Exception) {
-                Timber.tag(loggerTag.value).e(e, "## importInboundGroupSession() : Update for megolm session $senderKey/$sessionId")
-            }
-
-            // sanity check
-            if (candidateSessionToImport?.olmInboundGroupSession == null) {
-                Timber.tag(loggerTag.value).e("## importInboundGroupSession : invalid session")
-                continue
-            }
-
-            val candidateOlmInboundGroupSession = candidateSessionToImport.olmInboundGroupSession
-            try {
-                if (candidateOlmInboundGroupSession?.sessionIdentifier() != sessionId) {
-                    Timber.tag(loggerTag.value).e("## importInboundGroupSession : ERROR: Mismatched group session ID from senderKey: $senderKey")
-                    candidateOlmInboundGroupSession?.releaseSession()
-                    continue
-                }
-            } catch (e: Exception) {
-                Timber.tag(loggerTag.value).e(e, "## importInboundGroupSession : sessionIdentifier() failed")
-                candidateOlmInboundGroupSession?.releaseSession()
+            val candidateSessionToImport =  try {
+                 MXInboundMegolmSessionWrapper.newFromMegolmData(megolmSessionData, true)
+            } catch (e: Throwable) {
+                Timber.tag(loggerTag.value).e(e, "## importInboundGroupSession() : Failed to import session $senderKey/$sessionId")
                 continue
             }
 
+            val candidateOlmInboundGroupSession = candidateSessionToImport.session
             val existingSessionHolder = tryOrNull { getInboundGroupSession(sessionId, senderKey, roomId) }
             val existingSession = existingSessionHolder?.wrapper
 
@@ -721,16 +718,16 @@ internal class MXOlmDevice @Inject constructor(
                 sessions.add(candidateSessionToImport)
             } else {
                 Timber.tag(loggerTag.value).e("## importInboundGroupSession() : Update for megolm session $senderKey/$sessionId")
-                val existingFirstKnown = tryOrNull { existingSession.firstKnownIndex }
-                val candidateFirstKnownIndex = tryOrNull { candidateSessionToImport.firstKnownIndex }
+                val existingFirstKnown = tryOrNull { existingSession.session.firstKnownIndex }
+                val candidateFirstKnownIndex = tryOrNull { candidateSessionToImport.session.firstKnownIndex }
 
                 if (existingFirstKnown == null || candidateFirstKnownIndex == null) {
                     // should not happen?
-                    candidateSessionToImport.olmInboundGroupSession?.releaseSession()
+                    candidateSessionToImport.session.releaseSession()
                     Timber.tag(loggerTag.value)
                             .w("## importInboundGroupSession() : Can't check session null index $existingFirstKnown/$candidateFirstKnownIndex")
                 } else {
-                    if (existingFirstKnown <= candidateSessionToImport.firstKnownIndex!!) {
+                    if (existingFirstKnown <= candidateFirstKnownIndex) {
                         // Ignore this, keep existing
                         candidateOlmInboundGroupSession.releaseSession()
                     } else {
@@ -774,8 +771,7 @@ internal class MXOlmDevice @Inject constructor(
     ): OlmDecryptionResult {
         val sessionHolder = getInboundGroupSession(sessionId, senderKey, roomId)
         val wrapper = sessionHolder.wrapper
-        val inboundGroupSession = wrapper.olmInboundGroupSession
-                ?: throw MXCryptoError.Base(MXCryptoError.ErrorType.UNABLE_TO_DECRYPT, "Session is null")
+        val inboundGroupSession = wrapper.session
         if (roomId != wrapper.roomId) {
             // Check that the room id matches the original one for the session. This stops
             // the HS pretending a message was targeting a different room.
@@ -822,9 +818,9 @@ internal class MXOlmDevice @Inject constructor(
 
         return OlmDecryptionResult(
                 payload,
-                wrapper.keysClaimed,
+                wrapper.sessionData.keysClaimed,
                 senderKey,
-                wrapper.forwardingCurve25519KeyChain
+                wrapper.sessionData.forwardingCurve25519KeyChain
         )
     }
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MegolmSessionData.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MegolmSessionData.kt
index f6bc9a9148..ca0bdc8a0e 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MegolmSessionData.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MegolmSessionData.kt
@@ -69,5 +69,13 @@ internal data class MegolmSessionData(
          * Devices which forwarded this session to us (normally empty).
          */
         @Json(name = "forwarding_curve25519_key_chain")
-        val forwardingCurve25519KeyChain: List<String>? = null
+        val forwardingCurve25519KeyChain: List<String>? = null,
+
+        /**
+         * Flag that indicates whether or not the current inboundSession will be shared to
+         * invited users to decrypt past messages.
+         */
+        // When this feature lands in spec name = shared_history should be used
+        @Json(name = "org.matrix.msc3061.shared_history")
+        val sharedHistory: Boolean = false,
 )
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OutgoingKeyRequestManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OutgoingKeyRequestManager.kt
index 6b22cc09d6..810699d933 100755
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OutgoingKeyRequestManager.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OutgoingKeyRequestManager.kt
@@ -437,7 +437,10 @@ internal class OutgoingKeyRequestManager @Inject constructor(
         if (perSessionBackupQueryRateLimiter.tryFromBackupIfPossible(sessionId, roomId)) {
             // let's see what's the index
             val knownIndex = tryOrNull {
-                inboundGroupSessionStore.getInboundGroupSession(sessionId, request.requestBody?.senderKey ?: "")?.wrapper?.firstKnownIndex
+                inboundGroupSessionStore.getInboundGroupSession(sessionId, request.requestBody?.senderKey ?: "")
+                        ?.wrapper
+                        ?.session
+                        ?.firstKnownIndex
             }
             if (knownIndex != null && knownIndex <= request.fromIndex) {
                 // we found the key in backup with good enough index, so we can just mark as cancelled, no need to send request
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/actions/MegolmSessionDataImporter.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/actions/MegolmSessionDataImporter.kt
index f6ab96aee6..a624b92a19 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/actions/MegolmSessionDataImporter.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/actions/MegolmSessionDataImporter.kt
@@ -84,8 +84,9 @@ internal class MegolmSessionDataImporter @Inject constructor(
                             megolmSessionData.senderKey ?: "",
                             tryOrNull {
                                 olmInboundGroupSessionWrappers
-                                        .firstOrNull { it.olmInboundGroupSession?.sessionIdentifier() == megolmSessionData.sessionId }
-                                        ?.firstKnownIndex?.toInt()
+                                        .firstOrNull { it.session.sessionIdentifier() == megolmSessionData.sessionId }
+                                        ?.session?.firstKnownIndex
+                                        ?.toInt()
                             } ?: 0
                     )
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/IMXEncrypting.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/IMXEncrypting.kt
index 73ce5a5004..1454f5b486 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/IMXEncrypting.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/IMXEncrypting.kt
@@ -16,7 +16,9 @@
 
 package org.matrix.android.sdk.internal.crypto.algorithms
 
+import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
 import org.matrix.android.sdk.api.session.events.model.Content
+import org.matrix.android.sdk.internal.crypto.InboundGroupSessionHolder
 
 /**
  * An interface for encrypting data.
@@ -32,4 +34,6 @@ internal interface IMXEncrypting {
      * @return the encrypted content
      */
     suspend fun encryptEventContent(eventContent: Content, eventType: String, userIds: List<String>): Content
+
+    suspend fun shareHistoryKeysWithDevice(inboundSessionWrapper: InboundGroupSessionHolder, deviceInfo: CryptoDeviceInfo) {}
 }
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 141d6f74cd..410b74e19f 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,6 +17,7 @@
 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.logger.LoggerTag
 import org.matrix.android.sdk.api.session.crypto.MXCryptoError
 import org.matrix.android.sdk.api.session.crypto.NewSessionListener
@@ -41,6 +42,7 @@ internal class MXMegolmDecryption(
         private val olmDevice: MXOlmDevice,
         private val outgoingKeyRequestManager: OutgoingKeyRequestManager,
         private val cryptoStore: IMXCryptoStore,
+        private val matrixConfiguration: MatrixConfiguration,
         private val liveEventManager: Lazy<StreamEventsManager>
 ) : IMXDecrypting {
 
@@ -240,13 +242,14 @@ internal class MXMegolmDecryption(
 
         Timber.tag(loggerTag.value).i("onRoomKeyEvent addInboundGroupSession ${roomKeyContent.sessionId}")
         val addSessionResult = olmDevice.addInboundGroupSession(
-                roomKeyContent.sessionId,
-                roomKeyContent.sessionKey,
-                roomKeyContent.roomId,
-                senderKey,
-                forwardingCurve25519KeyChain,
-                keysClaimed,
-                exportFormat
+                sessionId = roomKeyContent.sessionId,
+                sessionKey = roomKeyContent.sessionKey,
+                roomId = roomKeyContent.roomId,
+                senderKey = senderKey,
+                forwardingCurve25519KeyChain = forwardingCurve25519KeyChain,
+                keysClaimed = keysClaimed,
+                exportFormat = exportFormat,
+                sharedHistory = roomKeyContent.getSharedKey()
         )
 
         when (addSessionResult) {
@@ -296,6 +299,14 @@ internal class MXMegolmDecryption(
         }
     }
 
+    /**
+     * Returns boolean shared key flag, if enabled with respect to matrix configuration.
+     */
+    private fun RoomKeyContent.getSharedKey(): Boolean {
+        if (!cryptoStore.isShareKeysOnInviteEnabled()) return false
+        return sharedHistory ?: false
+    }
+
     /**
      * Check if the some messages can be decrypted with a new session.
      *
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 81a6fb28c0..414416a0f6 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,6 +17,7 @@
 package org.matrix.android.sdk.internal.crypto.algorithms.megolm
 
 import dagger.Lazy
+import org.matrix.android.sdk.api.MatrixConfiguration
 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
@@ -27,6 +28,7 @@ internal class MXMegolmDecryptionFactory @Inject constructor(
         private val olmDevice: MXOlmDevice,
         private val outgoingKeyRequestManager: OutgoingKeyRequestManager,
         private val cryptoStore: IMXCryptoStore,
+        private val matrixConfiguration: MatrixConfiguration,
         private val eventsManager: Lazy<StreamEventsManager>
 ) {
 
@@ -35,7 +37,7 @@ internal class MXMegolmDecryptionFactory @Inject constructor(
                 olmDevice,
                 outgoingKeyRequestManager,
                 cryptoStore,
-                eventsManager
-        )
+                matrixConfiguration,
+                eventsManager)
     }
 }
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 7bfbae6edf..48a25f2a8b 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
@@ -32,6 +32,7 @@ import org.matrix.android.sdk.api.session.events.model.EventType
 import org.matrix.android.sdk.api.session.events.model.content.RoomKeyWithHeldContent
 import org.matrix.android.sdk.api.session.events.model.content.WithHeldCode
 import org.matrix.android.sdk.internal.crypto.DeviceListManager
+import org.matrix.android.sdk.internal.crypto.InboundGroupSessionHolder
 import org.matrix.android.sdk.internal.crypto.MXOlmDevice
 import org.matrix.android.sdk.internal.crypto.actions.EnsureOlmSessionsForDevicesAction
 import org.matrix.android.sdk.internal.crypto.actions.MessageEncrypter
@@ -151,14 +152,27 @@ internal class MXMegolmEncryption(
                 "ed25519" to olmDevice.deviceEd25519Key!!
         )
 
+        val sharedHistory = cryptoStore.shouldShareHistory(roomId)
+        Timber.tag(loggerTag.value).v("prepareNewSessionInRoom() as sharedHistory $sharedHistory")
         olmDevice.addInboundGroupSession(
-                sessionId!!, olmDevice.getSessionKey(sessionId)!!, roomId, olmDevice.deviceCurve25519Key!!,
-                emptyList(), keysClaimedMap, false
+                sessionId = sessionId!!,
+                sessionKey = olmDevice.getSessionKey(sessionId)!!,
+                roomId = roomId,
+                senderKey = olmDevice.deviceCurve25519Key!!,
+                forwardingCurve25519KeyChain = emptyList(),
+                keysClaimed = keysClaimedMap,
+                exportFormat = false,
+                sharedHistory = sharedHistory
         )
 
         defaultKeysBackupService.maybeBackupKeys()
 
-        return MXOutboundSessionInfo(sessionId, SharedWithHelper(roomId, sessionId, cryptoStore), clock)
+        return MXOutboundSessionInfo(
+                sessionId = sessionId,
+                sharedWithHelper = SharedWithHelper(roomId, sessionId, cryptoStore),
+                clock = clock,
+                sharedHistory = sharedHistory
+        )
     }
 
     /**
@@ -172,6 +186,8 @@ internal class MXMegolmEncryption(
         if (session == null ||
                 // Need to make a brand new session?
                 session.needsRotation(sessionRotationPeriodMsgs, sessionRotationPeriodMs) ||
+                // Is there a room history visibility change since the last outboundSession
+                cryptoStore.shouldShareHistory(roomId) != session.sharedHistory ||
                 // Determine if we have shared with anyone we shouldn't have
                 session.sharedWithTooManyDevices(devicesInRoom)) {
             Timber.tag(loggerTag.value).d("roomId:$roomId Starting new megolm session because we need to rotate.")
@@ -231,26 +247,27 @@ internal class MXMegolmEncryption(
     /**
      * Share the device keys of a an user.
      *
-     * @param session the session info
+     * @param sessionInfo the session info
      * @param devicesByUser the devices map
      */
-    private suspend fun shareUserDevicesKey(
-            session: MXOutboundSessionInfo,
-            devicesByUser: Map<String, List<CryptoDeviceInfo>>
-    ) {
-        val sessionKey = olmDevice.getSessionKey(session.sessionId)
-        val chainIndex = olmDevice.getMessageIndex(session.sessionId)
+    private suspend fun shareUserDevicesKey(sessionInfo: MXOutboundSessionInfo,
+                                            devicesByUser: Map<String, List<CryptoDeviceInfo>>) {
+        val sessionKey = olmDevice.getSessionKey(sessionInfo.sessionId) ?: return Unit.also {
+            Timber.tag(loggerTag.value).v("shareUserDevicesKey() Failed to share session, failed to export")
+        }
+        val chainIndex = olmDevice.getMessageIndex(sessionInfo.sessionId)
 
-        val submap = HashMap<String, Any>()
-        submap["algorithm"] = MXCRYPTO_ALGORITHM_MEGOLM
-        submap["room_id"] = roomId
-        submap["session_id"] = session.sessionId
-        submap["session_key"] = sessionKey!!
-        submap["chain_index"] = chainIndex
-
-        val payload = HashMap<String, Any>()
-        payload["type"] = EventType.ROOM_KEY
-        payload["content"] = submap
+        val payload = mapOf(
+                "type" to EventType.ROOM_KEY,
+                "content" to mapOf(
+                        "algorithm" to MXCRYPTO_ALGORITHM_MEGOLM,
+                        "room_id" to roomId,
+                        "session_id" to sessionInfo.sessionId,
+                        "session_key" to sessionKey,
+                        "chain_index" to chainIndex,
+                        "org.matrix.msc3061.shared_history" to sessionInfo.sharedHistory
+                )
+        )
 
         var t0 = clock.epochMillis()
         Timber.tag(loggerTag.value).v("shareUserDevicesKey() : starts")
@@ -292,7 +309,7 @@ internal class MXMegolmEncryption(
         // for dead devices on every message.
         for ((_, devicesToShareWith) in devicesByUser) {
             for (deviceInfo in devicesToShareWith) {
-                session.sharedWithHelper.markedSessionAsShared(deviceInfo, chainIndex)
+                sessionInfo.sharedWithHelper.markedSessionAsShared(deviceInfo, chainIndex)
                 // XXX is it needed to add it to the audit trail?
                 // For now decided that no, we are more interested by forward trail
             }
@@ -300,8 +317,8 @@ internal class MXMegolmEncryption(
 
         if (haveTargets) {
             t0 = clock.epochMillis()
-            Timber.tag(loggerTag.value).i("shareUserDevicesKey() ${session.sessionId} : has target")
-            Timber.tag(loggerTag.value).d("sending to device room key for ${session.sessionId} to ${contentMap.toDebugString()}")
+            Timber.tag(loggerTag.value).i("shareUserDevicesKey() ${sessionInfo.sessionId} : has target")
+            Timber.tag(loggerTag.value).d("sending to device room key for ${sessionInfo.sessionId} to ${contentMap.toDebugString()}")
             val sendToDeviceParams = SendToDeviceTask.Params(EventType.ENCRYPTED, contentMap)
             try {
                 withContext(coroutineDispatchers.io) {
@@ -310,7 +327,7 @@ internal class MXMegolmEncryption(
                 Timber.tag(loggerTag.value).i("shareUserDevicesKey() : sendToDevice succeeds after ${clock.epochMillis() - t0} ms")
             } catch (failure: Throwable) {
                 // What to do here...
-                Timber.tag(loggerTag.value).e("shareUserDevicesKey() : Failed to share <${session.sessionId}>")
+                Timber.tag(loggerTag.value).e("shareUserDevicesKey() : Failed to share <${sessionInfo.sessionId}>")
             }
         } else {
             Timber.tag(loggerTag.value).i("shareUserDevicesKey() : no need to share key")
@@ -320,7 +337,7 @@ internal class MXMegolmEncryption(
             // XXX offload?, as they won't read the message anyhow?
             notifyKeyWithHeld(
                     noOlmToNotify,
-                    session.sessionId,
+                    sessionInfo.sessionId,
                     olmDevice.deviceCurve25519Key,
                     WithHeldCode.NO_OLM
             )
@@ -514,6 +531,51 @@ internal class MXMegolmEncryption(
         }
     }
 
+    @Throws
+    override suspend fun shareHistoryKeysWithDevice(inboundSessionWrapper: InboundGroupSessionHolder, deviceInfo: CryptoDeviceInfo) {
+        if (!inboundSessionWrapper.wrapper.sessionData.sharedHistory) throw IllegalArgumentException("This key can't be shared")
+        Timber.tag(loggerTag.value).i("process shareHistoryKeys for ${inboundSessionWrapper.wrapper.safeSessionId} to ${deviceInfo.shortDebugString()}")
+        val userId = deviceInfo.userId
+        val deviceId = deviceInfo.deviceId
+        val devicesByUser = mapOf(userId to listOf(deviceInfo))
+        val usersDeviceMap = try {
+            ensureOlmSessionsForDevicesAction.handle(devicesByUser)
+        } catch (failure: Throwable) {
+            Timber.tag(loggerTag.value).i(failure, "process shareHistoryKeys failed to ensure olm")
+            // process anyway?
+            null
+        }
+        val olmSessionResult = usersDeviceMap?.getObject(userId, deviceId)
+        if (olmSessionResult?.sessionId == null) {
+            Timber.tag(loggerTag.value).w("shareHistoryKeys: no session with this device, probably because there were no one-time keys")
+            return
+        }
+
+        val export = inboundSessionWrapper.mutex.withLock {
+            inboundSessionWrapper.wrapper.exportKeys()
+        } ?: return Unit.also {
+            Timber.tag(loggerTag.value).e("shareHistoryKeys: failed to export group session ${inboundSessionWrapper.wrapper.safeSessionId}")
+        }
+
+        val payloadJson = mapOf(
+                "type" to EventType.FORWARDED_ROOM_KEY,
+                "content" to export
+        )
+
+        val encodedPayload =
+                withContext(coroutineDispatchers.computation) {
+                    messageEncrypter.encryptMessage(payloadJson, listOf(deviceInfo))
+                }
+        val sendToDeviceMap = MXUsersDevicesMap<Any>()
+        sendToDeviceMap.setObject(userId, deviceId, encodedPayload)
+        Timber.tag(loggerTag.value)
+                .d("shareHistoryKeys() : sending session ${inboundSessionWrapper.wrapper.safeSessionId} to ${deviceInfo.shortDebugString()}")
+        val sendToDeviceParams = SendToDeviceTask.Params(EventType.ENCRYPTED, sendToDeviceMap)
+        withContext(coroutineDispatchers.io) {
+            sendToDeviceTask.execute(sendToDeviceParams)
+        }
+    }
+
     data class DeviceInRoomInfo(
             val allowedDevices: MXUsersDevicesMap<CryptoDeviceInfo> = MXUsersDevicesMap(),
             val withHeldDevices: MXUsersDevicesMap<WithHeldCode> = MXUsersDevicesMap()
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXOutboundSessionInfo.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXOutboundSessionInfo.kt
index 28d925d8fd..e0caa0d9a5 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXOutboundSessionInfo.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXOutboundSessionInfo.kt
@@ -28,6 +28,7 @@ internal class MXOutboundSessionInfo(
         private val clock: Clock,
         // When the session was created
         private val creationTime: Long = clock.epochMillis(),
+        val sharedHistory: Boolean = false
 ) {
 
     // Number of times this session has been used
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 5eaa106af3..49cf60d051 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
@@ -24,8 +24,10 @@ import androidx.annotation.WorkerThread
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.launch
+import kotlinx.coroutines.sync.withLock
 import kotlinx.coroutines.withContext
 import org.matrix.android.sdk.api.MatrixCallback
+import org.matrix.android.sdk.api.MatrixConfiguration
 import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
 import org.matrix.android.sdk.api.auth.data.Credentials
 import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM_BACKUP
@@ -50,6 +52,7 @@ import org.matrix.android.sdk.api.session.crypto.keysbackup.toKeysVersionResult
 import org.matrix.android.sdk.api.session.crypto.model.ImportRoomKeysResult
 import org.matrix.android.sdk.api.util.awaitCallback
 import org.matrix.android.sdk.api.util.fromBase64
+import org.matrix.android.sdk.internal.crypto.InboundGroupSessionStore
 import org.matrix.android.sdk.internal.crypto.MXOlmDevice
 import org.matrix.android.sdk.internal.crypto.MegolmSessionData
 import org.matrix.android.sdk.internal.crypto.ObjectSigner
@@ -71,7 +74,7 @@ import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.GetRoomSessionsDa
 import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.GetSessionsDataTask
 import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.StoreSessionsDataTask
 import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.UpdateKeysBackupVersionTask
-import org.matrix.android.sdk.internal.crypto.model.OlmInboundGroupSessionWrapper2
+import org.matrix.android.sdk.internal.crypto.model.MXInboundMegolmSessionWrapper
 import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
 import org.matrix.android.sdk.internal.crypto.store.db.model.KeysBackupDataEntity
 import org.matrix.android.sdk.internal.di.MoshiProvider
@@ -118,6 +121,8 @@ internal class DefaultKeysBackupService @Inject constructor(
         private val updateKeysBackupVersionTask: UpdateKeysBackupVersionTask,
         // Task executor
         private val taskExecutor: TaskExecutor,
+        private val matrixConfiguration: MatrixConfiguration,
+        private val inboundGroupSessionStore: InboundGroupSessionStore,
         private val coroutineDispatchers: MatrixCoroutineDispatchers,
         private val cryptoCoroutineScope: CoroutineScope
 ) : KeysBackupService {
@@ -1316,7 +1321,7 @@ internal class DefaultKeysBackupService @Inject constructor(
 
                 olmInboundGroupSessionWrappers.forEach { olmInboundGroupSessionWrapper ->
                     val roomId = olmInboundGroupSessionWrapper.roomId ?: return@forEach
-                    val olmInboundGroupSession = olmInboundGroupSessionWrapper.olmInboundGroupSession ?: return@forEach
+                    val olmInboundGroupSession = olmInboundGroupSessionWrapper.session
 
                     try {
                         encryptGroupSession(olmInboundGroupSessionWrapper)
@@ -1405,19 +1410,29 @@ internal class DefaultKeysBackupService @Inject constructor(
 
     @VisibleForTesting
     @WorkerThread
-    fun encryptGroupSession(olmInboundGroupSessionWrapper: OlmInboundGroupSessionWrapper2): KeyBackupData? {
+    suspend fun encryptGroupSession(olmInboundGroupSessionWrapper: MXInboundMegolmSessionWrapper): KeyBackupData? {
+        olmInboundGroupSessionWrapper.safeSessionId ?: return null
+        olmInboundGroupSessionWrapper.senderKey ?: return null
         // Gather information for each key
-        val device = olmInboundGroupSessionWrapper.senderKey?.let { cryptoStore.deviceWithIdentityKey(it) }
+        val device = cryptoStore.deviceWithIdentityKey(olmInboundGroupSessionWrapper.senderKey)
 
         // Build the m.megolm_backup.v1.curve25519-aes-sha2 data as defined at
         // https://github.com/uhoreg/matrix-doc/blob/e2e_backup/proposals/1219-storing-megolm-keys-serverside.md#mmegolm_backupv1curve25519-aes-sha2-key-format
-        val sessionData = olmInboundGroupSessionWrapper.exportKeys() ?: return null
+        val sessionData = inboundGroupSessionStore
+                .getInboundGroupSession(olmInboundGroupSessionWrapper.safeSessionId, olmInboundGroupSessionWrapper.senderKey)
+                ?.let {
+                    withContext(coroutineDispatchers.computation) {
+                        it.mutex.withLock { it.wrapper.exportKeys() }
+                    }
+                }
+                ?: return null
         val sessionBackupData = mapOf(
                 "algorithm" to sessionData.algorithm,
                 "sender_key" to sessionData.senderKey,
                 "sender_claimed_keys" to sessionData.senderClaimedKeys,
                 "forwarding_curve25519_key_chain" to (sessionData.forwardingCurve25519KeyChain.orEmpty()),
-                "session_key" to sessionData.sessionKey
+                "session_key" to sessionData.sessionKey,
+                "org.matrix.msc3061.shared_history" to sessionData.sharedHistory
         )
 
         val json = MoshiProvider.providesMoshi()
@@ -1425,7 +1440,9 @@ internal class DefaultKeysBackupService @Inject constructor(
                 .toJson(sessionBackupData)
 
         val encryptedSessionBackupData = try {
-            backupOlmPkEncryption?.encrypt(json)
+            withContext(coroutineDispatchers.computation) {
+                backupOlmPkEncryption?.encrypt(json)
+            }
         } catch (e: OlmException) {
             Timber.e(e, "OlmException")
             null
@@ -1435,14 +1452,14 @@ internal class DefaultKeysBackupService @Inject constructor(
         // Build backup data for that key
         return KeyBackupData(
                 firstMessageIndex = try {
-                    olmInboundGroupSessionWrapper.olmInboundGroupSession?.firstKnownIndex ?: 0
+                    olmInboundGroupSessionWrapper.session.firstKnownIndex
                 } catch (e: OlmException) {
                     Timber.e(e, "OlmException")
                     0L
                 },
-                forwardedCount = olmInboundGroupSessionWrapper.forwardingCurve25519KeyChain.orEmpty().size,
+                forwardedCount = olmInboundGroupSessionWrapper.sessionData.forwardingCurve25519KeyChain.orEmpty().size,
                 isVerified = device?.isVerified == true,
-
+                sharedHistory = olmInboundGroupSessionWrapper.getSharedKey(),
                 sessionData = mapOf(
                         "ciphertext" to encryptedSessionBackupData.mCipherText,
                         "mac" to encryptedSessionBackupData.mMac,
@@ -1451,6 +1468,14 @@ internal class DefaultKeysBackupService @Inject constructor(
         )
     }
 
+    /**
+     * Returns boolean shared key flag, if enabled with respect to matrix configuration.
+     */
+    private fun MXInboundMegolmSessionWrapper.getSharedKey(): Boolean {
+        if (!cryptoStore.isShareKeysOnInviteEnabled()) return false
+        return sessionData.sharedHistory
+    }
+
     @VisibleForTesting
     @WorkerThread
     fun decryptKeyBackupData(keyBackupData: KeyBackupData, sessionId: String, roomId: String, decryption: OlmPkDecryption): MegolmSessionData? {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/model/rest/KeyBackupData.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/model/rest/KeyBackupData.kt
index 5c3d0c12b0..1817b18e2a 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/model/rest/KeyBackupData.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/model/rest/KeyBackupData.kt
@@ -50,5 +50,12 @@ internal data class KeyBackupData(
          * Algorithm-dependent data.
          */
         @Json(name = "session_data")
-        val sessionData: JsonDict
+        val sessionData: JsonDict,
+
+        /**
+         * Flag that indicates whether or not the current inboundSession will be shared to
+         * invited users to decrypt past messages.
+         */
+        @Json(name = "org.matrix.msc3061.shared_history")
+        val sharedHistory: Boolean = false
 )
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
new file mode 100644
index 0000000000..2ce36aa209
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/InboundGroupSessionData.kt
@@ -0,0 +1,51 @@
+/*
+ * 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.model
+
+import com.squareup.moshi.Json
+import com.squareup.moshi.JsonClass
+
+@JsonClass(generateAdapter = true)
+data class InboundGroupSessionData(
+
+        /** The room in which this session is used. */
+        @Json(name = "room_id")
+        var roomId: String? = null,
+
+        /** The base64-encoded curve25519 key of the sender. */
+        @Json(name = "sender_key")
+        var senderKey: String? = null,
+
+        /** Other keys the sender claims. */
+        @Json(name = "keys_claimed")
+        var keysClaimed: Map<String, String>? = null,
+
+        /** Devices which forwarded this session to us (normally emty). */
+        @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.
+         */
+        @Json(name = "shared_history")
+        val sharedHistory: Boolean = false,
+
+        )
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
new file mode 100644
index 0000000000..2772b34835
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/MXInboundMegolmSessionWrapper.kt
@@ -0,0 +1,97 @@
+/*
+ * 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.model
+
+import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM
+import org.matrix.android.sdk.api.extensions.tryOrNull
+import org.matrix.android.sdk.internal.crypto.MegolmSessionData
+import org.matrix.olm.OlmInboundGroupSession
+import timber.log.Timber
+
+data class MXInboundMegolmSessionWrapper(
+        // olm object
+        val session: OlmInboundGroupSession,
+        // data about the session
+        val sessionData: InboundGroupSessionData
+) {
+    // shortcut
+    val roomId = sessionData.roomId
+    val senderKey = sessionData.senderKey
+    val safeSessionId = tryOrNull("Fail to get megolm session Id") { session.sessionIdentifier() }
+
+    /**
+     * Export the inbound group session keys.
+     * @param index the index to export. If null, the first known index will be used
+     * @return the inbound group session as MegolmSessionData if the operation succeeds
+     */
+    internal fun exportKeys(index: Long? = null): MegolmSessionData? {
+        return try {
+            val keysClaimed = sessionData.keysClaimed ?: return null
+            val wantedIndex = index ?: session.firstKnownIndex
+
+            MegolmSessionData(
+                    senderClaimedEd25519Key = sessionData.keysClaimed?.get("ed25519"),
+                    forwardingCurve25519KeyChain = sessionData.forwardingCurve25519KeyChain?.toList().orEmpty(),
+                    sessionKey = session.export(wantedIndex),
+                    senderClaimedKeys = keysClaimed,
+                    roomId = sessionData.roomId,
+                    sessionId = session.sessionIdentifier(),
+                    senderKey = senderKey,
+                    algorithm = MXCRYPTO_ALGORITHM_MEGOLM,
+                    sharedHistory = sessionData.sharedHistory
+            )
+        } catch (e: Exception) {
+            Timber.e(e, "## Failed to export megolm : sessionID ${tryOrNull { session.sessionIdentifier() }} failed")
+            null
+        }
+    }
+
+    companion object {
+
+        /**
+         * @exportFormat true if the megolm keys are in export format
+         *    (ie, they lack an ed25519 signature)
+         */
+        @Throws
+        internal fun newFromMegolmData(megolmSessionData: MegolmSessionData, exportFormat: Boolean): MXInboundMegolmSessionWrapper {
+            val exportedKey = megolmSessionData.sessionKey ?: throw IllegalArgumentException("key data not found")
+            val inboundSession = if (exportFormat) {
+                OlmInboundGroupSession.importSession(exportedKey)
+            } else {
+                OlmInboundGroupSession(exportedKey)
+            }
+                    .also {
+                        if (it.sessionIdentifier() != megolmSessionData.sessionId) {
+                            it.releaseSession()
+                            throw IllegalStateException("Mismatched group session Id")
+                        }
+                    }
+            val data = InboundGroupSessionData(
+                    roomId = megolmSessionData.roomId,
+                    senderKey = megolmSessionData.senderKey,
+                    keysClaimed = megolmSessionData.senderClaimedKeys,
+                    forwardingCurve25519KeyChain = megolmSessionData.forwardingCurve25519KeyChain,
+                    sharedHistory = megolmSessionData.sharedHistory,
+            )
+
+            return MXInboundMegolmSessionWrapper(
+                    inboundSession,
+                    data
+            )
+        }
+    }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/OlmInboundGroupSessionWrapper2.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/OlmInboundGroupSessionWrapper2.kt
index 289c169d6d..600fcb1003 100755
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/OlmInboundGroupSessionWrapper2.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/OlmInboundGroupSessionWrapper2.kt
@@ -26,6 +26,8 @@ import java.io.Serializable
  * This class adds more context to a OlmInboundGroupSession object.
  * This allows additional checks. The class implements Serializable so that the context can be stored.
  */
+// Note used anymore, just for database migration
+// Deprecated("Use MXInboundMegolmSessionWrapper")
 internal class OlmInboundGroupSessionWrapper2 : Serializable {
 
     // The associated olm inbound group session.
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/OutboundGroupSessionWrapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/OutboundGroupSessionWrapper.kt
index 4ac87f44ce..5a6d1f4bc1 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/OutboundGroupSessionWrapper.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/OutboundGroupSessionWrapper.kt
@@ -20,5 +20,9 @@ import org.matrix.olm.OlmOutboundGroupSession
 
 internal data class OutboundGroupSessionWrapper(
         val outboundGroupSession: OlmOutboundGroupSession,
-        val creationTime: Long
+        val creationTime: Long,
+        /**
+         * As per MSC 3061, declares if this key could be shared when inviting a new user to the room.
+         */
+        val sharedHistory: Boolean = false
 )
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/SessionInfo.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/SessionInfo.kt
new file mode 100644
index 0000000000..b3a2ba4dfe
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/SessionInfo.kt
@@ -0,0 +1,22 @@
+/*
+ * 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.model
+
+data class SessionInfo(
+        val sessionId: String,
+        val senderKey: String
+)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt
index b5b8d8e974..0413fc730c 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt
@@ -35,7 +35,7 @@ import org.matrix.android.sdk.api.session.events.model.Event
 import org.matrix.android.sdk.api.session.events.model.content.RoomKeyWithHeldContent
 import org.matrix.android.sdk.api.session.events.model.content.WithHeldCode
 import org.matrix.android.sdk.api.util.Optional
-import org.matrix.android.sdk.internal.crypto.model.OlmInboundGroupSessionWrapper2
+import org.matrix.android.sdk.internal.crypto.model.MXInboundMegolmSessionWrapper
 import org.matrix.android.sdk.internal.crypto.model.OlmSessionWrapper
 import org.matrix.android.sdk.internal.crypto.model.OutboundGroupSessionWrapper
 import org.matrix.android.sdk.internal.crypto.store.db.model.KeysBackupDataEntity
@@ -64,7 +64,15 @@ internal interface IMXCryptoStore {
      *
      * @return the list of all known group sessions, to export them.
      */
-    fun getInboundGroupSessions(): List<OlmInboundGroupSessionWrapper2>
+    fun getInboundGroupSessions(): List<MXInboundMegolmSessionWrapper>
+
+    /**
+     * Retrieve the known inbound group sessions for the specified room.
+     *
+     * @param roomId The roomId that the sessions will be returned
+     * @return the list of all known group sessions, for the provided roomId
+     */
+    fun getInboundGroupSessions(roomId: String): List<MXInboundMegolmSessionWrapper>
 
     /**
      * @return true to unilaterally blacklist all unverified devices.
@@ -90,6 +98,20 @@ internal interface IMXCryptoStore {
 
     fun isKeyGossipingEnabled(): Boolean
 
+    /**
+     * As per MSC3061.
+     * If true will make it possible to share part of e2ee room history
+     * on invite depending on the room visibility setting.
+     */
+    fun enableShareKeyOnInvite(enable: Boolean)
+
+    /**
+     * As per MSC3061.
+     * If true will make it possible to share part of e2ee room history
+     * on invite depending on the room visibility setting.
+     */
+    fun isShareKeysOnInviteEnabled(): Boolean
+
     /**
      * Provides the rooms ids list in which the messages are not encrypted for the unverified devices.
      *
@@ -250,6 +272,17 @@ internal interface IMXCryptoStore {
 
     fun setShouldEncryptForInvitedMembers(roomId: String, shouldEncryptForInvitedMembers: Boolean)
 
+    fun shouldShareHistory(roomId: String): Boolean
+
+    /**
+     * Sets a boolean flag that will determine whether or not room history (existing inbound sessions)
+     * will be shared to new user invites.
+     *
+     * @param roomId the room id
+     * @param shouldShareHistory The boolean flag
+     */
+    fun setShouldShareHistory(roomId: String, shouldShareHistory: Boolean)
+
     /**
      * Store a session between the logged-in user and another device.
      *
@@ -290,7 +323,7 @@ internal interface IMXCryptoStore {
      *
      * @param sessions the inbound group sessions to store.
      */
-    fun storeInboundGroupSessions(sessions: List<OlmInboundGroupSessionWrapper2>)
+    fun storeInboundGroupSessions(sessions: List<MXInboundMegolmSessionWrapper>)
 
     /**
      * Retrieve an inbound group session.
@@ -299,7 +332,17 @@ internal interface IMXCryptoStore {
      * @param senderKey the base64-encoded curve25519 key of the sender.
      * @return an inbound group session.
      */
-    fun getInboundGroupSession(sessionId: String, senderKey: String): OlmInboundGroupSessionWrapper2?
+    fun getInboundGroupSession(sessionId: String, senderKey: String): MXInboundMegolmSessionWrapper?
+
+    /**
+     * Retrieve an inbound group session, filtering shared history.
+     *
+     * @param sessionId the session identifier.
+     * @param senderKey the base64-encoded curve25519 key of the sender.
+     * @param sharedHistory filter inbound session with respect to shared history field
+     * @return an inbound group session.
+     */
+    fun getInboundGroupSession(sessionId: String, senderKey: String, sharedHistory: Boolean): MXInboundMegolmSessionWrapper?
 
     /**
      * Get the current outbound group session for this encrypted room.
@@ -333,7 +376,7 @@ internal interface IMXCryptoStore {
      *
      * @param olmInboundGroupSessionWrappers the sessions
      */
-    fun markBackupDoneForInboundGroupSessions(olmInboundGroupSessionWrappers: List<OlmInboundGroupSessionWrapper2>)
+    fun markBackupDoneForInboundGroupSessions(olmInboundGroupSessionWrappers: List<MXInboundMegolmSessionWrapper>)
 
     /**
      * Retrieve inbound group sessions that are not yet backed up.
@@ -341,7 +384,7 @@ internal interface IMXCryptoStore {
      * @param limit the maximum number of sessions to return.
      * @return an array of non backed up inbound group sessions.
      */
-    fun inboundGroupSessionsToBackup(limit: Int): List<OlmInboundGroupSessionWrapper2>
+    fun inboundGroupSessionsToBackup(limit: Int): List<MXInboundMegolmSessionWrapper>
 
     /**
      * Number of stored inbound group sessions.
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt
index 028d8f73f9..20ca357d1a 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt
@@ -50,7 +50,7 @@ import org.matrix.android.sdk.api.session.events.model.content.RoomKeyWithHeldCo
 import org.matrix.android.sdk.api.session.events.model.content.WithHeldCode
 import org.matrix.android.sdk.api.util.Optional
 import org.matrix.android.sdk.api.util.toOptional
-import org.matrix.android.sdk.internal.crypto.model.OlmInboundGroupSessionWrapper2
+import org.matrix.android.sdk.internal.crypto.model.MXInboundMegolmSessionWrapper
 import org.matrix.android.sdk.internal.crypto.model.OlmSessionWrapper
 import org.matrix.android.sdk.internal.crypto.model.OutboundGroupSessionWrapper
 import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
@@ -657,12 +657,28 @@ internal class RealmCryptoStore @Inject constructor(
                 ?: false
     }
 
+    override fun shouldShareHistory(roomId: String): Boolean {
+        if (!isShareKeysOnInviteEnabled()) return false
+        return doWithRealm(realmConfiguration) {
+            CryptoRoomEntity.getById(it, roomId)?.shouldShareHistory
+        }
+                ?: false
+    }
+
     override fun setShouldEncryptForInvitedMembers(roomId: String, shouldEncryptForInvitedMembers: Boolean) {
         doRealmTransaction(realmConfiguration) {
             CryptoRoomEntity.getOrCreate(it, roomId).shouldEncryptForInvitedMembers = shouldEncryptForInvitedMembers
         }
     }
 
+    override fun setShouldShareHistory(roomId: String, shouldShareHistory: Boolean) {
+        Timber.tag(loggerTag.value)
+                .v("setShouldShareHistory for room $roomId is $shouldShareHistory")
+        doRealmTransaction(realmConfiguration) {
+            CryptoRoomEntity.getOrCreate(it, roomId).shouldShareHistory = shouldShareHistory
+        }
+    }
+
     override fun storeSession(olmSessionWrapper: OlmSessionWrapper, deviceKey: String) {
         var sessionIdentifier: String? = null
 
@@ -727,54 +743,55 @@ internal class RealmCryptoStore @Inject constructor(
         }
     }
 
-    override fun storeInboundGroupSessions(sessions: List<OlmInboundGroupSessionWrapper2>) {
+    override fun storeInboundGroupSessions(sessions: List<MXInboundMegolmSessionWrapper>) {
         if (sessions.isEmpty()) {
             return
         }
 
         doRealmTransaction(realmConfiguration) { realm ->
-            sessions.forEach { session ->
-                var sessionIdentifier: String? = null
+            sessions.forEach { wrapper ->
 
-                try {
-                    sessionIdentifier = session.olmInboundGroupSession?.sessionIdentifier()
+                val sessionIdentifier = try {
+                    wrapper.session.sessionIdentifier()
                 } catch (e: OlmException) {
                     Timber.e(e, "## storeInboundGroupSession() : sessionIdentifier failed")
+                    return@forEach
                 }
 
-                if (sessionIdentifier != null) {
-                    val key = OlmInboundGroupSessionEntity.createPrimaryKey(sessionIdentifier, session.senderKey)
+//                    val shouldShareHistory = session.roomId?.let { roomId ->
+//                        CryptoRoomEntity.getById(realm, roomId)?.shouldShareHistory
+//                    } ?: false
+                val key = OlmInboundGroupSessionEntity.createPrimaryKey(sessionIdentifier, wrapper.sessionData.senderKey)
 
-                    val existing = realm.where<OlmInboundGroupSessionEntity>()
-                            .equalTo(OlmInboundGroupSessionEntityFields.PRIMARY_KEY, key)
-                            .findFirst()
-
-                    if (existing != null) {
-                        // we want to keep the existing backup status
-                        existing.putInboundGroupSession(session)
-                    } else {
-                        val realmOlmInboundGroupSession = OlmInboundGroupSessionEntity().apply {
-                            primaryKey = key
-                            sessionId = sessionIdentifier
-                            senderKey = session.senderKey
-                            putInboundGroupSession(session)
-                        }
-
-                        realm.insertOrUpdate(realmOlmInboundGroupSession)
-                    }
+                val realmOlmInboundGroupSession = OlmInboundGroupSessionEntity().apply {
+                    primaryKey = key
+                    store(wrapper)
                 }
+                Timber.i("## CRYPTO | shouldShareHistory: ${wrapper.sessionData.sharedHistory} for $key")
+                realm.insertOrUpdate(realmOlmInboundGroupSession)
             }
         }
     }
 
-    override fun getInboundGroupSession(sessionId: String, senderKey: String): OlmInboundGroupSessionWrapper2? {
+    override fun getInboundGroupSession(sessionId: String, senderKey: String): MXInboundMegolmSessionWrapper? {
         val key = OlmInboundGroupSessionEntity.createPrimaryKey(sessionId, senderKey)
 
-        return doWithRealm(realmConfiguration) {
-            it.where<OlmInboundGroupSessionEntity>()
+        return doWithRealm(realmConfiguration) { realm ->
+            realm.where<OlmInboundGroupSessionEntity>()
                     .equalTo(OlmInboundGroupSessionEntityFields.PRIMARY_KEY, key)
                     .findFirst()
-                    ?.getInboundGroupSession()
+                    ?.toModel()
+        }
+    }
+
+    override fun getInboundGroupSession(sessionId: String, senderKey: String, sharedHistory: Boolean): MXInboundMegolmSessionWrapper? {
+        val key = OlmInboundGroupSessionEntity.createPrimaryKey(sessionId, senderKey)
+        return doWithRealm(realmConfiguration) {
+            it.where<OlmInboundGroupSessionEntity>()
+                    .equalTo(OlmInboundGroupSessionEntityFields.SHARED_HISTORY, sharedHistory)
+                    .equalTo(OlmInboundGroupSessionEntityFields.PRIMARY_KEY, key)
+                    .findFirst()
+                    ?.toModel()
         }
     }
 
@@ -786,7 +803,8 @@ internal class RealmCryptoStore @Inject constructor(
                         entity.getOutboundGroupSession()?.let {
                             OutboundGroupSessionWrapper(
                                     it,
-                                    entity.creationTime ?: 0
+                                    entity.creationTime ?: 0,
+                                    entity.shouldShareHistory
                             )
                         }
                     }
@@ -806,6 +824,8 @@ internal class RealmCryptoStore @Inject constructor(
                 if (outboundGroupSession != null) {
                     val info = realm.createObject(OutboundGroupSessionInfoEntity::class.java).apply {
                         creationTime = clock.epochMillis()
+                        // Store the room history visibility on the outbound session creation
+                        shouldShareHistory = entity.shouldShareHistory
                         putOutboundGroupSession(outboundGroupSession)
                     }
                     entity.outboundSessionInfo = info
@@ -814,17 +834,32 @@ internal class RealmCryptoStore @Inject constructor(
         }
     }
 
+//    override fun needsRotationDueToVisibilityChange(roomId: String): Boolean {
+//        return doWithRealm(realmConfiguration) { realm ->
+//            CryptoRoomEntity.getById(realm, roomId)?.let { entity ->
+//                entity.shouldShareHistory != entity.outboundSessionInfo?.shouldShareHistory
+//            }
+//        } ?: false
+//    }
+
     /**
      * Note: the result will be only use to export all the keys and not to use the OlmInboundGroupSessionWrapper2,
      * so there is no need to use or update `inboundGroupSessionToRelease` for native memory management.
      */
-    override fun getInboundGroupSessions(): List<OlmInboundGroupSessionWrapper2> {
-        return doWithRealm(realmConfiguration) {
-            it.where<OlmInboundGroupSessionEntity>()
+    override fun getInboundGroupSessions(): List<MXInboundMegolmSessionWrapper> {
+        return doWithRealm(realmConfiguration) { realm ->
+            realm.where<OlmInboundGroupSessionEntity>()
                     .findAll()
-                    .mapNotNull { inboundGroupSessionEntity ->
-                        inboundGroupSessionEntity.getInboundGroupSession()
-                    }
+                    .mapNotNull { it.toModel() }
+        }
+    }
+
+    override fun getInboundGroupSessions(roomId: String): List<MXInboundMegolmSessionWrapper> {
+        return doWithRealm(realmConfiguration) { realm ->
+            realm.where<OlmInboundGroupSessionEntity>()
+                    .equalTo(OlmInboundGroupSessionEntityFields.ROOM_ID, roomId)
+                    .findAll()
+                    .mapNotNull { it.toModel() }
         }
     }
 
@@ -885,7 +920,7 @@ internal class RealmCryptoStore @Inject constructor(
         }
     }
 
-    override fun markBackupDoneForInboundGroupSessions(olmInboundGroupSessionWrappers: List<OlmInboundGroupSessionWrapper2>) {
+    override fun markBackupDoneForInboundGroupSessions(olmInboundGroupSessionWrappers: List<MXInboundMegolmSessionWrapper>) {
         if (olmInboundGroupSessionWrappers.isEmpty()) {
             return
         }
@@ -893,10 +928,13 @@ internal class RealmCryptoStore @Inject constructor(
         doRealmTransaction(realmConfiguration) { realm ->
             olmInboundGroupSessionWrappers.forEach { olmInboundGroupSessionWrapper ->
                 try {
-                    val sessionIdentifier = olmInboundGroupSessionWrapper.olmInboundGroupSession?.sessionIdentifier()
+                    val sessionIdentifier =
+                            tryOrNull("Failed to get session identifier") {
+                                olmInboundGroupSessionWrapper.session.sessionIdentifier()
+                            } ?: return@forEach
                     val key = OlmInboundGroupSessionEntity.createPrimaryKey(
                             sessionIdentifier,
-                            olmInboundGroupSessionWrapper.senderKey
+                            olmInboundGroupSessionWrapper.sessionData.senderKey
                     )
 
                     val existing = realm.where<OlmInboundGroupSessionEntity>()
@@ -909,9 +947,7 @@ internal class RealmCryptoStore @Inject constructor(
                         // ... might be in cache but not yet persisted, create a record to persist backedup state
                         val realmOlmInboundGroupSession = OlmInboundGroupSessionEntity().apply {
                             primaryKey = key
-                            sessionId = sessionIdentifier
-                            senderKey = olmInboundGroupSessionWrapper.senderKey
-                            putInboundGroupSession(olmInboundGroupSessionWrapper)
+                            store(olmInboundGroupSessionWrapper)
                             backedUp = true
                         }
 
@@ -924,15 +960,13 @@ internal class RealmCryptoStore @Inject constructor(
         }
     }
 
-    override fun inboundGroupSessionsToBackup(limit: Int): List<OlmInboundGroupSessionWrapper2> {
+    override fun inboundGroupSessionsToBackup(limit: Int): List<MXInboundMegolmSessionWrapper> {
         return doWithRealm(realmConfiguration) {
             it.where<OlmInboundGroupSessionEntity>()
                     .equalTo(OlmInboundGroupSessionEntityFields.BACKED_UP, false)
                     .limit(limit.toLong())
                     .findAll()
-                    .mapNotNull { inboundGroupSession ->
-                        inboundGroupSession.getInboundGroupSession()
-                    }
+                    .mapNotNull { it.toModel() }
         }
     }
 
@@ -973,6 +1007,18 @@ internal class RealmCryptoStore @Inject constructor(
         } ?: false
     }
 
+    override fun isShareKeysOnInviteEnabled(): Boolean {
+        return doWithRealm(realmConfiguration) {
+            it.where<CryptoMetadataEntity>().findFirst()?.enableKeyForwardingOnInvite
+        } ?: false
+    }
+
+    override fun enableShareKeyOnInvite(enable: Boolean) {
+        doRealmTransaction(realmConfiguration) {
+            it.where<CryptoMetadataEntity>().findFirst()?.enableKeyForwardingOnInvite = enable
+        }
+    }
+
     override fun setDeviceKeysUploaded(uploaded: Boolean) {
         doRealmTransaction(realmConfiguration) {
             it.where<CryptoMetadataEntity>().findFirst()?.deviceKeysSentToServer = uploaded
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 02c2a27dec..4ca9d44f98 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.MigrateCryptoTo014
 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.util.time.Clock
 import timber.log.Timber
 import javax.inject.Inject
@@ -51,7 +52,7 @@ internal class RealmCryptoStoreMigration @Inject constructor(
     // 0, 1, 2: legacy Riot-Android
     // 3: migrate to RiotX schema
     // 4, 5, 6, 7, 8, 9: migrations from RiotX (which was previously 1, 2, 3, 4, 5, 6)
-    val schemaVersion = 16L
+    val schemaVersion = 17L
 
     override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) {
         Timber.d("Migrating Realm Crypto from $oldVersion to $newVersion")
@@ -72,5 +73,6 @@ internal class RealmCryptoStoreMigration @Inject constructor(
         if (oldVersion < 14) MigrateCryptoTo014(realm).perform()
         if (oldVersion < 15) MigrateCryptoTo015(realm).perform()
         if (oldVersion < 16) MigrateCryptoTo016(realm).perform()
+        if (oldVersion < 17) MigrateCryptoTo017(realm).perform()
     }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo017.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo017.kt
new file mode 100644
index 0000000000..8904c412cd
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo017.kt
@@ -0,0 +1,101 @@
+/*
+ * 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.api.extensions.tryOrNull
+import org.matrix.android.sdk.internal.crypto.model.InboundGroupSessionData
+import org.matrix.android.sdk.internal.crypto.store.db.deserializeFromRealm
+import org.matrix.android.sdk.internal.crypto.store.db.model.CryptoMetadataEntityFields
+import org.matrix.android.sdk.internal.crypto.store.db.model.CryptoRoomEntityFields
+import org.matrix.android.sdk.internal.crypto.store.db.model.OlmInboundGroupSessionEntityFields
+import org.matrix.android.sdk.internal.crypto.store.db.model.OutboundGroupSessionInfoEntityFields
+import org.matrix.android.sdk.internal.crypto.store.db.serializeForRealm
+import org.matrix.android.sdk.internal.di.MoshiProvider
+import org.matrix.android.sdk.internal.util.database.RealmMigrator
+import timber.log.Timber
+
+/**
+ * Version 17L enhance OlmInboundGroupSessionEntity to support shared history for MSC3061.
+ * Also migrates how megolm session are stored to avoid additional serialized frozen class.
+ */
+internal class MigrateCryptoTo017(realm: DynamicRealm) : RealmMigrator(realm, 17) {
+
+    override fun doMigrate(realm: DynamicRealm) {
+        realm.schema.get("CryptoRoomEntity")
+                ?.addField(CryptoRoomEntityFields.SHOULD_SHARE_HISTORY, Boolean::class.java)?.transform {
+                    // We don't have access to the session database to check for the state here and set the good value.
+                    // But for now as it's behind a lab flag, will set to false and force initial sync when enabled
+                    it.setBoolean(CryptoRoomEntityFields.SHOULD_SHARE_HISTORY, false)
+                }
+
+        realm.schema.get("OutboundGroupSessionInfoEntity")
+                ?.addField(OutboundGroupSessionInfoEntityFields.SHOULD_SHARE_HISTORY, Boolean::class.java)?.transform {
+                    // We don't have access to the session database to check for the state here and set the good value.
+                    // But for now as it's behind a lab flag, will set to false and force initial sync when enabled
+                    it.setBoolean(OutboundGroupSessionInfoEntityFields.SHOULD_SHARE_HISTORY, false)
+                }
+
+        realm.schema.get("CryptoMetadataEntity")
+                ?.addField(CryptoMetadataEntityFields.ENABLE_KEY_FORWARDING_ON_INVITE, Boolean::class.java)
+                ?.transform { obj ->
+                    // default to false
+                    obj.setBoolean(CryptoMetadataEntityFields.ENABLE_KEY_FORWARDING_ON_INVITE, false)
+                }
+
+        val moshiAdapter = MoshiProvider.providesMoshi().adapter(InboundGroupSessionData::class.java)
+
+        realm.schema.get("OlmInboundGroupSessionEntity")
+                ?.addField(OlmInboundGroupSessionEntityFields.SHARED_HISTORY, Boolean::class.java)
+                ?.addField(OlmInboundGroupSessionEntityFields.ROOM_ID, String::class.java)
+                ?.addField(OlmInboundGroupSessionEntityFields.INBOUND_GROUP_SESSION_DATA_JSON, String::class.java)
+                ?.addField(OlmInboundGroupSessionEntityFields.SERIALIZED_OLM_INBOUND_GROUP_SESSION, String::class.java)
+                ?.transform { dynamicObject ->
+                    try {
+                        // we want to convert the old wrapper frozen class into a
+                        // map of sessionData & the pickled session herself
+                        dynamicObject.getString(OlmInboundGroupSessionEntityFields.OLM_INBOUND_GROUP_SESSION_DATA)?.let { oldData ->
+                            val oldWrapper = tryOrNull("Failed to convert megolm inbound group data") {
+                                @Suppress("DEPRECATION")
+                                deserializeFromRealm<org.matrix.android.sdk.internal.crypto.model.OlmInboundGroupSessionWrapper2?>(oldData)
+                            }
+                            val groupSession = oldWrapper?.olmInboundGroupSession
+                                    ?: return@transform Unit.also {
+                                        Timber.w("Failed to migrate megolm session, no olmInboundGroupSession")
+                                    }
+                            // now convert to new data
+                            val data = InboundGroupSessionData(
+                                    senderKey = oldWrapper.senderKey,
+                                    roomId = oldWrapper.roomId,
+                                    keysClaimed = oldWrapper.keysClaimed,
+                                    forwardingCurve25519KeyChain = oldWrapper.forwardingCurve25519KeyChain,
+                                    sharedHistory = false,
+                            )
+
+                            dynamicObject.setString(OlmInboundGroupSessionEntityFields.INBOUND_GROUP_SESSION_DATA_JSON, moshiAdapter.toJson(data))
+                            dynamicObject.setString(OlmInboundGroupSessionEntityFields.SERIALIZED_OLM_INBOUND_GROUP_SESSION, serializeForRealm(groupSession))
+
+                            // denormalized fields
+                            dynamicObject.setString(OlmInboundGroupSessionEntityFields.ROOM_ID, oldWrapper.roomId)
+                            dynamicObject.setBoolean(OlmInboundGroupSessionEntityFields.SHARED_HISTORY, false)
+                        }
+                    } 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/store/db/model/CryptoMetadataEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/CryptoMetadataEntity.kt
index 63ed0e537e..88708f824e 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/CryptoMetadataEntity.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/CryptoMetadataEntity.kt
@@ -35,6 +35,11 @@ internal open class CryptoMetadataEntity(
         var globalBlacklistUnverifiedDevices: Boolean = false,
         // setting to enable or disable key gossiping
         var globalEnableKeyGossiping: Boolean = true,
+
+        // MSC3061: Sharing room keys for past messages
+        // If set to true key history will be shared to invited users with respect to room setting
+        var enableKeyForwardingOnInvite: Boolean = false,
+
         // The keys backup version currently used. Null means no backup.
         var backupVersion: String? = null,
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/CryptoRoomEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/CryptoRoomEntity.kt
index 114a596964..be57586163 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/CryptoRoomEntity.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/CryptoRoomEntity.kt
@@ -24,6 +24,8 @@ internal open class CryptoRoomEntity(
         var algorithm: String? = null,
         var shouldEncryptForInvitedMembers: Boolean? = null,
         var blacklistUnverifiedDevices: Boolean = false,
+        // Determines whether or not room history should be shared on new member invites
+        var shouldShareHistory: Boolean = false,
         // Store the current outbound session for this room,
         // to avoid re-create and re-share at each startup (if rotation not needed..)
         // This is specific to megolm but not sure how to model it better
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/OlmInboundGroupSessionEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/OlmInboundGroupSessionEntity.kt
index a4f6c279ac..62ab73e379 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/OlmInboundGroupSessionEntity.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/OlmInboundGroupSessionEntity.kt
@@ -18,9 +18,12 @@ package org.matrix.android.sdk.internal.crypto.store.db.model
 
 import io.realm.RealmObject
 import io.realm.annotations.PrimaryKey
-import org.matrix.android.sdk.internal.crypto.model.OlmInboundGroupSessionWrapper2
+import org.matrix.android.sdk.internal.crypto.model.InboundGroupSessionData
+import org.matrix.android.sdk.internal.crypto.model.MXInboundMegolmSessionWrapper
 import org.matrix.android.sdk.internal.crypto.store.db.deserializeFromRealm
 import org.matrix.android.sdk.internal.crypto.store.db.serializeForRealm
+import org.matrix.android.sdk.internal.di.MoshiProvider
+import org.matrix.olm.OlmInboundGroupSession
 import timber.log.Timber
 
 internal fun OlmInboundGroupSessionEntity.Companion.createPrimaryKey(sessionId: String?, senderKey: String?) = "$sessionId|$senderKey"
@@ -28,27 +31,83 @@ internal fun OlmInboundGroupSessionEntity.Companion.createPrimaryKey(sessionId:
 internal open class OlmInboundGroupSessionEntity(
         // Combined value to build a primary key
         @PrimaryKey var primaryKey: String? = null,
+
+        // denormalization for faster querying (these fields are in the inboundGroupSessionDataJson)
         var sessionId: String? = null,
         var senderKey: String? = null,
-        // olmInboundGroupSessionData contains Json
+        var roomId: String? = null,
+
+        // Deprecated, used for migration / olmInboundGroupSessionData contains Json
+        // keep it in case of problem to have a chance to recover
         var olmInboundGroupSessionData: String? = null,
+
+        // Stores the session data in an extensible format
+        // to allow to store data not yet supported for later use
+        var inboundGroupSessionDataJson: String? = null,
+
+        // The pickled session
+        var serializedOlmInboundGroupSession: String? = null,
+
+        // Flag that indicates whether or not the current inboundSession will be shared to
+        // invited users to decrypt past messages
+        var sharedHistory: Boolean = false,
         // Indicate if the key has been backed up to the homeserver
         var backedUp: Boolean = false
 ) :
         RealmObject() {
 
-    fun getInboundGroupSession(): OlmInboundGroupSessionWrapper2? {
+    fun store(wrapper: MXInboundMegolmSessionWrapper) {
+        this.serializedOlmInboundGroupSession = serializeForRealm(wrapper.session)
+        this.inboundGroupSessionDataJson = adapter.toJson(wrapper.sessionData)
+        this.roomId = wrapper.sessionData.roomId
+        this.senderKey = wrapper.sessionData.senderKey
+        this.sessionId = wrapper.session.sessionIdentifier()
+        this.sharedHistory = wrapper.sessionData.sharedHistory
+    }
+//    fun getInboundGroupSession(): OlmInboundGroupSessionWrapper2? {
+//        return try {
+//            deserializeFromRealm<OlmInboundGroupSessionWrapper2?>(olmInboundGroupSessionData)
+//        } catch (failure: Throwable) {
+//            Timber.e(failure, "## Deserialization failure")
+//            return null
+//        }
+//    }
+//
+//    fun putInboundGroupSession(olmInboundGroupSessionWrapper: OlmInboundGroupSessionWrapper2?) {
+//        olmInboundGroupSessionData = serializeForRealm(olmInboundGroupSessionWrapper)
+//    }
+
+    fun getOlmGroupSession(): OlmInboundGroupSession? {
         return try {
-            deserializeFromRealm<OlmInboundGroupSessionWrapper2?>(olmInboundGroupSessionData)
+            deserializeFromRealm(serializedOlmInboundGroupSession)
         } catch (failure: Throwable) {
             Timber.e(failure, "## Deserialization failure")
             return null
         }
     }
 
-    fun putInboundGroupSession(olmInboundGroupSessionWrapper: OlmInboundGroupSessionWrapper2?) {
-        olmInboundGroupSessionData = serializeForRealm(olmInboundGroupSessionWrapper)
+    fun getData(): InboundGroupSessionData? {
+        return try {
+            inboundGroupSessionDataJson?.let {
+                adapter.fromJson(it)
+            }
+        } catch (failure: Throwable) {
+            Timber.e(failure, "## Deserialization failure")
+            return null
+        }
     }
 
-    companion object
+    fun toModel(): MXInboundMegolmSessionWrapper? {
+        val data = getData() ?: return null
+        val session = getOlmGroupSession() ?: return null
+        return MXInboundMegolmSessionWrapper(
+                session = session,
+                sessionData = data
+        )
+    }
+
+    companion object {
+        private val adapter = MoshiProvider.providesMoshi()
+                .adapter(InboundGroupSessionData::class.java)
+    }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/OutboundGroupSessionInfoEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/OutboundGroupSessionInfoEntity.kt
index d50db78415..2ebd550201 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/OutboundGroupSessionInfoEntity.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/OutboundGroupSessionInfoEntity.kt
@@ -24,7 +24,8 @@ import timber.log.Timber
 
 internal open class OutboundGroupSessionInfoEntity(
         var serializedOutboundSessionData: String? = null,
-        var creationTime: Long? = null
+        var creationTime: Long? = null,
+        var shouldShareHistory: Boolean = false
 ) : RealmObject() {
 
     fun getOutboundGroupSession(): OlmOutboundGroupSession? {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendEventTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendEventTask.kt
index fbd9d245d9..bb14b417dd 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendEventTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendEventTask.kt
@@ -15,7 +15,6 @@
  */
 package org.matrix.android.sdk.internal.crypto.tasks
 
-import org.matrix.android.sdk.api.extensions.tryOrNull
 import org.matrix.android.sdk.api.session.events.model.Event
 import org.matrix.android.sdk.api.session.room.send.SendState
 import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
@@ -48,8 +47,12 @@ internal class DefaultSendEventTask @Inject constructor(
             params.event.roomId
                     ?.takeIf { params.encrypt }
                     ?.let { roomId ->
-                        tryOrNull {
+                        try {
                             loadRoomMembersTask.execute(LoadRoomMembersTask.Params(roomId))
+                        } catch (failure: Throwable) {
+                            // send any way?
+                            // the result is that some users won't probably be able to decrypt :/
+                            Timber.w(failure, "SendEvent: failed to load members in room ${params.event.roomId}")
                         }
                     }
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ChunkEntityHelper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ChunkEntityHelper.kt
index 234caec970..221abe0df5 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ChunkEntityHelper.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ChunkEntityHelper.kt
@@ -18,7 +18,11 @@ package org.matrix.android.sdk.internal.database.helper
 
 import io.realm.Realm
 import io.realm.kotlin.createObject
+import org.matrix.android.sdk.api.session.events.model.content.EncryptedEventContent
+import org.matrix.android.sdk.api.session.events.model.toModel
 import org.matrix.android.sdk.api.session.room.model.RoomMemberContent
+import org.matrix.android.sdk.internal.crypto.model.SessionInfo
+import org.matrix.android.sdk.internal.database.mapper.asDomain
 import org.matrix.android.sdk.internal.database.model.ChunkEntity
 import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntityFields
 import org.matrix.android.sdk.internal.database.model.EventAnnotationsSummaryEntity
@@ -31,6 +35,7 @@ import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntityFie
 import org.matrix.android.sdk.internal.database.model.TimelineEventEntity
 import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields
 import org.matrix.android.sdk.internal.database.query.find
+import org.matrix.android.sdk.internal.database.query.findLastForwardChunkOfRoom
 import org.matrix.android.sdk.internal.database.query.getOrCreate
 import org.matrix.android.sdk.internal.database.query.where
 import org.matrix.android.sdk.internal.session.room.timeline.PaginationDirection
@@ -180,3 +185,12 @@ internal fun ChunkEntity.isMoreRecentThan(chunkToCheck: ChunkEntity): Boolean {
     // We don't know, so we assume it's false
     return false
 }
+
+internal fun ChunkEntity.Companion.findLatestSessionInfo(realm: Realm, roomId: String): Set<SessionInfo>? =
+        ChunkEntity.findLastForwardChunkOfRoom(realm, roomId)?.timelineEvents?.mapNotNull { timelineEvent ->
+            timelineEvent?.root?.asDomain()?.content?.toModel<EncryptedEventContent>()?.let { content ->
+                content.sessionId ?: return@mapNotNull null
+                content.senderKey ?: return@mapNotNull null
+                SessionInfo(content.sessionId, content.senderKey)
+            }
+        }?.toSet()
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/NetworkModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/NetworkModule.kt
index b5b46a3f5a..113e780e5c 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/NetworkModule.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/NetworkModule.kt
@@ -21,6 +21,7 @@ import com.squareup.moshi.Moshi
 import dagger.Module
 import dagger.Provides
 import okhttp3.ConnectionSpec
+import okhttp3.Dispatcher
 import okhttp3.OkHttpClient
 import okhttp3.Protocol
 import okhttp3.logging.HttpLoggingInterceptor
@@ -73,7 +74,9 @@ internal object NetworkModule {
             apiInterceptor: ApiInterceptor
     ): OkHttpClient {
         val spec = ConnectionSpec.Builder(matrixConfiguration.connectionSpec).build()
-
+        val dispatcher = Dispatcher().apply {
+            maxRequestsPerHost = 20
+        }
         return OkHttpClient.Builder()
                 // workaround for #4669
                 .protocols(listOf(Protocol.HTTP_1_1))
@@ -94,6 +97,7 @@ internal object NetworkModule {
                         addInterceptor(curlLoggingInterceptor)
                     }
                 }
+                .dispatcher(dispatcher)
                 .connectionSpecs(Collections.singletonList(spec))
                 .applyMatrixConfiguration(matrixConfiguration)
                 .build()
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/NetworkCallbackStrategy.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/NetworkCallbackStrategy.kt
index f75fb01746..90d2719e25 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/NetworkCallbackStrategy.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/NetworkCallbackStrategy.kt
@@ -70,7 +70,15 @@ internal class PreferredNetworkCallbackStrategy @Inject constructor(context: Con
 
     override fun register(hasChanged: () -> Unit) {
         hasChangedCallback = hasChanged
-        conn.registerDefaultNetworkCallback(networkCallback)
+        // Add a try catch for safety
+        // XXX: It happens when running all tests in CI, at some points we reach a limit here causing TooManyRequestsException
+        // and crashing the sync thread. We might have problem here, would need some investigation
+        // for now adding a catch to allow CI to continue running
+        try {
+            conn.registerDefaultNetworkCallback(networkCallback)
+        } catch (t: Throwable) {
+            Timber.e(t, "Unable to register default network callback")
+        }
     }
 
     override fun unregister() {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/DefaultMembershipService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/DefaultMembershipService.kt
index ec140e7b17..20708b3814 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/DefaultMembershipService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/DefaultMembershipService.kt
@@ -17,18 +17,23 @@
 package org.matrix.android.sdk.internal.session.room.membership
 
 import androidx.lifecycle.LiveData
+import com.otaliastudios.opengl.core.use
 import com.zhuinden.monarchy.Monarchy
 import dagger.assisted.Assisted
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
 import io.realm.Realm
 import io.realm.RealmQuery
+import org.matrix.android.sdk.api.MatrixConfiguration
+import org.matrix.android.sdk.api.session.crypto.CryptoService
 import org.matrix.android.sdk.api.session.identity.ThreePid
 import org.matrix.android.sdk.api.session.room.members.MembershipService
 import org.matrix.android.sdk.api.session.room.members.RoomMemberQueryParams
 import org.matrix.android.sdk.api.session.room.model.Membership
 import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary
+import org.matrix.android.sdk.internal.database.helper.findLatestSessionInfo
 import org.matrix.android.sdk.internal.database.mapper.asDomain
+import org.matrix.android.sdk.internal.database.model.ChunkEntity
 import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntity
 import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntityFields
 import org.matrix.android.sdk.internal.database.model.RoomMembersLoadStatusType
@@ -50,8 +55,10 @@ internal class DefaultMembershipService @AssistedInject constructor(
         private val inviteThreePidTask: InviteThreePidTask,
         private val membershipAdminTask: MembershipAdminTask,
         private val roomDataSource: RoomDataSource,
+        private val cryptoService: CryptoService,
         @UserId
         private val userId: String,
+        private val matrixConfiguration: MatrixConfiguration,
         private val queryStringValueProcessor: QueryStringValueProcessor
 ) : MembershipService {
 
@@ -139,10 +146,20 @@ internal class DefaultMembershipService @AssistedInject constructor(
     }
 
     override suspend fun invite(userId: String, reason: String?) {
+        sendShareHistoryKeysIfNeeded(userId)
         val params = InviteTask.Params(roomId, userId, reason)
         inviteTask.execute(params)
     }
 
+    private suspend fun sendShareHistoryKeysIfNeeded(userId: String) {
+        if (!cryptoService.isShareKeysOnInviteEnabled()) return
+        // TODO not sure it's the right way to get the latest messages in a room
+        val sessionInfo = Realm.getInstance(monarchy.realmConfiguration).use {
+            ChunkEntity.findLatestSessionInfo(it, roomId)
+        }
+        cryptoService.sendSharedHistoryKeys(roomId, userId, sessionInfo)
+    }
+
     override suspend fun invite3pid(threePid: ThreePid) {
         val params = InviteThreePidTask.Params(roomId, threePid)
         return inviteThreePidTask.execute(params)
diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt b/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt
index 6d91dc3348..ea039f0ed8 100755
--- a/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt
@@ -168,6 +168,8 @@ class VectorPreferences @Inject constructor(
         private const val SETTINGS_DEVELOPER_MODE_FAIL_FAST_PREFERENCE_KEY = "SETTINGS_DEVELOPER_MODE_FAIL_FAST_PREFERENCE_KEY"
         private const val SETTINGS_DEVELOPER_MODE_SHOW_INFO_ON_SCREEN_KEY = "SETTINGS_DEVELOPER_MODE_SHOW_INFO_ON_SCREEN_KEY"
 
+        const val SETTINGS_LABS_MSC3061_SHARE_KEYS_HISTORY = "SETTINGS_LABS_MSC3061_SHARE_KEYS_HISTORY"
+
         // SETTINGS_LABS_HIDE_TECHNICAL_E2E_ERRORS
         private const val SETTINGS_LABS_SHOW_COMPLETE_HISTORY_IN_ENCRYPTED_ROOM = "SETTINGS_LABS_SHOW_COMPLETE_HISTORY_IN_ENCRYPTED_ROOM"
         const val SETTINGS_LABS_UNREAD_NOTIFICATIONS_AS_TAB = "SETTINGS_LABS_UNREAD_NOTIFICATIONS_AS_TAB"
diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsLabsFragment.kt b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsLabsFragment.kt
index 3b9fdc5e55..70908d7560 100644
--- a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsLabsFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsLabsFragment.kt
@@ -20,6 +20,7 @@ import android.os.Bundle
 import android.text.method.LinkMovementMethod
 import android.widget.TextView
 import androidx.preference.Preference
+import androidx.preference.SwitchPreference
 import com.google.android.material.dialog.MaterialAlertDialogBuilder
 import im.vector.app.R
 import im.vector.app.core.preference.VectorSwitchPreference
@@ -57,6 +58,17 @@ class VectorSettingsLabsFragment @Inject constructor(
                 false
             }
         }
+
+        findPreference<SwitchPreference>(VectorPreferences.SETTINGS_LABS_MSC3061_SHARE_KEYS_HISTORY)?.let { pref ->
+            // ensure correct default
+            pref.isChecked = session.cryptoService().isShareKeysOnInviteEnabled()
+
+            pref.onPreferenceClickListener = Preference.OnPreferenceClickListener {
+                session.cryptoService().enableShareKeyOnInvite(pref.isChecked)
+                MainActivity.restartApp(requireActivity(), MainActivityArgs(clearCache = true))
+                true
+            }
+        }
     }
 
     /**
diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml
index 933f3f0602..bc1b59ebd4 100644
--- a/vector/src/main/res/values/strings.xml
+++ b/vector/src/main/res/values/strings.xml
@@ -2958,6 +2958,9 @@
     <string name="labs_enable_latex_maths">Enable LaTeX mathematics</string>
     <string name="restart_the_application_to_apply_changes">Restart the application for the change to take effect.</string>
 
+    <string name="labs_enable_msc3061_share_history">MSC3061: Sharing room keys for past messages</string>
+    <string name="labs_enable_msc3061_share_history_desc">When inviting in an encrypted room that is sharing history, encrypted history will be visible.</string>
+
     <!-- Poll -->
     <string name="create_poll_title">Create Poll</string>
     <string name="create_poll_question_title">Poll question or topic</string>
diff --git a/vector/src/main/res/xml/vector_settings_labs.xml b/vector/src/main/res/xml/vector_settings_labs.xml
index 9b9293e2e2..555470e643 100644
--- a/vector/src/main/res/xml/vector_settings_labs.xml
+++ b/vector/src/main/res/xml/vector_settings_labs.xml
@@ -45,6 +45,13 @@
         android:key="SETTINGS_LABS_UNREAD_NOTIFICATIONS_AS_TAB"
         android:title="@string/labs_show_unread_notifications_as_tab" />
 
+    <im.vector.app.core.preference.VectorSwitchPreference
+        android:defaultValue="false"
+        android:persistent="false"
+        android:key="SETTINGS_LABS_MSC3061_SHARE_KEYS_HISTORY"
+        android:summary="@string/labs_enable_msc3061_share_history_desc"
+        android:title="@string/labs_enable_msc3061_share_history" />
+
     <im.vector.app.core.preference.VectorSwitchPreference
         android:defaultValue="false"
         android:key="SETTINGS_LABS_ENABLE_LATEX_MATHS"